@marmooo/midy 0.2.5 → 0.2.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 +17 -12
- package/esm/midy-GM1.d.ts +85 -87
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +188 -237
- package/esm/midy-GM2.d.ts +129 -126
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +374 -390
- package/esm/midy-GMLite.d.ts +85 -84
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +191 -190
- package/esm/midy.d.ts +153 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +420 -442
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +85 -87
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +188 -237
- package/script/midy-GM2.d.ts +129 -126
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +374 -390
- package/script/midy-GMLite.d.ts +85 -84
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +191 -190
- package/script/midy.d.ts +153 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +420 -442
package/script/midy-GM1.js
CHANGED
|
@@ -69,37 +69,31 @@ class Note {
|
|
|
69
69
|
writable: true,
|
|
70
70
|
value: void 0
|
|
71
71
|
});
|
|
72
|
-
Object.defineProperty(this, "
|
|
73
|
-
enumerable: true,
|
|
74
|
-
configurable: true,
|
|
75
|
-
writable: true,
|
|
76
|
-
value: void 0
|
|
77
|
-
});
|
|
78
|
-
Object.defineProperty(this, "volumeDepth", {
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
79
73
|
enumerable: true,
|
|
80
74
|
configurable: true,
|
|
81
75
|
writable: true,
|
|
82
76
|
value: void 0
|
|
83
77
|
});
|
|
84
|
-
Object.defineProperty(this, "
|
|
78
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
85
79
|
enumerable: true,
|
|
86
80
|
configurable: true,
|
|
87
81
|
writable: true,
|
|
88
82
|
value: void 0
|
|
89
83
|
});
|
|
90
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
91
85
|
enumerable: true,
|
|
92
86
|
configurable: true,
|
|
93
87
|
writable: true,
|
|
94
88
|
value: void 0
|
|
95
89
|
});
|
|
96
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "modulationLFO", {
|
|
97
91
|
enumerable: true,
|
|
98
92
|
configurable: true,
|
|
99
93
|
writable: true,
|
|
100
94
|
value: void 0
|
|
101
95
|
});
|
|
102
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "modulationDepth", {
|
|
103
97
|
enumerable: true,
|
|
104
98
|
configurable: true,
|
|
105
99
|
writable: true,
|
|
@@ -412,31 +406,32 @@ class MidyGM1 {
|
|
|
412
406
|
const event = this.timeline[queueIndex];
|
|
413
407
|
if (event.startTime > t + this.lookAhead)
|
|
414
408
|
break;
|
|
409
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
415
410
|
switch (event.type) {
|
|
416
411
|
case "noteOn":
|
|
417
412
|
if (event.velocity !== 0) {
|
|
418
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
413
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
419
414
|
break;
|
|
420
415
|
}
|
|
421
416
|
/* falls through */
|
|
422
417
|
case "noteOff": {
|
|
423
|
-
const notePromise = this.
|
|
418
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
|
|
424
419
|
if (notePromise) {
|
|
425
420
|
this.notePromises.push(notePromise);
|
|
426
421
|
}
|
|
427
422
|
break;
|
|
428
423
|
}
|
|
429
424
|
case "controller":
|
|
430
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
425
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
431
426
|
break;
|
|
432
427
|
case "programChange":
|
|
433
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
428
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
434
429
|
break;
|
|
435
430
|
case "pitchBend":
|
|
436
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
431
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
437
432
|
break;
|
|
438
433
|
case "sysEx":
|
|
439
|
-
this.handleSysEx(event.data);
|
|
434
|
+
this.handleSysEx(event.data, startTime);
|
|
440
435
|
}
|
|
441
436
|
queueIndex++;
|
|
442
437
|
}
|
|
@@ -467,10 +462,11 @@ class MidyGM1 {
|
|
|
467
462
|
resolve();
|
|
468
463
|
return;
|
|
469
464
|
}
|
|
470
|
-
const
|
|
465
|
+
const now = this.audioContext.currentTime;
|
|
466
|
+
const t = now + offset;
|
|
471
467
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
472
468
|
if (this.isPausing) {
|
|
473
|
-
await this.stopNotes(0, true);
|
|
469
|
+
await this.stopNotes(0, true, now);
|
|
474
470
|
this.notePromises = [];
|
|
475
471
|
resolve();
|
|
476
472
|
this.isPausing = false;
|
|
@@ -478,7 +474,7 @@ class MidyGM1 {
|
|
|
478
474
|
return;
|
|
479
475
|
}
|
|
480
476
|
else if (this.isStopping) {
|
|
481
|
-
await this.stopNotes(0, true);
|
|
477
|
+
await this.stopNotes(0, true, now);
|
|
482
478
|
this.notePromises = [];
|
|
483
479
|
this.exclusiveClassMap.clear();
|
|
484
480
|
this.audioBufferCache.clear();
|
|
@@ -488,7 +484,7 @@ class MidyGM1 {
|
|
|
488
484
|
return;
|
|
489
485
|
}
|
|
490
486
|
else if (this.isSeeking) {
|
|
491
|
-
this.stopNotes(0, true);
|
|
487
|
+
this.stopNotes(0, true, now);
|
|
492
488
|
this.exclusiveClassMap.clear();
|
|
493
489
|
this.startTime = this.audioContext.currentTime;
|
|
494
490
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -497,7 +493,6 @@ class MidyGM1 {
|
|
|
497
493
|
await schedulePlayback();
|
|
498
494
|
}
|
|
499
495
|
else {
|
|
500
|
-
const now = this.audioContext.currentTime;
|
|
501
496
|
const waitTime = now + this.noteCheckInterval;
|
|
502
497
|
await this.scheduleTask(() => { }, waitTime);
|
|
503
498
|
await schedulePlayback();
|
|
@@ -581,24 +576,26 @@ class MidyGM1 {
|
|
|
581
576
|
}
|
|
582
577
|
return { instruments, timeline };
|
|
583
578
|
}
|
|
584
|
-
|
|
585
|
-
const now = this.audioContext.currentTime;
|
|
579
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
586
580
|
const channel = this.channels[channelNumber];
|
|
581
|
+
const promises = [];
|
|
587
582
|
channel.scheduledNotes.forEach((noteList) => {
|
|
588
583
|
for (let i = 0; i < noteList.length; i++) {
|
|
589
584
|
const note = noteList[i];
|
|
590
585
|
if (!note)
|
|
591
586
|
continue;
|
|
592
|
-
const promise = this.
|
|
587
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
593
588
|
this.notePromises.push(promise);
|
|
589
|
+
promises.push(promise);
|
|
594
590
|
}
|
|
595
591
|
});
|
|
596
592
|
channel.scheduledNotes.clear();
|
|
597
|
-
|
|
593
|
+
return Promise.all(promises);
|
|
598
594
|
}
|
|
599
|
-
stopNotes(velocity, force) {
|
|
595
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
596
|
+
const promises = [];
|
|
600
597
|
for (let i = 0; i < this.channels.length; i++) {
|
|
601
|
-
this.stopChannelNotes(i, velocity, force);
|
|
598
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
602
599
|
}
|
|
603
600
|
return Promise.all(this.notePromises);
|
|
604
601
|
}
|
|
@@ -646,22 +643,34 @@ class MidyGM1 {
|
|
|
646
643
|
const now = this.audioContext.currentTime;
|
|
647
644
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
648
645
|
}
|
|
649
|
-
|
|
646
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
647
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
648
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
649
|
+
const note = noteList[i];
|
|
650
|
+
if (!note)
|
|
651
|
+
continue;
|
|
652
|
+
if (scheduleTime < note.startTime)
|
|
653
|
+
continue;
|
|
654
|
+
callback(note);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
getActiveNotes(channel, scheduleTime) {
|
|
650
659
|
const activeNotes = new SparseMap(128);
|
|
651
660
|
channel.scheduledNotes.forEach((noteList) => {
|
|
652
|
-
const activeNote = this.getActiveNote(noteList,
|
|
661
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
653
662
|
if (activeNote) {
|
|
654
663
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
655
664
|
}
|
|
656
665
|
});
|
|
657
666
|
return activeNotes;
|
|
658
667
|
}
|
|
659
|
-
getActiveNote(noteList,
|
|
668
|
+
getActiveNote(noteList, scheduleTime) {
|
|
660
669
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
661
670
|
const note = noteList[i];
|
|
662
671
|
if (!note)
|
|
663
672
|
return;
|
|
664
|
-
if (
|
|
673
|
+
if (scheduleTime < note.startTime)
|
|
665
674
|
continue;
|
|
666
675
|
return (note.ending) ? null : note;
|
|
667
676
|
}
|
|
@@ -686,24 +695,17 @@ class MidyGM1 {
|
|
|
686
695
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
687
696
|
return tuning + pitch;
|
|
688
697
|
}
|
|
689
|
-
updateChannelDetune(channel) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const note = noteList[i];
|
|
693
|
-
if (!note)
|
|
694
|
-
continue;
|
|
695
|
-
this.updateDetune(channel, note);
|
|
696
|
-
}
|
|
698
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
699
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
700
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
697
701
|
});
|
|
698
702
|
}
|
|
699
|
-
updateDetune(channel, note) {
|
|
700
|
-
const now = this.audioContext.currentTime;
|
|
703
|
+
updateDetune(channel, note, scheduleTime) {
|
|
701
704
|
note.bufferSource.detune
|
|
702
|
-
.cancelScheduledValues(
|
|
703
|
-
.setValueAtTime(channel.detune,
|
|
705
|
+
.cancelScheduledValues(scheduleTime)
|
|
706
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
704
707
|
}
|
|
705
|
-
setVolumeEnvelope(note) {
|
|
706
|
-
const now = this.audioContext.currentTime;
|
|
708
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
707
709
|
const { voiceParams, startTime } = note;
|
|
708
710
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
709
711
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -712,27 +714,26 @@ class MidyGM1 {
|
|
|
712
714
|
const volHold = volAttack + voiceParams.volHold;
|
|
713
715
|
const volDecay = volHold + voiceParams.volDecay;
|
|
714
716
|
note.volumeEnvelopeNode.gain
|
|
715
|
-
.cancelScheduledValues(
|
|
717
|
+
.cancelScheduledValues(scheduleTime)
|
|
716
718
|
.setValueAtTime(0, startTime)
|
|
717
719
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
718
720
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
719
721
|
.setValueAtTime(attackVolume, volHold)
|
|
720
722
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
721
723
|
}
|
|
722
|
-
setPitchEnvelope(note) {
|
|
723
|
-
const now = this.audioContext.currentTime;
|
|
724
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
724
725
|
const { voiceParams } = note;
|
|
725
726
|
const baseRate = voiceParams.playbackRate;
|
|
726
727
|
note.bufferSource.playbackRate
|
|
727
|
-
.cancelScheduledValues(
|
|
728
|
-
.setValueAtTime(baseRate,
|
|
728
|
+
.cancelScheduledValues(scheduleTime)
|
|
729
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
729
730
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
730
731
|
if (modEnvToPitch === 0)
|
|
731
732
|
return;
|
|
732
733
|
const basePitch = this.rateToCent(baseRate);
|
|
733
734
|
const peekPitch = basePitch + modEnvToPitch;
|
|
734
735
|
const peekRate = this.centToRate(peekPitch);
|
|
735
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
736
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
736
737
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
737
738
|
const modHold = modAttack + voiceParams.modHold;
|
|
738
739
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -747,8 +748,7 @@ class MidyGM1 {
|
|
|
747
748
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
748
749
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
749
750
|
}
|
|
750
|
-
setFilterEnvelope(note) {
|
|
751
|
-
const now = this.audioContext.currentTime;
|
|
751
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
752
752
|
const { voiceParams, startTime } = note;
|
|
753
753
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
754
754
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -762,14 +762,14 @@ class MidyGM1 {
|
|
|
762
762
|
const modHold = modAttack + voiceParams.modHold;
|
|
763
763
|
const modDecay = modHold + voiceParams.modDecay;
|
|
764
764
|
note.filterNode.frequency
|
|
765
|
-
.cancelScheduledValues(
|
|
765
|
+
.cancelScheduledValues(scheduleTime)
|
|
766
766
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
767
767
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
768
768
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
769
769
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
770
770
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
771
771
|
}
|
|
772
|
-
startModulation(channel, note,
|
|
772
|
+
startModulation(channel, note, scheduleTime) {
|
|
773
773
|
const { voiceParams } = note;
|
|
774
774
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
775
775
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -778,10 +778,10 @@ class MidyGM1 {
|
|
|
778
778
|
gain: voiceParams.modLfoToFilterFc,
|
|
779
779
|
});
|
|
780
780
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
781
|
-
this.setModLfoToPitch(channel, note);
|
|
781
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
782
782
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
783
|
-
this.setModLfoToVolume(note);
|
|
784
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
783
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
784
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
785
785
|
note.modulationLFO.connect(note.filterDepth);
|
|
786
786
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
787
787
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -808,6 +808,7 @@ class MidyGM1 {
|
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
811
|
+
const now = this.audioContext.currentTime;
|
|
811
812
|
const state = channel.state;
|
|
812
813
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
813
814
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -819,11 +820,11 @@ class MidyGM1 {
|
|
|
819
820
|
type: "lowpass",
|
|
820
821
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
821
822
|
});
|
|
822
|
-
this.setVolumeEnvelope(note);
|
|
823
|
-
this.setFilterEnvelope(note);
|
|
824
|
-
this.setPitchEnvelope(note);
|
|
823
|
+
this.setVolumeEnvelope(note, now);
|
|
824
|
+
this.setFilterEnvelope(note, now);
|
|
825
|
+
this.setPitchEnvelope(note, now);
|
|
825
826
|
if (0 < state.modulationDepth) {
|
|
826
|
-
this.startModulation(channel, note,
|
|
827
|
+
this.startModulation(channel, note, now);
|
|
827
828
|
}
|
|
828
829
|
note.bufferSource.connect(note.filterNode);
|
|
829
830
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -850,7 +851,7 @@ class MidyGM1 {
|
|
|
850
851
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
851
852
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
852
853
|
if (!prevNote.ending) {
|
|
853
|
-
this.
|
|
854
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
854
855
|
startTime, undefined, // portamentoNoteNumber
|
|
855
856
|
true);
|
|
856
857
|
}
|
|
@@ -865,9 +866,9 @@ class MidyGM1 {
|
|
|
865
866
|
scheduledNotes.set(noteNumber, [note]);
|
|
866
867
|
}
|
|
867
868
|
}
|
|
868
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
869
|
-
|
|
870
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
869
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
870
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
871
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
871
872
|
}
|
|
872
873
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
873
874
|
const note = scheduledNotes[index];
|
|
@@ -889,16 +890,12 @@ class MidyGM1 {
|
|
|
889
890
|
note.modulationDepth.disconnect();
|
|
890
891
|
note.modulationLFO.stop();
|
|
891
892
|
}
|
|
892
|
-
if (note.vibratoDepth) {
|
|
893
|
-
note.vibratoDepth.disconnect();
|
|
894
|
-
note.vibratoLFO.stop();
|
|
895
|
-
}
|
|
896
893
|
resolve();
|
|
897
894
|
};
|
|
898
895
|
note.bufferSource.stop(stopTime);
|
|
899
896
|
});
|
|
900
897
|
}
|
|
901
|
-
|
|
898
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
902
899
|
const channel = this.channels[channelNumber];
|
|
903
900
|
if (!force && 0.5 < channel.state.sustainPedal)
|
|
904
901
|
return;
|
|
@@ -920,161 +917,119 @@ class MidyGM1 {
|
|
|
920
917
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
921
918
|
}
|
|
922
919
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
return this.
|
|
920
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
921
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
922
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
926
923
|
}
|
|
927
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
924
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
928
925
|
const velocity = halfVelocity * 2;
|
|
929
926
|
const channel = this.channels[channelNumber];
|
|
930
927
|
const promises = [];
|
|
931
|
-
channel
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (!note)
|
|
936
|
-
continue;
|
|
937
|
-
const { noteNumber } = note;
|
|
938
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
939
|
-
promises.push(promise);
|
|
940
|
-
}
|
|
928
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
929
|
+
const { noteNumber } = note;
|
|
930
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
931
|
+
promises.push(promise);
|
|
941
932
|
});
|
|
942
933
|
return promises;
|
|
943
934
|
}
|
|
944
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
935
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
945
936
|
const channelNumber = statusByte & 0x0F;
|
|
946
937
|
const messageType = statusByte & 0xF0;
|
|
947
938
|
switch (messageType) {
|
|
948
939
|
case 0x80:
|
|
949
|
-
return this.
|
|
940
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
950
941
|
case 0x90:
|
|
951
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
942
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
952
943
|
case 0xB0:
|
|
953
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
944
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
954
945
|
case 0xC0:
|
|
955
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
946
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
956
947
|
case 0xE0:
|
|
957
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
948
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
958
949
|
default:
|
|
959
950
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
960
951
|
}
|
|
961
952
|
}
|
|
962
|
-
handleProgramChange(channelNumber, program) {
|
|
953
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
963
954
|
const channel = this.channels[channelNumber];
|
|
964
955
|
channel.program = program;
|
|
965
956
|
}
|
|
966
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
957
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
967
958
|
const pitchBend = msb * 128 + lsb;
|
|
968
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
959
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
969
960
|
}
|
|
970
|
-
setPitchBend(channelNumber, value) {
|
|
961
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
962
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
971
963
|
const channel = this.channels[channelNumber];
|
|
972
964
|
const state = channel.state;
|
|
973
965
|
const prev = state.pitchWheel * 2 - 1;
|
|
974
966
|
const next = (value - 8192) / 8192;
|
|
975
967
|
state.pitchWheel = value / 16383;
|
|
976
968
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
977
|
-
this.updateChannelDetune(channel);
|
|
978
|
-
this.applyVoiceParams(channel, 14);
|
|
969
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
970
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
979
971
|
}
|
|
980
|
-
setModLfoToPitch(channel, note) {
|
|
981
|
-
const now = this.audioContext.currentTime;
|
|
972
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
982
973
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
983
974
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
984
975
|
channel.state.modulationDepth;
|
|
985
976
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
986
977
|
note.modulationDepth.gain
|
|
987
|
-
.cancelScheduledValues(
|
|
988
|
-
.setValueAtTime(modulationDepth,
|
|
978
|
+
.cancelScheduledValues(scheduleTime)
|
|
979
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
989
980
|
}
|
|
990
|
-
|
|
991
|
-
const now = this.audioContext.currentTime;
|
|
992
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
993
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
994
|
-
2;
|
|
995
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
996
|
-
note.vibratoDepth.gain
|
|
997
|
-
.cancelScheduledValues(now)
|
|
998
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
999
|
-
}
|
|
1000
|
-
setModLfoToFilterFc(note) {
|
|
1001
|
-
const now = this.audioContext.currentTime;
|
|
981
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
1002
982
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1003
983
|
note.filterDepth.gain
|
|
1004
|
-
.cancelScheduledValues(
|
|
1005
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
984
|
+
.cancelScheduledValues(scheduleTime)
|
|
985
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1006
986
|
}
|
|
1007
|
-
setModLfoToVolume(note) {
|
|
1008
|
-
const now = this.audioContext.currentTime;
|
|
987
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1009
988
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1010
989
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1011
990
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1012
991
|
note.volumeDepth.gain
|
|
1013
|
-
.cancelScheduledValues(
|
|
1014
|
-
.setValueAtTime(volumeDepth,
|
|
992
|
+
.cancelScheduledValues(scheduleTime)
|
|
993
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1015
994
|
}
|
|
1016
|
-
setDelayModLFO(note) {
|
|
1017
|
-
const now = this.audioContext.currentTime;
|
|
995
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1018
996
|
const startTime = note.startTime;
|
|
1019
|
-
if (startTime <
|
|
997
|
+
if (startTime < scheduleTime)
|
|
1020
998
|
return;
|
|
1021
|
-
note.modulationLFO.stop(
|
|
999
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1022
1000
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1023
1001
|
note.modulationLFO.connect(note.filterDepth);
|
|
1024
1002
|
}
|
|
1025
|
-
setFreqModLFO(note) {
|
|
1026
|
-
const now = this.audioContext.currentTime;
|
|
1003
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1027
1004
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1028
1005
|
note.modulationLFO.frequency
|
|
1029
|
-
.cancelScheduledValues(
|
|
1030
|
-
.setValueAtTime(freqModLFO,
|
|
1006
|
+
.cancelScheduledValues(scheduleTime)
|
|
1007
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1031
1008
|
}
|
|
1032
1009
|
createVoiceParamsHandlers() {
|
|
1033
1010
|
return {
|
|
1034
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1011
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1035
1012
|
if (0 < channel.state.modulationDepth) {
|
|
1036
|
-
this.setModLfoToPitch(channel, note);
|
|
1037
|
-
}
|
|
1038
|
-
},
|
|
1039
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1040
|
-
if (0 < channel.state.vibratoDepth) {
|
|
1041
|
-
this.setVibLfoToPitch(channel, note);
|
|
1013
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1042
1014
|
}
|
|
1043
1015
|
},
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1049
|
-
if (0 < channel.state.modulationDepth)
|
|
1050
|
-
this.setModLfoToVolume(note);
|
|
1051
|
-
},
|
|
1052
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1053
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1054
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1055
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1056
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1057
|
-
if (0 < channel.state.vibratoDepth) {
|
|
1058
|
-
const now = this.audioContext.currentTime;
|
|
1059
|
-
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1060
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1061
|
-
if (now < prevStartTime)
|
|
1062
|
-
return;
|
|
1063
|
-
const value = note.voiceParams.delayVibLFO;
|
|
1064
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
1065
|
-
note.vibratoLFO.stop(now);
|
|
1066
|
-
note.vibratoLFO.start(startTime);
|
|
1016
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1017
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1018
|
+
if (0 < channel.state.modulationDepth) {
|
|
1019
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1067
1020
|
}
|
|
1068
1021
|
},
|
|
1069
|
-
|
|
1070
|
-
if (0 < channel.state.
|
|
1071
|
-
|
|
1072
|
-
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1073
|
-
note.vibratoLFO.frequency
|
|
1074
|
-
.cancelScheduledValues(now)
|
|
1075
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
|
|
1022
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1023
|
+
if (0 < channel.state.modulationDepth) {
|
|
1024
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1076
1025
|
}
|
|
1077
1026
|
},
|
|
1027
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1028
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1029
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1030
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1031
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1032
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1078
1033
|
};
|
|
1079
1034
|
}
|
|
1080
1035
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1084,7 +1039,7 @@ class MidyGM1 {
|
|
|
1084
1039
|
state[3] = noteNumber / 127;
|
|
1085
1040
|
return state;
|
|
1086
1041
|
}
|
|
1087
|
-
applyVoiceParams(channel, controllerType) {
|
|
1042
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1088
1043
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1089
1044
|
for (let i = 0; i < noteList.length; i++) {
|
|
1090
1045
|
const note = noteList[i];
|
|
@@ -1100,7 +1055,7 @@ class MidyGM1 {
|
|
|
1100
1055
|
continue;
|
|
1101
1056
|
note.voiceParams[key] = value;
|
|
1102
1057
|
if (key in this.voiceParamsHandlers) {
|
|
1103
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1058
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1104
1059
|
}
|
|
1105
1060
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1106
1061
|
if (appliedFilterEnvelope)
|
|
@@ -1112,8 +1067,8 @@ class MidyGM1 {
|
|
|
1112
1067
|
if (key in voiceParams)
|
|
1113
1068
|
noteVoiceParams[key] = voiceParams[key];
|
|
1114
1069
|
}
|
|
1115
|
-
this.setFilterEnvelope(note);
|
|
1116
|
-
this.setPitchEnvelope(note);
|
|
1070
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1071
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1117
1072
|
}
|
|
1118
1073
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1119
1074
|
if (appliedVolumeEnvelope)
|
|
@@ -1125,7 +1080,7 @@ class MidyGM1 {
|
|
|
1125
1080
|
if (key in voiceParams)
|
|
1126
1081
|
noteVoiceParams[key] = voiceParams[key];
|
|
1127
1082
|
}
|
|
1128
|
-
this.setVolumeEnvelope(channel, note);
|
|
1083
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1129
1084
|
}
|
|
1130
1085
|
}
|
|
1131
1086
|
}
|
|
@@ -1147,44 +1102,39 @@ class MidyGM1 {
|
|
|
1147
1102
|
123: this.allNotesOff,
|
|
1148
1103
|
};
|
|
1149
1104
|
}
|
|
1150
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1105
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1151
1106
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1152
1107
|
if (handler) {
|
|
1153
|
-
handler.call(this, channelNumber, value);
|
|
1108
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1154
1109
|
const channel = this.channels[channelNumber];
|
|
1155
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1110
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1156
1111
|
}
|
|
1157
1112
|
else {
|
|
1158
1113
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1159
1114
|
}
|
|
1160
1115
|
}
|
|
1161
|
-
updateModulation(channel) {
|
|
1162
|
-
|
|
1116
|
+
updateModulation(channel, scheduleTime) {
|
|
1117
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1163
1118
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
else {
|
|
1173
|
-
this.setPitchEnvelope(note);
|
|
1174
|
-
this.startModulation(channel, note, now);
|
|
1175
|
-
}
|
|
1119
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1120
|
+
if (note.modulationDepth) {
|
|
1121
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1125
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1176
1126
|
}
|
|
1177
1127
|
});
|
|
1178
1128
|
}
|
|
1179
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1129
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1180
1130
|
const channel = this.channels[channelNumber];
|
|
1181
1131
|
channel.state.modulationDepth = modulation / 127;
|
|
1182
|
-
this.updateModulation(channel);
|
|
1132
|
+
this.updateModulation(channel, scheduleTime);
|
|
1183
1133
|
}
|
|
1184
|
-
setVolume(channelNumber, volume) {
|
|
1134
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1185
1135
|
const channel = this.channels[channelNumber];
|
|
1186
1136
|
channel.state.volume = volume / 127;
|
|
1187
|
-
this.updateChannelVolume(channel);
|
|
1137
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1188
1138
|
}
|
|
1189
1139
|
panToGain(pan) {
|
|
1190
1140
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1193,36 +1143,36 @@ class MidyGM1 {
|
|
|
1193
1143
|
gainRight: Math.sin(theta),
|
|
1194
1144
|
};
|
|
1195
1145
|
}
|
|
1196
|
-
setPan(channelNumber, pan) {
|
|
1146
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1197
1147
|
const channel = this.channels[channelNumber];
|
|
1198
1148
|
channel.state.pan = pan / 127;
|
|
1199
|
-
this.updateChannelVolume(channel);
|
|
1149
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1200
1150
|
}
|
|
1201
|
-
setExpression(channelNumber, expression) {
|
|
1151
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1202
1152
|
const channel = this.channels[channelNumber];
|
|
1203
1153
|
channel.state.expression = expression / 127;
|
|
1204
|
-
this.updateChannelVolume(channel);
|
|
1154
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1205
1155
|
}
|
|
1206
|
-
dataEntryLSB(channelNumber, value) {
|
|
1156
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1207
1157
|
this.channels[channelNumber].dataLSB = value;
|
|
1208
|
-
this.handleRPN(channelNumber,
|
|
1158
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1209
1159
|
}
|
|
1210
|
-
updateChannelVolume(channel) {
|
|
1211
|
-
const now = this.audioContext.currentTime;
|
|
1160
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1212
1161
|
const state = channel.state;
|
|
1213
1162
|
const volume = state.volume * state.expression;
|
|
1214
1163
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1215
1164
|
channel.gainL.gain
|
|
1216
|
-
.cancelScheduledValues(
|
|
1217
|
-
.setValueAtTime(volume * gainLeft,
|
|
1165
|
+
.cancelScheduledValues(scheduleTime)
|
|
1166
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1218
1167
|
channel.gainR.gain
|
|
1219
|
-
.cancelScheduledValues(
|
|
1220
|
-
.setValueAtTime(volume * gainRight,
|
|
1168
|
+
.cancelScheduledValues(scheduleTime)
|
|
1169
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1221
1170
|
}
|
|
1222
|
-
setSustainPedal(channelNumber, value) {
|
|
1171
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1172
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1223
1173
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1224
1174
|
if (value < 64) {
|
|
1225
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1175
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1226
1176
|
}
|
|
1227
1177
|
}
|
|
1228
1178
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1251,12 +1201,12 @@ class MidyGM1 {
|
|
|
1251
1201
|
channel.dataMSB = minMSB;
|
|
1252
1202
|
}
|
|
1253
1203
|
}
|
|
1254
|
-
handleRPN(channelNumber) {
|
|
1204
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1255
1205
|
const channel = this.channels[channelNumber];
|
|
1256
1206
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1257
1207
|
switch (rpn) {
|
|
1258
1208
|
case 0:
|
|
1259
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1209
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1260
1210
|
break;
|
|
1261
1211
|
case 1:
|
|
1262
1212
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1274,25 +1224,26 @@ class MidyGM1 {
|
|
|
1274
1224
|
setRPNLSB(channelNumber, value) {
|
|
1275
1225
|
this.channels[channelNumber].rpnLSB = value;
|
|
1276
1226
|
}
|
|
1277
|
-
dataEntryMSB(channelNumber, value) {
|
|
1227
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1278
1228
|
this.channels[channelNumber].dataMSB = value;
|
|
1279
|
-
this.handleRPN(channelNumber);
|
|
1229
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1280
1230
|
}
|
|
1281
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1231
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1282
1232
|
const channel = this.channels[channelNumber];
|
|
1283
1233
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1284
1234
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1285
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1235
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1286
1236
|
}
|
|
1287
|
-
setPitchBendRange(channelNumber, value) {
|
|
1237
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1238
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1288
1239
|
const channel = this.channels[channelNumber];
|
|
1289
1240
|
const state = channel.state;
|
|
1290
1241
|
const prev = state.pitchWheelSensitivity;
|
|
1291
1242
|
const next = value / 128;
|
|
1292
1243
|
state.pitchWheelSensitivity = next;
|
|
1293
1244
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1294
|
-
this.updateChannelDetune(channel);
|
|
1295
|
-
this.applyVoiceParams(channel, 16);
|
|
1245
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1246
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1296
1247
|
}
|
|
1297
1248
|
handleFineTuningRPN(channelNumber) {
|
|
1298
1249
|
const channel = this.channels[channelNumber];
|
|
@@ -1322,8 +1273,9 @@ class MidyGM1 {
|
|
|
1322
1273
|
channel.detune += next - prev;
|
|
1323
1274
|
this.updateChannelDetune(channel);
|
|
1324
1275
|
}
|
|
1325
|
-
allSoundOff(channelNumber) {
|
|
1326
|
-
|
|
1276
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1277
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1278
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1327
1279
|
}
|
|
1328
1280
|
resetAllControllers(channelNumber) {
|
|
1329
1281
|
const stateTypes = [
|
|
@@ -1347,10 +1299,11 @@ class MidyGM1 {
|
|
|
1347
1299
|
channel[type] = this.constructor.channelSettings[type];
|
|
1348
1300
|
}
|
|
1349
1301
|
}
|
|
1350
|
-
allNotesOff(channelNumber) {
|
|
1351
|
-
|
|
1302
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1303
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1304
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1352
1305
|
}
|
|
1353
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1306
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1354
1307
|
switch (data[2]) {
|
|
1355
1308
|
case 9:
|
|
1356
1309
|
switch (data[3]) {
|
|
@@ -1374,12 +1327,12 @@ class MidyGM1 {
|
|
|
1374
1327
|
}
|
|
1375
1328
|
this.channels[9].bank = 128;
|
|
1376
1329
|
}
|
|
1377
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1330
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1378
1331
|
switch (data[2]) {
|
|
1379
1332
|
case 4:
|
|
1380
1333
|
switch (data[3]) {
|
|
1381
1334
|
case 1:
|
|
1382
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1335
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1383
1336
|
default:
|
|
1384
1337
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1385
1338
|
}
|
|
@@ -1388,42 +1341,40 @@ class MidyGM1 {
|
|
|
1388
1341
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1389
1342
|
}
|
|
1390
1343
|
}
|
|
1391
|
-
handleMasterVolumeSysEx(data) {
|
|
1344
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1392
1345
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1393
|
-
this.setMasterVolume(volume);
|
|
1346
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1394
1347
|
}
|
|
1395
|
-
setMasterVolume(volume) {
|
|
1348
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1349
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1396
1350
|
if (volume < 0 && 1 < volume) {
|
|
1397
1351
|
console.error("Master Volume is out of range");
|
|
1398
1352
|
}
|
|
1399
1353
|
else {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1354
|
+
this.masterVolume.gain
|
|
1355
|
+
.cancelScheduledValues(scheduleTime)
|
|
1356
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1403
1357
|
}
|
|
1404
1358
|
}
|
|
1405
|
-
|
|
1406
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1407
|
-
}
|
|
1408
|
-
handleSysEx(data) {
|
|
1359
|
+
handleSysEx(data, scheduleTime) {
|
|
1409
1360
|
switch (data[0]) {
|
|
1410
1361
|
case 126:
|
|
1411
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1362
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1412
1363
|
case 127:
|
|
1413
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1364
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1414
1365
|
default:
|
|
1415
|
-
|
|
1366
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1416
1367
|
}
|
|
1417
1368
|
}
|
|
1418
|
-
scheduleTask(callback,
|
|
1369
|
+
scheduleTask(callback, scheduleTime) {
|
|
1419
1370
|
return new Promise((resolve) => {
|
|
1420
1371
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1421
1372
|
bufferSource.onended = () => {
|
|
1422
1373
|
callback();
|
|
1423
1374
|
resolve();
|
|
1424
1375
|
};
|
|
1425
|
-
bufferSource.start(
|
|
1426
|
-
bufferSource.stop(
|
|
1376
|
+
bufferSource.start(scheduleTime);
|
|
1377
|
+
bufferSource.stop(scheduleTime);
|
|
1427
1378
|
});
|
|
1428
1379
|
}
|
|
1429
1380
|
}
|