-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmidi_clock_worklet.js
More file actions
82 lines (74 loc) · 2.4 KB
/
Copy pathmidi_clock_worklet.js
File metadata and controls
82 lines (74 loc) · 2.4 KB
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
// AudioWorklet processor for background-tab-safe MIDI scheduling
class MidiClockProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.notes = [];
this.ccEvents = [];
this.playing = false;
this.startFrame = 0;
this.lapseFrames = 0;
this.speed = 1;
this.lastNoteIdx = -1;
this.lastCCIdx = -1;
this.port.onmessage = e => {
const { type, data } = e.data;
switch (type) {
case 'load':
this.notes = data.notes;
this.ccEvents = data.cc || [];
this.lastNoteIdx = -1;
this.lastCCIdx = -1;
this.lapseFrames = 0;
break;
case 'play':
this.playing = true;
this.startFrame = currentFrame - this.lapseFrames / this.speed;
break;
case 'pause':
this.playing = false;
break;
case 'seek':
this.lapseFrames = data.time * sampleRate;
this.startFrame = currentFrame - this.lapseFrames / this.speed;
this.lastNoteIdx = this.notes.findIndex(n => n.time > data.time) - 1;
this.lastCCIdx = this.ccEvents.findIndex(c => c.time > data.time) - 1;
break;
case 'speed':
this.speed = data.speed;
this.startFrame = currentFrame - this.lapseFrames / this.speed;
break;
}
};
}
process() {
if (!this.playing || !this.notes.length) return true;
this.lapseFrames = (currentFrame - this.startFrame) * this.speed;
const lapse = this.lapseFrames / sampleRate;
// Check notes
for (let i = this.lastNoteIdx + 1; i < this.notes.length; i++) {
const note = this.notes[i];
if (!note) break;
if (note.time > lapse + 0.05) break; // 50ms lookahead
if (note.time >= lapse - 0.02) {
this.port.postMessage({ type: 'noteOn', note });
}
this.lastNoteIdx = i;
}
// Check CC events
for (let i = this.lastCCIdx + 1; i < this.ccEvents.length; i++) {
const cc = this.ccEvents[i];
if (!cc) break;
if (cc.time > lapse + 0.05) break;
if (cc.time >= lapse - 0.02) {
this.port.postMessage({ type: 'cc', cc });
}
this.lastCCIdx = i;
}
// Post time update every ~50ms (2048 samples at 44.1k)
if (currentFrame % 2048 < 128) {
this.port.postMessage({ type: 'time', lapse });
}
return true;
}
}
registerProcessor('midi-clock', MidiClockProcessor);