From d0722f4b4b16f2bf9b416e609a6280da969b1f76 Mon Sep 17 00:00:00 2001 From: Blake Gilmore Date: Mon, 30 Nov 2015 12:34:13 -0800 Subject: [PATCH] add pitch matcher --- .DS_Store | Bin 0 -> 6148 bytes js/pitchdetect.js | 168 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 147 insertions(+), 21 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..79c0fe241ed7a74c74c9e51d5ff86dcf8e2062c7 GIT binary patch literal 6148 zcmeHK%}T>S5Zp-@por6wfMm6DVuwMZ%C>|^-<7=q|K z_$c0-*2%diU;d)>G-e136KD!uc}RCao1*2?7z_}Fv#vsxqS))F`A z#1*%FqGr+$JY!egr151RL{W2KyWXJ;x^;VDCkhi+hCzEM^z=xEe$=f*ozRa@Yx;dh z(oY(7d$HOME+Mq)hQXnXr6-#*xw3Nj)a|8SuUy_Jit^f4zbJZ}o28;yUoZ9h_OoRz zuWW4ZAGi9~H@A29506hTulfWyCXF($VKBZ8EHa1zVt^Q!Xa>wRXcZXwec)w8F8Fw_&NDL4IqYO;xsg3o2@8|x1G>B%z05LF84Djr3ZMOm` znc6y*9M)O^dJc+$`%;aA5HM6JhP_ydmq3+(U!Vc#7)&*S2ZVkE6b&>G1AofECw(Da ACIA2c literal 0 HcmV?d00001 diff --git a/js/pitchdetect.js b/js/pitchdetect.js index fc607b0..2d7ee98 100644 --- a/js/pitchdetect.js +++ b/js/pitchdetect.js @@ -1,3 +1,10 @@ +/*The following code builds upon Chris Wilson's +pitch detector : https://github.com/cwilso/PitchDetect. +Blake Gilmore restructured and added to Chris' code to +create a pitch matcher, which allows you to try to match + the pitch of your favorite song. See code for the matcher below autoCorrelate(). +*/ + /* The MIT License (MIT) @@ -12,14 +19,6 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. */ window.AudioContext = window.AudioContext || window.webkitAudioContext; @@ -39,11 +38,23 @@ var detectorElem, detuneElem, detuneAmount; -window.onload = function() { +var notes=[], +displayed=[], +targetNotes=0, +missedNotes=[], +accuracy=0, +userCount=0, +listening=true, +targetNoteNum=168, +pathToSong="../static/new_recording_2.ogg"; +notesRight=0; + +// loads the song when a button is clicked +function loadSong() { 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.open("GET", pathToSong, true); request.responseType = "arraybuffer"; request.onload = function() { audioContext.decodeAudioData( request.response, function(buffer) { @@ -88,9 +99,7 @@ window.onload = function() { return false; }; - - -} +} // close the onload function for the song function error() { alert('Stream generation failed.'); @@ -119,6 +128,7 @@ function gotStream(stream) { updatePitch(); } +// mostly for testing purposes-plays a single tone function toggleOscillator() { if (isPlaying) { //stop playing and return @@ -145,7 +155,12 @@ function toggleOscillator() { return "stop"; } +// called when it's the user's turn to sing +// calls updatePitch() continually to result in a stream of notes function toggleLiveInput() { + userCount+=1; + notes=[]; + listening=true; if (isPlaying) { //stop playing and return sourceNode.stop( 0 ); @@ -170,6 +185,8 @@ function toggleLiveInput() { }, gotStream); } +// called to play target audio for matching +// calls updatePitch() continually to result in a stream of notes function togglePlayback() { if (isPlaying) { //stop playing and return @@ -184,8 +201,8 @@ function togglePlayback() { } sourceNode = audioContext.createBufferSource(); - sourceNode.buffer = theBuffer; - sourceNode.loop = true; + sourceNode.buffer= theBuffer; + sourceNode.loop = false; analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; @@ -195,7 +212,6 @@ function togglePlayback() { isPlaying = true; isLiveInput = false; updatePitch(); - return "stop"; } @@ -204,8 +220,10 @@ var tracks = null; var buflen = 1024; var buf = new Float32Array( buflen ); +// the notes var noteStrings = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; +// start of the math to calculate the notes function noteFromPitch( frequency ) { var noteNum = 12 * (Math.log( frequency / 440 )/Math.log(2) ); return Math.round( noteNum ) + 69; @@ -257,6 +275,7 @@ function autoCorrelateFloat( buf, sampleRate ) { var MIN_SAMPLES = 0; // will be initialized when AudioContext is created. +// yields a number 'ac' that's used to get the note function autoCorrelate( buf, sampleRate ) { var SIZE = buf.length; var MAX_SAMPLES = Math.floor(SIZE/2); @@ -312,11 +331,113 @@ function autoCorrelate( buf, sampleRate ) { // var best_frequency = sampleRate/best_offset; } +function tunerShow(){ + $("#level_buttons").css("display","none"); + $("#tuner_stuff").css("display", "block"); +} + +// clear the page and set up the tuner when you click on freeplay +// start showing notes +$(".freeplay-button").on('click',function(){ + targetNoteNum=1000; + doEasy(); + $("#listen-button").css("display","none"); + $("#myturn").css("display","none"); + $("#return-button").css("display", "block"); + toggleLiveInput(); +}); + + +// when you click on easy, call the function to make tuner_stuff show +$(".easy-button").on('click', function(){ + doEasy(); +}); + +// when you click on challenge, call the function to make tuner_stuff show +$('.challenge-button').on('click', function(){ + doChallenge(); + tunerShow(); +}); + +// change settings for challenge mode +function doChallenge(){ + targetNoteNum=290; + pathToSong="../static/mariah_sample.ogg"; + loadSong(); +} + +// load song for easy mode +function doEasy(){ + loadSong(); + tunerShow(); +} + +// calculate the user's accuracy by comparing the +// array of target notes to the array of notes the user sang +function calculateAccuracy(){ + for (i=0;i 75){ + notesRight="3/3"; + } + else if (55 < accuracy && accuracy < 75){ + notesRight="2/3"; + } + else if (35 < accuracy && accuracy < 55) { + notesRight="1/2"; + } + else if (15 < accuracy && accuracy < 35) { + notesRight="1/3"; + } + else { + notesRight="0"; + } + $("#index-alert").prepend("you got about "+notesRight+" of the notes."); + $(".accuracy").css("display", "block"); + } + console.log(accuracy+"%"); + listening=false; + userCount=0; +} + +/* +if the length of the array of notes is longer than the specified value, +calculate the accuracy (if the user has sang their notes) +OR +save the array as the target array for calculation +after the user has sang their notes as well +*/ +function checkNote(noteString){ + if (notes.length0 && targetNotes!=0){ + calculateAccuracy(); + } else { + targetNotes=notes; + } + } +} + +// uses the number calculated above to yield a note from noteStrings function updatePitch( time ) { var cycles = new Array; analyser.getFloatTimeDomainData( buf ); - var ac = autoCorrelate( buf, audioContext.sampleRate ); - // TODO: Paint confidence meter on canvasElem here. + var ac = autoCorrelate( buf, audioContext.sampleRate ); if (DEBUGCANVAS) { // This draws the current waveform, useful for debugging waveCanvas.clearRect(0,0,512,256); @@ -342,18 +463,22 @@ function updatePitch( time ) { waveCanvas.stroke(); } + if (ac == -1) { - detectorElem.className = "vague"; + detectorElem.className = "container vague"; pitchElem.innerText = "--"; noteElem.innerText = "-"; detuneElem.className = ""; detuneAmount.innerText = "--"; } else { - detectorElem.className = "confident"; + detectorElem.className = "container confident"; pitch = ac; pitchElem.innerText = Math.round( pitch ) ; var note = noteFromPitch( pitch ); - noteElem.innerHTML = noteStrings[note%12]; + var noteString = noteStrings[note%12]; + if (listening === true){ + checkNote(noteString); + } var detune = centsOffFromPitch( pitch, note ); if (detune == 0 ) { detuneElem.className = ""; @@ -371,3 +496,4 @@ function updatePitch( time ) { window.requestAnimationFrame = window.webkitRequestAnimationFrame; rafID = window.requestAnimationFrame( updatePitch ); } +