@marmooo/midy 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -12
- package/esm/midy-GM1.d.ts +68 -69
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +143 -156
- package/esm/midy-GM2.d.ts +104 -105
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +251 -280
- package/esm/midy-GMLite.d.ts +68 -69
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +143 -156
- package/esm/midy.d.ts +127 -128
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +274 -307
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +68 -69
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +143 -156
- package/script/midy-GM2.d.ts +104 -105
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +251 -280
- package/script/midy-GMLite.d.ts +68 -69
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +143 -156
- package/script/midy.d.ts +127 -128
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +274 -307
package/script/midy-GMLite.js
CHANGED
|
@@ -415,7 +415,7 @@ class MidyGMLite {
|
|
|
415
415
|
}
|
|
416
416
|
/* falls through */
|
|
417
417
|
case "noteOff": {
|
|
418
|
-
const notePromise = this.
|
|
418
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
|
|
419
419
|
if (notePromise) {
|
|
420
420
|
this.notePromises.push(notePromise);
|
|
421
421
|
}
|
|
@@ -462,10 +462,11 @@ class MidyGMLite {
|
|
|
462
462
|
resolve();
|
|
463
463
|
return;
|
|
464
464
|
}
|
|
465
|
-
const
|
|
465
|
+
const now = this.audioContext.currentTime;
|
|
466
|
+
const t = now + offset;
|
|
466
467
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
467
468
|
if (this.isPausing) {
|
|
468
|
-
await this.stopNotes(0, true);
|
|
469
|
+
await this.stopNotes(0, true, now);
|
|
469
470
|
this.notePromises = [];
|
|
470
471
|
resolve();
|
|
471
472
|
this.isPausing = false;
|
|
@@ -473,7 +474,7 @@ class MidyGMLite {
|
|
|
473
474
|
return;
|
|
474
475
|
}
|
|
475
476
|
else if (this.isStopping) {
|
|
476
|
-
await this.stopNotes(0, true);
|
|
477
|
+
await this.stopNotes(0, true, now);
|
|
477
478
|
this.notePromises = [];
|
|
478
479
|
this.exclusiveClassMap.clear();
|
|
479
480
|
this.audioBufferCache.clear();
|
|
@@ -483,7 +484,7 @@ class MidyGMLite {
|
|
|
483
484
|
return;
|
|
484
485
|
}
|
|
485
486
|
else if (this.isSeeking) {
|
|
486
|
-
this.stopNotes(0, true);
|
|
487
|
+
this.stopNotes(0, true, now);
|
|
487
488
|
this.exclusiveClassMap.clear();
|
|
488
489
|
this.startTime = this.audioContext.currentTime;
|
|
489
490
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -492,7 +493,6 @@ class MidyGMLite {
|
|
|
492
493
|
await schedulePlayback();
|
|
493
494
|
}
|
|
494
495
|
else {
|
|
495
|
-
const now = this.audioContext.currentTime;
|
|
496
496
|
const waitTime = now + this.noteCheckInterval;
|
|
497
497
|
await this.scheduleTask(() => { }, waitTime);
|
|
498
498
|
await schedulePlayback();
|
|
@@ -576,24 +576,26 @@ class MidyGMLite {
|
|
|
576
576
|
}
|
|
577
577
|
return { instruments, timeline };
|
|
578
578
|
}
|
|
579
|
-
|
|
580
|
-
const now = this.audioContext.currentTime;
|
|
579
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
581
580
|
const channel = this.channels[channelNumber];
|
|
581
|
+
const promises = [];
|
|
582
582
|
channel.scheduledNotes.forEach((noteList) => {
|
|
583
583
|
for (let i = 0; i < noteList.length; i++) {
|
|
584
584
|
const note = noteList[i];
|
|
585
585
|
if (!note)
|
|
586
586
|
continue;
|
|
587
|
-
const promise = this.
|
|
587
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
588
588
|
this.notePromises.push(promise);
|
|
589
|
+
promises.push(promise);
|
|
589
590
|
}
|
|
590
591
|
});
|
|
591
592
|
channel.scheduledNotes.clear();
|
|
592
|
-
|
|
593
|
+
return Promise.all(promises);
|
|
593
594
|
}
|
|
594
|
-
stopNotes(velocity, force) {
|
|
595
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
596
|
+
const promises = [];
|
|
595
597
|
for (let i = 0; i < this.channels.length; i++) {
|
|
596
|
-
this.stopChannelNotes(i, velocity, force);
|
|
598
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
597
599
|
}
|
|
598
600
|
return Promise.all(this.notePromises);
|
|
599
601
|
}
|
|
@@ -653,22 +655,22 @@ class MidyGMLite {
|
|
|
653
655
|
}
|
|
654
656
|
});
|
|
655
657
|
}
|
|
656
|
-
getActiveNotes(channel,
|
|
658
|
+
getActiveNotes(channel, scheduleTime) {
|
|
657
659
|
const activeNotes = new SparseMap(128);
|
|
658
660
|
channel.scheduledNotes.forEach((noteList) => {
|
|
659
|
-
const activeNote = this.getActiveNote(noteList,
|
|
661
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
660
662
|
if (activeNote) {
|
|
661
663
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
662
664
|
}
|
|
663
665
|
});
|
|
664
666
|
return activeNotes;
|
|
665
667
|
}
|
|
666
|
-
getActiveNote(noteList,
|
|
668
|
+
getActiveNote(noteList, scheduleTime) {
|
|
667
669
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
668
670
|
const note = noteList[i];
|
|
669
671
|
if (!note)
|
|
670
672
|
return;
|
|
671
|
-
if (
|
|
673
|
+
if (scheduleTime < note.startTime)
|
|
672
674
|
continue;
|
|
673
675
|
return (note.ending) ? null : note;
|
|
674
676
|
}
|
|
@@ -691,24 +693,17 @@ class MidyGMLite {
|
|
|
691
693
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
692
694
|
return pitchWheel * pitchWheelSensitivity;
|
|
693
695
|
}
|
|
694
|
-
updateChannelDetune(channel) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const note = noteList[i];
|
|
698
|
-
if (!note)
|
|
699
|
-
continue;
|
|
700
|
-
this.updateDetune(channel, note);
|
|
701
|
-
}
|
|
696
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
697
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
698
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
702
699
|
});
|
|
703
700
|
}
|
|
704
|
-
updateDetune(channel, note) {
|
|
705
|
-
const now = this.audioContext.currentTime;
|
|
701
|
+
updateDetune(channel, note, scheduleTime) {
|
|
706
702
|
note.bufferSource.detune
|
|
707
|
-
.cancelScheduledValues(
|
|
708
|
-
.setValueAtTime(channel.detune,
|
|
703
|
+
.cancelScheduledValues(scheduleTime)
|
|
704
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
709
705
|
}
|
|
710
|
-
setVolumeEnvelope(note) {
|
|
711
|
-
const now = this.audioContext.currentTime;
|
|
706
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
712
707
|
const { voiceParams, startTime } = note;
|
|
713
708
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
714
709
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -717,7 +712,7 @@ class MidyGMLite {
|
|
|
717
712
|
const volHold = volAttack + voiceParams.volHold;
|
|
718
713
|
const volDecay = volHold + voiceParams.volDecay;
|
|
719
714
|
note.volumeEnvelopeNode.gain
|
|
720
|
-
.cancelScheduledValues(
|
|
715
|
+
.cancelScheduledValues(scheduleTime)
|
|
721
716
|
.setValueAtTime(0, startTime)
|
|
722
717
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
723
718
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -725,7 +720,6 @@ class MidyGMLite {
|
|
|
725
720
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
726
721
|
}
|
|
727
722
|
setPitchEnvelope(note, scheduleTime) {
|
|
728
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
729
723
|
const { voiceParams } = note;
|
|
730
724
|
const baseRate = voiceParams.playbackRate;
|
|
731
725
|
note.bufferSource.playbackRate
|
|
@@ -752,8 +746,7 @@ class MidyGMLite {
|
|
|
752
746
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
753
747
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
754
748
|
}
|
|
755
|
-
setFilterEnvelope(note) {
|
|
756
|
-
const now = this.audioContext.currentTime;
|
|
749
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
757
750
|
const { voiceParams, startTime } = note;
|
|
758
751
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
759
752
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -767,14 +760,14 @@ class MidyGMLite {
|
|
|
767
760
|
const modHold = modAttack + voiceParams.modHold;
|
|
768
761
|
const modDecay = modHold + voiceParams.modDecay;
|
|
769
762
|
note.filterNode.frequency
|
|
770
|
-
.cancelScheduledValues(
|
|
763
|
+
.cancelScheduledValues(scheduleTime)
|
|
771
764
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
772
765
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
773
766
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
774
767
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
775
768
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
776
769
|
}
|
|
777
|
-
startModulation(channel, note,
|
|
770
|
+
startModulation(channel, note, scheduleTime) {
|
|
778
771
|
const { voiceParams } = note;
|
|
779
772
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
780
773
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -783,10 +776,10 @@ class MidyGMLite {
|
|
|
783
776
|
gain: voiceParams.modLfoToFilterFc,
|
|
784
777
|
});
|
|
785
778
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
786
|
-
this.setModLfoToPitch(channel, note);
|
|
779
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
787
780
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
788
|
-
this.setModLfoToVolume(note);
|
|
789
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
781
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
782
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
790
783
|
note.modulationLFO.connect(note.filterDepth);
|
|
791
784
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
792
785
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -813,6 +806,7 @@ class MidyGMLite {
|
|
|
813
806
|
}
|
|
814
807
|
}
|
|
815
808
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
809
|
+
const now = this.audioContext.currentTime;
|
|
816
810
|
const state = channel.state;
|
|
817
811
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
818
812
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -824,11 +818,11 @@ class MidyGMLite {
|
|
|
824
818
|
type: "lowpass",
|
|
825
819
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
826
820
|
});
|
|
827
|
-
this.setVolumeEnvelope(note);
|
|
828
|
-
this.setFilterEnvelope(note);
|
|
829
|
-
this.setPitchEnvelope(note);
|
|
821
|
+
this.setVolumeEnvelope(note, now);
|
|
822
|
+
this.setFilterEnvelope(note, now);
|
|
823
|
+
this.setPitchEnvelope(note, now);
|
|
830
824
|
if (0 < state.modulationDepth) {
|
|
831
|
-
this.startModulation(channel, note,
|
|
825
|
+
this.startModulation(channel, note, now);
|
|
832
826
|
}
|
|
833
827
|
note.bufferSource.connect(note.filterNode);
|
|
834
828
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -855,7 +849,7 @@ class MidyGMLite {
|
|
|
855
849
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
856
850
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
857
851
|
if (!prevNote.ending) {
|
|
858
|
-
this.
|
|
852
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
859
853
|
startTime, undefined, // portamentoNoteNumber
|
|
860
854
|
true);
|
|
861
855
|
}
|
|
@@ -870,9 +864,9 @@ class MidyGMLite {
|
|
|
870
864
|
scheduledNotes.set(noteNumber, [note]);
|
|
871
865
|
}
|
|
872
866
|
}
|
|
873
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
874
|
-
|
|
875
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
867
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
868
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
869
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
876
870
|
}
|
|
877
871
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
878
872
|
const note = scheduledNotes[index];
|
|
@@ -899,7 +893,7 @@ class MidyGMLite {
|
|
|
899
893
|
note.bufferSource.stop(stopTime);
|
|
900
894
|
});
|
|
901
895
|
}
|
|
902
|
-
|
|
896
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
903
897
|
const channel = this.channels[channelNumber];
|
|
904
898
|
if (!force && 0.5 < channel.state.sustainPedal)
|
|
905
899
|
return;
|
|
@@ -921,127 +915,119 @@ class MidyGMLite {
|
|
|
921
915
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
922
916
|
}
|
|
923
917
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
return this.
|
|
918
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
919
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
920
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
927
921
|
}
|
|
928
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
922
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
929
923
|
const velocity = halfVelocity * 2;
|
|
930
924
|
const channel = this.channels[channelNumber];
|
|
931
925
|
const promises = [];
|
|
932
|
-
channel
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (!note)
|
|
937
|
-
continue;
|
|
938
|
-
const { noteNumber } = note;
|
|
939
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
940
|
-
promises.push(promise);
|
|
941
|
-
}
|
|
926
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
927
|
+
const { noteNumber } = note;
|
|
928
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
929
|
+
promises.push(promise);
|
|
942
930
|
});
|
|
943
931
|
return promises;
|
|
944
932
|
}
|
|
945
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
933
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
946
934
|
const channelNumber = statusByte & 0x0F;
|
|
947
935
|
const messageType = statusByte & 0xF0;
|
|
948
936
|
switch (messageType) {
|
|
949
937
|
case 0x80:
|
|
950
|
-
return this.
|
|
938
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
951
939
|
case 0x90:
|
|
952
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
940
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
953
941
|
case 0xB0:
|
|
954
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
942
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
955
943
|
case 0xC0:
|
|
956
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
944
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
957
945
|
case 0xE0:
|
|
958
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
946
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
959
947
|
default:
|
|
960
948
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
961
949
|
}
|
|
962
950
|
}
|
|
963
|
-
handleProgramChange(channelNumber, program) {
|
|
951
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
964
952
|
const channel = this.channels[channelNumber];
|
|
965
953
|
channel.program = program;
|
|
966
954
|
}
|
|
967
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
955
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
968
956
|
const pitchBend = msb * 128 + lsb;
|
|
969
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
957
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
970
958
|
}
|
|
971
|
-
setPitchBend(channelNumber, value) {
|
|
959
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
960
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
972
961
|
const channel = this.channels[channelNumber];
|
|
973
962
|
const state = channel.state;
|
|
974
963
|
const prev = state.pitchWheel * 2 - 1;
|
|
975
964
|
const next = (value - 8192) / 8192;
|
|
976
965
|
state.pitchWheel = value / 16383;
|
|
977
966
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
978
|
-
this.updateChannelDetune(channel);
|
|
979
|
-
this.applyVoiceParams(channel, 14);
|
|
967
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
968
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
980
969
|
}
|
|
981
|
-
setModLfoToPitch(channel, note) {
|
|
982
|
-
const now = this.audioContext.currentTime;
|
|
970
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
983
971
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
984
972
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
985
973
|
channel.state.modulationDepth;
|
|
986
974
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
987
975
|
note.modulationDepth.gain
|
|
988
|
-
.cancelScheduledValues(
|
|
989
|
-
.setValueAtTime(modulationDepth,
|
|
976
|
+
.cancelScheduledValues(scheduleTime)
|
|
977
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
990
978
|
}
|
|
991
|
-
setModLfoToFilterFc(note) {
|
|
992
|
-
const now = this.audioContext.currentTime;
|
|
979
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
993
980
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
994
981
|
note.filterDepth.gain
|
|
995
|
-
.cancelScheduledValues(
|
|
996
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
982
|
+
.cancelScheduledValues(scheduleTime)
|
|
983
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
997
984
|
}
|
|
998
|
-
setModLfoToVolume(note) {
|
|
999
|
-
const now = this.audioContext.currentTime;
|
|
985
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1000
986
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1001
987
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1002
988
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1003
989
|
note.volumeDepth.gain
|
|
1004
|
-
.cancelScheduledValues(
|
|
1005
|
-
.setValueAtTime(volumeDepth,
|
|
990
|
+
.cancelScheduledValues(scheduleTime)
|
|
991
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1006
992
|
}
|
|
1007
|
-
setDelayModLFO(note) {
|
|
1008
|
-
const now = this.audioContext.currentTime;
|
|
993
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1009
994
|
const startTime = note.startTime;
|
|
1010
|
-
if (startTime <
|
|
995
|
+
if (startTime < scheduleTime)
|
|
1011
996
|
return;
|
|
1012
|
-
note.modulationLFO.stop(
|
|
997
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1013
998
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1014
999
|
note.modulationLFO.connect(note.filterDepth);
|
|
1015
1000
|
}
|
|
1016
|
-
setFreqModLFO(note) {
|
|
1017
|
-
const now = this.audioContext.currentTime;
|
|
1001
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1018
1002
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1019
1003
|
note.modulationLFO.frequency
|
|
1020
|
-
.cancelScheduledValues(
|
|
1021
|
-
.setValueAtTime(freqModLFO,
|
|
1004
|
+
.cancelScheduledValues(scheduleTime)
|
|
1005
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1022
1006
|
}
|
|
1023
1007
|
createVoiceParamsHandlers() {
|
|
1024
1008
|
return {
|
|
1025
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1009
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1026
1010
|
if (0 < channel.state.modulationDepth) {
|
|
1027
|
-
this.setModLfoToPitch(channel, note);
|
|
1011
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1028
1012
|
}
|
|
1029
1013
|
},
|
|
1030
|
-
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
1031
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1032
|
-
if (0 < channel.state.modulationDepth)
|
|
1033
|
-
this.setModLfoToFilterFc(note);
|
|
1014
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1015
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1016
|
+
if (0 < channel.state.modulationDepth) {
|
|
1017
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1018
|
+
}
|
|
1034
1019
|
},
|
|
1035
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1036
|
-
if (0 < channel.state.modulationDepth)
|
|
1037
|
-
this.setModLfoToVolume(note);
|
|
1020
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1021
|
+
if (0 < channel.state.modulationDepth) {
|
|
1022
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1023
|
+
}
|
|
1038
1024
|
},
|
|
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) => { },
|
|
1025
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1026
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1027
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1028
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1029
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1030
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1045
1031
|
};
|
|
1046
1032
|
}
|
|
1047
1033
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1051,7 +1037,7 @@ class MidyGMLite {
|
|
|
1051
1037
|
state[3] = noteNumber / 127;
|
|
1052
1038
|
return state;
|
|
1053
1039
|
}
|
|
1054
|
-
applyVoiceParams(channel, controllerType) {
|
|
1040
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1055
1041
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1056
1042
|
for (let i = 0; i < noteList.length; i++) {
|
|
1057
1043
|
const note = noteList[i];
|
|
@@ -1067,7 +1053,7 @@ class MidyGMLite {
|
|
|
1067
1053
|
continue;
|
|
1068
1054
|
note.voiceParams[key] = value;
|
|
1069
1055
|
if (key in this.voiceParamsHandlers) {
|
|
1070
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1056
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1071
1057
|
}
|
|
1072
1058
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1073
1059
|
if (appliedFilterEnvelope)
|
|
@@ -1079,8 +1065,8 @@ class MidyGMLite {
|
|
|
1079
1065
|
if (key in voiceParams)
|
|
1080
1066
|
noteVoiceParams[key] = voiceParams[key];
|
|
1081
1067
|
}
|
|
1082
|
-
this.setFilterEnvelope(note);
|
|
1083
|
-
this.setPitchEnvelope(note);
|
|
1068
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1069
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1084
1070
|
}
|
|
1085
1071
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1086
1072
|
if (appliedVolumeEnvelope)
|
|
@@ -1092,7 +1078,7 @@ class MidyGMLite {
|
|
|
1092
1078
|
if (key in voiceParams)
|
|
1093
1079
|
noteVoiceParams[key] = voiceParams[key];
|
|
1094
1080
|
}
|
|
1095
|
-
this.setVolumeEnvelope(channel, note);
|
|
1081
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1096
1082
|
}
|
|
1097
1083
|
}
|
|
1098
1084
|
}
|
|
@@ -1114,12 +1100,12 @@ class MidyGMLite {
|
|
|
1114
1100
|
123: this.allNotesOff,
|
|
1115
1101
|
};
|
|
1116
1102
|
}
|
|
1117
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1103
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1118
1104
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1119
1105
|
if (handler) {
|
|
1120
|
-
handler.call(this, channelNumber, value,
|
|
1106
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1121
1107
|
const channel = this.channels[channelNumber];
|
|
1122
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1108
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1123
1109
|
}
|
|
1124
1110
|
else {
|
|
1125
1111
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1165,26 +1151,26 @@ class MidyGMLite {
|
|
|
1165
1151
|
channel.state.expression = expression / 127;
|
|
1166
1152
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1167
1153
|
}
|
|
1168
|
-
dataEntryLSB(channelNumber, value) {
|
|
1154
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1169
1155
|
this.channels[channelNumber].dataLSB = value;
|
|
1170
|
-
this.handleRPN(channelNumber);
|
|
1156
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1171
1157
|
}
|
|
1172
1158
|
updateChannelVolume(channel, scheduleTime) {
|
|
1173
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1174
1159
|
const state = channel.state;
|
|
1175
1160
|
const volume = state.volume * state.expression;
|
|
1176
1161
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1177
1162
|
channel.gainL.gain
|
|
1178
|
-
.cancelScheduledValues(
|
|
1163
|
+
.cancelScheduledValues(scheduleTime)
|
|
1179
1164
|
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1180
1165
|
channel.gainR.gain
|
|
1181
|
-
.cancelScheduledValues(
|
|
1166
|
+
.cancelScheduledValues(scheduleTime)
|
|
1182
1167
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1183
1168
|
}
|
|
1184
|
-
setSustainPedal(channelNumber, value) {
|
|
1169
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1170
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1185
1171
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1186
1172
|
if (value < 64) {
|
|
1187
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1173
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1188
1174
|
}
|
|
1189
1175
|
}
|
|
1190
1176
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1205,12 +1191,12 @@ class MidyGMLite {
|
|
|
1205
1191
|
channel.dataLSB = minLSB;
|
|
1206
1192
|
}
|
|
1207
1193
|
}
|
|
1208
|
-
handleRPN(channelNumber) {
|
|
1194
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1209
1195
|
const channel = this.channels[channelNumber];
|
|
1210
1196
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1211
1197
|
switch (rpn) {
|
|
1212
1198
|
case 0:
|
|
1213
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1199
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1214
1200
|
break;
|
|
1215
1201
|
default:
|
|
1216
1202
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1222,28 +1208,30 @@ class MidyGMLite {
|
|
|
1222
1208
|
setRPNLSB(channelNumber, value) {
|
|
1223
1209
|
this.channels[channelNumber].rpnLSB = value;
|
|
1224
1210
|
}
|
|
1225
|
-
dataEntryMSB(channelNumber, value) {
|
|
1211
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1226
1212
|
this.channels[channelNumber].dataMSB = value;
|
|
1227
|
-
this.handleRPN(channelNumber);
|
|
1213
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1228
1214
|
}
|
|
1229
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1215
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1230
1216
|
const channel = this.channels[channelNumber];
|
|
1231
1217
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1232
1218
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1233
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1219
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1234
1220
|
}
|
|
1235
|
-
setPitchBendRange(channelNumber, value) {
|
|
1221
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1222
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1236
1223
|
const channel = this.channels[channelNumber];
|
|
1237
1224
|
const state = channel.state;
|
|
1238
1225
|
const prev = state.pitchWheelSensitivity;
|
|
1239
1226
|
const next = value / 128;
|
|
1240
1227
|
state.pitchWheelSensitivity = next;
|
|
1241
1228
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1242
|
-
this.updateChannelDetune(channel);
|
|
1243
|
-
this.applyVoiceParams(channel, 16);
|
|
1229
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1230
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1244
1231
|
}
|
|
1245
|
-
allSoundOff(channelNumber) {
|
|
1246
|
-
|
|
1232
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1233
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1234
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1247
1235
|
}
|
|
1248
1236
|
resetAllControllers(channelNumber) {
|
|
1249
1237
|
const stateTypes = [
|
|
@@ -1267,10 +1255,11 @@ class MidyGMLite {
|
|
|
1267
1255
|
channel[type] = this.constructor.channelSettings[type];
|
|
1268
1256
|
}
|
|
1269
1257
|
}
|
|
1270
|
-
allNotesOff(channelNumber) {
|
|
1271
|
-
|
|
1258
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1259
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1260
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1272
1261
|
}
|
|
1273
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1262
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1274
1263
|
switch (data[2]) {
|
|
1275
1264
|
case 9:
|
|
1276
1265
|
switch (data[3]) {
|
|
@@ -1294,12 +1283,12 @@ class MidyGMLite {
|
|
|
1294
1283
|
}
|
|
1295
1284
|
this.channels[9].bank = 128;
|
|
1296
1285
|
}
|
|
1297
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1286
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1298
1287
|
switch (data[2]) {
|
|
1299
1288
|
case 4:
|
|
1300
1289
|
switch (data[3]) {
|
|
1301
1290
|
case 1:
|
|
1302
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1291
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1303
1292
|
default:
|
|
1304
1293
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1305
1294
|
}
|
|
@@ -1308,42 +1297,40 @@ class MidyGMLite {
|
|
|
1308
1297
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1309
1298
|
}
|
|
1310
1299
|
}
|
|
1311
|
-
handleMasterVolumeSysEx(data) {
|
|
1300
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1312
1301
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1313
|
-
this.setMasterVolume(volume);
|
|
1302
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1314
1303
|
}
|
|
1315
|
-
setMasterVolume(volume) {
|
|
1304
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1305
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1316
1306
|
if (volume < 0 && 1 < volume) {
|
|
1317
1307
|
console.error("Master Volume is out of range");
|
|
1318
1308
|
}
|
|
1319
1309
|
else {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1310
|
+
this.masterVolume.gain
|
|
1311
|
+
.cancelScheduledValues(scheduleTime)
|
|
1312
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1323
1313
|
}
|
|
1324
1314
|
}
|
|
1325
|
-
|
|
1326
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1327
|
-
}
|
|
1328
|
-
handleSysEx(data) {
|
|
1315
|
+
handleSysEx(data, scheduleTime) {
|
|
1329
1316
|
switch (data[0]) {
|
|
1330
1317
|
case 126:
|
|
1331
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1318
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1332
1319
|
case 127:
|
|
1333
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1320
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1334
1321
|
default:
|
|
1335
|
-
|
|
1322
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1336
1323
|
}
|
|
1337
1324
|
}
|
|
1338
|
-
scheduleTask(callback,
|
|
1325
|
+
scheduleTask(callback, scheduleTime) {
|
|
1339
1326
|
return new Promise((resolve) => {
|
|
1340
1327
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1341
1328
|
bufferSource.onended = () => {
|
|
1342
1329
|
callback();
|
|
1343
1330
|
resolve();
|
|
1344
1331
|
};
|
|
1345
|
-
bufferSource.start(
|
|
1346
|
-
bufferSource.stop(
|
|
1332
|
+
bufferSource.start(scheduleTime);
|
|
1333
|
+
bufferSource.stop(scheduleTime);
|
|
1347
1334
|
});
|
|
1348
1335
|
}
|
|
1349
1336
|
}
|