diff --git a/index.html b/index.html
index 14bb85e..d0a4218 100644
--- a/index.html
+++ b/index.html
@@ -21,7 +21,6 @@
-
@@ -31,7 +30,7 @@
--Hz
-
--
+
--
--cents ♭cents ♯
diff --git a/js/pitchdetect.js b/js/pitchdetect.js
index 213666a..281e2c3 100644
--- a/js/pitchdetect.js
+++ b/js/pitchdetect.js
@@ -31,172 +31,176 @@ var analyser = null;
var theBuffer = null;
var DEBUGCANVAS = null;
var mediaStreamSource = null;
-var detectorElem,
- canvasElem,
- waveCanvas,
- pitchElem,
- noteElem,
- detuneElem,
- detuneAmount;
+var detectorElem,
+ canvasElem,
+ waveCanvas,
+ pitchElem,
+ noteElem,
+ detuneElem,
+ detuneAmount;
window.onload = function() {
- audioContext = new AudioContext();
- MAX_SIZE = Math.max(4,Math.floor(audioContext.sampleRate/5000)); // corresponds to a 5kHz signal
- var request = new XMLHttpRequest();
- request.open("GET", "../sounds/whistling3.ogg", true);
- request.responseType = "arraybuffer";
- request.onload = function() {
- audioContext.decodeAudioData( request.response, function(buffer) {
- theBuffer = buffer;
- } );
- }
- request.send();
-
- detectorElem = document.getElementById( "detector" );
- canvasElem = document.getElementById( "output" );
- DEBUGCANVAS = document.getElementById( "waveform" );
- if (DEBUGCANVAS) {
- waveCanvas = DEBUGCANVAS.getContext("2d");
- waveCanvas.strokeStyle = "black";
- waveCanvas.lineWidth = 1;
- }
- pitchElem = document.getElementById( "pitch" );
- noteElem = document.getElementById( "note" );
- detuneElem = document.getElementById( "detune" );
- detuneAmount = document.getElementById( "detune_amt" );
-
- detectorElem.ondragenter = function () {
- this.classList.add("droptarget");
- return false; };
- detectorElem.ondragleave = function () { this.classList.remove("droptarget"); return false; };
- detectorElem.ondrop = function (e) {
- this.classList.remove("droptarget");
- e.preventDefault();
- theBuffer = null;
-
- var reader = new FileReader();
- reader.onload = function (event) {
- audioContext.decodeAudioData( event.target.result, function(buffer) {
- theBuffer = buffer;
- }, function(){alert("error loading!");} );
-
- };
- reader.onerror = function (event) {
- alert("Error: " + reader.error );
- };
- reader.readAsArrayBuffer(e.dataTransfer.files[0]);
- return false;
- };
+ audioContext = new AudioContext();
+ MAX_SIZE = Math.max(4,Math.floor(audioContext.sampleRate/5000)); // corresponds to a 5kHz signal
+ var request = new XMLHttpRequest();
+ request.open("GET", "sounds/whitman.mp3", true);
+ request.responseType = "arraybuffer";
+ request.onload = function() {
+ audioContext.decodeAudioData( request.response, function(buffer) {
+ theBuffer = buffer;
+ } );
+ }
+ request.send();
+
+ detectorElem = document.getElementById( "detector" );
+ canvasElem = document.getElementById( "output" );
+ DEBUGCANVAS = document.getElementById( "waveform" );
+ if (DEBUGCANVAS) {
+ waveCanvas = DEBUGCANVAS.getContext("2d");
+ waveCanvas.strokeStyle = "black";
+ waveCanvas.lineWidth = 1;
+ }
+ pitchElem = document.getElementById( "pitch" );
+ noteElem = document.getElementById( "note" );
+ detuneElem = document.getElementById( "detune" );
+ detuneAmount = document.getElementById( "detune_amt" );
+
+ detectorElem.ondragenter = function () {
+ this.classList.add("droptarget");
+ return false; };
+ detectorElem.ondragleave = function () { this.classList.remove("droptarget"); return false; };
+ detectorElem.ondrop = function (e) {
+ this.classList.remove("droptarget");
+ e.preventDefault();
+ theBuffer = null;
+
+ var reader = new FileReader();
+ reader.onload = function (event) {
+ audioContext.decodeAudioData( event.target.result, function(buffer) {
+ theBuffer = buffer;
+ }, function(){alert("error loading!");} );
+
+ };
+ reader.onerror = function (event) {
+ alert("Error: " + reader.error );
+ };
+ reader.readAsArrayBuffer(e.dataTransfer.files[0]);
+ return false;
+ };
}
function error() {
- alert('Stream generation failed.');
+ alert('Stream generation failed.');
}
function getUserMedia(dictionary, callback) {
- try {
- navigator.getUserMedia =
- navigator.getUserMedia ||
- navigator.webkitGetUserMedia ||
- navigator.mozGetUserMedia;
- navigator.getUserMedia(dictionary, callback, error);
- } catch (e) {
- alert('getUserMedia threw exception :' + e);
- }
+ try {
+ navigator.getUserMedia =
+ navigator.getUserMedia ||
+ navigator.webkitGetUserMedia ||
+ navigator.mozGetUserMedia;
+ navigator.getUserMedia(dictionary, callback, error);
+ } catch (e) {
+ alert('getUserMedia threw exception :' + e);
+ }
}
function gotStream(stream) {
- // Create an AudioNode from the stream.
- mediaStreamSource = audioContext.createMediaStreamSource(stream);
-
- // Connect it to the destination.
- analyser = audioContext.createAnalyser();
- analyser.fftSize = 2048;
- mediaStreamSource.connect( analyser );
- updatePitch();
+ // Create an AudioNode from the stream.
+ mediaStreamSource = audioContext.createMediaStreamSource(stream);
+
+ // Connect it to the destination.
+ analyser = audioContext.createAnalyser();
+ analyser.fftSize = 2048;
+ mediaStreamSource.connect( analyser );
+ updatePitch();
}
function toggleOscillator() {
- if (isPlaying) {
- //stop playing and return
- sourceNode.stop( 0 );
- sourceNode = null;
- analyser = null;
- isPlaying = false;
- if (!window.cancelAnimationFrame)
- window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
- window.cancelAnimationFrame( rafID );
- return "play oscillator";
- }
- sourceNode = audioContext.createOscillator();
-
- analyser = audioContext.createAnalyser();
- analyser.fftSize = 2048;
- sourceNode.connect( analyser );
- analyser.connect( audioContext.destination );
- sourceNode.start(0);
- isPlaying = true;
- isLiveInput = false;
- updatePitch();
-
- return "stop";
+ audioContext.resume();
+ if (isPlaying) {
+ //stop playing and return
+ sourceNode.stop( 0 );
+ sourceNode = null;
+ analyser = null;
+ isPlaying = false;
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
+ window.cancelAnimationFrame( rafID );
+ return "play oscillator";
+ }
+ sourceNode = audioContext.createOscillator();
+
+ analyser = audioContext.createAnalyser();
+ analyser.fftSize = 2048;
+ sourceNode.connect( analyser );
+ analyser.connect( audioContext.destination );
+ sourceNode.start(0);
+ isPlaying = true;
+ isLiveInput = false;
+ updatePitch();
+
+ return "stop";
}
function toggleLiveInput() {
- if (isPlaying) {
- //stop playing and return
- sourceNode.stop( 0 );
- sourceNode = null;
- analyser = null;
- isPlaying = false;
- if (!window.cancelAnimationFrame)
- window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
- window.cancelAnimationFrame( rafID );
- }
- getUserMedia(
- {
- "audio": {
- "mandatory": {
- "googEchoCancellation": "false",
- "googAutoGainControl": "false",
- "googNoiseSuppression": "false",
- "googHighpassFilter": "false"
- },
- "optional": []
- },
- }, gotStream);
+ audioContext.resume();
+ if (isPlaying) {
+ //stop playing and return
+ sourceNode.stop( 0 );
+ sourceNode = null;
+ analyser = null;
+ isPlaying = false;
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
+ window.cancelAnimationFrame( rafID );
+ }
+ getUserMedia(
+ {
+ "audio": {
+ "mandatory": {
+ "googEchoCancellation": "false",
+ "googAutoGainControl": "false",
+ "googNoiseSuppression": "false",
+ "googHighpassFilter": "false"
+ },
+ "optional": []
+ },
+ }, gotStream);
}
function togglePlayback() {
- if (isPlaying) {
- //stop playing and return
- sourceNode.stop( 0 );
- sourceNode = null;
- analyser = null;
- isPlaying = false;
- if (!window.cancelAnimationFrame)
- window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
- window.cancelAnimationFrame( rafID );
- return "start";
- }
-
- sourceNode = audioContext.createBufferSource();
- sourceNode.buffer = theBuffer;
- sourceNode.loop = true;
-
- analyser = audioContext.createAnalyser();
- analyser.fftSize = 2048;
- sourceNode.connect( analyser );
- analyser.connect( audioContext.destination );
- sourceNode.start( 0 );
- isPlaying = true;
- isLiveInput = false;
- updatePitch();
-
- return "stop";
+ audioContext.resume();
+ console.log(`togglePlayback`);
+ if (isPlaying) {
+ //stop playing and return
+ sourceNode.stop( 0 );
+ sourceNode = null;
+ analyser = null;
+ isPlaying = false;
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = window.webkitCancelAnimationFrame;
+ window.cancelAnimationFrame( rafID );
+ return "start";
+ }
+
+ sourceNode = audioContext.createBufferSource();
+ sourceNode.buffer = theBuffer;
+ sourceNode.loop = true;
+
+ analyser = audioContext.createAnalyser();
+ analyser.fftSize = 2048;
+ sourceNode.connect( analyser );
+ analyser.connect( audioContext.destination );
+ sourceNode.start( 0 );
+ isPlaying = true;
+ isLiveInput = false;
+ updatePitch();
+
+ return "stop";
}
var rafID = null;
@@ -207,168 +211,168 @@ var buf = new Float32Array( buflen );
var noteStrings = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
function noteFromPitch( frequency ) {
- var noteNum = 12 * (Math.log( frequency / 440 )/Math.log(2) );
- return Math.round( noteNum ) + 69;
+ var noteNum = 12 * (Math.log( frequency / 440 )/Math.log(2) );
+ return Math.round( noteNum ) + 69;
}
function frequencyFromNoteNumber( note ) {
- return 440 * Math.pow(2,(note-69)/12);
+ return 440 * Math.pow(2,(note-69)/12);
}
function centsOffFromPitch( frequency, note ) {
- return Math.floor( 1200 * Math.log( frequency / frequencyFromNoteNumber( note ))/Math.log(2) );
+ return Math.floor( 1200 * Math.log( frequency / frequencyFromNoteNumber( note ))/Math.log(2) );
}
// this is a float version of the algorithm below - but it's not currently used.
/*
function autoCorrelateFloat( buf, sampleRate ) {
- var MIN_SAMPLES = 4; // corresponds to an 11kHz signal
- var MAX_SAMPLES = 1000; // corresponds to a 44Hz signal
- var SIZE = 1000;
- var best_offset = -1;
- var best_correlation = 0;
- var rms = 0;
-
- if (buf.length < (SIZE + MAX_SAMPLES - MIN_SAMPLES))
- return -1; // Not enough data
-
- for (var i=0;i best_correlation) {
- best_correlation = correlation;
- best_offset = offset;
- }
- }
- if ((rms>0.1)&&(best_correlation > 0.1)) {
- console.log("f = " + sampleRate/best_offset + "Hz (rms: " + rms + " confidence: " + best_correlation + ")");
- }
-// var best_frequency = sampleRate/best_offset;
+ var MIN_SAMPLES = 4; // corresponds to an 11kHz signal
+ var MAX_SAMPLES = 1000; // corresponds to a 44Hz signal
+ var SIZE = 1000;
+ var best_offset = -1;
+ var best_correlation = 0;
+ var rms = 0;
+
+ if (buf.length < (SIZE + MAX_SAMPLES - MIN_SAMPLES))
+ return -1; // Not enough data
+
+ for (var i=0;i best_correlation) {
+ best_correlation = correlation;
+ best_offset = offset;
+ }
+ }
+ if ((rms>0.1)&&(best_correlation > 0.1)) {
+ console.log("f = " + sampleRate/best_offset + "Hz (rms: " + rms + " confidence: " + best_correlation + ")");
+ }
+ // var best_frequency = sampleRate/best_offset;
}
*/
-var MIN_SAMPLES = 0; // will be initialized when AudioContext is created.
-var GOOD_ENOUGH_CORRELATION = 0.9; // this is the "bar" for how close a correlation needs to be
-
-function autoCorrelate( buf, sampleRate ) {
- var SIZE = buf.length;
- var MAX_SAMPLES = Math.floor(SIZE/2);
- var best_offset = -1;
- var best_correlation = 0;
- var rms = 0;
- var foundGoodCorrelation = false;
- var correlations = new Array(MAX_SAMPLES);
-
- for (var i=0;iGOOD_ENOUGH_CORRELATION) && (correlation > lastCorrelation)) {
- foundGoodCorrelation = true;
- if (correlation > best_correlation) {
- best_correlation = correlation;
- best_offset = offset;
- }
- } else if (foundGoodCorrelation) {
- // short-circuit - we found a good correlation, then a bad one, so we'd just be seeing copies from here.
- // Now we need to tweak the offset - by interpolating between the values to the left and right of the
- // best offset, and shifting it a bit. This is complex, and HACKY in this code (happy to take PRs!) -
- // we need to do a curve fit on correlations[] around best_offset in order to better determine precise
- // (anti-aliased) offset.
-
- // we know best_offset >=1,
- // since foundGoodCorrelation cannot go to true until the second pass (offset=1), and
- // we can't drop into this clause until the following pass (else if).
- var shift = (correlations[best_offset+1] - correlations[best_offset-1])/correlations[best_offset];
- return sampleRate/(best_offset+(8*shift));
- }
- lastCorrelation = correlation;
- }
- if (best_correlation > 0.01) {
- // console.log("f = " + sampleRate/best_offset + "Hz (rms: " + rms + " confidence: " + best_correlation + ")")
- return sampleRate/best_offset;
- }
- return -1;
-// var best_frequency = sampleRate/best_offset;
-}
+ var MIN_SAMPLES = 0; // will be initialized when AudioContext is created.
+ var GOOD_ENOUGH_CORRELATION = 0.9; // this is the "bar" for how close a correlation needs to be
+
+ function autoCorrelate( buf, sampleRate ) {
+ var SIZE = buf.length;
+ var MAX_SAMPLES = Math.floor(SIZE/2);
+ var best_offset = -1;
+ var best_correlation = 0;
+ var rms = 0;
+ var foundGoodCorrelation = false;
+ var correlations = new Array(MAX_SAMPLES);
+
+ for (var i=0;iGOOD_ENOUGH_CORRELATION) && (correlation > lastCorrelation)) {
+ foundGoodCorrelation = true;
+ if (correlation > best_correlation) {
+ best_correlation = correlation;
+ best_offset = offset;
+ }
+ } else if (foundGoodCorrelation) {
+ // short-circuit - we found a good correlation, then a bad one, so we'd just be seeing copies from here.
+ // Now we need to tweak the offset - by interpolating between the values to the left and right of the
+ // best offset, and shifting it a bit. This is complex, and HACKY in this code (happy to take PRs!) -
+ // we need to do a curve fit on correlations[] around best_offset in order to better determine precise
+ // (anti-aliased) offset.
+
+ // we know best_offset >=1,
+ // since foundGoodCorrelation cannot go to true until the second pass (offset=1), and
+ // we can't drop into this clause until the following pass (else if).
+ var shift = (correlations[best_offset+1] - correlations[best_offset-1])/correlations[best_offset];
+ return sampleRate/(best_offset+(8*shift));
+ }
+ lastCorrelation = correlation;
+ }
+ if (best_correlation > 0.01) {
+ // console.log("f = " + sampleRate/best_offset + "Hz (rms: " + rms + " confidence: " + best_correlation + ")")
+ return sampleRate/best_offset;
+ }
+ return -1;
+ // var best_frequency = sampleRate/best_offset;
+ }
function updatePitch( time ) {
- var cycles = new Array;
- analyser.getFloatTimeDomainData( buf );
- var ac = autoCorrelate( buf, audioContext.sampleRate );
- // TODO: Paint confidence meter on canvasElem here.
-
- if (DEBUGCANVAS) { // This draws the current waveform, useful for debugging
- waveCanvas.clearRect(0,0,512,256);
- waveCanvas.strokeStyle = "red";
- waveCanvas.beginPath();
- waveCanvas.moveTo(0,0);
- waveCanvas.lineTo(0,256);
- waveCanvas.moveTo(128,0);
- waveCanvas.lineTo(128,256);
- waveCanvas.moveTo(256,0);
- waveCanvas.lineTo(256,256);
- waveCanvas.moveTo(384,0);
- waveCanvas.lineTo(384,256);
- waveCanvas.moveTo(512,0);
- waveCanvas.lineTo(512,256);
- waveCanvas.stroke();
- waveCanvas.strokeStyle = "black";
- waveCanvas.beginPath();
- waveCanvas.moveTo(0,buf[0]);
- for (var i=1;i<512;i++) {
- waveCanvas.lineTo(i,128+(buf[i]*128));
- }
- waveCanvas.stroke();
- }
-
- if (ac == -1) {
- detectorElem.className = "vague";
- pitchElem.innerText = "--";
- noteElem.innerText = "-";
- detuneElem.className = "";
- detuneAmount.innerText = "--";
- } else {
- detectorElem.className = "confident";
- pitch = ac;
- pitchElem.innerText = Math.round( pitch ) ;
- var note = noteFromPitch( pitch );
- noteElem.innerHTML = noteStrings[note%12];
- var detune = centsOffFromPitch( pitch, note );
- if (detune == 0 ) {
- detuneElem.className = "";
- detuneAmount.innerHTML = "--";
- } else {
- if (detune < 0)
- detuneElem.className = "flat";
- else
- detuneElem.className = "sharp";
- detuneAmount.innerHTML = Math.abs( detune );
- }
- }
-
- if (!window.requestAnimationFrame)
- window.requestAnimationFrame = window.webkitRequestAnimationFrame;
- rafID = window.requestAnimationFrame( updatePitch );
+ var cycles = new Array;
+ analyser.getFloatTimeDomainData( buf );
+ var ac = autoCorrelate( buf, audioContext.sampleRate );
+ // TODO: Paint confidence meter on canvasElem here.
+
+ if (DEBUGCANVAS) { // This draws the current waveform, useful for debugging
+ waveCanvas.clearRect(0,0,512,256);
+ waveCanvas.strokeStyle = "red";
+ waveCanvas.beginPath();
+ waveCanvas.moveTo(0,0);
+ waveCanvas.lineTo(0,256);
+ waveCanvas.moveTo(128,0);
+ waveCanvas.lineTo(128,256);
+ waveCanvas.moveTo(256,0);
+ waveCanvas.lineTo(256,256);
+ waveCanvas.moveTo(384,0);
+ waveCanvas.lineTo(384,256);
+ waveCanvas.moveTo(512,0);
+ waveCanvas.lineTo(512,256);
+ waveCanvas.stroke();
+ waveCanvas.strokeStyle = "black";
+ waveCanvas.beginPath();
+ waveCanvas.moveTo(0,buf[0]);
+ for (var i=1;i<512;i++) {
+ waveCanvas.lineTo(i,128+(buf[i]*128));
+ }
+ waveCanvas.stroke();
+ }
+
+ if (ac == -1) {
+ detectorElem.className = "vague";
+ pitchElem.innerText = "--";
+ noteElem.innerText = "-";
+ detuneElem.className = "";
+ detuneAmount.innerText = "--";
+ } else {
+ detectorElem.className = "confident";
+ pitch = ac;
+ pitchElem.innerText = Math.round( pitch ) ;
+ var note = noteFromPitch( pitch );
+ noteElem.innerHTML = noteStrings[note%12];
+ var detune = centsOffFromPitch( pitch, note );
+ if (detune == 0 ) {
+ detuneElem.className = "";
+ detuneAmount.innerHTML = "--";
+ } else {
+ if (detune < 0)
+ detuneElem.className = "flat";
+ else
+ detuneElem.className = "sharp";
+ detuneAmount.innerHTML = Math.abs( detune );
+ }
+ }
+
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = window.webkitRequestAnimationFrame;
+ rafID = window.requestAnimationFrame( updatePitch );
}
diff --git a/sounds/whitman.mp3 b/sounds/whitman.mp3
new file mode 100644
index 0000000..fe49d37
Binary files /dev/null and b/sounds/whitman.mp3 differ