@marmooo/midy 0.3.3 → 0.3.4
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/esm/midy-GM1.d.ts +3 -3
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +47 -50
- package/esm/midy-GM2.d.ts +13 -31
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +185 -206
- package/esm/midy-GMLite.d.ts +3 -3
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +47 -50
- package/esm/midy.d.ts +14 -31
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +225 -225
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +3 -3
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +47 -50
- package/script/midy-GM2.d.ts +13 -31
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +185 -206
- package/script/midy-GMLite.d.ts +3 -3
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +47 -50
- package/script/midy.d.ts +14 -31
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +225 -225
package/script/midy-GM2.js
CHANGED
|
@@ -47,24 +47,6 @@ class Note {
|
|
|
47
47
|
writable: true,
|
|
48
48
|
value: void 0
|
|
49
49
|
});
|
|
50
|
-
Object.defineProperty(this, "volumeNode", {
|
|
51
|
-
enumerable: true,
|
|
52
|
-
configurable: true,
|
|
53
|
-
writable: true,
|
|
54
|
-
value: void 0
|
|
55
|
-
});
|
|
56
|
-
Object.defineProperty(this, "gainL", {
|
|
57
|
-
enumerable: true,
|
|
58
|
-
configurable: true,
|
|
59
|
-
writable: true,
|
|
60
|
-
value: void 0
|
|
61
|
-
});
|
|
62
|
-
Object.defineProperty(this, "gainR", {
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
value: void 0
|
|
67
|
-
});
|
|
68
50
|
Object.defineProperty(this, "modulationLFO", {
|
|
69
51
|
enumerable: true,
|
|
70
52
|
configurable: true,
|
|
@@ -202,6 +184,16 @@ class ControllerState {
|
|
|
202
184
|
}
|
|
203
185
|
}
|
|
204
186
|
}
|
|
187
|
+
const volumeEnvelopeKeys = [
|
|
188
|
+
"volDelay",
|
|
189
|
+
"volAttack",
|
|
190
|
+
"volHold",
|
|
191
|
+
"volDecay",
|
|
192
|
+
"volSustain",
|
|
193
|
+
"volRelease",
|
|
194
|
+
"initialAttenuation",
|
|
195
|
+
];
|
|
196
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
205
197
|
const filterEnvelopeKeys = [
|
|
206
198
|
"modEnvToPitch",
|
|
207
199
|
"initialFilterFc",
|
|
@@ -211,22 +203,20 @@ const filterEnvelopeKeys = [
|
|
|
211
203
|
"modHold",
|
|
212
204
|
"modDecay",
|
|
213
205
|
"modSustain",
|
|
214
|
-
"modRelease",
|
|
215
|
-
"playbackRate",
|
|
216
206
|
];
|
|
217
207
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
218
|
-
const
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
208
|
+
const pitchEnvelopeKeys = [
|
|
209
|
+
"modEnvToPitch",
|
|
210
|
+
"modDelay",
|
|
211
|
+
"modAttack",
|
|
212
|
+
"modHold",
|
|
213
|
+
"modDecay",
|
|
214
|
+
"modSustain",
|
|
215
|
+
"playbackRate",
|
|
226
216
|
];
|
|
227
|
-
const
|
|
217
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
228
218
|
class MidyGM2 {
|
|
229
|
-
constructor(audioContext
|
|
219
|
+
constructor(audioContext) {
|
|
230
220
|
Object.defineProperty(this, "mode", {
|
|
231
221
|
enumerable: true,
|
|
232
222
|
configurable: true,
|
|
@@ -250,6 +240,7 @@ class MidyGM2 {
|
|
|
250
240
|
configurable: true,
|
|
251
241
|
writable: true,
|
|
252
242
|
value: {
|
|
243
|
+
algorithm: "SchroederReverb",
|
|
253
244
|
time: this.getReverbTime(64),
|
|
254
245
|
feedback: 0.8,
|
|
255
246
|
}
|
|
@@ -398,30 +389,7 @@ class MidyGM2 {
|
|
|
398
389
|
writable: true,
|
|
399
390
|
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
400
391
|
});
|
|
401
|
-
Object.defineProperty(this, "defaultOptions", {
|
|
402
|
-
enumerable: true,
|
|
403
|
-
configurable: true,
|
|
404
|
-
writable: true,
|
|
405
|
-
value: {
|
|
406
|
-
reverbAlgorithm: (audioContext) => {
|
|
407
|
-
const { time: rt60, feedback } = this.reverb;
|
|
408
|
-
// const delay = this.calcDelay(rt60, feedback);
|
|
409
|
-
// const impulse = this.createConvolutionReverbImpulse(
|
|
410
|
-
// audioContext,
|
|
411
|
-
// rt60,
|
|
412
|
-
// delay,
|
|
413
|
-
// );
|
|
414
|
-
// return this.createConvolutionReverb(audioContext, impulse);
|
|
415
|
-
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
416
|
-
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
417
|
-
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
418
|
-
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
419
|
-
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
420
|
-
},
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
392
|
this.audioContext = audioContext;
|
|
424
|
-
this.options = { ...this.defaultOptions, ...options };
|
|
425
393
|
this.masterVolume = new GainNode(audioContext);
|
|
426
394
|
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
427
395
|
this.schedulerBuffer = new AudioBuffer({
|
|
@@ -431,7 +399,7 @@ class MidyGM2 {
|
|
|
431
399
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
432
400
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
433
401
|
this.channels = this.createChannels(audioContext);
|
|
434
|
-
this.reverbEffect = this.
|
|
402
|
+
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
435
403
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
436
404
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
437
405
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
@@ -495,7 +463,7 @@ class MidyGM2 {
|
|
|
495
463
|
this.timeline = midiData.timeline;
|
|
496
464
|
this.totalTime = this.calcTotalTime();
|
|
497
465
|
}
|
|
498
|
-
|
|
466
|
+
createChannelAudioNodes(audioContext) {
|
|
499
467
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
500
468
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
501
469
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -510,10 +478,9 @@ class MidyGM2 {
|
|
|
510
478
|
};
|
|
511
479
|
}
|
|
512
480
|
resetChannelTable(channel) {
|
|
513
|
-
|
|
481
|
+
channel.controlTable.fill(-1);
|
|
514
482
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
515
|
-
channel.channelPressureTable.
|
|
516
|
-
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
483
|
+
channel.channelPressureTable.fill(-1);
|
|
517
484
|
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
518
485
|
}
|
|
519
486
|
createChannels(audioContext) {
|
|
@@ -523,14 +490,16 @@ class MidyGM2 {
|
|
|
523
490
|
isDrum: false,
|
|
524
491
|
state: new ControllerState(),
|
|
525
492
|
...this.constructor.channelSettings,
|
|
526
|
-
...this.
|
|
493
|
+
...this.createChannelAudioNodes(audioContext),
|
|
527
494
|
scheduledNotes: [],
|
|
528
495
|
sustainNotes: [],
|
|
529
496
|
sostenutoNotes: [],
|
|
530
497
|
controlTable: this.initControlTable(),
|
|
531
498
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
532
|
-
channelPressureTable: new
|
|
499
|
+
channelPressureTable: new Int8Array(6).fill(-1),
|
|
533
500
|
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
501
|
+
keyBasedGainLs: new Array(128),
|
|
502
|
+
keyBasedGainRs: new Array(128),
|
|
534
503
|
};
|
|
535
504
|
});
|
|
536
505
|
return channels;
|
|
@@ -572,10 +541,9 @@ class MidyGM2 {
|
|
|
572
541
|
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
573
542
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
574
543
|
bufferSource.buffer = audioBuffer;
|
|
575
|
-
bufferSource.loop =
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
544
|
+
bufferSource.loop = channel.isDrum
|
|
545
|
+
? this.isLoopDrum(channel, noteNumber)
|
|
546
|
+
: (voiceParams.sampleModes % 2 !== 0);
|
|
579
547
|
if (bufferSource.loop) {
|
|
580
548
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
581
549
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -816,7 +784,7 @@ class MidyGM2 {
|
|
|
816
784
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
817
785
|
const channel = this.channels[channelNumber];
|
|
818
786
|
const promises = [];
|
|
819
|
-
this.processScheduledNotes(channel, (note) => {
|
|
787
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
820
788
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
821
789
|
this.notePromises.push(promise);
|
|
822
790
|
promises.push(promise);
|
|
@@ -875,7 +843,7 @@ class MidyGM2 {
|
|
|
875
843
|
const now = this.audioContext.currentTime;
|
|
876
844
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
877
845
|
}
|
|
878
|
-
processScheduledNotes(channel, callback) {
|
|
846
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
879
847
|
const scheduledNotes = channel.scheduledNotes;
|
|
880
848
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
881
849
|
const note = scheduledNotes[i];
|
|
@@ -883,6 +851,8 @@ class MidyGM2 {
|
|
|
883
851
|
continue;
|
|
884
852
|
if (note.ending)
|
|
885
853
|
continue;
|
|
854
|
+
if (note.startTime < scheduleTime)
|
|
855
|
+
continue;
|
|
886
856
|
callback(note);
|
|
887
857
|
}
|
|
888
858
|
}
|
|
@@ -895,7 +865,7 @@ class MidyGM2 {
|
|
|
895
865
|
if (note.ending)
|
|
896
866
|
continue;
|
|
897
867
|
if (scheduleTime < note.startTime)
|
|
898
|
-
|
|
868
|
+
break;
|
|
899
869
|
callback(note);
|
|
900
870
|
}
|
|
901
871
|
}
|
|
@@ -984,6 +954,22 @@ class MidyGM2 {
|
|
|
984
954
|
const output = allpasses.at(-1);
|
|
985
955
|
return { input, output };
|
|
986
956
|
}
|
|
957
|
+
createReverbEffect(audioContext) {
|
|
958
|
+
const { algorithm, time: rt60, feedback } = this.reverb;
|
|
959
|
+
switch (algorithm) {
|
|
960
|
+
case "ConvolutionReverb": {
|
|
961
|
+
const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
|
|
962
|
+
return this.createConvolutionReverb(audioContext, impulse);
|
|
963
|
+
}
|
|
964
|
+
case "SchroederReverb": {
|
|
965
|
+
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
966
|
+
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
967
|
+
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
968
|
+
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
969
|
+
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
987
973
|
createChorusEffect(audioContext) {
|
|
988
974
|
const input = new GainNode(audioContext);
|
|
989
975
|
const output = new GainNode(audioContext);
|
|
@@ -1048,15 +1034,22 @@ class MidyGM2 {
|
|
|
1048
1034
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
1049
1035
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
1050
1036
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1037
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1038
|
+
if (0 <= channelPressureRaw) {
|
|
1039
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1040
|
+
const channelPressure = channelPressureDepth *
|
|
1041
|
+
channel.state.channelPressure;
|
|
1042
|
+
return tuning + pitch + channelPressure;
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
return tuning + pitch;
|
|
1046
|
+
}
|
|
1054
1047
|
}
|
|
1055
1048
|
calcNoteDetune(channel, note) {
|
|
1056
1049
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1057
1050
|
}
|
|
1058
1051
|
updateChannelDetune(channel, scheduleTime) {
|
|
1059
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1052
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1060
1053
|
this.updateDetune(channel, note, scheduleTime);
|
|
1061
1054
|
});
|
|
1062
1055
|
}
|
|
@@ -1303,9 +1296,6 @@ class MidyGM2 {
|
|
|
1303
1296
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1304
1297
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1305
1298
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1306
|
-
note.volumeNode = new GainNode(this.audioContext);
|
|
1307
|
-
note.gainL = new GainNode(this.audioContext);
|
|
1308
|
-
note.gainR = new GainNode(this.audioContext);
|
|
1309
1299
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1310
1300
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1311
1301
|
type: "lowpass",
|
|
@@ -1338,9 +1328,6 @@ class MidyGM2 {
|
|
|
1338
1328
|
}
|
|
1339
1329
|
note.bufferSource.connect(note.filterNode);
|
|
1340
1330
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1341
|
-
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1342
|
-
note.volumeNode.connect(note.gainL);
|
|
1343
|
-
note.volumeNode.connect(note.gainR);
|
|
1344
1331
|
if (0 < state.chorusSendLevel) {
|
|
1345
1332
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1346
1333
|
}
|
|
@@ -1399,14 +1386,7 @@ class MidyGM2 {
|
|
|
1399
1386
|
}
|
|
1400
1387
|
this.drumExclusiveClassNotes[index] = note;
|
|
1401
1388
|
}
|
|
1402
|
-
|
|
1403
|
-
if (!channel.isDrum)
|
|
1404
|
-
return false;
|
|
1405
|
-
const programNumber = channel.programNumber;
|
|
1406
|
-
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1407
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1408
|
-
}
|
|
1409
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1389
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1410
1390
|
const channel = this.channels[channelNumber];
|
|
1411
1391
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1412
1392
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1418,9 +1398,18 @@ class MidyGM2 {
|
|
|
1418
1398
|
return;
|
|
1419
1399
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1420
1400
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1401
|
+
if (channel.isDrum) {
|
|
1402
|
+
const audioContext = this.audioContext;
|
|
1403
|
+
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
1404
|
+
channel.keyBasedGainLs[noteNumber] = gainL;
|
|
1405
|
+
channel.keyBasedGainRs[noteNumber] = gainR;
|
|
1406
|
+
note.volumeEnvelopeNode.connect(gainL);
|
|
1407
|
+
note.volumeEnvelopeNode.connect(gainR);
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
1411
|
+
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
1412
|
+
}
|
|
1424
1413
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1425
1414
|
channel.sustainNotes.push(note);
|
|
1426
1415
|
}
|
|
@@ -1438,9 +1427,6 @@ class MidyGM2 {
|
|
|
1438
1427
|
note.bufferSource.disconnect();
|
|
1439
1428
|
note.filterNode.disconnect();
|
|
1440
1429
|
note.volumeEnvelopeNode.disconnect();
|
|
1441
|
-
note.volumeNode.disconnect();
|
|
1442
|
-
note.gainL.disconnect();
|
|
1443
|
-
note.gainR.disconnect();
|
|
1444
1430
|
if (note.modulationDepth) {
|
|
1445
1431
|
note.volumeDepth.disconnect();
|
|
1446
1432
|
note.modulationDepth.disconnect();
|
|
@@ -1583,9 +1569,10 @@ class MidyGM2 {
|
|
|
1583
1569
|
const prev = channel.state.channelPressure;
|
|
1584
1570
|
const next = value / 127;
|
|
1585
1571
|
channel.state.channelPressure = next;
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1572
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1573
|
+
if (0 <= channelPressureRaw) {
|
|
1574
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1575
|
+
channel.detune += channelPressureDepth * (next - prev);
|
|
1589
1576
|
}
|
|
1590
1577
|
const table = channel.channelPressureTable;
|
|
1591
1578
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
@@ -1645,10 +1632,12 @@ class MidyGM2 {
|
|
|
1645
1632
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1646
1633
|
}
|
|
1647
1634
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1648
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1649
1635
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1650
|
-
if (
|
|
1651
|
-
|
|
1636
|
+
if (channel.isDrum) {
|
|
1637
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1638
|
+
if (0 <= keyBasedValue) {
|
|
1639
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1640
|
+
}
|
|
1652
1641
|
}
|
|
1653
1642
|
if (0 < prevValue) {
|
|
1654
1643
|
if (0 < value) {
|
|
@@ -1673,10 +1662,12 @@ class MidyGM2 {
|
|
|
1673
1662
|
}
|
|
1674
1663
|
}
|
|
1675
1664
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1676
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1677
1665
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1678
|
-
if (
|
|
1679
|
-
|
|
1666
|
+
if (channel.isDrum) {
|
|
1667
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1668
|
+
if (0 <= keyBasedValue) {
|
|
1669
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1670
|
+
}
|
|
1680
1671
|
}
|
|
1681
1672
|
if (0 < prevValue) {
|
|
1682
1673
|
if (0 < vaule) {
|
|
@@ -1694,7 +1685,7 @@ class MidyGM2 {
|
|
|
1694
1685
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1695
1686
|
gain: value,
|
|
1696
1687
|
});
|
|
1697
|
-
note.
|
|
1688
|
+
note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
|
|
1698
1689
|
}
|
|
1699
1690
|
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1700
1691
|
}
|
|
@@ -1778,11 +1769,12 @@ class MidyGM2 {
|
|
|
1778
1769
|
return state;
|
|
1779
1770
|
}
|
|
1780
1771
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1781
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1772
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1782
1773
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1783
1774
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1784
|
-
let
|
|
1785
|
-
let
|
|
1775
|
+
let applyVolumeEnvelope = false;
|
|
1776
|
+
let applyFilterEnvelope = false;
|
|
1777
|
+
let applyPitchEnvelope = false;
|
|
1786
1778
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1787
1779
|
const prevValue = note.voiceParams[key];
|
|
1788
1780
|
if (value === prevValue)
|
|
@@ -1791,37 +1783,23 @@ class MidyGM2 {
|
|
|
1791
1783
|
if (key in this.voiceParamsHandlers) {
|
|
1792
1784
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1793
1785
|
}
|
|
1794
|
-
else
|
|
1795
|
-
if (
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
if (key in voiceParams)
|
|
1802
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1803
|
-
}
|
|
1804
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1805
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1806
|
-
}
|
|
1807
|
-
else {
|
|
1808
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1809
|
-
}
|
|
1810
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1811
|
-
}
|
|
1812
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1813
|
-
if (appliedVolumeEnvelope)
|
|
1814
|
-
continue;
|
|
1815
|
-
appliedVolumeEnvelope = true;
|
|
1816
|
-
const noteVoiceParams = note.voiceParams;
|
|
1817
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1818
|
-
const key = volumeEnvelopeKeys[i];
|
|
1819
|
-
if (key in voiceParams)
|
|
1820
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1821
|
-
}
|
|
1822
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1786
|
+
else {
|
|
1787
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1788
|
+
applyVolumeEnvelope = true;
|
|
1789
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1790
|
+
applyFilterEnvelope = true;
|
|
1791
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1792
|
+
applyPitchEnvelope = true;
|
|
1823
1793
|
}
|
|
1824
1794
|
}
|
|
1795
|
+
if (applyVolumeEnvelope) {
|
|
1796
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1797
|
+
}
|
|
1798
|
+
if (applyFilterEnvelope) {
|
|
1799
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1800
|
+
}
|
|
1801
|
+
if (applyPitchEnvelope)
|
|
1802
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1825
1803
|
});
|
|
1826
1804
|
}
|
|
1827
1805
|
createControlChangeHandlers() {
|
|
@@ -1858,7 +1836,7 @@ class MidyGM2 {
|
|
|
1858
1836
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1859
1837
|
const channel = this.channels[channelNumber];
|
|
1860
1838
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1861
|
-
this.applyControlTable(channel, controllerType);
|
|
1839
|
+
this.applyControlTable(channel, controllerType, scheduleTime);
|
|
1862
1840
|
}
|
|
1863
1841
|
else {
|
|
1864
1842
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1869,7 +1847,7 @@ class MidyGM2 {
|
|
|
1869
1847
|
}
|
|
1870
1848
|
updateModulation(channel, scheduleTime) {
|
|
1871
1849
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1872
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1850
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1873
1851
|
if (note.modulationDepth) {
|
|
1874
1852
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1875
1853
|
}
|
|
@@ -1888,7 +1866,7 @@ class MidyGM2 {
|
|
|
1888
1866
|
this.updateModulation(channel, scheduleTime);
|
|
1889
1867
|
}
|
|
1890
1868
|
updatePortamento(channel, scheduleTime) {
|
|
1891
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1869
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1892
1870
|
if (0.5 <= channel.state.portamento) {
|
|
1893
1871
|
if (0 <= note.portamentoNoteNumber) {
|
|
1894
1872
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1915,22 +1893,12 @@ class MidyGM2 {
|
|
|
1915
1893
|
return;
|
|
1916
1894
|
this.updatePortamento(channel, scheduleTime);
|
|
1917
1895
|
}
|
|
1918
|
-
setKeyBasedVolume(channel, scheduleTime) {
|
|
1919
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1920
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1921
|
-
if (0 <= keyBasedValue) {
|
|
1922
|
-
note.volumeNode.gain
|
|
1923
|
-
.cancelScheduledValues(scheduleTime)
|
|
1924
|
-
.setValueAtTime(keyBasedValue / 127, scheduleTime);
|
|
1925
|
-
}
|
|
1926
|
-
});
|
|
1927
|
-
}
|
|
1928
1896
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1929
1897
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1930
1898
|
const channel = this.channels[channelNumber];
|
|
1931
1899
|
channel.state.volume = volume / 127;
|
|
1932
1900
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1933
|
-
this.
|
|
1901
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1934
1902
|
}
|
|
1935
1903
|
panToGain(pan) {
|
|
1936
1904
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1939,26 +1907,12 @@ class MidyGM2 {
|
|
|
1939
1907
|
gainRight: Math.sin(theta),
|
|
1940
1908
|
};
|
|
1941
1909
|
}
|
|
1942
|
-
setKeyBasedPan(channel, scheduleTime) {
|
|
1943
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1944
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1945
|
-
if (0 <= keyBasedValue) {
|
|
1946
|
-
const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
|
|
1947
|
-
note.gainL.gain
|
|
1948
|
-
.cancelScheduledValues(scheduleTime)
|
|
1949
|
-
.setValueAtTime(gainLeft, scheduleTime);
|
|
1950
|
-
note.gainR.gain
|
|
1951
|
-
.cancelScheduledValues(scheduleTime)
|
|
1952
|
-
.setValueAtTime(gainRight, scheduleTime);
|
|
1953
|
-
}
|
|
1954
|
-
});
|
|
1955
|
-
}
|
|
1956
1910
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1957
1911
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1958
1912
|
const channel = this.channels[channelNumber];
|
|
1959
1913
|
channel.state.pan = pan / 127;
|
|
1960
1914
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1961
|
-
this.
|
|
1915
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
1962
1916
|
}
|
|
1963
1917
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1964
1918
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1984,6 +1938,34 @@ class MidyGM2 {
|
|
|
1984
1938
|
.cancelScheduledValues(scheduleTime)
|
|
1985
1939
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1986
1940
|
}
|
|
1941
|
+
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1942
|
+
if (!channel.isDrum)
|
|
1943
|
+
return;
|
|
1944
|
+
const state = channel.state;
|
|
1945
|
+
const defaultVolume = state.volume * state.expression;
|
|
1946
|
+
const defaultPan = state.pan;
|
|
1947
|
+
for (let i = 0; i < 128; i++) {
|
|
1948
|
+
const gainL = channel.keyBasedGainLs[i];
|
|
1949
|
+
const gainR = channel.keyBasedGainLs[i];
|
|
1950
|
+
if (!gainL)
|
|
1951
|
+
continue;
|
|
1952
|
+
if (!gainR)
|
|
1953
|
+
continue;
|
|
1954
|
+
const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
|
|
1955
|
+
const volume = (0 <= keyBasedVolume)
|
|
1956
|
+
? defaultVolume * keyBasedVolume / 64
|
|
1957
|
+
: defaultVolume;
|
|
1958
|
+
const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
|
|
1959
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1960
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1961
|
+
gainL.gain
|
|
1962
|
+
.cancelScheduledValues(scheduleTime)
|
|
1963
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1964
|
+
gainR.gain
|
|
1965
|
+
.cancelScheduledValues(scheduleTime)
|
|
1966
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1987
1969
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1988
1970
|
const channel = this.channels[channelNumber];
|
|
1989
1971
|
if (channel.isDrum)
|
|
@@ -1991,7 +1973,7 @@ class MidyGM2 {
|
|
|
1991
1973
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1992
1974
|
channel.state.sustainPedal = value / 127;
|
|
1993
1975
|
if (64 <= value) {
|
|
1994
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1976
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1995
1977
|
channel.sustainNotes.push(note);
|
|
1996
1978
|
});
|
|
1997
1979
|
}
|
|
@@ -2034,7 +2016,7 @@ class MidyGM2 {
|
|
|
2034
2016
|
const state = channel.state;
|
|
2035
2017
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2036
2018
|
state.softPedal = softPedal / 127;
|
|
2037
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2019
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2038
2020
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2039
2021
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2040
2022
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2058,7 +2040,7 @@ class MidyGM2 {
|
|
|
2058
2040
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2059
2041
|
}
|
|
2060
2042
|
else {
|
|
2061
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2043
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2062
2044
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2063
2045
|
return false;
|
|
2064
2046
|
if (note.reverbEffectsSend)
|
|
@@ -2068,7 +2050,7 @@ class MidyGM2 {
|
|
|
2068
2050
|
}
|
|
2069
2051
|
else {
|
|
2070
2052
|
if (0 < reverbSendLevel) {
|
|
2071
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2053
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2072
2054
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2073
2055
|
});
|
|
2074
2056
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2091,7 +2073,7 @@ class MidyGM2 {
|
|
|
2091
2073
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2092
2074
|
}
|
|
2093
2075
|
else {
|
|
2094
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2076
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2095
2077
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2096
2078
|
return false;
|
|
2097
2079
|
if (note.chorusEffectsSend)
|
|
@@ -2101,7 +2083,7 @@ class MidyGM2 {
|
|
|
2101
2083
|
}
|
|
2102
2084
|
else {
|
|
2103
2085
|
if (0 < chorusSendLevel) {
|
|
2104
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2086
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2105
2087
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2106
2088
|
});
|
|
2107
2089
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2485,8 +2467,7 @@ class MidyGM2 {
|
|
|
2485
2467
|
setReverbType(type) {
|
|
2486
2468
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
2487
2469
|
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
2488
|
-
|
|
2489
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2470
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2490
2471
|
}
|
|
2491
2472
|
getReverbTimeFromType(type) {
|
|
2492
2473
|
switch (type) {
|
|
@@ -2508,8 +2489,7 @@ class MidyGM2 {
|
|
|
2508
2489
|
}
|
|
2509
2490
|
setReverbTime(value) {
|
|
2510
2491
|
this.reverb.time = this.getReverbTime(value);
|
|
2511
|
-
|
|
2512
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2492
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2513
2493
|
}
|
|
2514
2494
|
getReverbTime(value) {
|
|
2515
2495
|
return Math.exp((value - 40) * 0.025);
|
|
@@ -2680,50 +2660,60 @@ class MidyGM2 {
|
|
|
2680
2660
|
}
|
|
2681
2661
|
}
|
|
2682
2662
|
getFilterCutoffControl(channel) {
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2663
|
+
const channelPressureRaw = channel.channelPressureTable[1];
|
|
2664
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2665
|
+
? (channelPressureRaw - 64) * channel.state.channelPressure
|
|
2666
|
+
: 0;
|
|
2685
2667
|
return channelPressure * 15;
|
|
2686
2668
|
}
|
|
2687
2669
|
getAmplitudeControl(channel) {
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2670
|
+
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2671
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2672
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2673
|
+
: 0;
|
|
2690
2674
|
return channelPressure / 64;
|
|
2691
2675
|
}
|
|
2692
2676
|
getLFOPitchDepth(channel) {
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2677
|
+
const channelPressureRaw = channel.channelPressureTable[3];
|
|
2678
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2679
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2680
|
+
: 0;
|
|
2695
2681
|
return channelPressure / 127 * 600;
|
|
2696
2682
|
}
|
|
2697
2683
|
getLFOFilterDepth(channel) {
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2684
|
+
const channelPressureRaw = channel.channelPressureTable[4];
|
|
2685
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2686
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2687
|
+
: 0;
|
|
2700
2688
|
return channelPressure / 127 * 2400;
|
|
2701
2689
|
}
|
|
2702
2690
|
getLFOAmplitudeDepth(channel) {
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2691
|
+
const channelPressureRaw = channel.channelPressureTable[5];
|
|
2692
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2693
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2694
|
+
: 0;
|
|
2705
2695
|
return channelPressure / 127;
|
|
2706
2696
|
}
|
|
2707
2697
|
setControllerParameters(channel, note, table) {
|
|
2708
|
-
if (table[0]
|
|
2698
|
+
if (0 <= table[0])
|
|
2709
2699
|
this.updateDetune(channel, note);
|
|
2710
2700
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2711
|
-
if (table[1]
|
|
2701
|
+
if (0 <= table[1])
|
|
2712
2702
|
this.setPortamentoFilterEnvelope(channel, note);
|
|
2713
|
-
if (table[2]
|
|
2703
|
+
if (0 <= table[2])
|
|
2714
2704
|
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2715
2705
|
}
|
|
2716
2706
|
else {
|
|
2717
|
-
if (table[1]
|
|
2707
|
+
if (0 <= table[1])
|
|
2718
2708
|
this.setFilterEnvelope(channel, note);
|
|
2719
|
-
if (table[2]
|
|
2709
|
+
if (0 <= table[2])
|
|
2720
2710
|
this.setVolumeEnvelope(channel, note);
|
|
2721
2711
|
}
|
|
2722
|
-
if (table[3]
|
|
2712
|
+
if (0 <= table[3])
|
|
2723
2713
|
this.setModLfoToPitch(channel, note);
|
|
2724
|
-
if (table[4]
|
|
2714
|
+
if (0 <= table[4])
|
|
2725
2715
|
this.setModLfoToFilterFc(channel, note);
|
|
2726
|
-
if (table[5]
|
|
2716
|
+
if (0 <= table[5])
|
|
2727
2717
|
this.setModLfoToVolume(channel, note);
|
|
2728
2718
|
}
|
|
2729
2719
|
handlePressureSysEx(data, tableName) {
|
|
@@ -2739,26 +2729,15 @@ class MidyGM2 {
|
|
|
2739
2729
|
}
|
|
2740
2730
|
}
|
|
2741
2731
|
initControlTable() {
|
|
2742
|
-
const
|
|
2732
|
+
const ccCount = 128;
|
|
2743
2733
|
const slotSize = 6;
|
|
2744
|
-
|
|
2745
|
-
return this.resetControlTable(table);
|
|
2734
|
+
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2746
2735
|
}
|
|
2747
|
-
|
|
2748
|
-
const channelCount = 128;
|
|
2749
|
-
const slotSize = 6;
|
|
2750
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2751
|
-
for (let ch = 0; ch < channelCount; ch++) {
|
|
2752
|
-
const offset = ch * slotSize;
|
|
2753
|
-
table.set(defaultValues, offset);
|
|
2754
|
-
}
|
|
2755
|
-
return table;
|
|
2756
|
-
}
|
|
2757
|
-
applyControlTable(channel, controllerType) {
|
|
2736
|
+
applyControlTable(channel, controllerType, scheduleTime) {
|
|
2758
2737
|
const slotSize = 6;
|
|
2759
2738
|
const offset = controllerType * slotSize;
|
|
2760
2739
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2761
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2740
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2762
2741
|
this.setControllerParameters(channel, note, table);
|
|
2763
2742
|
});
|
|
2764
2743
|
}
|
|
@@ -2783,7 +2762,7 @@ class MidyGM2 {
|
|
|
2783
2762
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2784
2763
|
const channelNumber = data[4];
|
|
2785
2764
|
const channel = this.channels[channelNumber];
|
|
2786
|
-
if (channel.isDrum)
|
|
2765
|
+
if (!channel.isDrum)
|
|
2787
2766
|
return;
|
|
2788
2767
|
const keyNumber = data[5];
|
|
2789
2768
|
const table = channel.keyBasedInstrumentControlTable;
|