@marmooo/midy 0.2.6 → 0.2.8
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 +18 -13
- package/esm/midy-GM1.d.ts +73 -74
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +207 -218
- package/esm/midy-GM2.d.ts +125 -127
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +358 -418
- package/esm/midy-GMLite.d.ts +69 -70
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +195 -207
- package/esm/midy.d.ts +148 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +404 -500
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +73 -74
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +207 -218
- package/script/midy-GM2.d.ts +125 -127
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +358 -418
- package/script/midy-GMLite.d.ts +69 -70
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +195 -207
- package/script/midy.d.ts +148 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +404 -500
package/script/midy-GM1.js
CHANGED
|
@@ -358,6 +358,7 @@ class MidyGM1 {
|
|
|
358
358
|
state: new ControllerState(),
|
|
359
359
|
...this.setChannelAudioNodes(audioContext),
|
|
360
360
|
scheduledNotes: new SparseMap(128),
|
|
361
|
+
sustainNotes: [],
|
|
361
362
|
};
|
|
362
363
|
});
|
|
363
364
|
return channels;
|
|
@@ -415,7 +416,7 @@ class MidyGM1 {
|
|
|
415
416
|
}
|
|
416
417
|
/* falls through */
|
|
417
418
|
case "noteOff": {
|
|
418
|
-
const notePromise = this.
|
|
419
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
419
420
|
if (notePromise) {
|
|
420
421
|
this.notePromises.push(notePromise);
|
|
421
422
|
}
|
|
@@ -462,10 +463,11 @@ class MidyGM1 {
|
|
|
462
463
|
resolve();
|
|
463
464
|
return;
|
|
464
465
|
}
|
|
465
|
-
const
|
|
466
|
+
const now = this.audioContext.currentTime;
|
|
467
|
+
const t = now + offset;
|
|
466
468
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
467
469
|
if (this.isPausing) {
|
|
468
|
-
await this.stopNotes(0, true);
|
|
470
|
+
await this.stopNotes(0, true, now);
|
|
469
471
|
this.notePromises = [];
|
|
470
472
|
resolve();
|
|
471
473
|
this.isPausing = false;
|
|
@@ -473,7 +475,7 @@ class MidyGM1 {
|
|
|
473
475
|
return;
|
|
474
476
|
}
|
|
475
477
|
else if (this.isStopping) {
|
|
476
|
-
await this.stopNotes(0, true);
|
|
478
|
+
await this.stopNotes(0, true, now);
|
|
477
479
|
this.notePromises = [];
|
|
478
480
|
this.exclusiveClassMap.clear();
|
|
479
481
|
this.audioBufferCache.clear();
|
|
@@ -483,7 +485,7 @@ class MidyGM1 {
|
|
|
483
485
|
return;
|
|
484
486
|
}
|
|
485
487
|
else if (this.isSeeking) {
|
|
486
|
-
this.stopNotes(0, true);
|
|
488
|
+
this.stopNotes(0, true, now);
|
|
487
489
|
this.exclusiveClassMap.clear();
|
|
488
490
|
this.startTime = this.audioContext.currentTime;
|
|
489
491
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -492,7 +494,6 @@ class MidyGM1 {
|
|
|
492
494
|
await schedulePlayback();
|
|
493
495
|
}
|
|
494
496
|
else {
|
|
495
|
-
const now = this.audioContext.currentTime;
|
|
496
497
|
const waitTime = now + this.noteCheckInterval;
|
|
497
498
|
await this.scheduleTask(() => { }, waitTime);
|
|
498
499
|
await schedulePlayback();
|
|
@@ -576,24 +577,21 @@ class MidyGM1 {
|
|
|
576
577
|
}
|
|
577
578
|
return { instruments, timeline };
|
|
578
579
|
}
|
|
579
|
-
|
|
580
|
-
const now = this.audioContext.currentTime;
|
|
580
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
581
581
|
const channel = this.channels[channelNumber];
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, force);
|
|
588
|
-
this.notePromises.push(promise);
|
|
589
|
-
}
|
|
582
|
+
const promises = [];
|
|
583
|
+
this.processScheduledNotes(channel, (note) => {
|
|
584
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
585
|
+
this.notePromises.push(promise);
|
|
586
|
+
promises.push(promise);
|
|
590
587
|
});
|
|
591
588
|
channel.scheduledNotes.clear();
|
|
592
|
-
|
|
589
|
+
return Promise.all(promises);
|
|
593
590
|
}
|
|
594
|
-
stopNotes(velocity, force) {
|
|
591
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
592
|
+
const promises = [];
|
|
595
593
|
for (let i = 0; i < this.channels.length; i++) {
|
|
596
|
-
this.stopChannelNotes(i, velocity, force);
|
|
594
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
597
595
|
}
|
|
598
596
|
return Promise.all(this.notePromises);
|
|
599
597
|
}
|
|
@@ -641,34 +639,32 @@ class MidyGM1 {
|
|
|
641
639
|
const now = this.audioContext.currentTime;
|
|
642
640
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
643
641
|
}
|
|
644
|
-
processScheduledNotes(channel,
|
|
642
|
+
processScheduledNotes(channel, callback) {
|
|
645
643
|
channel.scheduledNotes.forEach((noteList) => {
|
|
646
644
|
for (let i = 0; i < noteList.length; i++) {
|
|
647
645
|
const note = noteList[i];
|
|
648
646
|
if (!note)
|
|
649
647
|
continue;
|
|
650
|
-
if (scheduleTime < note.startTime)
|
|
651
|
-
continue;
|
|
652
648
|
callback(note);
|
|
653
649
|
}
|
|
654
650
|
});
|
|
655
651
|
}
|
|
656
|
-
getActiveNotes(channel,
|
|
652
|
+
getActiveNotes(channel, scheduleTime) {
|
|
657
653
|
const activeNotes = new SparseMap(128);
|
|
658
654
|
channel.scheduledNotes.forEach((noteList) => {
|
|
659
|
-
const activeNote = this.getActiveNote(noteList,
|
|
655
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
660
656
|
if (activeNote) {
|
|
661
657
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
662
658
|
}
|
|
663
659
|
});
|
|
664
660
|
return activeNotes;
|
|
665
661
|
}
|
|
666
|
-
getActiveNote(noteList,
|
|
662
|
+
getActiveNote(noteList, scheduleTime) {
|
|
667
663
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
668
664
|
const note = noteList[i];
|
|
669
665
|
if (!note)
|
|
670
666
|
return;
|
|
671
|
-
if (
|
|
667
|
+
if (scheduleTime < note.startTime)
|
|
672
668
|
continue;
|
|
673
669
|
return (note.ending) ? null : note;
|
|
674
670
|
}
|
|
@@ -693,24 +689,17 @@ class MidyGM1 {
|
|
|
693
689
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
694
690
|
return tuning + pitch;
|
|
695
691
|
}
|
|
696
|
-
updateChannelDetune(channel) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const note = noteList[i];
|
|
700
|
-
if (!note)
|
|
701
|
-
continue;
|
|
702
|
-
this.updateDetune(channel, note);
|
|
703
|
-
}
|
|
692
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
693
|
+
this.processScheduledNotes(channel, (note) => {
|
|
694
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
704
695
|
});
|
|
705
696
|
}
|
|
706
|
-
updateDetune(channel, note) {
|
|
707
|
-
const now = this.audioContext.currentTime;
|
|
697
|
+
updateDetune(channel, note, scheduleTime) {
|
|
708
698
|
note.bufferSource.detune
|
|
709
|
-
.cancelScheduledValues(
|
|
710
|
-
.setValueAtTime(channel.detune,
|
|
699
|
+
.cancelScheduledValues(scheduleTime)
|
|
700
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
711
701
|
}
|
|
712
|
-
setVolumeEnvelope(note) {
|
|
713
|
-
const now = this.audioContext.currentTime;
|
|
702
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
714
703
|
const { voiceParams, startTime } = note;
|
|
715
704
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
716
705
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -719,7 +708,7 @@ class MidyGM1 {
|
|
|
719
708
|
const volHold = volAttack + voiceParams.volHold;
|
|
720
709
|
const volDecay = volHold + voiceParams.volDecay;
|
|
721
710
|
note.volumeEnvelopeNode.gain
|
|
722
|
-
.cancelScheduledValues(
|
|
711
|
+
.cancelScheduledValues(scheduleTime)
|
|
723
712
|
.setValueAtTime(0, startTime)
|
|
724
713
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
725
714
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -727,7 +716,6 @@ class MidyGM1 {
|
|
|
727
716
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
728
717
|
}
|
|
729
718
|
setPitchEnvelope(note, scheduleTime) {
|
|
730
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
731
719
|
const { voiceParams } = note;
|
|
732
720
|
const baseRate = voiceParams.playbackRate;
|
|
733
721
|
note.bufferSource.playbackRate
|
|
@@ -754,8 +742,7 @@ class MidyGM1 {
|
|
|
754
742
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
755
743
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
756
744
|
}
|
|
757
|
-
setFilterEnvelope(note) {
|
|
758
|
-
const now = this.audioContext.currentTime;
|
|
745
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
759
746
|
const { voiceParams, startTime } = note;
|
|
760
747
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
761
748
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -769,14 +756,14 @@ class MidyGM1 {
|
|
|
769
756
|
const modHold = modAttack + voiceParams.modHold;
|
|
770
757
|
const modDecay = modHold + voiceParams.modDecay;
|
|
771
758
|
note.filterNode.frequency
|
|
772
|
-
.cancelScheduledValues(
|
|
759
|
+
.cancelScheduledValues(scheduleTime)
|
|
773
760
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
774
761
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
775
762
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
776
763
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
777
764
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
778
765
|
}
|
|
779
|
-
startModulation(channel, note,
|
|
766
|
+
startModulation(channel, note, scheduleTime) {
|
|
780
767
|
const { voiceParams } = note;
|
|
781
768
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
782
769
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -785,10 +772,10 @@ class MidyGM1 {
|
|
|
785
772
|
gain: voiceParams.modLfoToFilterFc,
|
|
786
773
|
});
|
|
787
774
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
788
|
-
this.setModLfoToPitch(channel, note);
|
|
775
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
789
776
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
790
|
-
this.setModLfoToVolume(note);
|
|
791
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
777
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
778
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
792
779
|
note.modulationLFO.connect(note.filterDepth);
|
|
793
780
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
794
781
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -815,6 +802,7 @@ class MidyGM1 {
|
|
|
815
802
|
}
|
|
816
803
|
}
|
|
817
804
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
805
|
+
const now = this.audioContext.currentTime;
|
|
818
806
|
const state = channel.state;
|
|
819
807
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
820
808
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -826,11 +814,11 @@ class MidyGM1 {
|
|
|
826
814
|
type: "lowpass",
|
|
827
815
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
828
816
|
});
|
|
829
|
-
this.setVolumeEnvelope(note);
|
|
830
|
-
this.setFilterEnvelope(note);
|
|
831
|
-
this.setPitchEnvelope(note);
|
|
817
|
+
this.setVolumeEnvelope(note, now);
|
|
818
|
+
this.setFilterEnvelope(note, now);
|
|
819
|
+
this.setPitchEnvelope(note, now);
|
|
832
820
|
if (0 < state.modulationDepth) {
|
|
833
|
-
this.startModulation(channel, note,
|
|
821
|
+
this.startModulation(channel, note, now);
|
|
834
822
|
}
|
|
835
823
|
note.bufferSource.connect(note.filterNode);
|
|
836
824
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -851,15 +839,17 @@ class MidyGM1 {
|
|
|
851
839
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
852
840
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
853
841
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
842
|
+
if (0.5 <= channel.state.sustainPedal) {
|
|
843
|
+
channel.sustainNotes.push(note);
|
|
844
|
+
}
|
|
854
845
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
855
846
|
if (exclusiveClass !== 0) {
|
|
856
847
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
857
848
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
858
849
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
859
850
|
if (!prevNote.ending) {
|
|
860
|
-
this.
|
|
861
|
-
startTime,
|
|
862
|
-
true);
|
|
851
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
852
|
+
startTime, true);
|
|
863
853
|
}
|
|
864
854
|
}
|
|
865
855
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -872,9 +862,9 @@ class MidyGM1 {
|
|
|
872
862
|
scheduledNotes.set(noteNumber, [note]);
|
|
873
863
|
}
|
|
874
864
|
}
|
|
875
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
876
|
-
|
|
877
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
865
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
866
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
867
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
878
868
|
}
|
|
879
869
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
880
870
|
const note = scheduledNotes[index];
|
|
@@ -901,9 +891,9 @@ class MidyGM1 {
|
|
|
901
891
|
note.bufferSource.stop(stopTime);
|
|
902
892
|
});
|
|
903
893
|
}
|
|
904
|
-
|
|
894
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
905
895
|
const channel = this.channels[channelNumber];
|
|
906
|
-
if (!force && 0.5
|
|
896
|
+
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
907
897
|
return;
|
|
908
898
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
909
899
|
return;
|
|
@@ -923,127 +913,119 @@ class MidyGM1 {
|
|
|
923
913
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
924
914
|
}
|
|
925
915
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
return this.
|
|
916
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
917
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
918
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
929
919
|
}
|
|
930
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
920
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
931
921
|
const velocity = halfVelocity * 2;
|
|
932
922
|
const channel = this.channels[channelNumber];
|
|
933
923
|
const promises = [];
|
|
934
|
-
channel.
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
continue;
|
|
940
|
-
const { noteNumber } = note;
|
|
941
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
942
|
-
promises.push(promise);
|
|
943
|
-
}
|
|
944
|
-
});
|
|
924
|
+
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
925
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
926
|
+
promises.push(promise);
|
|
927
|
+
}
|
|
928
|
+
channel.sustainNotes = [];
|
|
945
929
|
return promises;
|
|
946
930
|
}
|
|
947
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
931
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
948
932
|
const channelNumber = statusByte & 0x0F;
|
|
949
933
|
const messageType = statusByte & 0xF0;
|
|
950
934
|
switch (messageType) {
|
|
951
935
|
case 0x80:
|
|
952
|
-
return this.
|
|
936
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
953
937
|
case 0x90:
|
|
954
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
938
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
955
939
|
case 0xB0:
|
|
956
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
940
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
957
941
|
case 0xC0:
|
|
958
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
942
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
959
943
|
case 0xE0:
|
|
960
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
944
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
961
945
|
default:
|
|
962
946
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
963
947
|
}
|
|
964
948
|
}
|
|
965
|
-
handleProgramChange(channelNumber, program) {
|
|
949
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
966
950
|
const channel = this.channels[channelNumber];
|
|
967
951
|
channel.program = program;
|
|
968
952
|
}
|
|
969
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
953
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
970
954
|
const pitchBend = msb * 128 + lsb;
|
|
971
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
955
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
972
956
|
}
|
|
973
|
-
setPitchBend(channelNumber, value) {
|
|
957
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
958
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
974
959
|
const channel = this.channels[channelNumber];
|
|
975
960
|
const state = channel.state;
|
|
976
961
|
const prev = state.pitchWheel * 2 - 1;
|
|
977
962
|
const next = (value - 8192) / 8192;
|
|
978
963
|
state.pitchWheel = value / 16383;
|
|
979
964
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
980
|
-
this.updateChannelDetune(channel);
|
|
981
|
-
this.applyVoiceParams(channel, 14);
|
|
965
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
966
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
982
967
|
}
|
|
983
|
-
setModLfoToPitch(channel, note) {
|
|
984
|
-
const now = this.audioContext.currentTime;
|
|
968
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
985
969
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
986
970
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
987
971
|
channel.state.modulationDepth;
|
|
988
972
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
989
973
|
note.modulationDepth.gain
|
|
990
|
-
.cancelScheduledValues(
|
|
991
|
-
.setValueAtTime(modulationDepth,
|
|
974
|
+
.cancelScheduledValues(scheduleTime)
|
|
975
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
992
976
|
}
|
|
993
|
-
setModLfoToFilterFc(note) {
|
|
994
|
-
const now = this.audioContext.currentTime;
|
|
977
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
995
978
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
996
979
|
note.filterDepth.gain
|
|
997
|
-
.cancelScheduledValues(
|
|
998
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
980
|
+
.cancelScheduledValues(scheduleTime)
|
|
981
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
999
982
|
}
|
|
1000
|
-
setModLfoToVolume(note) {
|
|
1001
|
-
const now = this.audioContext.currentTime;
|
|
983
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1002
984
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1003
985
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1004
986
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1005
987
|
note.volumeDepth.gain
|
|
1006
|
-
.cancelScheduledValues(
|
|
1007
|
-
.setValueAtTime(volumeDepth,
|
|
988
|
+
.cancelScheduledValues(scheduleTime)
|
|
989
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1008
990
|
}
|
|
1009
|
-
setDelayModLFO(note) {
|
|
1010
|
-
const now = this.audioContext.currentTime;
|
|
991
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1011
992
|
const startTime = note.startTime;
|
|
1012
|
-
if (startTime <
|
|
993
|
+
if (startTime < scheduleTime)
|
|
1013
994
|
return;
|
|
1014
|
-
note.modulationLFO.stop(
|
|
995
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1015
996
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1016
997
|
note.modulationLFO.connect(note.filterDepth);
|
|
1017
998
|
}
|
|
1018
|
-
setFreqModLFO(note) {
|
|
1019
|
-
const now = this.audioContext.currentTime;
|
|
999
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1020
1000
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1021
1001
|
note.modulationLFO.frequency
|
|
1022
|
-
.cancelScheduledValues(
|
|
1023
|
-
.setValueAtTime(freqModLFO,
|
|
1002
|
+
.cancelScheduledValues(scheduleTime)
|
|
1003
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1024
1004
|
}
|
|
1025
1005
|
createVoiceParamsHandlers() {
|
|
1026
1006
|
return {
|
|
1027
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1007
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1028
1008
|
if (0 < channel.state.modulationDepth) {
|
|
1029
|
-
this.setModLfoToPitch(channel, note);
|
|
1009
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1030
1010
|
}
|
|
1031
1011
|
},
|
|
1032
|
-
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
1033
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1034
|
-
if (0 < channel.state.modulationDepth)
|
|
1035
|
-
this.setModLfoToFilterFc(note);
|
|
1012
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1013
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1014
|
+
if (0 < channel.state.modulationDepth) {
|
|
1015
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1016
|
+
}
|
|
1036
1017
|
},
|
|
1037
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1038
|
-
if (0 < channel.state.modulationDepth)
|
|
1039
|
-
this.setModLfoToVolume(note);
|
|
1018
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1019
|
+
if (0 < channel.state.modulationDepth) {
|
|
1020
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1021
|
+
}
|
|
1040
1022
|
},
|
|
1041
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1042
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1043
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1044
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1045
|
-
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
1046
|
-
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
1023
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1024
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1025
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1026
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1027
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1028
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1047
1029
|
};
|
|
1048
1030
|
}
|
|
1049
1031
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1053,49 +1035,44 @@ class MidyGM1 {
|
|
|
1053
1035
|
state[3] = noteNumber / 127;
|
|
1054
1036
|
return state;
|
|
1055
1037
|
}
|
|
1056
|
-
applyVoiceParams(channel, controllerType) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1038
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1039
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1040
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1041
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1042
|
+
let appliedFilterEnvelope = false;
|
|
1043
|
+
let appliedVolumeEnvelope = false;
|
|
1044
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1045
|
+
const prevValue = note.voiceParams[key];
|
|
1046
|
+
if (value === prevValue)
|
|
1061
1047
|
continue;
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (value === prevValue)
|
|
1048
|
+
note.voiceParams[key] = value;
|
|
1049
|
+
if (key in this.voiceParamsHandlers) {
|
|
1050
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1051
|
+
}
|
|
1052
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1053
|
+
if (appliedFilterEnvelope)
|
|
1069
1054
|
continue;
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
continue;
|
|
1077
|
-
appliedFilterEnvelope = true;
|
|
1078
|
-
const noteVoiceParams = note.voiceParams;
|
|
1079
|
-
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1080
|
-
const key = filterEnvelopeKeys[i];
|
|
1081
|
-
if (key in voiceParams)
|
|
1082
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1083
|
-
}
|
|
1084
|
-
this.setFilterEnvelope(note);
|
|
1085
|
-
this.setPitchEnvelope(note);
|
|
1055
|
+
appliedFilterEnvelope = true;
|
|
1056
|
+
const noteVoiceParams = note.voiceParams;
|
|
1057
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1058
|
+
const key = filterEnvelopeKeys[i];
|
|
1059
|
+
if (key in voiceParams)
|
|
1060
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1086
1061
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1062
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1063
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1064
|
+
}
|
|
1065
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1066
|
+
if (appliedVolumeEnvelope)
|
|
1067
|
+
continue;
|
|
1068
|
+
appliedVolumeEnvelope = true;
|
|
1069
|
+
const noteVoiceParams = note.voiceParams;
|
|
1070
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1071
|
+
const key = volumeEnvelopeKeys[i];
|
|
1072
|
+
if (key in voiceParams)
|
|
1073
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1098
1074
|
}
|
|
1075
|
+
this.setVolumeEnvelope(note, scheduleTime);
|
|
1099
1076
|
}
|
|
1100
1077
|
}
|
|
1101
1078
|
});
|
|
@@ -1116,21 +1093,20 @@ class MidyGM1 {
|
|
|
1116
1093
|
123: this.allNotesOff,
|
|
1117
1094
|
};
|
|
1118
1095
|
}
|
|
1119
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1096
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1120
1097
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1121
1098
|
if (handler) {
|
|
1122
|
-
handler.call(this, channelNumber, value,
|
|
1099
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1123
1100
|
const channel = this.channels[channelNumber];
|
|
1124
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1101
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1125
1102
|
}
|
|
1126
1103
|
else {
|
|
1127
1104
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1128
1105
|
}
|
|
1129
1106
|
}
|
|
1130
1107
|
updateModulation(channel, scheduleTime) {
|
|
1131
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1132
1108
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1133
|
-
this.processScheduledNotes(channel,
|
|
1109
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1134
1110
|
if (note.modulationDepth) {
|
|
1135
1111
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1136
1112
|
}
|
|
@@ -1141,11 +1117,13 @@ class MidyGM1 {
|
|
|
1141
1117
|
});
|
|
1142
1118
|
}
|
|
1143
1119
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1120
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1144
1121
|
const channel = this.channels[channelNumber];
|
|
1145
1122
|
channel.state.modulationDepth = modulation / 127;
|
|
1146
1123
|
this.updateModulation(channel, scheduleTime);
|
|
1147
1124
|
}
|
|
1148
1125
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1126
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1149
1127
|
const channel = this.channels[channelNumber];
|
|
1150
1128
|
channel.state.volume = volume / 127;
|
|
1151
1129
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1158,35 +1136,43 @@ class MidyGM1 {
|
|
|
1158
1136
|
};
|
|
1159
1137
|
}
|
|
1160
1138
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1139
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1161
1140
|
const channel = this.channels[channelNumber];
|
|
1162
1141
|
channel.state.pan = pan / 127;
|
|
1163
1142
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1164
1143
|
}
|
|
1165
1144
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1145
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1166
1146
|
const channel = this.channels[channelNumber];
|
|
1167
1147
|
channel.state.expression = expression / 127;
|
|
1168
1148
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1169
1149
|
}
|
|
1170
|
-
dataEntryLSB(channelNumber, value) {
|
|
1150
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1171
1151
|
this.channels[channelNumber].dataLSB = value;
|
|
1172
|
-
this.handleRPN(channelNumber,
|
|
1152
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1173
1153
|
}
|
|
1174
1154
|
updateChannelVolume(channel, scheduleTime) {
|
|
1175
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1176
1155
|
const state = channel.state;
|
|
1177
1156
|
const volume = state.volume * state.expression;
|
|
1178
1157
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1179
1158
|
channel.gainL.gain
|
|
1180
|
-
.cancelScheduledValues(
|
|
1159
|
+
.cancelScheduledValues(scheduleTime)
|
|
1181
1160
|
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1182
1161
|
channel.gainR.gain
|
|
1183
|
-
.cancelScheduledValues(
|
|
1162
|
+
.cancelScheduledValues(scheduleTime)
|
|
1184
1163
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1185
1164
|
}
|
|
1186
|
-
setSustainPedal(channelNumber, value) {
|
|
1187
|
-
this.
|
|
1188
|
-
|
|
1189
|
-
|
|
1165
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1166
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1167
|
+
const channel = this.channels[channelNumber];
|
|
1168
|
+
channel.state.sustainPedal = value / 127;
|
|
1169
|
+
if (64 <= value) {
|
|
1170
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1171
|
+
channel.sustainNotes.push(note);
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1190
1176
|
}
|
|
1191
1177
|
}
|
|
1192
1178
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1215,18 +1201,18 @@ class MidyGM1 {
|
|
|
1215
1201
|
channel.dataMSB = minMSB;
|
|
1216
1202
|
}
|
|
1217
1203
|
}
|
|
1218
|
-
handleRPN(channelNumber) {
|
|
1204
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1219
1205
|
const channel = this.channels[channelNumber];
|
|
1220
1206
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1221
1207
|
switch (rpn) {
|
|
1222
1208
|
case 0:
|
|
1223
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1209
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1224
1210
|
break;
|
|
1225
1211
|
case 1:
|
|
1226
|
-
this.handleFineTuningRPN(channelNumber);
|
|
1212
|
+
this.handleFineTuningRPN(channelNumber, scheduleTime);
|
|
1227
1213
|
break;
|
|
1228
1214
|
case 2:
|
|
1229
|
-
this.handleCoarseTuningRPN(channelNumber);
|
|
1215
|
+
this.handleCoarseTuningRPN(channelNumber, scheduleTime);
|
|
1230
1216
|
break;
|
|
1231
1217
|
default:
|
|
1232
1218
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1238,56 +1224,60 @@ class MidyGM1 {
|
|
|
1238
1224
|
setRPNLSB(channelNumber, value) {
|
|
1239
1225
|
this.channels[channelNumber].rpnLSB = value;
|
|
1240
1226
|
}
|
|
1241
|
-
dataEntryMSB(channelNumber, value) {
|
|
1227
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1242
1228
|
this.channels[channelNumber].dataMSB = value;
|
|
1243
|
-
this.handleRPN(channelNumber);
|
|
1229
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1244
1230
|
}
|
|
1245
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1231
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1246
1232
|
const channel = this.channels[channelNumber];
|
|
1247
1233
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1248
1234
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1249
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1235
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1250
1236
|
}
|
|
1251
|
-
setPitchBendRange(channelNumber, value) {
|
|
1237
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1238
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1252
1239
|
const channel = this.channels[channelNumber];
|
|
1253
1240
|
const state = channel.state;
|
|
1254
1241
|
const prev = state.pitchWheelSensitivity;
|
|
1255
1242
|
const next = value / 128;
|
|
1256
1243
|
state.pitchWheelSensitivity = next;
|
|
1257
1244
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1258
|
-
this.updateChannelDetune(channel);
|
|
1259
|
-
this.applyVoiceParams(channel, 16);
|
|
1245
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1246
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1260
1247
|
}
|
|
1261
|
-
handleFineTuningRPN(channelNumber) {
|
|
1248
|
+
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
1262
1249
|
const channel = this.channels[channelNumber];
|
|
1263
1250
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1264
1251
|
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1265
|
-
this.setFineTuning(channelNumber, fineTuning);
|
|
1252
|
+
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
1266
1253
|
}
|
|
1267
|
-
setFineTuning(channelNumber, value) {
|
|
1254
|
+
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1255
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1268
1256
|
const channel = this.channels[channelNumber];
|
|
1269
1257
|
const prev = channel.fineTuning;
|
|
1270
1258
|
const next = (value - 8192) / 8.192; // cent
|
|
1271
1259
|
channel.fineTuning = next;
|
|
1272
1260
|
channel.detune += next - prev;
|
|
1273
|
-
this.updateChannelDetune(channel);
|
|
1261
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1274
1262
|
}
|
|
1275
|
-
handleCoarseTuningRPN(channelNumber) {
|
|
1263
|
+
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
1276
1264
|
const channel = this.channels[channelNumber];
|
|
1277
1265
|
this.limitDataMSB(channel, 0, 127);
|
|
1278
1266
|
const coarseTuning = channel.dataMSB;
|
|
1279
|
-
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1267
|
+
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
1280
1268
|
}
|
|
1281
|
-
setCoarseTuning(channelNumber, value) {
|
|
1269
|
+
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1270
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1282
1271
|
const channel = this.channels[channelNumber];
|
|
1283
1272
|
const prev = channel.coarseTuning;
|
|
1284
1273
|
const next = (value - 64) * 100; // cent
|
|
1285
1274
|
channel.coarseTuning = next;
|
|
1286
1275
|
channel.detune += next - prev;
|
|
1287
|
-
this.updateChannelDetune(channel);
|
|
1276
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1288
1277
|
}
|
|
1289
|
-
allSoundOff(channelNumber) {
|
|
1290
|
-
|
|
1278
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1279
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1280
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1291
1281
|
}
|
|
1292
1282
|
resetAllControllers(channelNumber) {
|
|
1293
1283
|
const stateTypes = [
|
|
@@ -1311,10 +1301,11 @@ class MidyGM1 {
|
|
|
1311
1301
|
channel[type] = this.constructor.channelSettings[type];
|
|
1312
1302
|
}
|
|
1313
1303
|
}
|
|
1314
|
-
allNotesOff(channelNumber) {
|
|
1315
|
-
|
|
1304
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1305
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1306
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1316
1307
|
}
|
|
1317
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1308
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1318
1309
|
switch (data[2]) {
|
|
1319
1310
|
case 9:
|
|
1320
1311
|
switch (data[3]) {
|
|
@@ -1338,12 +1329,12 @@ class MidyGM1 {
|
|
|
1338
1329
|
}
|
|
1339
1330
|
this.channels[9].bank = 128;
|
|
1340
1331
|
}
|
|
1341
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1332
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1342
1333
|
switch (data[2]) {
|
|
1343
1334
|
case 4:
|
|
1344
1335
|
switch (data[3]) {
|
|
1345
1336
|
case 1:
|
|
1346
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1337
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1347
1338
|
default:
|
|
1348
1339
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1349
1340
|
}
|
|
@@ -1352,42 +1343,40 @@ class MidyGM1 {
|
|
|
1352
1343
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1353
1344
|
}
|
|
1354
1345
|
}
|
|
1355
|
-
handleMasterVolumeSysEx(data) {
|
|
1346
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1356
1347
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1357
|
-
this.setMasterVolume(volume);
|
|
1348
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1358
1349
|
}
|
|
1359
|
-
setMasterVolume(volume) {
|
|
1350
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1351
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1360
1352
|
if (volume < 0 && 1 < volume) {
|
|
1361
1353
|
console.error("Master Volume is out of range");
|
|
1362
1354
|
}
|
|
1363
1355
|
else {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1356
|
+
this.masterVolume.gain
|
|
1357
|
+
.cancelScheduledValues(scheduleTime)
|
|
1358
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1367
1359
|
}
|
|
1368
1360
|
}
|
|
1369
|
-
|
|
1370
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1371
|
-
}
|
|
1372
|
-
handleSysEx(data) {
|
|
1361
|
+
handleSysEx(data, scheduleTime) {
|
|
1373
1362
|
switch (data[0]) {
|
|
1374
1363
|
case 126:
|
|
1375
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1364
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1376
1365
|
case 127:
|
|
1377
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1366
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1378
1367
|
default:
|
|
1379
|
-
|
|
1368
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1380
1369
|
}
|
|
1381
1370
|
}
|
|
1382
|
-
scheduleTask(callback,
|
|
1371
|
+
scheduleTask(callback, scheduleTime) {
|
|
1383
1372
|
return new Promise((resolve) => {
|
|
1384
1373
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1385
1374
|
bufferSource.onended = () => {
|
|
1386
1375
|
callback();
|
|
1387
1376
|
resolve();
|
|
1388
1377
|
};
|
|
1389
|
-
bufferSource.start(
|
|
1390
|
-
bufferSource.stop(
|
|
1378
|
+
bufferSource.start(scheduleTime);
|
|
1379
|
+
bufferSource.stop(scheduleTime);
|
|
1391
1380
|
});
|
|
1392
1381
|
}
|
|
1393
1382
|
}
|