@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-GMLite.js
CHANGED
|
@@ -358,6 +358,7 @@ class MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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 MidyGMLite {
|
|
|
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
|
}
|
|
@@ -691,24 +687,17 @@ class MidyGMLite {
|
|
|
691
687
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
692
688
|
return pitchWheel * pitchWheelSensitivity;
|
|
693
689
|
}
|
|
694
|
-
updateChannelDetune(channel) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const note = noteList[i];
|
|
698
|
-
if (!note)
|
|
699
|
-
continue;
|
|
700
|
-
this.updateDetune(channel, note);
|
|
701
|
-
}
|
|
690
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
691
|
+
this.processScheduledNotes(channel, (note) => {
|
|
692
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
702
693
|
});
|
|
703
694
|
}
|
|
704
|
-
updateDetune(channel, note) {
|
|
705
|
-
const now = this.audioContext.currentTime;
|
|
695
|
+
updateDetune(channel, note, scheduleTime) {
|
|
706
696
|
note.bufferSource.detune
|
|
707
|
-
.cancelScheduledValues(
|
|
708
|
-
.setValueAtTime(channel.detune,
|
|
697
|
+
.cancelScheduledValues(scheduleTime)
|
|
698
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
709
699
|
}
|
|
710
|
-
setVolumeEnvelope(note) {
|
|
711
|
-
const now = this.audioContext.currentTime;
|
|
700
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
712
701
|
const { voiceParams, startTime } = note;
|
|
713
702
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
714
703
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -717,7 +706,7 @@ class MidyGMLite {
|
|
|
717
706
|
const volHold = volAttack + voiceParams.volHold;
|
|
718
707
|
const volDecay = volHold + voiceParams.volDecay;
|
|
719
708
|
note.volumeEnvelopeNode.gain
|
|
720
|
-
.cancelScheduledValues(
|
|
709
|
+
.cancelScheduledValues(scheduleTime)
|
|
721
710
|
.setValueAtTime(0, startTime)
|
|
722
711
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
723
712
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -725,7 +714,6 @@ class MidyGMLite {
|
|
|
725
714
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
726
715
|
}
|
|
727
716
|
setPitchEnvelope(note, scheduleTime) {
|
|
728
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
729
717
|
const { voiceParams } = note;
|
|
730
718
|
const baseRate = voiceParams.playbackRate;
|
|
731
719
|
note.bufferSource.playbackRate
|
|
@@ -752,8 +740,7 @@ class MidyGMLite {
|
|
|
752
740
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
753
741
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
754
742
|
}
|
|
755
|
-
setFilterEnvelope(note) {
|
|
756
|
-
const now = this.audioContext.currentTime;
|
|
743
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
757
744
|
const { voiceParams, startTime } = note;
|
|
758
745
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
759
746
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -767,14 +754,14 @@ class MidyGMLite {
|
|
|
767
754
|
const modHold = modAttack + voiceParams.modHold;
|
|
768
755
|
const modDecay = modHold + voiceParams.modDecay;
|
|
769
756
|
note.filterNode.frequency
|
|
770
|
-
.cancelScheduledValues(
|
|
757
|
+
.cancelScheduledValues(scheduleTime)
|
|
771
758
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
772
759
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
773
760
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
774
761
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
775
762
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
776
763
|
}
|
|
777
|
-
startModulation(channel, note,
|
|
764
|
+
startModulation(channel, note, scheduleTime) {
|
|
778
765
|
const { voiceParams } = note;
|
|
779
766
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
780
767
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -783,10 +770,10 @@ class MidyGMLite {
|
|
|
783
770
|
gain: voiceParams.modLfoToFilterFc,
|
|
784
771
|
});
|
|
785
772
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
786
|
-
this.setModLfoToPitch(channel, note);
|
|
773
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
787
774
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
788
|
-
this.setModLfoToVolume(note);
|
|
789
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
775
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
776
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
790
777
|
note.modulationLFO.connect(note.filterDepth);
|
|
791
778
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
792
779
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -813,6 +800,7 @@ class MidyGMLite {
|
|
|
813
800
|
}
|
|
814
801
|
}
|
|
815
802
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
803
|
+
const now = this.audioContext.currentTime;
|
|
816
804
|
const state = channel.state;
|
|
817
805
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
818
806
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -824,11 +812,11 @@ class MidyGMLite {
|
|
|
824
812
|
type: "lowpass",
|
|
825
813
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
826
814
|
});
|
|
827
|
-
this.setVolumeEnvelope(note);
|
|
828
|
-
this.setFilterEnvelope(note);
|
|
829
|
-
this.setPitchEnvelope(note);
|
|
815
|
+
this.setVolumeEnvelope(note, now);
|
|
816
|
+
this.setFilterEnvelope(note, now);
|
|
817
|
+
this.setPitchEnvelope(note, now);
|
|
830
818
|
if (0 < state.modulationDepth) {
|
|
831
|
-
this.startModulation(channel, note,
|
|
819
|
+
this.startModulation(channel, note, now);
|
|
832
820
|
}
|
|
833
821
|
note.bufferSource.connect(note.filterNode);
|
|
834
822
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -849,15 +837,17 @@ class MidyGMLite {
|
|
|
849
837
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
850
838
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
851
839
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
840
|
+
if (0.5 <= channel.state.sustainPedal) {
|
|
841
|
+
channel.sustainNotes.push(note);
|
|
842
|
+
}
|
|
852
843
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
853
844
|
if (exclusiveClass !== 0) {
|
|
854
845
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
855
846
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
856
847
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
857
848
|
if (!prevNote.ending) {
|
|
858
|
-
this.
|
|
859
|
-
startTime,
|
|
860
|
-
true);
|
|
849
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
850
|
+
startTime, true);
|
|
861
851
|
}
|
|
862
852
|
}
|
|
863
853
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -870,9 +860,9 @@ class MidyGMLite {
|
|
|
870
860
|
scheduledNotes.set(noteNumber, [note]);
|
|
871
861
|
}
|
|
872
862
|
}
|
|
873
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
874
|
-
|
|
875
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
863
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
864
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
865
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
876
866
|
}
|
|
877
867
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
878
868
|
const note = scheduledNotes[index];
|
|
@@ -899,9 +889,9 @@ class MidyGMLite {
|
|
|
899
889
|
note.bufferSource.stop(stopTime);
|
|
900
890
|
});
|
|
901
891
|
}
|
|
902
|
-
|
|
892
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
903
893
|
const channel = this.channels[channelNumber];
|
|
904
|
-
if (!force && 0.5
|
|
894
|
+
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
905
895
|
return;
|
|
906
896
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
907
897
|
return;
|
|
@@ -921,127 +911,119 @@ class MidyGMLite {
|
|
|
921
911
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
922
912
|
}
|
|
923
913
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
return this.
|
|
914
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
915
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
916
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
927
917
|
}
|
|
928
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
918
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
929
919
|
const velocity = halfVelocity * 2;
|
|
930
920
|
const channel = this.channels[channelNumber];
|
|
931
921
|
const promises = [];
|
|
932
|
-
channel.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
continue;
|
|
938
|
-
const { noteNumber } = note;
|
|
939
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
940
|
-
promises.push(promise);
|
|
941
|
-
}
|
|
942
|
-
});
|
|
922
|
+
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
923
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
924
|
+
promises.push(promise);
|
|
925
|
+
}
|
|
926
|
+
channel.sustainNotes = [];
|
|
943
927
|
return promises;
|
|
944
928
|
}
|
|
945
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
929
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
946
930
|
const channelNumber = statusByte & 0x0F;
|
|
947
931
|
const messageType = statusByte & 0xF0;
|
|
948
932
|
switch (messageType) {
|
|
949
933
|
case 0x80:
|
|
950
|
-
return this.
|
|
934
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
951
935
|
case 0x90:
|
|
952
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
936
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
953
937
|
case 0xB0:
|
|
954
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
938
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
955
939
|
case 0xC0:
|
|
956
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
940
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
957
941
|
case 0xE0:
|
|
958
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
942
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
959
943
|
default:
|
|
960
944
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
961
945
|
}
|
|
962
946
|
}
|
|
963
|
-
handleProgramChange(channelNumber, program) {
|
|
947
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
964
948
|
const channel = this.channels[channelNumber];
|
|
965
949
|
channel.program = program;
|
|
966
950
|
}
|
|
967
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
951
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
968
952
|
const pitchBend = msb * 128 + lsb;
|
|
969
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
953
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
970
954
|
}
|
|
971
|
-
setPitchBend(channelNumber, value) {
|
|
955
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
956
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
972
957
|
const channel = this.channels[channelNumber];
|
|
973
958
|
const state = channel.state;
|
|
974
959
|
const prev = state.pitchWheel * 2 - 1;
|
|
975
960
|
const next = (value - 8192) / 8192;
|
|
976
961
|
state.pitchWheel = value / 16383;
|
|
977
962
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
978
|
-
this.updateChannelDetune(channel);
|
|
979
|
-
this.applyVoiceParams(channel, 14);
|
|
963
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
964
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
980
965
|
}
|
|
981
|
-
setModLfoToPitch(channel, note) {
|
|
982
|
-
const now = this.audioContext.currentTime;
|
|
966
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
983
967
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
984
968
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
985
969
|
channel.state.modulationDepth;
|
|
986
970
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
987
971
|
note.modulationDepth.gain
|
|
988
|
-
.cancelScheduledValues(
|
|
989
|
-
.setValueAtTime(modulationDepth,
|
|
972
|
+
.cancelScheduledValues(scheduleTime)
|
|
973
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
990
974
|
}
|
|
991
|
-
setModLfoToFilterFc(note) {
|
|
992
|
-
const now = this.audioContext.currentTime;
|
|
975
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
993
976
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
994
977
|
note.filterDepth.gain
|
|
995
|
-
.cancelScheduledValues(
|
|
996
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
978
|
+
.cancelScheduledValues(scheduleTime)
|
|
979
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
997
980
|
}
|
|
998
|
-
setModLfoToVolume(note) {
|
|
999
|
-
const now = this.audioContext.currentTime;
|
|
981
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1000
982
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1001
983
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1002
984
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1003
985
|
note.volumeDepth.gain
|
|
1004
|
-
.cancelScheduledValues(
|
|
1005
|
-
.setValueAtTime(volumeDepth,
|
|
986
|
+
.cancelScheduledValues(scheduleTime)
|
|
987
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1006
988
|
}
|
|
1007
|
-
setDelayModLFO(note) {
|
|
1008
|
-
const now = this.audioContext.currentTime;
|
|
989
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1009
990
|
const startTime = note.startTime;
|
|
1010
|
-
if (startTime <
|
|
991
|
+
if (startTime < scheduleTime)
|
|
1011
992
|
return;
|
|
1012
|
-
note.modulationLFO.stop(
|
|
993
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1013
994
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1014
995
|
note.modulationLFO.connect(note.filterDepth);
|
|
1015
996
|
}
|
|
1016
|
-
setFreqModLFO(note) {
|
|
1017
|
-
const now = this.audioContext.currentTime;
|
|
997
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1018
998
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1019
999
|
note.modulationLFO.frequency
|
|
1020
|
-
.cancelScheduledValues(
|
|
1021
|
-
.setValueAtTime(freqModLFO,
|
|
1000
|
+
.cancelScheduledValues(scheduleTime)
|
|
1001
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1022
1002
|
}
|
|
1023
1003
|
createVoiceParamsHandlers() {
|
|
1024
1004
|
return {
|
|
1025
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1005
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1026
1006
|
if (0 < channel.state.modulationDepth) {
|
|
1027
|
-
this.setModLfoToPitch(channel, note);
|
|
1007
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1028
1008
|
}
|
|
1029
1009
|
},
|
|
1030
|
-
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
1031
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1032
|
-
if (0 < channel.state.modulationDepth)
|
|
1033
|
-
this.setModLfoToFilterFc(note);
|
|
1010
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1011
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1012
|
+
if (0 < channel.state.modulationDepth) {
|
|
1013
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1014
|
+
}
|
|
1034
1015
|
},
|
|
1035
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1036
|
-
if (0 < channel.state.modulationDepth)
|
|
1037
|
-
this.setModLfoToVolume(note);
|
|
1016
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1017
|
+
if (0 < channel.state.modulationDepth) {
|
|
1018
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1019
|
+
}
|
|
1038
1020
|
},
|
|
1039
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1040
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1041
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1042
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1043
|
-
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
1044
|
-
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
1021
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1022
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1023
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1024
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1025
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1026
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1045
1027
|
};
|
|
1046
1028
|
}
|
|
1047
1029
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1051,49 +1033,44 @@ class MidyGMLite {
|
|
|
1051
1033
|
state[3] = noteNumber / 127;
|
|
1052
1034
|
return state;
|
|
1053
1035
|
}
|
|
1054
|
-
applyVoiceParams(channel, controllerType) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1036
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1037
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1038
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1039
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1040
|
+
let appliedFilterEnvelope = false;
|
|
1041
|
+
let appliedVolumeEnvelope = false;
|
|
1042
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1043
|
+
const prevValue = note.voiceParams[key];
|
|
1044
|
+
if (value === prevValue)
|
|
1059
1045
|
continue;
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
if (value === prevValue)
|
|
1046
|
+
note.voiceParams[key] = value;
|
|
1047
|
+
if (key in this.voiceParamsHandlers) {
|
|
1048
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1049
|
+
}
|
|
1050
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1051
|
+
if (appliedFilterEnvelope)
|
|
1067
1052
|
continue;
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
continue;
|
|
1075
|
-
appliedFilterEnvelope = true;
|
|
1076
|
-
const noteVoiceParams = note.voiceParams;
|
|
1077
|
-
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1078
|
-
const key = filterEnvelopeKeys[i];
|
|
1079
|
-
if (key in voiceParams)
|
|
1080
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1081
|
-
}
|
|
1082
|
-
this.setFilterEnvelope(note);
|
|
1083
|
-
this.setPitchEnvelope(note);
|
|
1053
|
+
appliedFilterEnvelope = true;
|
|
1054
|
+
const noteVoiceParams = note.voiceParams;
|
|
1055
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1056
|
+
const key = filterEnvelopeKeys[i];
|
|
1057
|
+
if (key in voiceParams)
|
|
1058
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1084
1059
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1060
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1061
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1062
|
+
}
|
|
1063
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1064
|
+
if (appliedVolumeEnvelope)
|
|
1065
|
+
continue;
|
|
1066
|
+
appliedVolumeEnvelope = true;
|
|
1067
|
+
const noteVoiceParams = note.voiceParams;
|
|
1068
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1069
|
+
const key = volumeEnvelopeKeys[i];
|
|
1070
|
+
if (key in voiceParams)
|
|
1071
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1096
1072
|
}
|
|
1073
|
+
this.setVolumeEnvelope(note, scheduleTime);
|
|
1097
1074
|
}
|
|
1098
1075
|
}
|
|
1099
1076
|
});
|
|
@@ -1114,12 +1091,12 @@ class MidyGMLite {
|
|
|
1114
1091
|
123: this.allNotesOff,
|
|
1115
1092
|
};
|
|
1116
1093
|
}
|
|
1117
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1094
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1118
1095
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1119
1096
|
if (handler) {
|
|
1120
|
-
handler.call(this, channelNumber, value,
|
|
1097
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1121
1098
|
const channel = this.channels[channelNumber];
|
|
1122
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1099
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1123
1100
|
}
|
|
1124
1101
|
else {
|
|
1125
1102
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1128,7 +1105,7 @@ class MidyGMLite {
|
|
|
1128
1105
|
updateModulation(channel, scheduleTime) {
|
|
1129
1106
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1130
1107
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1131
|
-
this.processScheduledNotes(channel,
|
|
1108
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1132
1109
|
if (note.modulationDepth) {
|
|
1133
1110
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1134
1111
|
}
|
|
@@ -1139,11 +1116,13 @@ class MidyGMLite {
|
|
|
1139
1116
|
});
|
|
1140
1117
|
}
|
|
1141
1118
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1119
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1142
1120
|
const channel = this.channels[channelNumber];
|
|
1143
1121
|
channel.state.modulationDepth = modulation / 127;
|
|
1144
1122
|
this.updateModulation(channel, scheduleTime);
|
|
1145
1123
|
}
|
|
1146
1124
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1125
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1147
1126
|
const channel = this.channels[channelNumber];
|
|
1148
1127
|
channel.state.volume = volume / 127;
|
|
1149
1128
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1156,35 +1135,43 @@ class MidyGMLite {
|
|
|
1156
1135
|
};
|
|
1157
1136
|
}
|
|
1158
1137
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1138
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1159
1139
|
const channel = this.channels[channelNumber];
|
|
1160
1140
|
channel.state.pan = pan / 127;
|
|
1161
1141
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1162
1142
|
}
|
|
1163
1143
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1144
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1164
1145
|
const channel = this.channels[channelNumber];
|
|
1165
1146
|
channel.state.expression = expression / 127;
|
|
1166
1147
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1167
1148
|
}
|
|
1168
|
-
dataEntryLSB(channelNumber, value) {
|
|
1149
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1169
1150
|
this.channels[channelNumber].dataLSB = value;
|
|
1170
|
-
this.handleRPN(channelNumber);
|
|
1151
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1171
1152
|
}
|
|
1172
1153
|
updateChannelVolume(channel, scheduleTime) {
|
|
1173
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1174
1154
|
const state = channel.state;
|
|
1175
1155
|
const volume = state.volume * state.expression;
|
|
1176
1156
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1177
1157
|
channel.gainL.gain
|
|
1178
|
-
.cancelScheduledValues(
|
|
1158
|
+
.cancelScheduledValues(scheduleTime)
|
|
1179
1159
|
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1180
1160
|
channel.gainR.gain
|
|
1181
|
-
.cancelScheduledValues(
|
|
1161
|
+
.cancelScheduledValues(scheduleTime)
|
|
1182
1162
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1183
1163
|
}
|
|
1184
|
-
setSustainPedal(channelNumber, value) {
|
|
1185
|
-
this.
|
|
1186
|
-
|
|
1187
|
-
|
|
1164
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1165
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1166
|
+
const channel = this.channels[channelNumber];
|
|
1167
|
+
channel.state.sustainPedal = value / 127;
|
|
1168
|
+
if (64 <= value) {
|
|
1169
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1170
|
+
channel.sustainNotes.push(note);
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1188
1175
|
}
|
|
1189
1176
|
}
|
|
1190
1177
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1205,12 +1192,12 @@ class MidyGMLite {
|
|
|
1205
1192
|
channel.dataLSB = minLSB;
|
|
1206
1193
|
}
|
|
1207
1194
|
}
|
|
1208
|
-
handleRPN(channelNumber) {
|
|
1195
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1209
1196
|
const channel = this.channels[channelNumber];
|
|
1210
1197
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1211
1198
|
switch (rpn) {
|
|
1212
1199
|
case 0:
|
|
1213
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1200
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1214
1201
|
break;
|
|
1215
1202
|
default:
|
|
1216
1203
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1222,28 +1209,30 @@ class MidyGMLite {
|
|
|
1222
1209
|
setRPNLSB(channelNumber, value) {
|
|
1223
1210
|
this.channels[channelNumber].rpnLSB = value;
|
|
1224
1211
|
}
|
|
1225
|
-
dataEntryMSB(channelNumber, value) {
|
|
1212
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1226
1213
|
this.channels[channelNumber].dataMSB = value;
|
|
1227
|
-
this.handleRPN(channelNumber);
|
|
1214
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1228
1215
|
}
|
|
1229
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1216
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1230
1217
|
const channel = this.channels[channelNumber];
|
|
1231
1218
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1232
1219
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1233
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1220
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1234
1221
|
}
|
|
1235
|
-
setPitchBendRange(channelNumber, value) {
|
|
1222
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1223
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1236
1224
|
const channel = this.channels[channelNumber];
|
|
1237
1225
|
const state = channel.state;
|
|
1238
1226
|
const prev = state.pitchWheelSensitivity;
|
|
1239
1227
|
const next = value / 128;
|
|
1240
1228
|
state.pitchWheelSensitivity = next;
|
|
1241
1229
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1242
|
-
this.updateChannelDetune(channel);
|
|
1243
|
-
this.applyVoiceParams(channel, 16);
|
|
1230
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1231
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1244
1232
|
}
|
|
1245
|
-
allSoundOff(channelNumber) {
|
|
1246
|
-
|
|
1233
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1234
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1235
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1247
1236
|
}
|
|
1248
1237
|
resetAllControllers(channelNumber) {
|
|
1249
1238
|
const stateTypes = [
|
|
@@ -1267,10 +1256,11 @@ class MidyGMLite {
|
|
|
1267
1256
|
channel[type] = this.constructor.channelSettings[type];
|
|
1268
1257
|
}
|
|
1269
1258
|
}
|
|
1270
|
-
allNotesOff(channelNumber) {
|
|
1271
|
-
|
|
1259
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1260
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1261
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1272
1262
|
}
|
|
1273
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1263
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1274
1264
|
switch (data[2]) {
|
|
1275
1265
|
case 9:
|
|
1276
1266
|
switch (data[3]) {
|
|
@@ -1294,12 +1284,12 @@ class MidyGMLite {
|
|
|
1294
1284
|
}
|
|
1295
1285
|
this.channels[9].bank = 128;
|
|
1296
1286
|
}
|
|
1297
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1287
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1298
1288
|
switch (data[2]) {
|
|
1299
1289
|
case 4:
|
|
1300
1290
|
switch (data[3]) {
|
|
1301
1291
|
case 1:
|
|
1302
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1292
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1303
1293
|
default:
|
|
1304
1294
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1305
1295
|
}
|
|
@@ -1308,42 +1298,40 @@ class MidyGMLite {
|
|
|
1308
1298
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1309
1299
|
}
|
|
1310
1300
|
}
|
|
1311
|
-
handleMasterVolumeSysEx(data) {
|
|
1301
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1312
1302
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1313
|
-
this.setMasterVolume(volume);
|
|
1303
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1314
1304
|
}
|
|
1315
|
-
setMasterVolume(volume) {
|
|
1305
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1306
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1316
1307
|
if (volume < 0 && 1 < volume) {
|
|
1317
1308
|
console.error("Master Volume is out of range");
|
|
1318
1309
|
}
|
|
1319
1310
|
else {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1311
|
+
this.masterVolume.gain
|
|
1312
|
+
.cancelScheduledValues(scheduleTime)
|
|
1313
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1323
1314
|
}
|
|
1324
1315
|
}
|
|
1325
|
-
|
|
1326
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1327
|
-
}
|
|
1328
|
-
handleSysEx(data) {
|
|
1316
|
+
handleSysEx(data, scheduleTime) {
|
|
1329
1317
|
switch (data[0]) {
|
|
1330
1318
|
case 126:
|
|
1331
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1319
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1332
1320
|
case 127:
|
|
1333
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1321
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1334
1322
|
default:
|
|
1335
|
-
|
|
1323
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1336
1324
|
}
|
|
1337
1325
|
}
|
|
1338
|
-
scheduleTask(callback,
|
|
1326
|
+
scheduleTask(callback, scheduleTime) {
|
|
1339
1327
|
return new Promise((resolve) => {
|
|
1340
1328
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1341
1329
|
bufferSource.onended = () => {
|
|
1342
1330
|
callback();
|
|
1343
1331
|
resolve();
|
|
1344
1332
|
};
|
|
1345
|
-
bufferSource.start(
|
|
1346
|
-
bufferSource.stop(
|
|
1333
|
+
bufferSource.start(scheduleTime);
|
|
1334
|
+
bufferSource.stop(scheduleTime);
|
|
1347
1335
|
});
|
|
1348
1336
|
}
|
|
1349
1337
|
}
|