@marmooo/midy 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/esm/midy-GM1.d.ts +8 -26
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +140 -80
- package/esm/midy-GM2.d.ts +8 -31
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +155 -95
- package/esm/midy-GMLite.d.ts +8 -26
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +134 -75
- package/esm/midy.d.ts +15 -37
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +228 -111
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +8 -26
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +140 -80
- package/script/midy-GM2.d.ts +8 -31
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +155 -95
- package/script/midy-GMLite.d.ts +8 -26
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +134 -75
- package/script/midy.d.ts +15 -37
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +228 -111
package/script/midy.js
CHANGED
|
@@ -4,7 +4,19 @@ exports.Midy = void 0;
|
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime
|
|
7
|
+
constructor(noteNumber, velocity, startTime) {
|
|
8
|
+
Object.defineProperty(this, "voice", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "voiceParams", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
8
20
|
Object.defineProperty(this, "index", {
|
|
9
21
|
enumerable: true,
|
|
10
22
|
configurable: true,
|
|
@@ -17,6 +29,12 @@ class Note {
|
|
|
17
29
|
writable: true,
|
|
18
30
|
value: false
|
|
19
31
|
});
|
|
32
|
+
Object.defineProperty(this, "pending", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: true
|
|
37
|
+
});
|
|
20
38
|
Object.defineProperty(this, "bufferSource", {
|
|
21
39
|
enumerable: true,
|
|
22
40
|
configurable: true,
|
|
@@ -98,8 +116,6 @@ class Note {
|
|
|
98
116
|
this.noteNumber = noteNumber;
|
|
99
117
|
this.velocity = velocity;
|
|
100
118
|
this.startTime = startTime;
|
|
101
|
-
this.voice = voice;
|
|
102
|
-
this.voiceParams = voiceParams;
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
121
|
const drumExclusiveClassesByKit = new Array(57);
|
|
@@ -145,14 +161,19 @@ const defaultControllerState = {
|
|
|
145
161
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
146
162
|
link: { type: 127, defaultValue: 0 },
|
|
147
163
|
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
modulationDepthMSB: { type: 128 + 1, defaultValue: 0 },
|
|
165
|
+
portamentoTimeMSB: { type: 128 + 5, defaultValue: 0 },
|
|
150
166
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
167
|
+
volumeMSB: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
168
|
+
panMSB: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
169
|
+
expressionMSB: { type: 128 + 11, defaultValue: 1 },
|
|
154
170
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
171
|
+
modulationDepthLSB: { type: 128 + 33, defaultValue: 0 },
|
|
172
|
+
portamentoTimeLSB: { type: 128 + 37, defaultValue: 0 },
|
|
155
173
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
174
|
+
volumeLSB: { type: 128 + 39, defaultValue: 0 },
|
|
175
|
+
panLSB: { type: 128 + 42, defaultValue: 0 },
|
|
176
|
+
expressionLSB: { type: 128 + 43, defaultValue: 0 },
|
|
156
177
|
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
157
178
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
158
179
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
@@ -165,6 +186,7 @@ const defaultControllerState = {
|
|
|
165
186
|
vibratoRate: { type: 128 + 76, defaultValue: 64 / 127 },
|
|
166
187
|
vibratoDepth: { type: 128 + 77, defaultValue: 64 / 127 },
|
|
167
188
|
vibratoDelay: { type: 128 + 78, defaultValue: 64 / 127 },
|
|
189
|
+
portamentoNoteNumber: { type: 128 + 84, defaultValue: 0 },
|
|
168
190
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
169
191
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
170
192
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -356,6 +378,12 @@ class Midy {
|
|
|
356
378
|
writable: true,
|
|
357
379
|
value: new Map()
|
|
358
380
|
});
|
|
381
|
+
Object.defineProperty(this, "realtimeVoiceCache", {
|
|
382
|
+
enumerable: true,
|
|
383
|
+
configurable: true,
|
|
384
|
+
writable: true,
|
|
385
|
+
value: new Map()
|
|
386
|
+
});
|
|
359
387
|
Object.defineProperty(this, "isPlaying", {
|
|
360
388
|
enumerable: true,
|
|
361
389
|
configurable: true,
|
|
@@ -542,7 +570,7 @@ class Midy {
|
|
|
542
570
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
543
571
|
}
|
|
544
572
|
createChannelAudioNodes(audioContext) {
|
|
545
|
-
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.
|
|
573
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.panMSB.defaultValue);
|
|
546
574
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
547
575
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
548
576
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -585,10 +613,9 @@ class Midy {
|
|
|
585
613
|
return channels;
|
|
586
614
|
}
|
|
587
615
|
async createAudioBuffer(voiceParams) {
|
|
588
|
-
const sample = voiceParams
|
|
589
|
-
const
|
|
590
|
-
const
|
|
591
|
-
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
616
|
+
const { sample, start, end } = voiceParams;
|
|
617
|
+
const sampleEnd = sample.data.length + end;
|
|
618
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
|
|
592
619
|
return audioBuffer;
|
|
593
620
|
}
|
|
594
621
|
isLoopDrum(channel, noteNumber) {
|
|
@@ -620,12 +647,10 @@ class Midy {
|
|
|
620
647
|
const startTime = event.startTime + schedulingOffset;
|
|
621
648
|
switch (event.type) {
|
|
622
649
|
case "noteOn":
|
|
623
|
-
await this.
|
|
650
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
624
651
|
break;
|
|
625
652
|
case "noteOff": {
|
|
626
|
-
|
|
627
|
-
if (notePromise)
|
|
628
|
-
this.notePromises.push(notePromise);
|
|
653
|
+
this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
629
654
|
break;
|
|
630
655
|
}
|
|
631
656
|
case "noteAftertouch":
|
|
@@ -662,6 +687,7 @@ class Midy {
|
|
|
662
687
|
this.exclusiveClassNotes.fill(undefined);
|
|
663
688
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
664
689
|
this.voiceCache.clear();
|
|
690
|
+
this.realtimeVoiceCache.clear();
|
|
665
691
|
for (let i = 0; i < this.channels.length; i++) {
|
|
666
692
|
this.channels[i].scheduledNotes = [];
|
|
667
693
|
this.resetChannelStates(i);
|
|
@@ -706,7 +732,6 @@ class Midy {
|
|
|
706
732
|
finished = true;
|
|
707
733
|
break;
|
|
708
734
|
}
|
|
709
|
-
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
710
735
|
if (this.isPausing) {
|
|
711
736
|
await this.stopNotes(0, true, now);
|
|
712
737
|
await this.audioContext.suspend();
|
|
@@ -728,9 +753,16 @@ class Midy {
|
|
|
728
753
|
this.isSeeking = false;
|
|
729
754
|
continue;
|
|
730
755
|
}
|
|
756
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
731
757
|
const waitTime = now + this.noteCheckInterval;
|
|
732
758
|
await this.scheduleTask(() => { }, waitTime);
|
|
733
759
|
}
|
|
760
|
+
if (this.timeline.length <= queueIndex) {
|
|
761
|
+
const now = this.audioContext.currentTime;
|
|
762
|
+
await this.stopNotes(0, true, now);
|
|
763
|
+
await this.audioContext.suspend();
|
|
764
|
+
finished = true;
|
|
765
|
+
}
|
|
734
766
|
if (finished) {
|
|
735
767
|
this.notePromises = [];
|
|
736
768
|
this.resetAllStates();
|
|
@@ -836,7 +868,7 @@ class Midy {
|
|
|
836
868
|
const channel = this.channels[channelNumber];
|
|
837
869
|
const promises = [];
|
|
838
870
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
839
|
-
const promise = this.
|
|
871
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
840
872
|
this.notePromises.push(promise);
|
|
841
873
|
promises.push(promise);
|
|
842
874
|
});
|
|
@@ -846,7 +878,7 @@ class Midy {
|
|
|
846
878
|
const channel = this.channels[channelNumber];
|
|
847
879
|
const promises = [];
|
|
848
880
|
this.processScheduledNotes(channel, (note) => {
|
|
849
|
-
const promise = this.
|
|
881
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
850
882
|
this.notePromises.push(promise);
|
|
851
883
|
promises.push(promise);
|
|
852
884
|
});
|
|
@@ -879,7 +911,7 @@ class Midy {
|
|
|
879
911
|
if (!this.isPlaying || this.isPaused)
|
|
880
912
|
return;
|
|
881
913
|
const now = this.audioContext.currentTime;
|
|
882
|
-
this.resumeTime
|
|
914
|
+
this.resumeTime = now - this.startTime - this.startDelay;
|
|
883
915
|
this.isPausing = true;
|
|
884
916
|
await this.playPromise;
|
|
885
917
|
this.isPausing = false;
|
|
@@ -905,11 +937,13 @@ class Midy {
|
|
|
905
937
|
if (totalTime < event.startTime)
|
|
906
938
|
totalTime = event.startTime;
|
|
907
939
|
}
|
|
908
|
-
return totalTime;
|
|
940
|
+
return totalTime + this.startDelay;
|
|
909
941
|
}
|
|
910
942
|
currentTime() {
|
|
943
|
+
if (!this.isPlaying)
|
|
944
|
+
return this.resumeTime;
|
|
911
945
|
const now = this.audioContext.currentTime;
|
|
912
|
-
return
|
|
946
|
+
return now + this.resumeTime - this.startTime;
|
|
913
947
|
}
|
|
914
948
|
processScheduledNotes(channel, callback) {
|
|
915
949
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -1121,6 +1155,13 @@ class Midy {
|
|
|
1121
1155
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1122
1156
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1123
1157
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1158
|
+
if (channel.portamentoControl) {
|
|
1159
|
+
const state = channel.state;
|
|
1160
|
+
const portamentoNoteNumber = Math.ceil(state.portamentoNoteNumber * 127);
|
|
1161
|
+
note.portamentoNoteNumber = portamentoNoteNumber;
|
|
1162
|
+
channel.portamentoControl = false;
|
|
1163
|
+
state.portamentoNoteNumber = 0;
|
|
1164
|
+
}
|
|
1124
1165
|
if (this.isPortamento(channel, note)) {
|
|
1125
1166
|
const startTime = note.startTime;
|
|
1126
1167
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
@@ -1137,8 +1178,10 @@ class Midy {
|
|
|
1137
1178
|
}
|
|
1138
1179
|
}
|
|
1139
1180
|
getPortamentoTime(channel, note) {
|
|
1181
|
+
const { portamentoTimeMSB, portamentoTimeLSB } = channel.state;
|
|
1182
|
+
const portamentoTime = portamentoTimeMSB + portamentoTimeLSB / 128;
|
|
1140
1183
|
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1141
|
-
const value = Math.ceil(
|
|
1184
|
+
const value = Math.ceil(portamentoTime * 128);
|
|
1142
1185
|
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1143
1186
|
}
|
|
1144
1187
|
getPitchIncrementSpeed(value) {
|
|
@@ -1341,31 +1384,42 @@ class Midy {
|
|
|
1341
1384
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1342
1385
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1343
1386
|
}
|
|
1344
|
-
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1387
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
1345
1388
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
this.voiceCache.delete(audioBufferId);
|
|
1351
|
-
}
|
|
1352
|
-
return cache.audioBuffer;
|
|
1353
|
-
}
|
|
1354
|
-
else {
|
|
1355
|
-
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1389
|
+
if (realtime) {
|
|
1390
|
+
const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
|
|
1391
|
+
if (cachedAudioBuffer)
|
|
1392
|
+
return cachedAudioBuffer;
|
|
1356
1393
|
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1357
|
-
|
|
1358
|
-
this.voiceCache.set(audioBufferId, cache);
|
|
1394
|
+
this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
|
|
1359
1395
|
return audioBuffer;
|
|
1360
1396
|
}
|
|
1397
|
+
else {
|
|
1398
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1399
|
+
if (cache) {
|
|
1400
|
+
cache.counter += 1;
|
|
1401
|
+
if (cache.maxCount <= cache.counter) {
|
|
1402
|
+
this.voiceCache.delete(audioBufferId);
|
|
1403
|
+
}
|
|
1404
|
+
return cache.audioBuffer;
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1408
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1409
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1410
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1411
|
+
return audioBuffer;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1361
1414
|
}
|
|
1362
|
-
async
|
|
1415
|
+
async setNoteAudioNode(channel, note, realtime) {
|
|
1363
1416
|
const now = this.audioContext.currentTime;
|
|
1417
|
+
const { noteNumber, velocity, startTime } = note;
|
|
1364
1418
|
const state = channel.state;
|
|
1365
1419
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1366
|
-
const voiceParams = voice.getAllParams(controllerState);
|
|
1367
|
-
|
|
1368
|
-
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1420
|
+
const voiceParams = note.voice.getAllParams(controllerState);
|
|
1421
|
+
note.voiceParams = voiceParams;
|
|
1422
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1369
1423
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1370
1424
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1371
1425
|
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
@@ -1391,7 +1445,7 @@ class Midy {
|
|
|
1391
1445
|
if (0 < state.vibratoDepth) {
|
|
1392
1446
|
this.startVibrato(channel, note, now);
|
|
1393
1447
|
}
|
|
1394
|
-
if (0 < state.
|
|
1448
|
+
if (0 < state.modulationDepthMSB + state.modulationDepthLSB) {
|
|
1395
1449
|
this.startModulation(channel, note, now);
|
|
1396
1450
|
}
|
|
1397
1451
|
if (channel.mono && channel.currentBufferSource) {
|
|
@@ -1402,7 +1456,13 @@ class Midy {
|
|
|
1402
1456
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1403
1457
|
this.setChorusSend(channel, note, now);
|
|
1404
1458
|
this.setReverbSend(channel, note, now);
|
|
1405
|
-
|
|
1459
|
+
if (voiceParams.sample.type === "compressed") {
|
|
1460
|
+
const offset = voiceParams.start / audioBuffer.sampleRate;
|
|
1461
|
+
note.bufferSource.start(startTime, offset);
|
|
1462
|
+
}
|
|
1463
|
+
else {
|
|
1464
|
+
note.bufferSource.start(startTime);
|
|
1465
|
+
}
|
|
1406
1466
|
return note;
|
|
1407
1467
|
}
|
|
1408
1468
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
@@ -1413,7 +1473,7 @@ class Midy {
|
|
|
1413
1473
|
if (prev) {
|
|
1414
1474
|
const [prevNote, prevChannelNumber] = prev;
|
|
1415
1475
|
if (prevNote && !prevNote.ending) {
|
|
1416
|
-
this.
|
|
1476
|
+
this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1417
1477
|
startTime, true);
|
|
1418
1478
|
}
|
|
1419
1479
|
}
|
|
@@ -1433,27 +1493,14 @@ class Midy {
|
|
|
1433
1493
|
channelNumber;
|
|
1434
1494
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1435
1495
|
if (prevNote && !prevNote.ending) {
|
|
1436
|
-
this.
|
|
1496
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1437
1497
|
startTime, true);
|
|
1438
1498
|
}
|
|
1439
1499
|
this.drumExclusiveClassNotes[index] = note;
|
|
1440
1500
|
}
|
|
1441
|
-
|
|
1501
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
1442
1502
|
const channel = this.channels[channelNumber];
|
|
1443
|
-
const
|
|
1444
|
-
const bankTable = this.soundFontTable[programNumber];
|
|
1445
|
-
if (!bankTable)
|
|
1446
|
-
return;
|
|
1447
|
-
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1448
|
-
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1449
|
-
const soundFontIndex = bankTable[bank];
|
|
1450
|
-
if (soundFontIndex === undefined)
|
|
1451
|
-
return;
|
|
1452
|
-
const soundFont = this.soundFonts[soundFontIndex];
|
|
1453
|
-
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1454
|
-
if (!voice)
|
|
1455
|
-
return;
|
|
1456
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1503
|
+
const { noteNumber, volumeEnvelopeNode } = note;
|
|
1457
1504
|
if (channel.isDrum) {
|
|
1458
1505
|
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1459
1506
|
let gainL = keyBasedGainLs[noteNumber];
|
|
@@ -1463,25 +1510,48 @@ class Midy {
|
|
|
1463
1510
|
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1464
1511
|
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1465
1512
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1513
|
+
volumeEnvelopeNode.connect(gainL);
|
|
1514
|
+
volumeEnvelopeNode.connect(gainR);
|
|
1468
1515
|
}
|
|
1469
1516
|
else {
|
|
1470
|
-
|
|
1471
|
-
|
|
1517
|
+
volumeEnvelopeNode.connect(channel.gainL);
|
|
1518
|
+
volumeEnvelopeNode.connect(channel.gainR);
|
|
1472
1519
|
}
|
|
1473
1520
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1474
1521
|
channel.sustainNotes.push(note);
|
|
1475
1522
|
}
|
|
1476
1523
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1477
1524
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1525
|
+
}
|
|
1526
|
+
async noteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1527
|
+
const channel = this.channels[channelNumber];
|
|
1528
|
+
const realtime = startTime === undefined;
|
|
1529
|
+
if (realtime)
|
|
1530
|
+
startTime = this.audioContext.currentTime;
|
|
1531
|
+
const note = new Note(noteNumber, velocity, startTime);
|
|
1478
1532
|
const scheduledNotes = channel.scheduledNotes;
|
|
1479
1533
|
note.index = scheduledNotes.length;
|
|
1480
1534
|
scheduledNotes.push(note);
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1535
|
+
const programNumber = channel.programNumber;
|
|
1536
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
1537
|
+
if (!bankTable)
|
|
1538
|
+
return;
|
|
1539
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1540
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1541
|
+
const soundFontIndex = bankTable[bank];
|
|
1542
|
+
if (soundFontIndex === undefined)
|
|
1543
|
+
return;
|
|
1544
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
1545
|
+
note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1546
|
+
if (!note.voice)
|
|
1547
|
+
return;
|
|
1548
|
+
await this.setNoteAudioNode(channel, note, realtime);
|
|
1549
|
+
this.setNoteRouting(channelNumber, note, startTime);
|
|
1550
|
+
note.pending = false;
|
|
1551
|
+
const off = note.offEvent;
|
|
1552
|
+
if (off) {
|
|
1553
|
+
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
1554
|
+
}
|
|
1485
1555
|
}
|
|
1486
1556
|
disconnectNote(note) {
|
|
1487
1557
|
note.bufferSource.disconnect();
|
|
@@ -1504,6 +1574,7 @@ class Midy {
|
|
|
1504
1574
|
}
|
|
1505
1575
|
}
|
|
1506
1576
|
releaseNote(channel, note, endTime) {
|
|
1577
|
+
endTime ??= this.audioContext.currentTime;
|
|
1507
1578
|
const releaseTime = this.getRelativeKeyBasedValue(channel, note, 72) * 2;
|
|
1508
1579
|
const volRelease = endTime + note.voiceParams.volRelease * releaseTime;
|
|
1509
1580
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
@@ -1525,7 +1596,7 @@ class Midy {
|
|
|
1525
1596
|
}, stopTime);
|
|
1526
1597
|
});
|
|
1527
1598
|
}
|
|
1528
|
-
|
|
1599
|
+
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
1529
1600
|
const channel = this.channels[channelNumber];
|
|
1530
1601
|
const state = channel.state;
|
|
1531
1602
|
if (!force) {
|
|
@@ -1544,9 +1615,15 @@ class Midy {
|
|
|
1544
1615
|
if (index < 0)
|
|
1545
1616
|
return;
|
|
1546
1617
|
const note = channel.scheduledNotes[index];
|
|
1618
|
+
if (note.pending) {
|
|
1619
|
+
note.offEvent = { velocity, startTime: endTime };
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1547
1622
|
note.ending = true;
|
|
1548
1623
|
this.setNoteIndex(channel, index);
|
|
1549
|
-
this.releaseNote(channel, note, endTime);
|
|
1624
|
+
const promise = this.releaseNote(channel, note, endTime);
|
|
1625
|
+
this.notePromises.push(promise);
|
|
1626
|
+
return promise;
|
|
1550
1627
|
}
|
|
1551
1628
|
setNoteIndex(channel, index) {
|
|
1552
1629
|
let allEnds = true;
|
|
@@ -1574,16 +1651,12 @@ class Midy {
|
|
|
1574
1651
|
}
|
|
1575
1652
|
return -1;
|
|
1576
1653
|
}
|
|
1577
|
-
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1578
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1579
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1580
|
-
}
|
|
1581
1654
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1582
1655
|
const velocity = halfVelocity * 2;
|
|
1583
1656
|
const channel = this.channels[channelNumber];
|
|
1584
1657
|
const promises = [];
|
|
1585
1658
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1586
|
-
const promise = this.
|
|
1659
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1587
1660
|
promises.push(promise);
|
|
1588
1661
|
}
|
|
1589
1662
|
channel.sustainNotes = [];
|
|
@@ -1597,7 +1670,7 @@ class Midy {
|
|
|
1597
1670
|
channel.state.sostenutoPedal = 0;
|
|
1598
1671
|
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1599
1672
|
const note = sostenutoNotes[i];
|
|
1600
|
-
const promise = this.
|
|
1673
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1601
1674
|
promises.push(promise);
|
|
1602
1675
|
}
|
|
1603
1676
|
channel.sostenutoNotes = [];
|
|
@@ -1703,9 +1776,11 @@ class Midy {
|
|
|
1703
1776
|
}
|
|
1704
1777
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1705
1778
|
if (note.modulationDepth) {
|
|
1779
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1780
|
+
const modulationDepth = modulationDepthMSB + modulationDepthLSB / 128;
|
|
1706
1781
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1707
1782
|
this.getLFOPitchDepth(channel, note);
|
|
1708
|
-
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1783
|
+
const baseDepth = Math.abs(modLfoToPitch) + modulationDepth;
|
|
1709
1784
|
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1710
1785
|
note.modulationDepth.gain
|
|
1711
1786
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1838,7 +1913,8 @@ class Midy {
|
|
|
1838
1913
|
createVoiceParamsHandlers() {
|
|
1839
1914
|
return {
|
|
1840
1915
|
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1841
|
-
|
|
1916
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1917
|
+
if (0 < modulationDepthMSB + modulationDepthLSB) {
|
|
1842
1918
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1843
1919
|
}
|
|
1844
1920
|
},
|
|
@@ -1848,12 +1924,14 @@ class Midy {
|
|
|
1848
1924
|
}
|
|
1849
1925
|
},
|
|
1850
1926
|
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1851
|
-
|
|
1927
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1928
|
+
if (0 < modulationDepthMSB + modulationDepthLSB) {
|
|
1852
1929
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1853
1930
|
}
|
|
1854
1931
|
},
|
|
1855
1932
|
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1856
|
-
|
|
1933
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1934
|
+
if (0 < modulationDepthMSB + modulationDepthLSB) {
|
|
1857
1935
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1858
1936
|
}
|
|
1859
1937
|
},
|
|
@@ -1864,12 +1942,14 @@ class Midy {
|
|
|
1864
1942
|
this.setReverbSend(channel, note, scheduleTime);
|
|
1865
1943
|
},
|
|
1866
1944
|
delayModLFO: (_channel, note, _scheduleTime) => {
|
|
1867
|
-
|
|
1945
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1946
|
+
if (0 < modulationDepthMSB + modulationDepthLSB) {
|
|
1868
1947
|
this.setDelayModLFO(note);
|
|
1869
1948
|
}
|
|
1870
1949
|
},
|
|
1871
1950
|
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1872
|
-
|
|
1951
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
1952
|
+
if (0 < modulationDepthMSB + modulationDepthLSB) {
|
|
1873
1953
|
this.setFreqModLFO(note, scheduleTime);
|
|
1874
1954
|
}
|
|
1875
1955
|
},
|
|
@@ -1938,7 +2018,12 @@ class Midy {
|
|
|
1938
2018
|
handlers[10] = this.setPan;
|
|
1939
2019
|
handlers[11] = this.setExpression;
|
|
1940
2020
|
handlers[32] = this.setBankLSB;
|
|
2021
|
+
handlers[33] = this.setModulationDepth;
|
|
2022
|
+
handlers[37] = this.setPortamentoTime;
|
|
1941
2023
|
handlers[38] = this.dataEntryLSB;
|
|
2024
|
+
handlers[39] = this.setVolume;
|
|
2025
|
+
handlers[42] = this.setPan;
|
|
2026
|
+
handlers[43] = this.setExpression;
|
|
1942
2027
|
handlers[64] = this.setSustainPedal;
|
|
1943
2028
|
handlers[65] = this.setPortamento;
|
|
1944
2029
|
handlers[66] = this.setSostenutoPedal;
|
|
@@ -1951,6 +2036,7 @@ class Midy {
|
|
|
1951
2036
|
handlers[76] = this.setVibratoRate;
|
|
1952
2037
|
handlers[77] = this.setVibratoDepth;
|
|
1953
2038
|
handlers[78] = this.setVibratoDelay;
|
|
2039
|
+
handlers[84] = this.setPortamentoNoteNumber;
|
|
1954
2040
|
handlers[91] = this.setReverbSendLevel;
|
|
1955
2041
|
handlers[93] = this.setChorusSendLevel;
|
|
1956
2042
|
handlers[96] = this.dataIncrement;
|
|
@@ -1982,7 +2068,9 @@ class Midy {
|
|
|
1982
2068
|
this.channels[channelNumber].bankMSB = msb;
|
|
1983
2069
|
}
|
|
1984
2070
|
updateModulation(channel, scheduleTime) {
|
|
1985
|
-
const
|
|
2071
|
+
const { modulationDepthMSB, modulationDepthLSB } = channel.state;
|
|
2072
|
+
const modulationDepth = modulationDepthMSB + modulationDepthLSB / 128;
|
|
2073
|
+
const depth = modulationDepth * channel.modulationDepthRange;
|
|
1986
2074
|
this.processScheduledNotes(channel, (note) => {
|
|
1987
2075
|
if (note.modulationDepth) {
|
|
1988
2076
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
@@ -1992,12 +2080,15 @@ class Midy {
|
|
|
1992
2080
|
}
|
|
1993
2081
|
});
|
|
1994
2082
|
}
|
|
1995
|
-
setModulationDepth(channelNumber,
|
|
2083
|
+
setModulationDepth(channelNumber, value, scheduleTime) {
|
|
1996
2084
|
const channel = this.channels[channelNumber];
|
|
1997
2085
|
if (channel.isDrum)
|
|
1998
2086
|
return;
|
|
1999
2087
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2000
|
-
|
|
2088
|
+
const state = channel.state;
|
|
2089
|
+
const intPart = Math.trunc(value);
|
|
2090
|
+
state.modulationDepthMSB = intPart / 127;
|
|
2091
|
+
state.modulationDepthLSB = value - intPart;
|
|
2001
2092
|
this.updateModulation(channel, scheduleTime);
|
|
2002
2093
|
}
|
|
2003
2094
|
updatePortamento(channel, scheduleTime) {
|
|
@@ -2018,18 +2109,24 @@ class Midy {
|
|
|
2018
2109
|
}
|
|
2019
2110
|
});
|
|
2020
2111
|
}
|
|
2021
|
-
setPortamentoTime(channelNumber,
|
|
2022
|
-
const channel = this.channels[channelNumber];
|
|
2112
|
+
setPortamentoTime(channelNumber, value, scheduleTime) {
|
|
2023
2113
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2024
|
-
channel
|
|
2114
|
+
const channel = this.channels[channelNumber];
|
|
2115
|
+
const state = channel.state;
|
|
2116
|
+
const intPart = Math.trunc(value);
|
|
2117
|
+
state.portamentoTimeMSB = intPart / 127;
|
|
2118
|
+
state.portamentoTimeLSB = value - 127;
|
|
2025
2119
|
if (channel.isDrum)
|
|
2026
2120
|
return;
|
|
2027
2121
|
this.updatePortamento(channel, scheduleTime);
|
|
2028
2122
|
}
|
|
2029
|
-
setVolume(channelNumber,
|
|
2123
|
+
setVolume(channelNumber, value, scheduleTime) {
|
|
2030
2124
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2031
2125
|
const channel = this.channels[channelNumber];
|
|
2032
|
-
|
|
2126
|
+
const state = channel.state;
|
|
2127
|
+
const intPart = Math.trunc(value);
|
|
2128
|
+
state.volumeMSB = intPart / 127;
|
|
2129
|
+
state.volumeLSB = value - intPart;
|
|
2033
2130
|
if (channel.isDrum) {
|
|
2034
2131
|
for (let i = 0; i < 128; i++) {
|
|
2035
2132
|
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
@@ -2040,16 +2137,19 @@ class Midy {
|
|
|
2040
2137
|
}
|
|
2041
2138
|
}
|
|
2042
2139
|
panToGain(pan) {
|
|
2043
|
-
const theta = Math.PI / 2 * Math.max(
|
|
2140
|
+
const theta = Math.PI / 2 * Math.max(pan * 127 - 1) / 126;
|
|
2044
2141
|
return {
|
|
2045
2142
|
gainLeft: Math.cos(theta),
|
|
2046
2143
|
gainRight: Math.sin(theta),
|
|
2047
2144
|
};
|
|
2048
2145
|
}
|
|
2049
|
-
setPan(channelNumber,
|
|
2146
|
+
setPan(channelNumber, value, scheduleTime) {
|
|
2050
2147
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2051
2148
|
const channel = this.channels[channelNumber];
|
|
2052
|
-
|
|
2149
|
+
const state = channel.state;
|
|
2150
|
+
const intPart = Math.trunc(value);
|
|
2151
|
+
state.panMSB = intPart / 127;
|
|
2152
|
+
state.panLSB = value - intPart;
|
|
2053
2153
|
if (channel.isDrum) {
|
|
2054
2154
|
for (let i = 0; i < 128; i++) {
|
|
2055
2155
|
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
@@ -2059,10 +2159,13 @@ class Midy {
|
|
|
2059
2159
|
this.updateChannelVolume(channel, scheduleTime);
|
|
2060
2160
|
}
|
|
2061
2161
|
}
|
|
2062
|
-
setExpression(channelNumber,
|
|
2162
|
+
setExpression(channelNumber, value, scheduleTime) {
|
|
2063
2163
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2064
2164
|
const channel = this.channels[channelNumber];
|
|
2065
|
-
|
|
2165
|
+
const state = channel.state;
|
|
2166
|
+
const intPart = Math.trunc(value);
|
|
2167
|
+
state.expressionMSB = intPart / 127;
|
|
2168
|
+
state.expressionLSB = value - intPart;
|
|
2066
2169
|
this.updateChannelVolume(channel, scheduleTime);
|
|
2067
2170
|
}
|
|
2068
2171
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -2073,37 +2176,42 @@ class Midy {
|
|
|
2073
2176
|
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
2074
2177
|
}
|
|
2075
2178
|
updateChannelVolume(channel, scheduleTime) {
|
|
2076
|
-
const
|
|
2077
|
-
const volume =
|
|
2078
|
-
const
|
|
2179
|
+
const { expressionMSB, expressionLSB, volumeMSB, volumeLSB, panMSB, panLSB, } = channel.state;
|
|
2180
|
+
const volume = volumeMSB + volumeLSB / 128;
|
|
2181
|
+
const expression = expressionMSB + expressionLSB / 128;
|
|
2182
|
+
const pan = panMSB + panLSB / 128;
|
|
2183
|
+
const gain = volume * expression;
|
|
2184
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2079
2185
|
channel.gainL.gain
|
|
2080
2186
|
.cancelScheduledValues(scheduleTime)
|
|
2081
|
-
.setValueAtTime(
|
|
2187
|
+
.setValueAtTime(gain * gainLeft, scheduleTime);
|
|
2082
2188
|
channel.gainR.gain
|
|
2083
2189
|
.cancelScheduledValues(scheduleTime)
|
|
2084
|
-
.setValueAtTime(
|
|
2190
|
+
.setValueAtTime(gain * gainRight, scheduleTime);
|
|
2085
2191
|
}
|
|
2086
2192
|
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
2087
2193
|
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
2088
2194
|
if (!gainL)
|
|
2089
2195
|
return;
|
|
2090
2196
|
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2091
|
-
const
|
|
2092
|
-
const
|
|
2093
|
-
const
|
|
2197
|
+
const { expressionMSB, expressionLSB, volumeMSB, volumeLSB, panMSB, panLSB, } = channel.state;
|
|
2198
|
+
const volume = volumeMSB + volumeLSB / 128;
|
|
2199
|
+
const expression = expressionMSB + expressionLSB / 128;
|
|
2200
|
+
const defaultGain = volume * expression;
|
|
2201
|
+
const defaultPan = panMSB + panLSB / 128;
|
|
2094
2202
|
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
2095
|
-
const
|
|
2096
|
-
?
|
|
2097
|
-
:
|
|
2203
|
+
const gain = (0 <= keyBasedVolume)
|
|
2204
|
+
? defaultGain * keyBasedVolume / 64
|
|
2205
|
+
: defaultGain;
|
|
2098
2206
|
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
2099
2207
|
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2100
2208
|
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2101
2209
|
gainL.gain
|
|
2102
2210
|
.cancelScheduledValues(scheduleTime)
|
|
2103
|
-
.setValueAtTime(
|
|
2211
|
+
.setValueAtTime(gain * gainLeft, scheduleTime);
|
|
2104
2212
|
gainR.gain
|
|
2105
2213
|
.cancelScheduledValues(scheduleTime)
|
|
2106
|
-
.setValueAtTime(
|
|
2214
|
+
.setValueAtTime(gain * gainRight, scheduleTime);
|
|
2107
2215
|
}
|
|
2108
2216
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
2109
2217
|
const channel = this.channels[channelNumber];
|
|
@@ -2176,8 +2284,8 @@ class Midy {
|
|
|
2176
2284
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2177
2285
|
const state = channel.state;
|
|
2178
2286
|
state.filterResonance = ccValue / 127;
|
|
2179
|
-
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
2180
2287
|
this.processScheduledNotes(channel, (note) => {
|
|
2288
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
2181
2289
|
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
2182
2290
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2183
2291
|
});
|
|
@@ -2277,6 +2385,12 @@ class Midy {
|
|
|
2277
2385
|
});
|
|
2278
2386
|
}
|
|
2279
2387
|
}
|
|
2388
|
+
setPortamentoNoteNumber(channelNumber, value, scheduleTime) {
|
|
2389
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2390
|
+
const channel = this.channels[channelNumber];
|
|
2391
|
+
channel.portamentoControl = true;
|
|
2392
|
+
channel.state.portamentoNoteNumber = value / 127;
|
|
2393
|
+
}
|
|
2280
2394
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
2281
2395
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2282
2396
|
const channel = this.channels[channelNumber];
|
|
@@ -2464,8 +2578,10 @@ class Midy {
|
|
|
2464
2578
|
"polyphonicKeyPressure",
|
|
2465
2579
|
"channelPressure",
|
|
2466
2580
|
"pitchWheel",
|
|
2467
|
-
"
|
|
2468
|
-
"
|
|
2581
|
+
"expressionMSB",
|
|
2582
|
+
"expressionLSB",
|
|
2583
|
+
"modulationDepthMSB",
|
|
2584
|
+
"modulationDepthLSB",
|
|
2469
2585
|
"sustainPedal",
|
|
2470
2586
|
"portamento",
|
|
2471
2587
|
"sostenutoPedal",
|
|
@@ -3175,5 +3291,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3175
3291
|
modulationDepthRange: 50, // cent
|
|
3176
3292
|
fineTuning: 0, // cent
|
|
3177
3293
|
coarseTuning: 0, // cent
|
|
3294
|
+
portamentoControl: false,
|
|
3178
3295
|
}
|
|
3179
3296
|
});
|