@marmooo/midy 0.3.5 → 0.3.7
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 +7 -9
- package/esm/midy-GM1.d.ts +29 -26
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +136 -101
- package/esm/midy-GM2.d.ts +41 -37
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +325 -300
- package/esm/midy-GMLite.d.ts +29 -26
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +129 -96
- package/esm/midy.d.ts +43 -38
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +428 -325
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +29 -26
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +136 -101
- package/script/midy-GM2.d.ts +41 -37
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +325 -300
- package/script/midy-GMLite.d.ts +29 -26
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +129 -96
- package/script/midy.d.ts +43 -38
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +428 -325
package/esm/midy-GM2.js
CHANGED
|
@@ -68,13 +68,13 @@ class Note {
|
|
|
68
68
|
writable: true,
|
|
69
69
|
value: void 0
|
|
70
70
|
});
|
|
71
|
-
Object.defineProperty(this, "
|
|
71
|
+
Object.defineProperty(this, "reverbSend", {
|
|
72
72
|
enumerable: true,
|
|
73
73
|
configurable: true,
|
|
74
74
|
writable: true,
|
|
75
75
|
value: void 0
|
|
76
76
|
});
|
|
77
|
-
Object.defineProperty(this, "
|
|
77
|
+
Object.defineProperty(this, "chorusSend", {
|
|
78
78
|
enumerable: true,
|
|
79
79
|
configurable: true,
|
|
80
80
|
writable: true,
|
|
@@ -225,13 +225,13 @@ export class MidyGM2 {
|
|
|
225
225
|
configurable: true,
|
|
226
226
|
writable: true,
|
|
227
227
|
value: 0
|
|
228
|
-
}); //
|
|
228
|
+
}); // cent
|
|
229
229
|
Object.defineProperty(this, "masterCoarseTuning", {
|
|
230
230
|
enumerable: true,
|
|
231
231
|
configurable: true,
|
|
232
232
|
writable: true,
|
|
233
233
|
value: 0
|
|
234
|
-
}); //
|
|
234
|
+
}); // cent
|
|
235
235
|
Object.defineProperty(this, "reverb", {
|
|
236
236
|
enumerable: true,
|
|
237
237
|
configurable: true,
|
|
@@ -356,13 +356,13 @@ export class MidyGM2 {
|
|
|
356
356
|
writable: true,
|
|
357
357
|
value: false
|
|
358
358
|
});
|
|
359
|
-
Object.defineProperty(this, "
|
|
359
|
+
Object.defineProperty(this, "playPromise", {
|
|
360
360
|
enumerable: true,
|
|
361
361
|
configurable: true,
|
|
362
362
|
writable: true,
|
|
363
|
-
value:
|
|
363
|
+
value: void 0
|
|
364
364
|
});
|
|
365
|
-
Object.defineProperty(this, "
|
|
365
|
+
Object.defineProperty(this, "timeline", {
|
|
366
366
|
enumerable: true,
|
|
367
367
|
configurable: true,
|
|
368
368
|
writable: true,
|
|
@@ -374,6 +374,12 @@ export class MidyGM2 {
|
|
|
374
374
|
writable: true,
|
|
375
375
|
value: []
|
|
376
376
|
});
|
|
377
|
+
Object.defineProperty(this, "instruments", {
|
|
378
|
+
enumerable: true,
|
|
379
|
+
configurable: true,
|
|
380
|
+
writable: true,
|
|
381
|
+
value: new Set()
|
|
382
|
+
});
|
|
377
383
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
378
384
|
enumerable: true,
|
|
379
385
|
configurable: true,
|
|
@@ -504,7 +510,7 @@ export class MidyGM2 {
|
|
|
504
510
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
505
511
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
506
512
|
const { instrument, sampleID } = voice.generators;
|
|
507
|
-
return
|
|
513
|
+
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
508
514
|
}
|
|
509
515
|
createChannelAudioNodes(audioContext) {
|
|
510
516
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
@@ -524,7 +530,7 @@ export class MidyGM2 {
|
|
|
524
530
|
channel.controlTable.fill(-1);
|
|
525
531
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
526
532
|
channel.channelPressureTable.fill(-1);
|
|
527
|
-
channel.
|
|
533
|
+
channel.keyBasedTable.fill(-1);
|
|
528
534
|
}
|
|
529
535
|
createChannels(audioContext) {
|
|
530
536
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
@@ -540,7 +546,7 @@ export class MidyGM2 {
|
|
|
540
546
|
controlTable: this.initControlTable(),
|
|
541
547
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
542
548
|
channelPressureTable: new Int8Array(6).fill(-1),
|
|
543
|
-
|
|
549
|
+
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
544
550
|
keyBasedGainLs: new Array(128),
|
|
545
551
|
keyBasedGainRs: new Array(128),
|
|
546
552
|
};
|
|
@@ -615,72 +621,80 @@ export class MidyGM2 {
|
|
|
615
621
|
}
|
|
616
622
|
return 0;
|
|
617
623
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
624
|
+
resetAllStates() {
|
|
625
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
626
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
627
|
+
this.voiceCache.clear();
|
|
628
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
629
|
+
this.channels[i].scheduledNotes = [];
|
|
630
|
+
this.resetChannelStates(i);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
updateStates(queueIndex, nextQueueIndex) {
|
|
634
|
+
if (nextQueueIndex < queueIndex)
|
|
635
|
+
queueIndex = 0;
|
|
636
|
+
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
637
|
+
const event = this.timeline[i];
|
|
638
|
+
switch (event.type) {
|
|
639
|
+
case "controller":
|
|
640
|
+
this.setControlChange(event.channel, event.controllerType, event.value, 0);
|
|
641
|
+
break;
|
|
642
|
+
case "programChange":
|
|
643
|
+
this.setProgramChange(event.channel, event.programNumber, 0);
|
|
644
|
+
break;
|
|
645
|
+
case "pitchBend":
|
|
646
|
+
this.setPitchBend(event.channel, event.value + 8192, 0);
|
|
647
|
+
break;
|
|
648
|
+
case "sysEx":
|
|
649
|
+
this.handleSysEx(event.data, 0);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async playNotes() {
|
|
654
|
+
if (this.audioContext.state === "suspended") {
|
|
655
|
+
await this.audioContext.resume();
|
|
656
|
+
}
|
|
657
|
+
this.isPlaying = true;
|
|
658
|
+
this.isPaused = false;
|
|
659
|
+
this.startTime = this.audioContext.currentTime;
|
|
660
|
+
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
661
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
662
|
+
let finished = false;
|
|
663
|
+
this.notePromises = [];
|
|
664
|
+
while (queueIndex < this.timeline.length) {
|
|
665
|
+
const now = this.audioContext.currentTime;
|
|
666
|
+
const t = now + resumeTime;
|
|
667
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
668
|
+
if (this.isPausing) {
|
|
669
|
+
await this.stopNotes(0, true, now);
|
|
670
|
+
await this.audioContext.suspend();
|
|
671
|
+
this.notePromises = [];
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
else if (this.isStopping) {
|
|
675
|
+
await this.stopNotes(0, true, now);
|
|
676
|
+
await this.audioContext.suspend();
|
|
677
|
+
finished = true;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
else if (this.isSeeking) {
|
|
681
|
+
await this.stopNotes(0, true, now);
|
|
682
|
+
this.startTime = this.audioContext.currentTime;
|
|
683
|
+
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
684
|
+
this.updateStates(queueIndex, nextQueueIndex);
|
|
685
|
+
queueIndex = nextQueueIndex;
|
|
686
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
687
|
+
this.isSeeking = false;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
const waitTime = now + this.noteCheckInterval;
|
|
691
|
+
await this.scheduleTask(() => { }, waitTime);
|
|
692
|
+
}
|
|
693
|
+
if (finished) {
|
|
625
694
|
this.notePromises = [];
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
this.notePromises = [];
|
|
630
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
631
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
632
|
-
this.voiceCache.clear();
|
|
633
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
634
|
-
this.channels[i].scheduledNotes = [];
|
|
635
|
-
this.resetAllStates(i);
|
|
636
|
-
}
|
|
637
|
-
resolve();
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const now = this.audioContext.currentTime;
|
|
641
|
-
const t = now + resumeTime;
|
|
642
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
643
|
-
if (this.isPausing) {
|
|
644
|
-
await this.stopNotes(0, true, now);
|
|
645
|
-
this.notePromises = [];
|
|
646
|
-
this.isPausing = false;
|
|
647
|
-
this.isPaused = true;
|
|
648
|
-
resolve();
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
else if (this.isStopping) {
|
|
652
|
-
await this.stopNotes(0, true, now);
|
|
653
|
-
this.notePromises = [];
|
|
654
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
655
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
656
|
-
this.voiceCache.clear();
|
|
657
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
658
|
-
this.channels[i].scheduledNotes = [];
|
|
659
|
-
this.resetAllStates(i);
|
|
660
|
-
}
|
|
661
|
-
this.isStopping = false;
|
|
662
|
-
this.isPaused = false;
|
|
663
|
-
resolve();
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
else if (this.isSeeking) {
|
|
667
|
-
this.stopNotes(0, true, now);
|
|
668
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
669
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
670
|
-
this.startTime = this.audioContext.currentTime;
|
|
671
|
-
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
672
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
673
|
-
this.isSeeking = false;
|
|
674
|
-
await schedulePlayback();
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
const waitTime = now + this.noteCheckInterval;
|
|
678
|
-
await this.scheduleTask(() => { }, waitTime);
|
|
679
|
-
await schedulePlayback();
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
schedulePlayback();
|
|
683
|
-
});
|
|
695
|
+
this.resetAllStates();
|
|
696
|
+
}
|
|
697
|
+
this.isPlaying = false;
|
|
684
698
|
}
|
|
685
699
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
686
700
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -817,26 +831,32 @@ export class MidyGM2 {
|
|
|
817
831
|
this.resumeTime = 0;
|
|
818
832
|
if (this.voiceCounter.size === 0)
|
|
819
833
|
this.cacheVoiceIds();
|
|
820
|
-
|
|
821
|
-
this.
|
|
834
|
+
this.playPromise = this.playNotes();
|
|
835
|
+
await this.playPromise;
|
|
822
836
|
}
|
|
823
|
-
stop() {
|
|
837
|
+
async stop() {
|
|
824
838
|
if (!this.isPlaying)
|
|
825
839
|
return;
|
|
826
840
|
this.isStopping = true;
|
|
841
|
+
await this.playPromise;
|
|
842
|
+
this.isStopping = false;
|
|
827
843
|
}
|
|
828
|
-
pause() {
|
|
844
|
+
async pause() {
|
|
829
845
|
if (!this.isPlaying || this.isPaused)
|
|
830
846
|
return;
|
|
831
847
|
const now = this.audioContext.currentTime;
|
|
832
848
|
this.resumeTime += now - this.startTime - this.startDelay;
|
|
833
849
|
this.isPausing = true;
|
|
850
|
+
await this.playPromise;
|
|
851
|
+
this.isPausing = false;
|
|
852
|
+
this.isPaused = true;
|
|
834
853
|
}
|
|
835
854
|
async resume() {
|
|
836
855
|
if (!this.isPaused)
|
|
837
856
|
return;
|
|
838
|
-
|
|
839
|
-
this.
|
|
857
|
+
this.playPromise = this.playNotes();
|
|
858
|
+
await this.playPromise;
|
|
859
|
+
this.isPaused = false;
|
|
840
860
|
}
|
|
841
861
|
seekTo(second) {
|
|
842
862
|
this.resumeTime = second;
|
|
@@ -904,13 +924,11 @@ export class MidyGM2 {
|
|
|
904
924
|
return impulse;
|
|
905
925
|
}
|
|
906
926
|
createConvolutionReverb(audioContext, impulse) {
|
|
907
|
-
const input = new GainNode(audioContext);
|
|
908
927
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
909
928
|
buffer: impulse,
|
|
910
929
|
});
|
|
911
|
-
input.connect(convolverNode);
|
|
912
930
|
return {
|
|
913
|
-
input,
|
|
931
|
+
input: convolverNode,
|
|
914
932
|
output: convolverNode,
|
|
915
933
|
convolverNode,
|
|
916
934
|
};
|
|
@@ -1273,10 +1291,12 @@ export class MidyGM2 {
|
|
|
1273
1291
|
startVibrato(channel, note, scheduleTime) {
|
|
1274
1292
|
const { voiceParams } = note;
|
|
1275
1293
|
const state = channel.state;
|
|
1294
|
+
const vibratoRate = state.vibratoRate * 2;
|
|
1295
|
+
const vibratoDelay = state.vibratoDelay * 2;
|
|
1276
1296
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1277
|
-
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1297
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
|
|
1278
1298
|
});
|
|
1279
|
-
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO *
|
|
1299
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
|
|
1280
1300
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1281
1301
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1282
1302
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
@@ -1340,12 +1360,8 @@ export class MidyGM2 {
|
|
|
1340
1360
|
}
|
|
1341
1361
|
note.bufferSource.connect(note.filterNode);
|
|
1342
1362
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
if (0 < state.reverbSendLevel) {
|
|
1347
|
-
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1348
|
-
}
|
|
1363
|
+
this.setChorusSend(channel, note, now);
|
|
1364
|
+
this.setReverbSend(channel, note, now);
|
|
1349
1365
|
note.bufferSource.start(startTime);
|
|
1350
1366
|
return note;
|
|
1351
1367
|
}
|
|
@@ -1411,10 +1427,14 @@ export class MidyGM2 {
|
|
|
1411
1427
|
return;
|
|
1412
1428
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1413
1429
|
if (channel.isDrum) {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1430
|
+
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1431
|
+
let gainL = keyBasedGainLs[noteNumber];
|
|
1432
|
+
let gainR = keyBasedGainRs[noteNumber];
|
|
1433
|
+
if (!gainL) {
|
|
1434
|
+
const audioNodes = this.createChannelAudioNodes(this.audioContext);
|
|
1435
|
+
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1436
|
+
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1437
|
+
}
|
|
1418
1438
|
note.volumeEnvelopeNode.connect(gainL);
|
|
1419
1439
|
note.volumeEnvelopeNode.connect(gainR);
|
|
1420
1440
|
}
|
|
@@ -1448,11 +1468,11 @@ export class MidyGM2 {
|
|
|
1448
1468
|
note.vibratoDepth.disconnect();
|
|
1449
1469
|
note.vibratoLFO.stop();
|
|
1450
1470
|
}
|
|
1451
|
-
if (note.
|
|
1452
|
-
note.
|
|
1471
|
+
if (note.reverbSend) {
|
|
1472
|
+
note.reverbSend.disconnect();
|
|
1453
1473
|
}
|
|
1454
|
-
if (note.
|
|
1455
|
-
note.
|
|
1474
|
+
if (note.chorusSend) {
|
|
1475
|
+
note.chorusSend.disconnect();
|
|
1456
1476
|
}
|
|
1457
1477
|
}
|
|
1458
1478
|
releaseNote(channel, note, endTime) {
|
|
@@ -1588,6 +1608,7 @@ export class MidyGM2 {
|
|
|
1588
1608
|
break;
|
|
1589
1609
|
}
|
|
1590
1610
|
}
|
|
1611
|
+
channel.keyBasedTable.fill(-1);
|
|
1591
1612
|
}
|
|
1592
1613
|
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1593
1614
|
const channel = this.channels[channelNumber];
|
|
@@ -1603,7 +1624,7 @@ export class MidyGM2 {
|
|
|
1603
1624
|
}
|
|
1604
1625
|
const table = channel.channelPressureTable;
|
|
1605
1626
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1606
|
-
this.
|
|
1627
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1607
1628
|
});
|
|
1608
1629
|
this.applyVoiceParams(channel, 13);
|
|
1609
1630
|
}
|
|
@@ -1625,22 +1646,32 @@ export class MidyGM2 {
|
|
|
1625
1646
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1626
1647
|
}
|
|
1627
1648
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
.
|
|
1634
|
-
|
|
1649
|
+
if (note.modulationDepth) {
|
|
1650
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1651
|
+
this.getLFOPitchDepth(channel, note);
|
|
1652
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1653
|
+
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1654
|
+
note.modulationDepth.gain
|
|
1655
|
+
.cancelScheduledValues(scheduleTime)
|
|
1656
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1657
|
+
}
|
|
1658
|
+
else {
|
|
1659
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1660
|
+
}
|
|
1635
1661
|
}
|
|
1636
1662
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
.
|
|
1643
|
-
|
|
1663
|
+
if (note.vibratoDepth) {
|
|
1664
|
+
const vibratoDepth = channel.state.vibratoDepth * 2;
|
|
1665
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1666
|
+
const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
|
|
1667
|
+
const depth = baseDepth * Math.sign(vibLfoToPitch);
|
|
1668
|
+
note.vibratoDepth.gain
|
|
1669
|
+
.cancelScheduledValues(scheduleTime)
|
|
1670
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1674
|
+
}
|
|
1644
1675
|
}
|
|
1645
1676
|
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1646
1677
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
@@ -1658,73 +1689,72 @@ export class MidyGM2 {
|
|
|
1658
1689
|
.cancelScheduledValues(scheduleTime)
|
|
1659
1690
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1660
1691
|
}
|
|
1661
|
-
|
|
1662
|
-
let value = note.voiceParams.reverbEffectsSend
|
|
1692
|
+
setReverbSend(channel, note, scheduleTime) {
|
|
1693
|
+
let value = note.voiceParams.reverbEffectsSend *
|
|
1694
|
+
channel.state.reverbSendLevel;
|
|
1663
1695
|
if (channel.isDrum) {
|
|
1664
1696
|
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1665
|
-
if (0 <= keyBasedValue)
|
|
1666
|
-
value
|
|
1667
|
-
}
|
|
1697
|
+
if (0 <= keyBasedValue)
|
|
1698
|
+
value = keyBasedValue / 127;
|
|
1668
1699
|
}
|
|
1669
|
-
if (
|
|
1700
|
+
if (!note.reverbSend) {
|
|
1670
1701
|
if (0 < value) {
|
|
1671
|
-
note.
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
else {
|
|
1676
|
-
note.reverbEffectsSend.disconnect();
|
|
1702
|
+
note.reverbSend = new GainNode(this.audioContext, { gain: value });
|
|
1703
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1704
|
+
note.reverbSend.connect(this.reverbEffect.input);
|
|
1677
1705
|
}
|
|
1678
1706
|
}
|
|
1679
1707
|
else {
|
|
1708
|
+
note.reverbSend.gain
|
|
1709
|
+
.cancelScheduledValues(scheduleTime)
|
|
1710
|
+
.setValueAtTime(value, scheduleTime);
|
|
1680
1711
|
if (0 < value) {
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
note.
|
|
1712
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1713
|
+
}
|
|
1714
|
+
else {
|
|
1715
|
+
try {
|
|
1716
|
+
note.volumeEnvelopeNode.disconnect(note.reverbSend);
|
|
1686
1717
|
}
|
|
1687
|
-
|
|
1718
|
+
catch { /* empty */ }
|
|
1688
1719
|
}
|
|
1689
1720
|
}
|
|
1690
1721
|
}
|
|
1691
|
-
|
|
1692
|
-
let value = note.voiceParams.chorusEffectsSend
|
|
1722
|
+
setChorusSend(channel, note, scheduleTime) {
|
|
1723
|
+
let value = note.voiceParams.chorusEffectsSend *
|
|
1724
|
+
channel.state.chorusSendLevel;
|
|
1693
1725
|
if (channel.isDrum) {
|
|
1694
1726
|
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1695
|
-
if (0 <= keyBasedValue)
|
|
1696
|
-
value
|
|
1697
|
-
}
|
|
1727
|
+
if (0 <= keyBasedValue)
|
|
1728
|
+
value = keyBasedValue / 127;
|
|
1698
1729
|
}
|
|
1699
|
-
if (
|
|
1730
|
+
if (!note.chorusSend) {
|
|
1700
1731
|
if (0 < value) {
|
|
1701
|
-
note.
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1705
|
-
else {
|
|
1706
|
-
note.chorusEffectsSend.disconnect();
|
|
1732
|
+
note.chorusSend = new GainNode(this.audioContext, { gain: value });
|
|
1733
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1734
|
+
note.chorusSend.connect(this.chorusEffect.input);
|
|
1707
1735
|
}
|
|
1708
1736
|
}
|
|
1709
1737
|
else {
|
|
1738
|
+
note.chorusSend.gain
|
|
1739
|
+
.cancelScheduledValues(scheduleTime)
|
|
1740
|
+
.setValueAtTime(value, scheduleTime);
|
|
1710
1741
|
if (0 < value) {
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
note.volumeEnvelopeNode.
|
|
1742
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
try {
|
|
1746
|
+
note.volumeEnvelopeNode.disconnect(note.chorusSend);
|
|
1716
1747
|
}
|
|
1717
|
-
|
|
1748
|
+
catch { /* empty */ }
|
|
1718
1749
|
}
|
|
1719
1750
|
}
|
|
1720
1751
|
}
|
|
1721
|
-
setDelayModLFO(note
|
|
1722
|
-
const startTime = note.startTime;
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
note.modulationLFO.connect(note.filterDepth);
|
|
1752
|
+
setDelayModLFO(note) {
|
|
1753
|
+
const startTime = note.startTime + note.voiceParams.delayModLFO;
|
|
1754
|
+
try {
|
|
1755
|
+
note.modulationLFO.start(startTime);
|
|
1756
|
+
}
|
|
1757
|
+
catch { /* empty */ }
|
|
1728
1758
|
}
|
|
1729
1759
|
setFreqModLFO(note, scheduleTime) {
|
|
1730
1760
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
@@ -1733,54 +1763,65 @@ export class MidyGM2 {
|
|
|
1733
1763
|
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1734
1764
|
}
|
|
1735
1765
|
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1766
|
+
const vibratoRate = channel.state.vibratoRate * 2;
|
|
1736
1767
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1737
1768
|
note.vibratoLFO.frequency
|
|
1738
1769
|
.cancelScheduledValues(scheduleTime)
|
|
1739
|
-
.setValueAtTime(freqVibLFO *
|
|
1770
|
+
.setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
|
|
1771
|
+
}
|
|
1772
|
+
setDelayVibLFO(channel, note) {
|
|
1773
|
+
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1774
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1775
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1776
|
+
try {
|
|
1777
|
+
note.vibratoLFO.start(startTime);
|
|
1778
|
+
}
|
|
1779
|
+
catch { /* empty */ }
|
|
1740
1780
|
}
|
|
1741
1781
|
createVoiceParamsHandlers() {
|
|
1742
1782
|
return {
|
|
1743
|
-
modLfoToPitch: (channel, note,
|
|
1783
|
+
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1744
1784
|
if (0 < channel.state.modulationDepth) {
|
|
1745
1785
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1746
1786
|
}
|
|
1747
1787
|
},
|
|
1748
|
-
vibLfoToPitch: (channel, note,
|
|
1788
|
+
vibLfoToPitch: (channel, note, scheduleTime) => {
|
|
1749
1789
|
if (0 < channel.state.vibratoDepth) {
|
|
1750
1790
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1751
1791
|
}
|
|
1752
1792
|
},
|
|
1753
|
-
modLfoToFilterFc: (channel, note,
|
|
1793
|
+
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1754
1794
|
if (0 < channel.state.modulationDepth) {
|
|
1755
1795
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1756
1796
|
}
|
|
1757
1797
|
},
|
|
1758
|
-
modLfoToVolume: (channel, note,
|
|
1798
|
+
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1759
1799
|
if (0 < channel.state.modulationDepth) {
|
|
1760
1800
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1761
1801
|
}
|
|
1762
1802
|
},
|
|
1763
|
-
chorusEffectsSend: (channel, note,
|
|
1764
|
-
this.
|
|
1803
|
+
chorusEffectsSend: (channel, note, scheduleTime) => {
|
|
1804
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
1805
|
+
},
|
|
1806
|
+
reverbEffectsSend: (channel, note, scheduleTime) => {
|
|
1807
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
1808
|
+
},
|
|
1809
|
+
delayModLFO: (_channel, note, _scheduleTime) => {
|
|
1810
|
+
if (0 < channel.state.modulationDepth) {
|
|
1811
|
+
this.setDelayModLFO(note);
|
|
1812
|
+
}
|
|
1765
1813
|
},
|
|
1766
|
-
|
|
1767
|
-
|
|
1814
|
+
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1815
|
+
if (0 < channel.state.modulationDepth) {
|
|
1816
|
+
this.setFreqModLFO(note, scheduleTime);
|
|
1817
|
+
}
|
|
1768
1818
|
},
|
|
1769
|
-
|
|
1770
|
-
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1771
|
-
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1819
|
+
delayVibLFO: (channel, note, _scheduleTime) => {
|
|
1772
1820
|
if (0 < channel.state.vibratoDepth) {
|
|
1773
|
-
|
|
1774
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1775
|
-
if (scheduleTime < prevStartTime)
|
|
1776
|
-
return;
|
|
1777
|
-
const value = note.voiceParams.delayVibLFO;
|
|
1778
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
1779
|
-
note.vibratoLFO.stop(scheduleTime);
|
|
1780
|
-
note.vibratoLFO.start(startTime);
|
|
1821
|
+
setDelayVibLFO(channel, note);
|
|
1781
1822
|
}
|
|
1782
1823
|
},
|
|
1783
|
-
freqVibLFO: (channel, note,
|
|
1824
|
+
freqVibLFO: (channel, note, scheduleTime) => {
|
|
1784
1825
|
if (0 < channel.state.vibratoDepth) {
|
|
1785
1826
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1786
1827
|
}
|
|
@@ -1808,7 +1849,7 @@ export class MidyGM2 {
|
|
|
1808
1849
|
continue;
|
|
1809
1850
|
note.voiceParams[key] = value;
|
|
1810
1851
|
if (key in this.voiceParamsHandlers) {
|
|
1811
|
-
this.voiceParamsHandlers[key](channel, note,
|
|
1852
|
+
this.voiceParamsHandlers[key](channel, note, scheduleTime);
|
|
1812
1853
|
}
|
|
1813
1854
|
else {
|
|
1814
1855
|
if (volumeEnvelopeKeySet.has(key))
|
|
@@ -1863,7 +1904,7 @@ export class MidyGM2 {
|
|
|
1863
1904
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1864
1905
|
const channel = this.channels[channelNumber];
|
|
1865
1906
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1866
|
-
this.
|
|
1907
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
1867
1908
|
}
|
|
1868
1909
|
else {
|
|
1869
1910
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1879,7 +1920,6 @@ export class MidyGM2 {
|
|
|
1879
1920
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1880
1921
|
}
|
|
1881
1922
|
else {
|
|
1882
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1883
1923
|
this.startModulation(channel, note, scheduleTime);
|
|
1884
1924
|
}
|
|
1885
1925
|
});
|
|
@@ -1924,8 +1964,14 @@ export class MidyGM2 {
|
|
|
1924
1964
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1925
1965
|
const channel = this.channels[channelNumber];
|
|
1926
1966
|
channel.state.volume = volume / 127;
|
|
1927
|
-
|
|
1928
|
-
|
|
1967
|
+
if (channel.isDrum) {
|
|
1968
|
+
for (let i = 0; i < 128; i++) {
|
|
1969
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
else {
|
|
1973
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1974
|
+
}
|
|
1929
1975
|
}
|
|
1930
1976
|
panToGain(pan) {
|
|
1931
1977
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1938,8 +1984,14 @@ export class MidyGM2 {
|
|
|
1938
1984
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1939
1985
|
const channel = this.channels[channelNumber];
|
|
1940
1986
|
channel.state.pan = pan / 127;
|
|
1941
|
-
|
|
1942
|
-
|
|
1987
|
+
if (channel.isDrum) {
|
|
1988
|
+
for (let i = 0; i < 128; i++) {
|
|
1989
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
else {
|
|
1993
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1994
|
+
}
|
|
1943
1995
|
}
|
|
1944
1996
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1945
1997
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1965,33 +2017,27 @@ export class MidyGM2 {
|
|
|
1965
2017
|
.cancelScheduledValues(scheduleTime)
|
|
1966
2018
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1967
2019
|
}
|
|
1968
|
-
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1969
|
-
|
|
2020
|
+
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
2021
|
+
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
2022
|
+
if (!gainL)
|
|
1970
2023
|
return;
|
|
2024
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
1971
2025
|
const state = channel.state;
|
|
1972
2026
|
const defaultVolume = state.volume * state.expression;
|
|
1973
2027
|
const defaultPan = state.pan;
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1988
|
-
gainL.gain
|
|
1989
|
-
.cancelScheduledValues(scheduleTime)
|
|
1990
|
-
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1991
|
-
gainR.gain
|
|
1992
|
-
.cancelScheduledValues(scheduleTime)
|
|
1993
|
-
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1994
|
-
}
|
|
2028
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
2029
|
+
const volume = (0 <= keyBasedVolume)
|
|
2030
|
+
? defaultVolume * keyBasedVolume / 64
|
|
2031
|
+
: defaultVolume;
|
|
2032
|
+
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
2033
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2034
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2035
|
+
gainL.gain
|
|
2036
|
+
.cancelScheduledValues(scheduleTime)
|
|
2037
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2038
|
+
gainR.gain
|
|
2039
|
+
.cancelScheduledValues(scheduleTime)
|
|
2040
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1995
2041
|
}
|
|
1996
2042
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1997
2043
|
const channel = this.channels[channelNumber];
|
|
@@ -2058,67 +2104,19 @@ export class MidyGM2 {
|
|
|
2058
2104
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2059
2105
|
const channel = this.channels[channelNumber];
|
|
2060
2106
|
const state = channel.state;
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
reverbEffect.input.gain
|
|
2066
|
-
.cancelScheduledValues(scheduleTime)
|
|
2067
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2068
|
-
}
|
|
2069
|
-
else {
|
|
2070
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2071
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2072
|
-
return false;
|
|
2073
|
-
if (note.reverbEffectsSend)
|
|
2074
|
-
note.reverbEffectsSend.disconnect();
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
else {
|
|
2079
|
-
if (0 < reverbSendLevel) {
|
|
2080
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2081
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2082
|
-
});
|
|
2083
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2084
|
-
reverbEffect.input.gain
|
|
2085
|
-
.cancelScheduledValues(scheduleTime)
|
|
2086
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2107
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2108
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2109
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2110
|
+
});
|
|
2089
2111
|
}
|
|
2090
2112
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2091
2113
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2092
2114
|
const channel = this.channels[channelNumber];
|
|
2093
2115
|
const state = channel.state;
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
chorusEffect.input.gain
|
|
2099
|
-
.cancelScheduledValues(scheduleTime)
|
|
2100
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2101
|
-
}
|
|
2102
|
-
else {
|
|
2103
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2104
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2105
|
-
return false;
|
|
2106
|
-
if (note.chorusEffectsSend)
|
|
2107
|
-
note.chorusEffectsSend.disconnect();
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
else {
|
|
2112
|
-
if (0 < chorusSendLevel) {
|
|
2113
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2114
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2115
|
-
});
|
|
2116
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2117
|
-
chorusEffect.input.gain
|
|
2118
|
-
.cancelScheduledValues(scheduleTime)
|
|
2119
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2116
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2117
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2118
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2119
|
+
});
|
|
2122
2120
|
}
|
|
2123
2121
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
2124
2122
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -2178,8 +2176,8 @@ export class MidyGM2 {
|
|
|
2178
2176
|
}
|
|
2179
2177
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2180
2178
|
const channel = this.channels[channelNumber];
|
|
2181
|
-
this.limitData(channel, 0, 127, 0,
|
|
2182
|
-
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2179
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
2180
|
+
const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2183
2181
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2184
2182
|
}
|
|
2185
2183
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
@@ -2189,7 +2187,7 @@ export class MidyGM2 {
|
|
|
2189
2187
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2190
2188
|
const state = channel.state;
|
|
2191
2189
|
const prev = state.pitchWheelSensitivity;
|
|
2192
|
-
const next = value /
|
|
2190
|
+
const next = value / 12800;
|
|
2193
2191
|
state.pitchWheelSensitivity = next;
|
|
2194
2192
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2195
2193
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2198,7 +2196,8 @@ export class MidyGM2 {
|
|
|
2198
2196
|
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
2199
2197
|
const channel = this.channels[channelNumber];
|
|
2200
2198
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2201
|
-
const
|
|
2199
|
+
const value = channel.dataMSB * 128 + channel.dataLSB;
|
|
2200
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2202
2201
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2203
2202
|
}
|
|
2204
2203
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2207,7 +2206,7 @@ export class MidyGM2 {
|
|
|
2207
2206
|
return;
|
|
2208
2207
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2209
2208
|
const prev = channel.fineTuning;
|
|
2210
|
-
const next =
|
|
2209
|
+
const next = value;
|
|
2211
2210
|
channel.fineTuning = next;
|
|
2212
2211
|
channel.detune += next - prev;
|
|
2213
2212
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2215,7 +2214,7 @@ export class MidyGM2 {
|
|
|
2215
2214
|
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2216
2215
|
const channel = this.channels[channelNumber];
|
|
2217
2216
|
this.limitDataMSB(channel, 0, 127);
|
|
2218
|
-
const coarseTuning = channel.dataMSB;
|
|
2217
|
+
const coarseTuning = (channel.dataMSB - 64) * 100;
|
|
2219
2218
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2220
2219
|
}
|
|
2221
2220
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2224,7 +2223,7 @@ export class MidyGM2 {
|
|
|
2224
2223
|
return;
|
|
2225
2224
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2226
2225
|
const prev = channel.coarseTuning;
|
|
2227
|
-
const next =
|
|
2226
|
+
const next = value;
|
|
2228
2227
|
channel.coarseTuning = next;
|
|
2229
2228
|
channel.detune += next - prev;
|
|
2230
2229
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2232,22 +2231,22 @@ export class MidyGM2 {
|
|
|
2232
2231
|
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2233
2232
|
const channel = this.channels[channelNumber];
|
|
2234
2233
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2235
|
-
const
|
|
2236
|
-
this.setModulationDepthRange(channelNumber,
|
|
2234
|
+
const value = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2235
|
+
this.setModulationDepthRange(channelNumber, value, scheduleTime);
|
|
2237
2236
|
}
|
|
2238
|
-
setModulationDepthRange(channelNumber,
|
|
2237
|
+
setModulationDepthRange(channelNumber, value, scheduleTime) {
|
|
2239
2238
|
const channel = this.channels[channelNumber];
|
|
2240
2239
|
if (channel.isDrum)
|
|
2241
2240
|
return;
|
|
2242
2241
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2243
|
-
channel.modulationDepthRange =
|
|
2242
|
+
channel.modulationDepthRange = value;
|
|
2244
2243
|
this.updateModulation(channel, scheduleTime);
|
|
2245
2244
|
}
|
|
2246
2245
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2247
2246
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2248
2247
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2249
2248
|
}
|
|
2250
|
-
|
|
2249
|
+
resetChannelStates(channelNumber) {
|
|
2251
2250
|
const scheduleTime = this.audioContext.currentTime;
|
|
2252
2251
|
const channel = this.channels[channelNumber];
|
|
2253
2252
|
const state = channel.state;
|
|
@@ -2265,8 +2264,8 @@ export class MidyGM2 {
|
|
|
2265
2264
|
}
|
|
2266
2265
|
this.resetChannelTable(channel);
|
|
2267
2266
|
this.mode = "GM2";
|
|
2268
|
-
this.masterFineTuning = 0; //
|
|
2269
|
-
this.masterCoarseTuning = 0; //
|
|
2267
|
+
this.masterFineTuning = 0; // cent
|
|
2268
|
+
this.masterCoarseTuning = 0; // cent
|
|
2270
2269
|
}
|
|
2271
2270
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2272
2271
|
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
@@ -2400,9 +2399,9 @@ export class MidyGM2 {
|
|
|
2400
2399
|
case 9:
|
|
2401
2400
|
switch (data[3]) {
|
|
2402
2401
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2403
|
-
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2402
|
+
return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
|
|
2404
2403
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2405
|
-
return this.handleControlChangeSysEx(data);
|
|
2404
|
+
return this.handleControlChangeSysEx(data, scheduleTime);
|
|
2406
2405
|
default:
|
|
2407
2406
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2408
2407
|
}
|
|
@@ -2435,12 +2434,13 @@ export class MidyGM2 {
|
|
|
2435
2434
|
}
|
|
2436
2435
|
}
|
|
2437
2436
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2438
|
-
const
|
|
2437
|
+
const value = (data[5] * 128 + data[4]) / 16383;
|
|
2438
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2439
2439
|
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2440
2440
|
}
|
|
2441
2441
|
setMasterFineTuning(value, scheduleTime) {
|
|
2442
2442
|
const prev = this.masterFineTuning;
|
|
2443
|
-
const next =
|
|
2443
|
+
const next = value;
|
|
2444
2444
|
this.masterFineTuning = next;
|
|
2445
2445
|
const detuneChange = next - prev;
|
|
2446
2446
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2452,12 +2452,12 @@ export class MidyGM2 {
|
|
|
2452
2452
|
}
|
|
2453
2453
|
}
|
|
2454
2454
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2455
|
-
const coarseTuning = data[4];
|
|
2455
|
+
const coarseTuning = (data[4] - 64) * 100;
|
|
2456
2456
|
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2457
2457
|
}
|
|
2458
2458
|
setMasterCoarseTuning(value, scheduleTime) {
|
|
2459
2459
|
const prev = this.masterCoarseTuning;
|
|
2460
|
-
const next =
|
|
2460
|
+
const next = value;
|
|
2461
2461
|
this.masterCoarseTuning = next;
|
|
2462
2462
|
const detuneChange = next - prev;
|
|
2463
2463
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2721,9 +2721,9 @@ export class MidyGM2 {
|
|
|
2721
2721
|
: 0;
|
|
2722
2722
|
return channelPressure / 127;
|
|
2723
2723
|
}
|
|
2724
|
-
|
|
2724
|
+
setEffects(channel, note, table, scheduleTime) {
|
|
2725
2725
|
if (0 <= table[0])
|
|
2726
|
-
this.updateDetune(channel, note,
|
|
2726
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2727
2727
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2728
2728
|
if (0 <= table[1]) {
|
|
2729
2729
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2745,7 +2745,7 @@ export class MidyGM2 {
|
|
|
2745
2745
|
if (0 <= table[5])
|
|
2746
2746
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2747
2747
|
}
|
|
2748
|
-
handlePressureSysEx(data, tableName) {
|
|
2748
|
+
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2749
2749
|
const channelNumber = data[4];
|
|
2750
2750
|
const channel = this.channels[channelNumber];
|
|
2751
2751
|
if (channel.isDrum)
|
|
@@ -2756,36 +2756,42 @@ export class MidyGM2 {
|
|
|
2756
2756
|
const rr = data[i + 1];
|
|
2757
2757
|
table[pp] = rr;
|
|
2758
2758
|
}
|
|
2759
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2760
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2761
|
+
});
|
|
2759
2762
|
}
|
|
2760
2763
|
initControlTable() {
|
|
2761
2764
|
const ccCount = 128;
|
|
2762
2765
|
const slotSize = 6;
|
|
2763
2766
|
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2764
2767
|
}
|
|
2765
|
-
|
|
2768
|
+
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2766
2769
|
const slotSize = 6;
|
|
2767
2770
|
const offset = controllerType * slotSize;
|
|
2768
2771
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2769
2772
|
this.processScheduledNotes(channel, (note) => {
|
|
2770
|
-
this.
|
|
2773
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2771
2774
|
});
|
|
2772
2775
|
}
|
|
2773
|
-
handleControlChangeSysEx(data) {
|
|
2776
|
+
handleControlChangeSysEx(data, scheduleTime) {
|
|
2774
2777
|
const channelNumber = data[4];
|
|
2775
2778
|
const channel = this.channels[channelNumber];
|
|
2776
2779
|
if (channel.isDrum)
|
|
2777
2780
|
return;
|
|
2781
|
+
const slotSize = 6;
|
|
2778
2782
|
const controllerType = data[5];
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2783
|
+
const offset = controllerType * slotSize;
|
|
2784
|
+
const table = channel.controlTable;
|
|
2785
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2781
2786
|
const pp = data[i];
|
|
2782
2787
|
const rr = data[i + 1];
|
|
2783
|
-
table[pp] = rr;
|
|
2788
|
+
table[offset + pp] = rr;
|
|
2784
2789
|
}
|
|
2790
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
2785
2791
|
}
|
|
2786
2792
|
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2787
2793
|
const index = keyNumber * 128 + controllerType;
|
|
2788
|
-
const controlValue = channel.
|
|
2794
|
+
const controlValue = channel.keyBasedTable[index];
|
|
2789
2795
|
return controlValue;
|
|
2790
2796
|
}
|
|
2791
2797
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
@@ -2794,14 +2800,33 @@ export class MidyGM2 {
|
|
|
2794
2800
|
if (!channel.isDrum)
|
|
2795
2801
|
return;
|
|
2796
2802
|
const keyNumber = data[5];
|
|
2797
|
-
const table = channel.
|
|
2798
|
-
for (let i = 6; i < data.length
|
|
2803
|
+
const table = channel.keyBasedTable;
|
|
2804
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2799
2805
|
const controllerType = data[i];
|
|
2800
2806
|
const value = data[i + 1];
|
|
2801
2807
|
const index = keyNumber * 128 + controllerType;
|
|
2802
2808
|
table[index] = value;
|
|
2809
|
+
switch (controllerType) {
|
|
2810
|
+
case 7:
|
|
2811
|
+
case 10:
|
|
2812
|
+
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2813
|
+
break;
|
|
2814
|
+
case 91:
|
|
2815
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2816
|
+
if (note.noteNumber === keyNumber) {
|
|
2817
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
break;
|
|
2821
|
+
case 93:
|
|
2822
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2823
|
+
if (note.noteNumber === keyNumber) {
|
|
2824
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
break;
|
|
2828
|
+
}
|
|
2803
2829
|
}
|
|
2804
|
-
this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2805
2830
|
}
|
|
2806
2831
|
handleSysEx(data, scheduleTime) {
|
|
2807
2832
|
switch (data[0]) {
|
|
@@ -2850,7 +2875,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2850
2875
|
rpnLSB: 127,
|
|
2851
2876
|
mono: false, // CC#124, CC#125
|
|
2852
2877
|
modulationDepthRange: 50, // cent
|
|
2853
|
-
fineTuning: 0, //
|
|
2854
|
-
coarseTuning: 0, //
|
|
2878
|
+
fineTuning: 0, // cent
|
|
2879
|
+
coarseTuning: 0, // cent
|
|
2855
2880
|
}
|
|
2856
2881
|
});
|