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