-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmetronome.js
91 lines (74 loc) · 2.64 KB
/
metronome.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* Snippet from https://github.com/grantjames/metronome/blob/master/metronome.js
*
*/
class Metronome {
constructor(tempo = 120) {
this.audioContext = null;
this.notesInQueue = []; // notes that have been put into the web audio and may or may not have been played yet {note, time}
this.currentBeatInBar = 0;
this.beatsPerBar = 4;
this.tempo = tempo;
this.lookahead = 25; // How frequently to call scheduling function (in milliseconds)
this.scheduleAheadTime = 0.1; // How far ahead to schedule audio (sec)
this.nextNoteTime = 0.0; // when the next note is due
this.isRunning = false;
this.intervalID = null;
}
nextNote() {
// Advance current note and time by a quarter note (crotchet if you're posh)
var secondsPerBeat = 60.0 / this.tempo; // Notice this picks up the CURRENT tempo value to calculate beat length.
this.nextNoteTime += secondsPerBeat; // Add beat length to last beat time
this.currentBeatInBar++; // Advance the beat number, wrap to zero
if (this.currentBeatInBar == this.beatsPerBar) {
this.currentBeatInBar = 0;
}
}
scheduleNote(beatNumber, time) {
// push the note on the queue, even if we're not playing.
this.notesInQueue.push({ note: beatNumber, time: time });
// create an oscillator
const osc = this.audioContext.createOscillator();
const envelope = this.audioContext.createGain();
osc.frequency.value = beatNumber % this.beatsPerBar == 0 ? 1000 : 800;
envelope.gain.value = 1;
envelope.gain.exponentialRampToValueAtTime(1, time + 0.001);
envelope.gain.exponentialRampToValueAtTime(0.001, time + 0.02);
osc.connect(envelope);
envelope.connect(this.audioContext.destination);
osc.start(time);
osc.stop(time + 0.03);
}
scheduler() {
// while there are notes that will need to play before the next interval, schedule them and advance the pointer.
while (
this.nextNoteTime <
this.audioContext.currentTime + this.scheduleAheadTime
) {
this.scheduleNote(this.currentBeatInBar, this.nextNoteTime);
this.nextNote();
}
}
start() {
if (this.isRunning) return;
if (this.audioContext == null) {
this.audioContext = new (window.AudioContext ||
window.webkitAudioContext)();
}
this.isRunning = true;
this.currentBeatInBar = 0;
this.nextNoteTime = this.audioContext.currentTime + 0.05;
this.intervalID = setInterval(() => this.scheduler(), this.lookahead);
}
stop() {
this.isRunning = false;
clearInterval(this.intervalID);
}
startStop() {
if (this.isRunning) {
this.stop();
} else {
this.start();
}
}
}