@marmooo/midy 0.2.5 → 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 +85 -87
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +188 -237
- package/esm/midy-GM2.d.ts +129 -126
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +374 -390
- package/esm/midy-GMLite.d.ts +85 -84
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +191 -190
- package/esm/midy.d.ts +153 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +420 -442
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +85 -87
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +188 -237
- package/script/midy-GM2.d.ts +129 -126
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +374 -390
- package/script/midy-GMLite.d.ts +85 -84
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +191 -190
- package/script/midy.d.ts +153 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +420 -442
package/esm/midy-GM1.js
CHANGED
|
@@ -66,37 +66,31 @@ class Note {
|
|
|
66
66
|
writable: true,
|
|
67
67
|
value: void 0
|
|
68
68
|
});
|
|
69
|
-
Object.defineProperty(this, "
|
|
70
|
-
enumerable: true,
|
|
71
|
-
configurable: true,
|
|
72
|
-
writable: true,
|
|
73
|
-
value: void 0
|
|
74
|
-
});
|
|
75
|
-
Object.defineProperty(this, "volumeDepth", {
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
76
70
|
enumerable: true,
|
|
77
71
|
configurable: true,
|
|
78
72
|
writable: true,
|
|
79
73
|
value: void 0
|
|
80
74
|
});
|
|
81
|
-
Object.defineProperty(this, "
|
|
75
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
82
76
|
enumerable: true,
|
|
83
77
|
configurable: true,
|
|
84
78
|
writable: true,
|
|
85
79
|
value: void 0
|
|
86
80
|
});
|
|
87
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
88
82
|
enumerable: true,
|
|
89
83
|
configurable: true,
|
|
90
84
|
writable: true,
|
|
91
85
|
value: void 0
|
|
92
86
|
});
|
|
93
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "modulationLFO", {
|
|
94
88
|
enumerable: true,
|
|
95
89
|
configurable: true,
|
|
96
90
|
writable: true,
|
|
97
91
|
value: void 0
|
|
98
92
|
});
|
|
99
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "modulationDepth", {
|
|
100
94
|
enumerable: true,
|
|
101
95
|
configurable: true,
|
|
102
96
|
writable: true,
|
|
@@ -409,31 +403,32 @@ export class MidyGM1 {
|
|
|
409
403
|
const event = this.timeline[queueIndex];
|
|
410
404
|
if (event.startTime > t + this.lookAhead)
|
|
411
405
|
break;
|
|
406
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
412
407
|
switch (event.type) {
|
|
413
408
|
case "noteOn":
|
|
414
409
|
if (event.velocity !== 0) {
|
|
415
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
410
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
416
411
|
break;
|
|
417
412
|
}
|
|
418
413
|
/* falls through */
|
|
419
414
|
case "noteOff": {
|
|
420
|
-
const notePromise = this.
|
|
415
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
|
|
421
416
|
if (notePromise) {
|
|
422
417
|
this.notePromises.push(notePromise);
|
|
423
418
|
}
|
|
424
419
|
break;
|
|
425
420
|
}
|
|
426
421
|
case "controller":
|
|
427
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
422
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
428
423
|
break;
|
|
429
424
|
case "programChange":
|
|
430
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
425
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
431
426
|
break;
|
|
432
427
|
case "pitchBend":
|
|
433
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
428
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
434
429
|
break;
|
|
435
430
|
case "sysEx":
|
|
436
|
-
this.handleSysEx(event.data);
|
|
431
|
+
this.handleSysEx(event.data, startTime);
|
|
437
432
|
}
|
|
438
433
|
queueIndex++;
|
|
439
434
|
}
|
|
@@ -464,10 +459,11 @@ export class MidyGM1 {
|
|
|
464
459
|
resolve();
|
|
465
460
|
return;
|
|
466
461
|
}
|
|
467
|
-
const
|
|
462
|
+
const now = this.audioContext.currentTime;
|
|
463
|
+
const t = now + offset;
|
|
468
464
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
469
465
|
if (this.isPausing) {
|
|
470
|
-
await this.stopNotes(0, true);
|
|
466
|
+
await this.stopNotes(0, true, now);
|
|
471
467
|
this.notePromises = [];
|
|
472
468
|
resolve();
|
|
473
469
|
this.isPausing = false;
|
|
@@ -475,7 +471,7 @@ export class MidyGM1 {
|
|
|
475
471
|
return;
|
|
476
472
|
}
|
|
477
473
|
else if (this.isStopping) {
|
|
478
|
-
await this.stopNotes(0, true);
|
|
474
|
+
await this.stopNotes(0, true, now);
|
|
479
475
|
this.notePromises = [];
|
|
480
476
|
this.exclusiveClassMap.clear();
|
|
481
477
|
this.audioBufferCache.clear();
|
|
@@ -485,7 +481,7 @@ export class MidyGM1 {
|
|
|
485
481
|
return;
|
|
486
482
|
}
|
|
487
483
|
else if (this.isSeeking) {
|
|
488
|
-
this.stopNotes(0, true);
|
|
484
|
+
this.stopNotes(0, true, now);
|
|
489
485
|
this.exclusiveClassMap.clear();
|
|
490
486
|
this.startTime = this.audioContext.currentTime;
|
|
491
487
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -494,7 +490,6 @@ export class MidyGM1 {
|
|
|
494
490
|
await schedulePlayback();
|
|
495
491
|
}
|
|
496
492
|
else {
|
|
497
|
-
const now = this.audioContext.currentTime;
|
|
498
493
|
const waitTime = now + this.noteCheckInterval;
|
|
499
494
|
await this.scheduleTask(() => { }, waitTime);
|
|
500
495
|
await schedulePlayback();
|
|
@@ -578,24 +573,26 @@ export class MidyGM1 {
|
|
|
578
573
|
}
|
|
579
574
|
return { instruments, timeline };
|
|
580
575
|
}
|
|
581
|
-
|
|
582
|
-
const now = this.audioContext.currentTime;
|
|
576
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
583
577
|
const channel = this.channels[channelNumber];
|
|
578
|
+
const promises = [];
|
|
584
579
|
channel.scheduledNotes.forEach((noteList) => {
|
|
585
580
|
for (let i = 0; i < noteList.length; i++) {
|
|
586
581
|
const note = noteList[i];
|
|
587
582
|
if (!note)
|
|
588
583
|
continue;
|
|
589
|
-
const promise = this.
|
|
584
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
590
585
|
this.notePromises.push(promise);
|
|
586
|
+
promises.push(promise);
|
|
591
587
|
}
|
|
592
588
|
});
|
|
593
589
|
channel.scheduledNotes.clear();
|
|
594
|
-
|
|
590
|
+
return Promise.all(promises);
|
|
595
591
|
}
|
|
596
|
-
stopNotes(velocity, force) {
|
|
592
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
593
|
+
const promises = [];
|
|
597
594
|
for (let i = 0; i < this.channels.length; i++) {
|
|
598
|
-
this.stopChannelNotes(i, velocity, force);
|
|
595
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
599
596
|
}
|
|
600
597
|
return Promise.all(this.notePromises);
|
|
601
598
|
}
|
|
@@ -643,22 +640,34 @@ export class MidyGM1 {
|
|
|
643
640
|
const now = this.audioContext.currentTime;
|
|
644
641
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
645
642
|
}
|
|
646
|
-
|
|
643
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
644
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
645
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
646
|
+
const note = noteList[i];
|
|
647
|
+
if (!note)
|
|
648
|
+
continue;
|
|
649
|
+
if (scheduleTime < note.startTime)
|
|
650
|
+
continue;
|
|
651
|
+
callback(note);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
getActiveNotes(channel, scheduleTime) {
|
|
647
656
|
const activeNotes = new SparseMap(128);
|
|
648
657
|
channel.scheduledNotes.forEach((noteList) => {
|
|
649
|
-
const activeNote = this.getActiveNote(noteList,
|
|
658
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
650
659
|
if (activeNote) {
|
|
651
660
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
652
661
|
}
|
|
653
662
|
});
|
|
654
663
|
return activeNotes;
|
|
655
664
|
}
|
|
656
|
-
getActiveNote(noteList,
|
|
665
|
+
getActiveNote(noteList, scheduleTime) {
|
|
657
666
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
658
667
|
const note = noteList[i];
|
|
659
668
|
if (!note)
|
|
660
669
|
return;
|
|
661
|
-
if (
|
|
670
|
+
if (scheduleTime < note.startTime)
|
|
662
671
|
continue;
|
|
663
672
|
return (note.ending) ? null : note;
|
|
664
673
|
}
|
|
@@ -683,24 +692,17 @@ export class MidyGM1 {
|
|
|
683
692
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
684
693
|
return tuning + pitch;
|
|
685
694
|
}
|
|
686
|
-
updateChannelDetune(channel) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const note = noteList[i];
|
|
690
|
-
if (!note)
|
|
691
|
-
continue;
|
|
692
|
-
this.updateDetune(channel, note);
|
|
693
|
-
}
|
|
695
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
696
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
697
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
694
698
|
});
|
|
695
699
|
}
|
|
696
|
-
updateDetune(channel, note) {
|
|
697
|
-
const now = this.audioContext.currentTime;
|
|
700
|
+
updateDetune(channel, note, scheduleTime) {
|
|
698
701
|
note.bufferSource.detune
|
|
699
|
-
.cancelScheduledValues(
|
|
700
|
-
.setValueAtTime(channel.detune,
|
|
702
|
+
.cancelScheduledValues(scheduleTime)
|
|
703
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
701
704
|
}
|
|
702
|
-
setVolumeEnvelope(note) {
|
|
703
|
-
const now = this.audioContext.currentTime;
|
|
705
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
704
706
|
const { voiceParams, startTime } = note;
|
|
705
707
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
706
708
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -709,27 +711,26 @@ export class MidyGM1 {
|
|
|
709
711
|
const volHold = volAttack + voiceParams.volHold;
|
|
710
712
|
const volDecay = volHold + voiceParams.volDecay;
|
|
711
713
|
note.volumeEnvelopeNode.gain
|
|
712
|
-
.cancelScheduledValues(
|
|
714
|
+
.cancelScheduledValues(scheduleTime)
|
|
713
715
|
.setValueAtTime(0, startTime)
|
|
714
716
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
715
717
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
716
718
|
.setValueAtTime(attackVolume, volHold)
|
|
717
719
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
718
720
|
}
|
|
719
|
-
setPitchEnvelope(note) {
|
|
720
|
-
const now = this.audioContext.currentTime;
|
|
721
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
721
722
|
const { voiceParams } = note;
|
|
722
723
|
const baseRate = voiceParams.playbackRate;
|
|
723
724
|
note.bufferSource.playbackRate
|
|
724
|
-
.cancelScheduledValues(
|
|
725
|
-
.setValueAtTime(baseRate,
|
|
725
|
+
.cancelScheduledValues(scheduleTime)
|
|
726
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
726
727
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
727
728
|
if (modEnvToPitch === 0)
|
|
728
729
|
return;
|
|
729
730
|
const basePitch = this.rateToCent(baseRate);
|
|
730
731
|
const peekPitch = basePitch + modEnvToPitch;
|
|
731
732
|
const peekRate = this.centToRate(peekPitch);
|
|
732
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
733
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
733
734
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
734
735
|
const modHold = modAttack + voiceParams.modHold;
|
|
735
736
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -744,8 +745,7 @@ export class MidyGM1 {
|
|
|
744
745
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
745
746
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
746
747
|
}
|
|
747
|
-
setFilterEnvelope(note) {
|
|
748
|
-
const now = this.audioContext.currentTime;
|
|
748
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
749
749
|
const { voiceParams, startTime } = note;
|
|
750
750
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
751
751
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -759,14 +759,14 @@ export class MidyGM1 {
|
|
|
759
759
|
const modHold = modAttack + voiceParams.modHold;
|
|
760
760
|
const modDecay = modHold + voiceParams.modDecay;
|
|
761
761
|
note.filterNode.frequency
|
|
762
|
-
.cancelScheduledValues(
|
|
762
|
+
.cancelScheduledValues(scheduleTime)
|
|
763
763
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
764
764
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
765
765
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
766
766
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
767
767
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
768
768
|
}
|
|
769
|
-
startModulation(channel, note,
|
|
769
|
+
startModulation(channel, note, scheduleTime) {
|
|
770
770
|
const { voiceParams } = note;
|
|
771
771
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
772
772
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -775,10 +775,10 @@ export class MidyGM1 {
|
|
|
775
775
|
gain: voiceParams.modLfoToFilterFc,
|
|
776
776
|
});
|
|
777
777
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
778
|
-
this.setModLfoToPitch(channel, note);
|
|
778
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
779
779
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
780
|
-
this.setModLfoToVolume(note);
|
|
781
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
780
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
781
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
782
782
|
note.modulationLFO.connect(note.filterDepth);
|
|
783
783
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
784
784
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -805,6 +805,7 @@ export class MidyGM1 {
|
|
|
805
805
|
}
|
|
806
806
|
}
|
|
807
807
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
808
|
+
const now = this.audioContext.currentTime;
|
|
808
809
|
const state = channel.state;
|
|
809
810
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
810
811
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -816,11 +817,11 @@ export class MidyGM1 {
|
|
|
816
817
|
type: "lowpass",
|
|
817
818
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
818
819
|
});
|
|
819
|
-
this.setVolumeEnvelope(note);
|
|
820
|
-
this.setFilterEnvelope(note);
|
|
821
|
-
this.setPitchEnvelope(note);
|
|
820
|
+
this.setVolumeEnvelope(note, now);
|
|
821
|
+
this.setFilterEnvelope(note, now);
|
|
822
|
+
this.setPitchEnvelope(note, now);
|
|
822
823
|
if (0 < state.modulationDepth) {
|
|
823
|
-
this.startModulation(channel, note,
|
|
824
|
+
this.startModulation(channel, note, now);
|
|
824
825
|
}
|
|
825
826
|
note.bufferSource.connect(note.filterNode);
|
|
826
827
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -847,7 +848,7 @@ export class MidyGM1 {
|
|
|
847
848
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
848
849
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
849
850
|
if (!prevNote.ending) {
|
|
850
|
-
this.
|
|
851
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
851
852
|
startTime, undefined, // portamentoNoteNumber
|
|
852
853
|
true);
|
|
853
854
|
}
|
|
@@ -862,9 +863,9 @@ export class MidyGM1 {
|
|
|
862
863
|
scheduledNotes.set(noteNumber, [note]);
|
|
863
864
|
}
|
|
864
865
|
}
|
|
865
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
866
|
-
|
|
867
|
-
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);
|
|
868
869
|
}
|
|
869
870
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
870
871
|
const note = scheduledNotes[index];
|
|
@@ -886,16 +887,12 @@ export class MidyGM1 {
|
|
|
886
887
|
note.modulationDepth.disconnect();
|
|
887
888
|
note.modulationLFO.stop();
|
|
888
889
|
}
|
|
889
|
-
if (note.vibratoDepth) {
|
|
890
|
-
note.vibratoDepth.disconnect();
|
|
891
|
-
note.vibratoLFO.stop();
|
|
892
|
-
}
|
|
893
890
|
resolve();
|
|
894
891
|
};
|
|
895
892
|
note.bufferSource.stop(stopTime);
|
|
896
893
|
});
|
|
897
894
|
}
|
|
898
|
-
|
|
895
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
899
896
|
const channel = this.channels[channelNumber];
|
|
900
897
|
if (!force && 0.5 < channel.state.sustainPedal)
|
|
901
898
|
return;
|
|
@@ -917,161 +914,119 @@ export class MidyGM1 {
|
|
|
917
914
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
918
915
|
}
|
|
919
916
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
return this.
|
|
917
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
918
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
919
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
923
920
|
}
|
|
924
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
921
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
925
922
|
const velocity = halfVelocity * 2;
|
|
926
923
|
const channel = this.channels[channelNumber];
|
|
927
924
|
const promises = [];
|
|
928
|
-
channel
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (!note)
|
|
933
|
-
continue;
|
|
934
|
-
const { noteNumber } = note;
|
|
935
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
936
|
-
promises.push(promise);
|
|
937
|
-
}
|
|
925
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
926
|
+
const { noteNumber } = note;
|
|
927
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
928
|
+
promises.push(promise);
|
|
938
929
|
});
|
|
939
930
|
return promises;
|
|
940
931
|
}
|
|
941
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
932
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
942
933
|
const channelNumber = statusByte & 0x0F;
|
|
943
934
|
const messageType = statusByte & 0xF0;
|
|
944
935
|
switch (messageType) {
|
|
945
936
|
case 0x80:
|
|
946
|
-
return this.
|
|
937
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
947
938
|
case 0x90:
|
|
948
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
939
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
949
940
|
case 0xB0:
|
|
950
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
941
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
951
942
|
case 0xC0:
|
|
952
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
943
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
953
944
|
case 0xE0:
|
|
954
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
945
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
955
946
|
default:
|
|
956
947
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
957
948
|
}
|
|
958
949
|
}
|
|
959
|
-
handleProgramChange(channelNumber, program) {
|
|
950
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
960
951
|
const channel = this.channels[channelNumber];
|
|
961
952
|
channel.program = program;
|
|
962
953
|
}
|
|
963
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
954
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
964
955
|
const pitchBend = msb * 128 + lsb;
|
|
965
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
956
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
966
957
|
}
|
|
967
|
-
setPitchBend(channelNumber, value) {
|
|
958
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
959
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
968
960
|
const channel = this.channels[channelNumber];
|
|
969
961
|
const state = channel.state;
|
|
970
962
|
const prev = state.pitchWheel * 2 - 1;
|
|
971
963
|
const next = (value - 8192) / 8192;
|
|
972
964
|
state.pitchWheel = value / 16383;
|
|
973
965
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
974
|
-
this.updateChannelDetune(channel);
|
|
975
|
-
this.applyVoiceParams(channel, 14);
|
|
966
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
967
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
976
968
|
}
|
|
977
|
-
setModLfoToPitch(channel, note) {
|
|
978
|
-
const now = this.audioContext.currentTime;
|
|
969
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
979
970
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
980
971
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
981
972
|
channel.state.modulationDepth;
|
|
982
973
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
983
974
|
note.modulationDepth.gain
|
|
984
|
-
.cancelScheduledValues(
|
|
985
|
-
.setValueAtTime(modulationDepth,
|
|
975
|
+
.cancelScheduledValues(scheduleTime)
|
|
976
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
986
977
|
}
|
|
987
|
-
|
|
988
|
-
const now = this.audioContext.currentTime;
|
|
989
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
990
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
991
|
-
2;
|
|
992
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
993
|
-
note.vibratoDepth.gain
|
|
994
|
-
.cancelScheduledValues(now)
|
|
995
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
996
|
-
}
|
|
997
|
-
setModLfoToFilterFc(note) {
|
|
998
|
-
const now = this.audioContext.currentTime;
|
|
978
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
999
979
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1000
980
|
note.filterDepth.gain
|
|
1001
|
-
.cancelScheduledValues(
|
|
1002
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
981
|
+
.cancelScheduledValues(scheduleTime)
|
|
982
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1003
983
|
}
|
|
1004
|
-
setModLfoToVolume(note) {
|
|
1005
|
-
const now = this.audioContext.currentTime;
|
|
984
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
1006
985
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1007
986
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1008
987
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
1009
988
|
note.volumeDepth.gain
|
|
1010
|
-
.cancelScheduledValues(
|
|
1011
|
-
.setValueAtTime(volumeDepth,
|
|
989
|
+
.cancelScheduledValues(scheduleTime)
|
|
990
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1012
991
|
}
|
|
1013
|
-
setDelayModLFO(note) {
|
|
1014
|
-
const now = this.audioContext.currentTime;
|
|
992
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1015
993
|
const startTime = note.startTime;
|
|
1016
|
-
if (startTime <
|
|
994
|
+
if (startTime < scheduleTime)
|
|
1017
995
|
return;
|
|
1018
|
-
note.modulationLFO.stop(
|
|
996
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1019
997
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1020
998
|
note.modulationLFO.connect(note.filterDepth);
|
|
1021
999
|
}
|
|
1022
|
-
setFreqModLFO(note) {
|
|
1023
|
-
const now = this.audioContext.currentTime;
|
|
1000
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1024
1001
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1025
1002
|
note.modulationLFO.frequency
|
|
1026
|
-
.cancelScheduledValues(
|
|
1027
|
-
.setValueAtTime(freqModLFO,
|
|
1003
|
+
.cancelScheduledValues(scheduleTime)
|
|
1004
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1028
1005
|
}
|
|
1029
1006
|
createVoiceParamsHandlers() {
|
|
1030
1007
|
return {
|
|
1031
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1008
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1032
1009
|
if (0 < channel.state.modulationDepth) {
|
|
1033
|
-
this.setModLfoToPitch(channel, note);
|
|
1034
|
-
}
|
|
1035
|
-
},
|
|
1036
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1037
|
-
if (0 < channel.state.vibratoDepth) {
|
|
1038
|
-
this.setVibLfoToPitch(channel, note);
|
|
1010
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1039
1011
|
}
|
|
1040
1012
|
},
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1046
|
-
if (0 < channel.state.modulationDepth)
|
|
1047
|
-
this.setModLfoToVolume(note);
|
|
1048
|
-
},
|
|
1049
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1050
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1051
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1052
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1053
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1054
|
-
if (0 < channel.state.vibratoDepth) {
|
|
1055
|
-
const now = this.audioContext.currentTime;
|
|
1056
|
-
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1057
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1058
|
-
if (now < prevStartTime)
|
|
1059
|
-
return;
|
|
1060
|
-
const value = note.voiceParams.delayVibLFO;
|
|
1061
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
1062
|
-
note.vibratoLFO.stop(now);
|
|
1063
|
-
note.vibratoLFO.start(startTime);
|
|
1013
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1014
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1015
|
+
if (0 < channel.state.modulationDepth) {
|
|
1016
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1064
1017
|
}
|
|
1065
1018
|
},
|
|
1066
|
-
|
|
1067
|
-
if (0 < channel.state.
|
|
1068
|
-
|
|
1069
|
-
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1070
|
-
note.vibratoLFO.frequency
|
|
1071
|
-
.cancelScheduledValues(now)
|
|
1072
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
|
|
1019
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1020
|
+
if (0 < channel.state.modulationDepth) {
|
|
1021
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1073
1022
|
}
|
|
1074
1023
|
},
|
|
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) => { },
|
|
1075
1030
|
};
|
|
1076
1031
|
}
|
|
1077
1032
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1081,7 +1036,7 @@ export class MidyGM1 {
|
|
|
1081
1036
|
state[3] = noteNumber / 127;
|
|
1082
1037
|
return state;
|
|
1083
1038
|
}
|
|
1084
|
-
applyVoiceParams(channel, controllerType) {
|
|
1039
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1085
1040
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1086
1041
|
for (let i = 0; i < noteList.length; i++) {
|
|
1087
1042
|
const note = noteList[i];
|
|
@@ -1097,7 +1052,7 @@ export class MidyGM1 {
|
|
|
1097
1052
|
continue;
|
|
1098
1053
|
note.voiceParams[key] = value;
|
|
1099
1054
|
if (key in this.voiceParamsHandlers) {
|
|
1100
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1055
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1101
1056
|
}
|
|
1102
1057
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1103
1058
|
if (appliedFilterEnvelope)
|
|
@@ -1109,8 +1064,8 @@ export class MidyGM1 {
|
|
|
1109
1064
|
if (key in voiceParams)
|
|
1110
1065
|
noteVoiceParams[key] = voiceParams[key];
|
|
1111
1066
|
}
|
|
1112
|
-
this.setFilterEnvelope(note);
|
|
1113
|
-
this.setPitchEnvelope(note);
|
|
1067
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1068
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1114
1069
|
}
|
|
1115
1070
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1116
1071
|
if (appliedVolumeEnvelope)
|
|
@@ -1122,7 +1077,7 @@ export class MidyGM1 {
|
|
|
1122
1077
|
if (key in voiceParams)
|
|
1123
1078
|
noteVoiceParams[key] = voiceParams[key];
|
|
1124
1079
|
}
|
|
1125
|
-
this.setVolumeEnvelope(channel, note);
|
|
1080
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1126
1081
|
}
|
|
1127
1082
|
}
|
|
1128
1083
|
}
|
|
@@ -1144,44 +1099,39 @@ export class MidyGM1 {
|
|
|
1144
1099
|
123: this.allNotesOff,
|
|
1145
1100
|
};
|
|
1146
1101
|
}
|
|
1147
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1102
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1148
1103
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1149
1104
|
if (handler) {
|
|
1150
|
-
handler.call(this, channelNumber, value);
|
|
1105
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1151
1106
|
const channel = this.channels[channelNumber];
|
|
1152
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1107
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1153
1108
|
}
|
|
1154
1109
|
else {
|
|
1155
1110
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1156
1111
|
}
|
|
1157
1112
|
}
|
|
1158
|
-
updateModulation(channel) {
|
|
1159
|
-
|
|
1113
|
+
updateModulation(channel, scheduleTime) {
|
|
1114
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1160
1115
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
else {
|
|
1170
|
-
this.setPitchEnvelope(note);
|
|
1171
|
-
this.startModulation(channel, note, now);
|
|
1172
|
-
}
|
|
1116
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1117
|
+
if (note.modulationDepth) {
|
|
1118
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1122
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1173
1123
|
}
|
|
1174
1124
|
});
|
|
1175
1125
|
}
|
|
1176
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1126
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1177
1127
|
const channel = this.channels[channelNumber];
|
|
1178
1128
|
channel.state.modulationDepth = modulation / 127;
|
|
1179
|
-
this.updateModulation(channel);
|
|
1129
|
+
this.updateModulation(channel, scheduleTime);
|
|
1180
1130
|
}
|
|
1181
|
-
setVolume(channelNumber, volume) {
|
|
1131
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1182
1132
|
const channel = this.channels[channelNumber];
|
|
1183
1133
|
channel.state.volume = volume / 127;
|
|
1184
|
-
this.updateChannelVolume(channel);
|
|
1134
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1185
1135
|
}
|
|
1186
1136
|
panToGain(pan) {
|
|
1187
1137
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1190,36 +1140,36 @@ export class MidyGM1 {
|
|
|
1190
1140
|
gainRight: Math.sin(theta),
|
|
1191
1141
|
};
|
|
1192
1142
|
}
|
|
1193
|
-
setPan(channelNumber, pan) {
|
|
1143
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1194
1144
|
const channel = this.channels[channelNumber];
|
|
1195
1145
|
channel.state.pan = pan / 127;
|
|
1196
|
-
this.updateChannelVolume(channel);
|
|
1146
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1197
1147
|
}
|
|
1198
|
-
setExpression(channelNumber, expression) {
|
|
1148
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1199
1149
|
const channel = this.channels[channelNumber];
|
|
1200
1150
|
channel.state.expression = expression / 127;
|
|
1201
|
-
this.updateChannelVolume(channel);
|
|
1151
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1202
1152
|
}
|
|
1203
|
-
dataEntryLSB(channelNumber, value) {
|
|
1153
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1204
1154
|
this.channels[channelNumber].dataLSB = value;
|
|
1205
|
-
this.handleRPN(channelNumber,
|
|
1155
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1206
1156
|
}
|
|
1207
|
-
updateChannelVolume(channel) {
|
|
1208
|
-
const now = this.audioContext.currentTime;
|
|
1157
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1209
1158
|
const state = channel.state;
|
|
1210
1159
|
const volume = state.volume * state.expression;
|
|
1211
1160
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1212
1161
|
channel.gainL.gain
|
|
1213
|
-
.cancelScheduledValues(
|
|
1214
|
-
.setValueAtTime(volume * gainLeft,
|
|
1162
|
+
.cancelScheduledValues(scheduleTime)
|
|
1163
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1215
1164
|
channel.gainR.gain
|
|
1216
|
-
.cancelScheduledValues(
|
|
1217
|
-
.setValueAtTime(volume * gainRight,
|
|
1165
|
+
.cancelScheduledValues(scheduleTime)
|
|
1166
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1218
1167
|
}
|
|
1219
|
-
setSustainPedal(channelNumber, value) {
|
|
1168
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1169
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1220
1170
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1221
1171
|
if (value < 64) {
|
|
1222
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1172
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1223
1173
|
}
|
|
1224
1174
|
}
|
|
1225
1175
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1248,12 +1198,12 @@ export class MidyGM1 {
|
|
|
1248
1198
|
channel.dataMSB = minMSB;
|
|
1249
1199
|
}
|
|
1250
1200
|
}
|
|
1251
|
-
handleRPN(channelNumber) {
|
|
1201
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1252
1202
|
const channel = this.channels[channelNumber];
|
|
1253
1203
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1254
1204
|
switch (rpn) {
|
|
1255
1205
|
case 0:
|
|
1256
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1206
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1257
1207
|
break;
|
|
1258
1208
|
case 1:
|
|
1259
1209
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1271,25 +1221,26 @@ export class MidyGM1 {
|
|
|
1271
1221
|
setRPNLSB(channelNumber, value) {
|
|
1272
1222
|
this.channels[channelNumber].rpnLSB = value;
|
|
1273
1223
|
}
|
|
1274
|
-
dataEntryMSB(channelNumber, value) {
|
|
1224
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1275
1225
|
this.channels[channelNumber].dataMSB = value;
|
|
1276
|
-
this.handleRPN(channelNumber);
|
|
1226
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1277
1227
|
}
|
|
1278
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1228
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1279
1229
|
const channel = this.channels[channelNumber];
|
|
1280
1230
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1281
1231
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1282
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1232
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1283
1233
|
}
|
|
1284
|
-
setPitchBendRange(channelNumber, value) {
|
|
1234
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1235
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1285
1236
|
const channel = this.channels[channelNumber];
|
|
1286
1237
|
const state = channel.state;
|
|
1287
1238
|
const prev = state.pitchWheelSensitivity;
|
|
1288
1239
|
const next = value / 128;
|
|
1289
1240
|
state.pitchWheelSensitivity = next;
|
|
1290
1241
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1291
|
-
this.updateChannelDetune(channel);
|
|
1292
|
-
this.applyVoiceParams(channel, 16);
|
|
1242
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1243
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1293
1244
|
}
|
|
1294
1245
|
handleFineTuningRPN(channelNumber) {
|
|
1295
1246
|
const channel = this.channels[channelNumber];
|
|
@@ -1319,8 +1270,9 @@ export class MidyGM1 {
|
|
|
1319
1270
|
channel.detune += next - prev;
|
|
1320
1271
|
this.updateChannelDetune(channel);
|
|
1321
1272
|
}
|
|
1322
|
-
allSoundOff(channelNumber) {
|
|
1323
|
-
|
|
1273
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1274
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1275
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1324
1276
|
}
|
|
1325
1277
|
resetAllControllers(channelNumber) {
|
|
1326
1278
|
const stateTypes = [
|
|
@@ -1344,10 +1296,11 @@ export class MidyGM1 {
|
|
|
1344
1296
|
channel[type] = this.constructor.channelSettings[type];
|
|
1345
1297
|
}
|
|
1346
1298
|
}
|
|
1347
|
-
allNotesOff(channelNumber) {
|
|
1348
|
-
|
|
1299
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1300
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1301
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1349
1302
|
}
|
|
1350
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1303
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1351
1304
|
switch (data[2]) {
|
|
1352
1305
|
case 9:
|
|
1353
1306
|
switch (data[3]) {
|
|
@@ -1371,12 +1324,12 @@ export class MidyGM1 {
|
|
|
1371
1324
|
}
|
|
1372
1325
|
this.channels[9].bank = 128;
|
|
1373
1326
|
}
|
|
1374
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1327
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1375
1328
|
switch (data[2]) {
|
|
1376
1329
|
case 4:
|
|
1377
1330
|
switch (data[3]) {
|
|
1378
1331
|
case 1:
|
|
1379
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1332
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1380
1333
|
default:
|
|
1381
1334
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1382
1335
|
}
|
|
@@ -1385,42 +1338,40 @@ export class MidyGM1 {
|
|
|
1385
1338
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1386
1339
|
}
|
|
1387
1340
|
}
|
|
1388
|
-
handleMasterVolumeSysEx(data) {
|
|
1341
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1389
1342
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1390
|
-
this.setMasterVolume(volume);
|
|
1343
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1391
1344
|
}
|
|
1392
|
-
setMasterVolume(volume) {
|
|
1345
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1346
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1393
1347
|
if (volume < 0 && 1 < volume) {
|
|
1394
1348
|
console.error("Master Volume is out of range");
|
|
1395
1349
|
}
|
|
1396
1350
|
else {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1351
|
+
this.masterVolume.gain
|
|
1352
|
+
.cancelScheduledValues(scheduleTime)
|
|
1353
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1400
1354
|
}
|
|
1401
1355
|
}
|
|
1402
|
-
|
|
1403
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1404
|
-
}
|
|
1405
|
-
handleSysEx(data) {
|
|
1356
|
+
handleSysEx(data, scheduleTime) {
|
|
1406
1357
|
switch (data[0]) {
|
|
1407
1358
|
case 126:
|
|
1408
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1359
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1409
1360
|
case 127:
|
|
1410
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1361
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1411
1362
|
default:
|
|
1412
|
-
|
|
1363
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1413
1364
|
}
|
|
1414
1365
|
}
|
|
1415
|
-
scheduleTask(callback,
|
|
1366
|
+
scheduleTask(callback, scheduleTime) {
|
|
1416
1367
|
return new Promise((resolve) => {
|
|
1417
1368
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1418
1369
|
bufferSource.onended = () => {
|
|
1419
1370
|
callback();
|
|
1420
1371
|
resolve();
|
|
1421
1372
|
};
|
|
1422
|
-
bufferSource.start(
|
|
1423
|
-
bufferSource.stop(
|
|
1373
|
+
bufferSource.start(scheduleTime);
|
|
1374
|
+
bufferSource.stop(scheduleTime);
|
|
1424
1375
|
});
|
|
1425
1376
|
}
|
|
1426
1377
|
}
|