Created
February 23, 2016 16:31
-
-
Save MT--/ece6e388a693416aa7e7 to your computer and use it in GitHub Desktop.
Angular(1) factory to create metronome based on Web Audio API with help from https://github.com/cwilso/metronome/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* make a new Metronome based on the provided temp | |
* ideas from: | |
* https://github.com/cwilso/metronome/ | |
*/ | |
angular.module('main') | |
.factory('Metronome', function ( $window ) { | |
// build the metronome for injection | |
var Metronome = function ( bpm ) { | |
// boot the audio platform | |
var AudioContext = $window.AudioContext || $window.webkitAudioContext; | |
var audioContext = new AudioContext(); | |
audioContext.suspend(); | |
// decrease the volume and connect to output | |
var gainNode = audioContext.createGain(); | |
gainNode.connect(audioContext.destination); | |
gainNode.gain.value = 0.1; | |
// specify the state | |
var nextNoteTime = 0, | |
currentSixteenth = 0, | |
lookahead = 250, | |
noteRes = 2, // 0 == 16th, 1 == 8th, 2 == 4th | |
noteLen = 0.05, | |
scheduleAheadTime = 0.25, | |
timerID = 0; | |
var isPlaying = false; | |
// calculate actual length of 1/4 note | |
// bpm is pulled from the song info, it won't change | |
var secPerBeat = 60.0 / bpm; | |
/** | |
* advance note and playhead by a 16th note | |
*/ | |
var nextNote = function () { | |
// add length of 16th note | |
nextNoteTime += 0.25 * secPerBeat; | |
// advance the 16th note marker | |
currentSixteenth++; | |
// wrap back to zero | |
if ( currentSixteenth === 16 ) { | |
currentSixteenth = 0; | |
} // end if | |
}; | |
/** | |
* do the next note/pitch | |
* @param beatNumber | |
* @param time | |
*/ | |
var scheduleNote = function ( beatNumber, time ) { | |
// we are skipping non-eighth sixteen notes | |
if ( (noteRes === 1) && (beatNumber % 2) ) { | |
return; | |
} // end if | |
// we are skipping non-quarter eighth notes | |
if ( (noteRes === 2) && (beatNumber % 4) ) { | |
return; | |
} // end if | |
// create osc | |
var osc = audioContext.createOscillator(); | |
osc.connect(gainNode); | |
// set some pitches to differentiate notes | |
if ( !(beatNumber % 16) ) { | |
osc.frequency.value = 1320.0; // Downbeat | |
} else if ( beatNumber % 4 ) { | |
osc.frequency.value = 440.0; // Sixteenth Notes | |
} else { | |
osc.frequency.value = 880.0; // Quarter Notes | |
} // end if | |
osc.start(time); | |
osc.stop(time + noteLen); | |
}; | |
/** | |
* notes that need to play before the next interval | |
* schedule these and advance playhead | |
*/ | |
var scheduler = function () { | |
while ( nextNoteTime < (audioContext.currentTime + scheduleAheadTime) ) { | |
scheduleNote(currentSixteenth, nextNoteTime); | |
nextNote(); | |
} // end while | |
timerID = setTimeout(scheduler, lookahead); | |
}; | |
/** | |
* start the metronome | |
*/ | |
this.play = function () { | |
isPlaying = !isPlaying; | |
if ( isPlaying ) { | |
audioContext.resume(); | |
currentSixteenth = 0; | |
nextNoteTime = audioContext.currentTime; | |
scheduler(); | |
} else { | |
clearTimeout(timerID); | |
} // end if | |
}; | |
/** | |
* tell the controller if the metronome is playing | |
* @returns {boolean} | |
*/ | |
this.getIsPlaying = function () { | |
return isPlaying; | |
}; | |
/** | |
* tell the controller the current playhead time | |
* @returns {*|number|Number} | |
*/ | |
this.getCurrentTime = function () { | |
return audioContext.currentTime; | |
}; | |
}; | |
return Metronome; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment