Skip to content

Commit

Permalink
Trying out the tuner if the microphone works
Browse files Browse the repository at this point in the history
  • Loading branch information
Meet-Vyas-Dev committed Jul 12, 2024
1 parent 28e09d8 commit ba7f981
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 1 deletion.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ <h1 class="mb-4"><a href="index.html" class="logo" style="color: #fff">Meet Vyas
<li><a target="_blank" href="projects.html"><span><small>05</small>Course Projects</span></a></li>
<li><a target="_blank" href="blog.html"><span><small>06</small>Blog</span></a></li>
<li><a target="_blank" href="https://meet-vyas.notion.site/2096f14202de434995ea109d4381ca71?v=227db1b3a43446a98e2aaa79d7ff9461&pvs=4"><span><small>07</small>Reading List</span></a></li>

<li><a target="_blank" href="tuner.html"><span><small>08</small>Tuner (Beta)</span></a></li>
</ul>
</div>
</div>
Expand Down
34 changes: 34 additions & 0 deletions js/correlation_worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
self.onmessage = function(event)
{
var timeseries = event.data.timeseries;
var test_frequencies = event.data.test_frequencies;
var sample_rate = event.data.sample_rate;
var amplitudes = compute_correlations(timeseries, test_frequencies, sample_rate);
self.postMessage({ "timeseries": timeseries, "frequency_amplitudes": amplitudes });
};

function compute_correlations(timeseries, test_frequencies, sample_rate)
{
// 2pi * frequency gives the appropriate period to sine.
// timeseries index / sample_rate gives the appropriate time coordinate.
var scale_factor = 2 * Math.PI / sample_rate;
var amplitudes = test_frequencies.map
(
function(f)
{
var frequency = f.frequency;

// Represent a complex number as a length-2 array [ real, imaginary ].
var accumulator = [ 0, 0 ];
for (var t = 0; t < timeseries.length; t++)
{
accumulator[0] += timeseries[t] * Math.cos(scale_factor * frequency * t);
accumulator[1] += timeseries[t] * Math.sin(scale_factor * frequency * t);
}

return accumulator;
}
);

return amplitudes;
}
147 changes: 147 additions & 0 deletions tuner.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<html>
<head>
<script>
// Define the set of test frequencies that we'll use to analyze microphone data.
var C2 = 65.41; // C2 note, in Hz.
var notes = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ];
var test_frequencies = [];
for (var i = 0; i < 30; i++)
{
var note_frequency = C2 * Math.pow(2, i / 12);
var note_name = notes[i % 12];
var note = { "frequency": note_frequency, "name": note_name };
var just_above = { "frequency": note_frequency * Math.pow(2, 1 / 48), "name": note_name + " (a bit sharp)" };
var just_below = { "frequency": note_frequency * Math.pow(2, -1 / 48), "name": note_name + " (a bit flat)" };
test_frequencies = test_frequencies.concat([ just_below, note, just_above ]);
}

window.addEventListener("load", initialize);

var correlation_worker = new Worker("correlation_worker.js");
correlation_worker.addEventListener("message", interpret_correlation_result);

function initialize()
{
var get_user_media = navigator.getUserMedia;
get_user_media = get_user_media || navigator.webkitGetUserMedia;
get_user_media = get_user_media || navigator.mozGetUserMedia;
get_user_media.call(navigator, { "audio": true }, use_stream, function() {});

document.getElementById("play-note").addEventListener("click", toggle_playing_note);
}

function use_stream(stream)
{
var audio_context = new AudioContext();
var microphone = audio_context.createMediaStreamSource(stream);
window.source = microphone; // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=934512
var script_processor = audio_context.createScriptProcessor(1024, 1, 1);

script_processor.connect(audio_context.destination);
microphone.connect(script_processor);

var buffer = [];
var sample_length_milliseconds = 100;
var recording = true;

// Need to leak this function into the global namespace so it doesn't get
// prematurely garbage-collected.
// http://lists.w3.org/Archives/Public/public-audio/2013JanMar/0304.html
window.capture_audio = function(event)
{
if (!recording)
return;

buffer = buffer.concat(Array.prototype.slice.call(event.inputBuffer.getChannelData(0)));

// Stop recording after sample_length_milliseconds.
if (buffer.length > sample_length_milliseconds * audio_context.sampleRate / 1000)
{
recording = false;

correlation_worker.postMessage
(
{
"timeseries": buffer,
"test_frequencies": test_frequencies,
"sample_rate": audio_context.sampleRate
}
);

buffer = [];
setTimeout(function() { recording = true; }, 250);
}
};

script_processor.onaudioprocess = window.capture_audio;
}

function interpret_correlation_result(event)
{
var timeseries = event.data.timeseries;
var frequency_amplitudes = event.data.frequency_amplitudes;

// Compute the (squared) magnitudes of the complex amplitudes for each
// test frequency.
var magnitudes = frequency_amplitudes.map(function(z) { return z[0] * z[0] + z[1] * z[1]; });

// Find the maximum in the list of magnitudes.
var maximum_index = -1;
var maximum_magnitude = 0;
for (var i = 0; i < magnitudes.length; i++)
{
if (magnitudes[i] <= maximum_magnitude)
continue;

maximum_index = i;
maximum_magnitude = magnitudes[i];
}

// Compute the average magnitude. We'll only pay attention to frequencies
// with magnitudes significantly above average.
var average = magnitudes.reduce(function(a, b) { return a + b; }, 0) / magnitudes.length;
var confidence = maximum_magnitude / average;
var confidence_threshold = 10; // empirical, arbitrary.
if (confidence > confidence_threshold)
{
var dominant_frequency = test_frequencies[maximum_index];
document.getElementById("note-name").textContent = dominant_frequency.name;
document.getElementById("frequency").textContent = dominant_frequency.frequency;
}
}

// Unnecessary addition of button to play an E note.
var note_context = new AudioContext();
var note_node = note_context.createOscillator();
var gain_node = note_context.createGain();
note_node.frequency = C2 * Math.pow(2, 4 / 12); // E, ~82.41 Hz.
gain_node.gain.value = 0;
note_node.connect(gain_node);
gain_node.connect(note_context.destination);
note_node.start();

var playing = false;
function toggle_playing_note()
{
playing = !playing;
if (playing)
gain_node.gain.value = 0.1;
else
gain_node.gain.value = 0;
}
</script>
</head>

<body>
<p>It sounds like you're playing...</p>
<h1 id="note-name"></h1>
<p>
<span>frequency (Hz):</span>
<span id="frequency"></span>
</p>
<hr>
<button id="play-note">start/stop an E note</button>
<hr>

</body>
</html>

0 comments on commit ba7f981

Please sign in to comment.