-
Notifications
You must be signed in to change notification settings - Fork 1
/
noteEvent.js
94 lines (89 loc) · 4.21 KB
/
noteEvent.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
93
94
// represents stream of note on/off events for a (single) midi pitch/channel
// if duration in milliseconds <= 0, then you'll need to explicitly release()
// the note event to send the requisite note off(s).
class NoteEvent {
constructor(note, delayRepeats = 0, delayTimeInMilliseconds = 500,
midiOutput, loggingCallback) {
// copy the note
this.note = note.copySelf();
// where to send the midi bytes
this.midiOutput = midiOutput;
// number of delay repeats and delay time
this.delayRepeats = delayRepeats;
this.delayTimeInMilliseconds = delayTimeInMilliseconds;
// the number of attacks/releases so we don't send unbalanced messages
// releases must always be <= attacks
this.attacks = 0;
this.releases = 0;
// executes with midi sent
if (loggingCallback == undefined) {
this.loggingCallback = () => {};
}
this.loggingCallback = loggingCallback;
}
// --------------------------------------------------------------- private
// sends bytes to the midi output and logs them with the logging callback
// https://www.w3.org/TR/webmidi/#midioutput-interface
send(midiBytesArray, when) {
this.midiOutput.send(midiBytesArray, when);
this.loggingCallback(midiBytesArray);
// for the number of (delay) repeats, trigger our delayed midi events
// with decaying velocity (to simulate a delay effect)
let [statusByte, pitch, velocity] = midiBytesArray;
for (let i = 1; i <= this.delayRepeats; ++i) {
when += this.delayTimeInMilliseconds;
let reductionFactor = i / (this.delayRepeats + 1);
// make sure velocity is at least 1, this way we can avoid
// sending a note off event through a note on with 0 velocity
let reducedVelocity = Math.max(1, velocity -
Math.floor(velocity * reductionFactor));
this.midiOutput.send([statusByte, pitch, reducedVelocity], when);
// as to why the delayed midi events above cannot be logged
// the midi system can *only* queue *midi* events (nothing else)
// arbitrary callbacks cannot be timed accurately
}
}
// ---------------------------------------------------------------- public
// triggers a (limited duration) note on/off event stream
// with an optional timestamp (see below URL for info on the timestamp)
// https://www.w3.org/TR/webmidi/#midioutput-interface
attackRelease(durationInMilliseconds = 500, when = 0) {
let noteOnMidiBytesArray = this.note.toNoteOnMidiBytesArray();
let noteOffMidiBytesArray = this.note.toNoteOffMidiBytesArray();
this.send(noteOnMidiBytesArray, when);
this.send(noteOffMidiBytesArray, when + durationInMilliseconds);
// there is the potential that the midiOutput.clear() method will be
// called in which case the attacks/releases will be inaccurate
// reflections of the actual state of note on and off pairs.
this.attacks += 1;
this.releases += 1;
}
// triggers (unlimited duration) note on (only) event stream
// with an optional timestamp (see below URL for info on the timestamp)
// https://www.w3.org/TR/webmidi/#midioutput-interface
attack(when) {
if (when === undefined) {
when = performance.now();
}
let noteOnMidiBytesArray = this.note.toNoteOnMidiBytesArray();
this.send(noteOnMidiBytesArray, when);
this.attacks += 1;
}
// triggers (unlimited duration) note off (only) event stream after startTimeInMilliseconds
// with an optional timestamp (see below URL for info on the timestamp)
// https://www.w3.org/TR/webmidi/#midioutput-interface
release(when) {
if (this.releases < this.attacks) {
if (when === undefined) {
when = performance.now();
}
let noteOffMidiBytesArray = this.note.toNoteOffMidiBytesArray();
this.send(noteOffMidiBytesArray, when);
this.releases += 1;
}
}
// gets a (copied) note struct object {pitch, velocity, channel}
getNote() {
return this.note.copySelf();
}
}