-
Notifications
You must be signed in to change notification settings - Fork 6
/
notes.txt
73 lines (55 loc) · 2.73 KB
/
notes.txt
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
You just discovered web audio, and you want to make music.
DON'T USE A FOR LOOP. JavaScript is single threaded.
I will not synchronize to the JavaScript clock.
DO NOT USE THE JAVASCRIPT CLOCK. Okay? Say it with me.
"I will not use the Javascript clock."
Now I know you really want to use the JavaScript clock. HERE'S WHY NOT.
setInterval and setTimeout aren't *actually* guaranteed to execute in the time specified.
JavaScript is single threaded, therefore each unit of execution blocks the progess of another asynchronous event.
JavaScript will not enqueue more than one instance of a setInterval handler. Whatever time you put, it's guaranteed to run AFTER that time, but it could be delayed if there is blocking code.
You don't want to rely on setInterval to schedule notes (which will FOR SURE be played at the time you scheduled them because Web Audio API), because you might schedule them at the wrong time. You might say, that event was being a clock block.
You might be tempted to do something like this:
```
function schedule() {
playNote(audioContext.currentTime);
setTimeout(schedule, secondsPerSixteenthNote);
}
```
So how do we fix this? You look ahead. When scheduling a note, you look ahead.
Sequencer:
Get a play event.
Reset startTime and noteTime
startTime is the time at which you hit "play".
currentTime is the time since startTime
```javascript
while (nextNoteTime < currentTime + lookAheadTime ) {
scheduleNote( nextNoteTime );
advanceNote();
}
```
the schedule loop looks something like this:
```
function schedule() {
var currentTime = audioContext.currentTime - startTime;
while (nextNoteTime < currentTime + lookAheadTime ) {
scheduleNotes( nextNoteTime );
advanceNote();
}
timeoutId = setTimeout(schedule, 0);
}
```
A few things to break down:
currentTime is the time SINCE hitting play and starting the sequencer
nextNoteTime is the scheduled time for the next note to play, based on the tempo.
lookAheadTime depends on the execution speed of JavaScript. In my case, I used 200 ms.
schedule notes AHEAD OF TIME, with the exact time that you want them to play.
save timeoutId so that you can stop the sequencer (clearTimeout(timeoutId);).
Most of the time, the while loop isn't even run, because after it executes successfully
and a note is scheduled, it increments nextNoteTime to the next time that a note is scheduled, which may or may not be less than currentTime + lookAheadTime.
```
function advanceNote() {
var secondsPerBeat = 60.0 / currentTempo();
nextNoteTime += beatGranularity * secondsPerBeat;
}
```
when advancing the note, need to grab the current tempo and advance the note based on the time signature. In my case, and in most cases, the grid will be sixteenth notes, so beatGranularity = 0.25.