@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-GM1.js
CHANGED
|
@@ -415,7 +415,7 @@ class MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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
|
}
|
|
@@ -693,24 +695,17 @@ class MidyGM1 {
|
|
|
693
695
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
694
696
|
return tuning + pitch;
|
|
695
697
|
}
|
|
696
|
-
updateChannelDetune(channel) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const note = noteList[i];
|
|
700
|
-
if (!note)
|
|
701
|
-
continue;
|
|
702
|
-
this.updateDetune(channel, note);
|
|
703
|
-
}
|
|
698
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
699
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
700
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
704
701
|
});
|
|
705
702
|
}
|
|
706
|
-
updateDetune(channel, note) {
|
|
707
|
-
const now = this.audioContext.currentTime;
|
|
703
|
+
updateDetune(channel, note, scheduleTime) {
|
|
708
704
|
note.bufferSource.detune
|
|
709
|
-
.cancelScheduledValues(
|
|
710
|
-
.setValueAtTime(channel.detune,
|
|
705
|
+
.cancelScheduledValues(scheduleTime)
|
|
706
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
711
707
|
}
|
|
712
|
-
setVolumeEnvelope(note) {
|
|
713
|
-
const now = this.audioContext.currentTime;
|
|
708
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
714
709
|
const { voiceParams, startTime } = note;
|
|
715
710
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
716
711
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -719,7 +714,7 @@ class MidyGM1 {
|
|
|
719
714
|
const volHold = volAttack + voiceParams.volHold;
|
|
720
715
|
const volDecay = volHold + voiceParams.volDecay;
|
|
721
716
|
note.volumeEnvelopeNode.gain
|
|
722
|
-
.cancelScheduledValues(
|
|
717
|
+
.cancelScheduledValues(scheduleTime)
|
|
723
718
|
.setValueAtTime(0, startTime)
|
|
724
719
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
725
720
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -727,7 +722,6 @@ class MidyGM1 {
|
|
|
727
722
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
728
723
|
}
|
|
729
724
|
setPitchEnvelope(note, scheduleTime) {
|
|
730
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
731
725
|
const { voiceParams } = note;
|
|
732
726
|
const baseRate = voiceParams.playbackRate;
|
|
733
727
|
note.bufferSource.playbackRate
|
|
@@ -754,8 +748,7 @@ class MidyGM1 {
|
|
|
754
748
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
755
749
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
756
750
|
}
|
|
757
|
-
setFilterEnvelope(note) {
|
|
758
|
-
const now = this.audioContext.currentTime;
|
|
751
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
759
752
|
const { voiceParams, startTime } = note;
|
|
760
753
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
761
754
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -769,14 +762,14 @@ class MidyGM1 {
|
|
|
769
762
|
const modHold = modAttack + voiceParams.modHold;
|
|
770
763
|
const modDecay = modHold + voiceParams.modDecay;
|
|
771
764
|
note.filterNode.frequency
|
|
772
|
-
.cancelScheduledValues(
|
|
765
|
+
.cancelScheduledValues(scheduleTime)
|
|
773
766
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
774
767
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
775
768
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
776
769
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
777
770
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
778
771
|
}
|
|
779
|
-
startModulation(channel, note,
|
|
772
|
+
startModulation(channel, note, scheduleTime) {
|
|
780
773
|
const { voiceParams } = note;
|
|
781
774
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
782
775
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -785,10 +778,10 @@ class MidyGM1 {
|
|
|
785
778
|
gain: voiceParams.modLfoToFilterFc,
|
|
786
779
|
});
|
|
787
780
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
788
|
-
this.setModLfoToPitch(channel, note);
|
|
781
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
789
782
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
790
|
-
this.setModLfoToVolume(note);
|
|
791
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
783
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
784
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
792
785
|
note.modulationLFO.connect(note.filterDepth);
|
|
793
786
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
794
787
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -815,6 +808,7 @@ class MidyGM1 {
|
|
|
815
808
|
}
|
|
816
809
|
}
|
|
817
810
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
811
|
+
const now = this.audioContext.currentTime;
|
|
818
812
|
const state = channel.state;
|
|
819
813
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
820
814
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -826,11 +820,11 @@ class MidyGM1 {
|
|
|
826
820
|
type: "lowpass",
|
|
827
821
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
828
822
|
});
|
|
829
|
-
this.setVolumeEnvelope(note);
|
|
830
|
-
this.setFilterEnvelope(note);
|
|
831
|
-
this.setPitchEnvelope(note);
|
|
823
|
+
this.setVolumeEnvelope(note, now);
|
|
824
|
+
this.setFilterEnvelope(note, now);
|
|
825
|
+
this.setPitchEnvelope(note, now);
|
|
832
826
|
if (0 < state.modulationDepth) {
|
|
833
|
-
this.startModulation(channel, note,
|
|
827
|
+
this.startModulation(channel, note, now);
|
|
834
828
|
}
|
|
835
829
|
note.bufferSource.connect(note.filterNode);
|
|
836
830
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -857,7 +851,7 @@ class MidyGM1 {
|
|
|
857
851
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
858
852
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
859
853
|
if (!prevNote.ending) {
|
|
860
|
-
this.
|
|
854
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
861
855
|
startTime, undefined, // portamentoNoteNumber
|
|
862
856
|
true);
|
|
863
857
|
}
|
|
@@ -872,9 +866,9 @@ class MidyGM1 {
|
|
|
872
866
|
scheduledNotes.set(noteNumber, [note]);
|
|
873
867
|
}
|
|
874
868
|
}
|
|
875
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
876
|
-
|
|
877
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
869
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
870
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
871
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
878
872
|
}
|
|
879
873
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
880
874
|
const note = scheduledNotes[index];
|
|
@@ -901,7 +895,7 @@ class MidyGM1 {
|
|
|
901
895
|
note.bufferSource.stop(stopTime);
|
|
902
896
|
});
|
|
903
897
|
}
|
|
904
|
-
|
|
898
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
905
899
|
const channel = this.channels[channelNumber];
|
|
906
900
|
if (!force && 0.5 < channel.state.sustainPedal)
|
|
907
901
|
return;
|
|
@@ -923,127 +917,119 @@ class MidyGM1 {
|
|
|
923
917
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
924
918
|
}
|
|
925
919
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
return this.
|
|
920
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
921
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
922
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
929
923
|
}
|
|
930
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
924
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
931
925
|
const velocity = halfVelocity * 2;
|
|
932
926
|
const channel = this.channels[channelNumber];
|
|
933
927
|
const promises = [];
|
|
934
|
-
channel
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (!note)
|
|
939
|
-
continue;
|
|
940
|
-
const { noteNumber } = note;
|
|
941
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
942
|
-
promises.push(promise);
|
|
943
|
-
}
|
|
928
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
929
|
+
const { noteNumber } = note;
|
|
930
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
931
|
+
promises.push(promise);
|
|
944
932
|
});
|
|
945
933
|
return promises;
|
|
946
934
|
}
|
|
947
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
935
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
948
936
|
const channelNumber = statusByte & 0x0F;
|
|
949
937
|
const messageType = statusByte & 0xF0;
|
|
950
938
|
switch (messageType) {
|
|
951
939
|
case 0x80:
|
|
952
|
-
return this.
|
|
940
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
953
941
|
case 0x90:
|
|
954
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
942
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
955
943
|
case 0xB0:
|
|
956
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
944
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
957
945
|
case 0xC0:
|
|
958
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
946
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
959
947
|
case 0xE0:
|
|
960
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
948
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
961
949
|
default:
|
|
962
950
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
963
951
|
}
|
|
964
952
|
}
|
|
965
|
-
handleProgramChange(channelNumber, program) {
|
|
953
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
966
954
|
const channel = this.channels[channelNumber];
|
|
967
955
|
channel.program = program;
|
|
968
956
|
}
|
|
969
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
957
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
970
958
|
const pitchBend = msb * 128 + lsb;
|
|
971
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
959
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
972
960
|
}
|
|
973
|
-
setPitchBend(channelNumber, value) {
|
|
961
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
962
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
974
963
|
const channel = this.channels[channelNumber];
|
|
975
964
|
const state = channel.state;
|
|
976
965
|
const prev = state.pitchWheel * 2 - 1;
|
|
977
966
|
const next = (value - 8192) / 8192;
|
|
978
967
|
state.pitchWheel = value / 16383;
|
|
979
968
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
980
|
-
this.updateChannelDetune(channel);
|
|
981
|
-
this.applyVoiceParams(channel, 14);
|
|
969
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
970
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
982
971
|
}
|
|
983
|
-
setModLfoToPitch(channel, note) {
|
|
984
|
-
const now = this.audioContext.currentTime;
|
|
972
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
985
973
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
986
974
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
987
975
|
channel.state.modulationDepth;
|
|
988
976
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
989
977
|
note.modulationDepth.gain
|
|
990
|
-
.cancelScheduledValues(
|
|
991
|
-
.setValueAtTime(modulationDepth,
|
|
978
|
+
.cancelScheduledValues(scheduleTime)
|
|
979
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
992
980
|
}
|
|
993
|
-
setModLfoToFilterFc(note) {
|
|
994
|
-
const now = this.audioContext.currentTime;
|
|
981
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
995
982
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
996
983
|
note.filterDepth.gain
|
|
997
|
-
.cancelScheduledValues(
|
|
998
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
984
|
+
.cancelScheduledValues(scheduleTime)
|
|
985
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
999
986
|
}
|
|
1000
|
-
setModLfoToVolume(note) {
|
|
1001
|
-
const now = this.audioContext.currentTime;
|
|
987
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1002
988
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1003
989
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1004
990
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1005
991
|
note.volumeDepth.gain
|
|
1006
|
-
.cancelScheduledValues(
|
|
1007
|
-
.setValueAtTime(volumeDepth,
|
|
992
|
+
.cancelScheduledValues(scheduleTime)
|
|
993
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1008
994
|
}
|
|
1009
|
-
setDelayModLFO(note) {
|
|
1010
|
-
const now = this.audioContext.currentTime;
|
|
995
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1011
996
|
const startTime = note.startTime;
|
|
1012
|
-
if (startTime <
|
|
997
|
+
if (startTime < scheduleTime)
|
|
1013
998
|
return;
|
|
1014
|
-
note.modulationLFO.stop(
|
|
999
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1015
1000
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1016
1001
|
note.modulationLFO.connect(note.filterDepth);
|
|
1017
1002
|
}
|
|
1018
|
-
setFreqModLFO(note) {
|
|
1019
|
-
const now = this.audioContext.currentTime;
|
|
1003
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1020
1004
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1021
1005
|
note.modulationLFO.frequency
|
|
1022
|
-
.cancelScheduledValues(
|
|
1023
|
-
.setValueAtTime(freqModLFO,
|
|
1006
|
+
.cancelScheduledValues(scheduleTime)
|
|
1007
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1024
1008
|
}
|
|
1025
1009
|
createVoiceParamsHandlers() {
|
|
1026
1010
|
return {
|
|
1027
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1011
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1028
1012
|
if (0 < channel.state.modulationDepth) {
|
|
1029
|
-
this.setModLfoToPitch(channel, note);
|
|
1013
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1030
1014
|
}
|
|
1031
1015
|
},
|
|
1032
|
-
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
1033
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1034
|
-
if (0 < channel.state.modulationDepth)
|
|
1035
|
-
this.setModLfoToFilterFc(note);
|
|
1016
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1017
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1018
|
+
if (0 < channel.state.modulationDepth) {
|
|
1019
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1020
|
+
}
|
|
1036
1021
|
},
|
|
1037
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1038
|
-
if (0 < channel.state.modulationDepth)
|
|
1039
|
-
this.setModLfoToVolume(note);
|
|
1022
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1023
|
+
if (0 < channel.state.modulationDepth) {
|
|
1024
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1025
|
+
}
|
|
1040
1026
|
},
|
|
1041
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1042
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1043
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1044
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1045
|
-
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
1046
|
-
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
1027
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1028
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1029
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1030
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1031
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1032
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1047
1033
|
};
|
|
1048
1034
|
}
|
|
1049
1035
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1053,7 +1039,7 @@ class MidyGM1 {
|
|
|
1053
1039
|
state[3] = noteNumber / 127;
|
|
1054
1040
|
return state;
|
|
1055
1041
|
}
|
|
1056
|
-
applyVoiceParams(channel, controllerType) {
|
|
1042
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1057
1043
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1058
1044
|
for (let i = 0; i < noteList.length; i++) {
|
|
1059
1045
|
const note = noteList[i];
|
|
@@ -1069,7 +1055,7 @@ class MidyGM1 {
|
|
|
1069
1055
|
continue;
|
|
1070
1056
|
note.voiceParams[key] = value;
|
|
1071
1057
|
if (key in this.voiceParamsHandlers) {
|
|
1072
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1058
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1073
1059
|
}
|
|
1074
1060
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1075
1061
|
if (appliedFilterEnvelope)
|
|
@@ -1081,8 +1067,8 @@ class MidyGM1 {
|
|
|
1081
1067
|
if (key in voiceParams)
|
|
1082
1068
|
noteVoiceParams[key] = voiceParams[key];
|
|
1083
1069
|
}
|
|
1084
|
-
this.setFilterEnvelope(note);
|
|
1085
|
-
this.setPitchEnvelope(note);
|
|
1070
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1071
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1086
1072
|
}
|
|
1087
1073
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1088
1074
|
if (appliedVolumeEnvelope)
|
|
@@ -1094,7 +1080,7 @@ class MidyGM1 {
|
|
|
1094
1080
|
if (key in voiceParams)
|
|
1095
1081
|
noteVoiceParams[key] = voiceParams[key];
|
|
1096
1082
|
}
|
|
1097
|
-
this.setVolumeEnvelope(channel, note);
|
|
1083
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1098
1084
|
}
|
|
1099
1085
|
}
|
|
1100
1086
|
}
|
|
@@ -1116,12 +1102,12 @@ class MidyGM1 {
|
|
|
1116
1102
|
123: this.allNotesOff,
|
|
1117
1103
|
};
|
|
1118
1104
|
}
|
|
1119
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1105
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1120
1106
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1121
1107
|
if (handler) {
|
|
1122
|
-
handler.call(this, channelNumber, value,
|
|
1108
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1123
1109
|
const channel = this.channels[channelNumber];
|
|
1124
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1110
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1125
1111
|
}
|
|
1126
1112
|
else {
|
|
1127
1113
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1167,26 +1153,26 @@ class MidyGM1 {
|
|
|
1167
1153
|
channel.state.expression = expression / 127;
|
|
1168
1154
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1169
1155
|
}
|
|
1170
|
-
dataEntryLSB(channelNumber, value) {
|
|
1156
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1171
1157
|
this.channels[channelNumber].dataLSB = value;
|
|
1172
|
-
this.handleRPN(channelNumber,
|
|
1158
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1173
1159
|
}
|
|
1174
1160
|
updateChannelVolume(channel, scheduleTime) {
|
|
1175
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1176
1161
|
const state = channel.state;
|
|
1177
1162
|
const volume = state.volume * state.expression;
|
|
1178
1163
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1179
1164
|
channel.gainL.gain
|
|
1180
|
-
.cancelScheduledValues(
|
|
1165
|
+
.cancelScheduledValues(scheduleTime)
|
|
1181
1166
|
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1182
1167
|
channel.gainR.gain
|
|
1183
|
-
.cancelScheduledValues(
|
|
1168
|
+
.cancelScheduledValues(scheduleTime)
|
|
1184
1169
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1185
1170
|
}
|
|
1186
|
-
setSustainPedal(channelNumber, value) {
|
|
1171
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1172
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1187
1173
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1188
1174
|
if (value < 64) {
|
|
1189
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1175
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1190
1176
|
}
|
|
1191
1177
|
}
|
|
1192
1178
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1215,12 +1201,12 @@ class MidyGM1 {
|
|
|
1215
1201
|
channel.dataMSB = minMSB;
|
|
1216
1202
|
}
|
|
1217
1203
|
}
|
|
1218
|
-
handleRPN(channelNumber) {
|
|
1204
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1219
1205
|
const channel = this.channels[channelNumber];
|
|
1220
1206
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1221
1207
|
switch (rpn) {
|
|
1222
1208
|
case 0:
|
|
1223
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1209
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1224
1210
|
break;
|
|
1225
1211
|
case 1:
|
|
1226
1212
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1238,25 +1224,26 @@ class MidyGM1 {
|
|
|
1238
1224
|
setRPNLSB(channelNumber, value) {
|
|
1239
1225
|
this.channels[channelNumber].rpnLSB = value;
|
|
1240
1226
|
}
|
|
1241
|
-
dataEntryMSB(channelNumber, value) {
|
|
1227
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1242
1228
|
this.channels[channelNumber].dataMSB = value;
|
|
1243
|
-
this.handleRPN(channelNumber);
|
|
1229
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1244
1230
|
}
|
|
1245
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1231
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1246
1232
|
const channel = this.channels[channelNumber];
|
|
1247
1233
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1248
1234
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1249
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1235
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1250
1236
|
}
|
|
1251
|
-
setPitchBendRange(channelNumber, value) {
|
|
1237
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1238
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1252
1239
|
const channel = this.channels[channelNumber];
|
|
1253
1240
|
const state = channel.state;
|
|
1254
1241
|
const prev = state.pitchWheelSensitivity;
|
|
1255
1242
|
const next = value / 128;
|
|
1256
1243
|
state.pitchWheelSensitivity = next;
|
|
1257
1244
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1258
|
-
this.updateChannelDetune(channel);
|
|
1259
|
-
this.applyVoiceParams(channel, 16);
|
|
1245
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1246
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1260
1247
|
}
|
|
1261
1248
|
handleFineTuningRPN(channelNumber) {
|
|
1262
1249
|
const channel = this.channels[channelNumber];
|
|
@@ -1286,8 +1273,9 @@ class MidyGM1 {
|
|
|
1286
1273
|
channel.detune += next - prev;
|
|
1287
1274
|
this.updateChannelDetune(channel);
|
|
1288
1275
|
}
|
|
1289
|
-
allSoundOff(channelNumber) {
|
|
1290
|
-
|
|
1276
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1277
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1278
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1291
1279
|
}
|
|
1292
1280
|
resetAllControllers(channelNumber) {
|
|
1293
1281
|
const stateTypes = [
|
|
@@ -1311,10 +1299,11 @@ class MidyGM1 {
|
|
|
1311
1299
|
channel[type] = this.constructor.channelSettings[type];
|
|
1312
1300
|
}
|
|
1313
1301
|
}
|
|
1314
|
-
allNotesOff(channelNumber) {
|
|
1315
|
-
|
|
1302
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1303
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1304
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1316
1305
|
}
|
|
1317
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1306
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1318
1307
|
switch (data[2]) {
|
|
1319
1308
|
case 9:
|
|
1320
1309
|
switch (data[3]) {
|
|
@@ -1338,12 +1327,12 @@ class MidyGM1 {
|
|
|
1338
1327
|
}
|
|
1339
1328
|
this.channels[9].bank = 128;
|
|
1340
1329
|
}
|
|
1341
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1330
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1342
1331
|
switch (data[2]) {
|
|
1343
1332
|
case 4:
|
|
1344
1333
|
switch (data[3]) {
|
|
1345
1334
|
case 1:
|
|
1346
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1335
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1347
1336
|
default:
|
|
1348
1337
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1349
1338
|
}
|
|
@@ -1352,42 +1341,40 @@ class MidyGM1 {
|
|
|
1352
1341
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1353
1342
|
}
|
|
1354
1343
|
}
|
|
1355
|
-
handleMasterVolumeSysEx(data) {
|
|
1344
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1356
1345
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1357
|
-
this.setMasterVolume(volume);
|
|
1346
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1358
1347
|
}
|
|
1359
|
-
setMasterVolume(volume) {
|
|
1348
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1349
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1360
1350
|
if (volume < 0 && 1 < volume) {
|
|
1361
1351
|
console.error("Master Volume is out of range");
|
|
1362
1352
|
}
|
|
1363
1353
|
else {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1354
|
+
this.masterVolume.gain
|
|
1355
|
+
.cancelScheduledValues(scheduleTime)
|
|
1356
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1367
1357
|
}
|
|
1368
1358
|
}
|
|
1369
|
-
|
|
1370
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1371
|
-
}
|
|
1372
|
-
handleSysEx(data) {
|
|
1359
|
+
handleSysEx(data, scheduleTime) {
|
|
1373
1360
|
switch (data[0]) {
|
|
1374
1361
|
case 126:
|
|
1375
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1362
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1376
1363
|
case 127:
|
|
1377
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1364
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1378
1365
|
default:
|
|
1379
|
-
|
|
1366
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1380
1367
|
}
|
|
1381
1368
|
}
|
|
1382
|
-
scheduleTask(callback,
|
|
1369
|
+
scheduleTask(callback, scheduleTime) {
|
|
1383
1370
|
return new Promise((resolve) => {
|
|
1384
1371
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1385
1372
|
bufferSource.onended = () => {
|
|
1386
1373
|
callback();
|
|
1387
1374
|
resolve();
|
|
1388
1375
|
};
|
|
1389
|
-
bufferSource.start(
|
|
1390
|
-
bufferSource.stop(
|
|
1376
|
+
bufferSource.start(scheduleTime);
|
|
1377
|
+
bufferSource.stop(scheduleTime);
|
|
1391
1378
|
});
|
|
1392
1379
|
}
|
|
1393
1380
|
}
|