-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathmetronome.js
92 lines (76 loc) · 2.93 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
92
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();
}
}
}