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