@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/script/midy-GMLite.js
CHANGED
|
@@ -69,6 +69,12 @@ class Note {
|
|
|
69
69
|
writable: true,
|
|
70
70
|
value: void 0
|
|
71
71
|
});
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
72
78
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
73
79
|
enumerable: true,
|
|
74
80
|
configurable: true,
|
|
@@ -400,31 +406,32 @@ class MidyGMLite {
|
|
|
400
406
|
const event = this.timeline[queueIndex];
|
|
401
407
|
if (event.startTime > t + this.lookAhead)
|
|
402
408
|
break;
|
|
409
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
403
410
|
switch (event.type) {
|
|
404
411
|
case "noteOn":
|
|
405
412
|
if (event.velocity !== 0) {
|
|
406
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
413
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
407
414
|
break;
|
|
408
415
|
}
|
|
409
416
|
/* falls through */
|
|
410
417
|
case "noteOff": {
|
|
411
|
-
const notePromise = this.
|
|
418
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
|
|
412
419
|
if (notePromise) {
|
|
413
420
|
this.notePromises.push(notePromise);
|
|
414
421
|
}
|
|
415
422
|
break;
|
|
416
423
|
}
|
|
417
424
|
case "controller":
|
|
418
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
425
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
419
426
|
break;
|
|
420
427
|
case "programChange":
|
|
421
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
428
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
422
429
|
break;
|
|
423
430
|
case "pitchBend":
|
|
424
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
431
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
425
432
|
break;
|
|
426
433
|
case "sysEx":
|
|
427
|
-
this.handleSysEx(event.data);
|
|
434
|
+
this.handleSysEx(event.data, startTime);
|
|
428
435
|
}
|
|
429
436
|
queueIndex++;
|
|
430
437
|
}
|
|
@@ -455,10 +462,11 @@ class MidyGMLite {
|
|
|
455
462
|
resolve();
|
|
456
463
|
return;
|
|
457
464
|
}
|
|
458
|
-
const
|
|
465
|
+
const now = this.audioContext.currentTime;
|
|
466
|
+
const t = now + offset;
|
|
459
467
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
460
468
|
if (this.isPausing) {
|
|
461
|
-
await this.stopNotes(0, true);
|
|
469
|
+
await this.stopNotes(0, true, now);
|
|
462
470
|
this.notePromises = [];
|
|
463
471
|
resolve();
|
|
464
472
|
this.isPausing = false;
|
|
@@ -466,7 +474,7 @@ class MidyGMLite {
|
|
|
466
474
|
return;
|
|
467
475
|
}
|
|
468
476
|
else if (this.isStopping) {
|
|
469
|
-
await this.stopNotes(0, true);
|
|
477
|
+
await this.stopNotes(0, true, now);
|
|
470
478
|
this.notePromises = [];
|
|
471
479
|
this.exclusiveClassMap.clear();
|
|
472
480
|
this.audioBufferCache.clear();
|
|
@@ -476,7 +484,7 @@ class MidyGMLite {
|
|
|
476
484
|
return;
|
|
477
485
|
}
|
|
478
486
|
else if (this.isSeeking) {
|
|
479
|
-
this.stopNotes(0, true);
|
|
487
|
+
this.stopNotes(0, true, now);
|
|
480
488
|
this.exclusiveClassMap.clear();
|
|
481
489
|
this.startTime = this.audioContext.currentTime;
|
|
482
490
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -485,7 +493,6 @@ class MidyGMLite {
|
|
|
485
493
|
await schedulePlayback();
|
|
486
494
|
}
|
|
487
495
|
else {
|
|
488
|
-
const now = this.audioContext.currentTime;
|
|
489
496
|
const waitTime = now + this.noteCheckInterval;
|
|
490
497
|
await this.scheduleTask(() => { }, waitTime);
|
|
491
498
|
await schedulePlayback();
|
|
@@ -569,24 +576,26 @@ class MidyGMLite {
|
|
|
569
576
|
}
|
|
570
577
|
return { instruments, timeline };
|
|
571
578
|
}
|
|
572
|
-
|
|
573
|
-
const now = this.audioContext.currentTime;
|
|
579
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
574
580
|
const channel = this.channels[channelNumber];
|
|
581
|
+
const promises = [];
|
|
575
582
|
channel.scheduledNotes.forEach((noteList) => {
|
|
576
583
|
for (let i = 0; i < noteList.length; i++) {
|
|
577
584
|
const note = noteList[i];
|
|
578
585
|
if (!note)
|
|
579
586
|
continue;
|
|
580
|
-
const promise = this.
|
|
587
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
581
588
|
this.notePromises.push(promise);
|
|
589
|
+
promises.push(promise);
|
|
582
590
|
}
|
|
583
591
|
});
|
|
584
592
|
channel.scheduledNotes.clear();
|
|
585
|
-
|
|
593
|
+
return Promise.all(promises);
|
|
586
594
|
}
|
|
587
|
-
stopNotes(velocity, force) {
|
|
595
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
596
|
+
const promises = [];
|
|
588
597
|
for (let i = 0; i < this.channels.length; i++) {
|
|
589
|
-
this.stopChannelNotes(i, velocity, force);
|
|
598
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
590
599
|
}
|
|
591
600
|
return Promise.all(this.notePromises);
|
|
592
601
|
}
|
|
@@ -634,22 +643,34 @@ class MidyGMLite {
|
|
|
634
643
|
const now = this.audioContext.currentTime;
|
|
635
644
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
636
645
|
}
|
|
637
|
-
|
|
646
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
647
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
648
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
649
|
+
const note = noteList[i];
|
|
650
|
+
if (!note)
|
|
651
|
+
continue;
|
|
652
|
+
if (scheduleTime < note.startTime)
|
|
653
|
+
continue;
|
|
654
|
+
callback(note);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
getActiveNotes(channel, scheduleTime) {
|
|
638
659
|
const activeNotes = new SparseMap(128);
|
|
639
660
|
channel.scheduledNotes.forEach((noteList) => {
|
|
640
|
-
const activeNote = this.getActiveNote(noteList,
|
|
661
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
641
662
|
if (activeNote) {
|
|
642
663
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
643
664
|
}
|
|
644
665
|
});
|
|
645
666
|
return activeNotes;
|
|
646
667
|
}
|
|
647
|
-
getActiveNote(noteList,
|
|
668
|
+
getActiveNote(noteList, scheduleTime) {
|
|
648
669
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
649
670
|
const note = noteList[i];
|
|
650
671
|
if (!note)
|
|
651
672
|
return;
|
|
652
|
-
if (
|
|
673
|
+
if (scheduleTime < note.startTime)
|
|
653
674
|
continue;
|
|
654
675
|
return (note.ending) ? null : note;
|
|
655
676
|
}
|
|
@@ -672,24 +693,17 @@ class MidyGMLite {
|
|
|
672
693
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
673
694
|
return pitchWheel * pitchWheelSensitivity;
|
|
674
695
|
}
|
|
675
|
-
updateChannelDetune(channel) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const note = noteList[i];
|
|
679
|
-
if (!note)
|
|
680
|
-
continue;
|
|
681
|
-
this.updateDetune(channel, note);
|
|
682
|
-
}
|
|
696
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
697
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
698
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
683
699
|
});
|
|
684
700
|
}
|
|
685
|
-
updateDetune(channel, note) {
|
|
686
|
-
const now = this.audioContext.currentTime;
|
|
701
|
+
updateDetune(channel, note, scheduleTime) {
|
|
687
702
|
note.bufferSource.detune
|
|
688
|
-
.cancelScheduledValues(
|
|
689
|
-
.setValueAtTime(channel.detune,
|
|
703
|
+
.cancelScheduledValues(scheduleTime)
|
|
704
|
+
.setValueAtTime(channel.detune, scheduleTime);
|
|
690
705
|
}
|
|
691
|
-
setVolumeEnvelope(note) {
|
|
692
|
-
const now = this.audioContext.currentTime;
|
|
706
|
+
setVolumeEnvelope(note, scheduleTime) {
|
|
693
707
|
const { voiceParams, startTime } = note;
|
|
694
708
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
695
709
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
@@ -698,27 +712,26 @@ class MidyGMLite {
|
|
|
698
712
|
const volHold = volAttack + voiceParams.volHold;
|
|
699
713
|
const volDecay = volHold + voiceParams.volDecay;
|
|
700
714
|
note.volumeEnvelopeNode.gain
|
|
701
|
-
.cancelScheduledValues(
|
|
715
|
+
.cancelScheduledValues(scheduleTime)
|
|
702
716
|
.setValueAtTime(0, startTime)
|
|
703
717
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
704
718
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
705
719
|
.setValueAtTime(attackVolume, volHold)
|
|
706
720
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
707
721
|
}
|
|
708
|
-
setPitchEnvelope(note) {
|
|
709
|
-
const now = this.audioContext.currentTime;
|
|
722
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
710
723
|
const { voiceParams } = note;
|
|
711
724
|
const baseRate = voiceParams.playbackRate;
|
|
712
725
|
note.bufferSource.playbackRate
|
|
713
|
-
.cancelScheduledValues(
|
|
714
|
-
.setValueAtTime(baseRate,
|
|
726
|
+
.cancelScheduledValues(scheduleTime)
|
|
727
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
715
728
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
716
729
|
if (modEnvToPitch === 0)
|
|
717
730
|
return;
|
|
718
731
|
const basePitch = this.rateToCent(baseRate);
|
|
719
732
|
const peekPitch = basePitch + modEnvToPitch;
|
|
720
733
|
const peekRate = this.centToRate(peekPitch);
|
|
721
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
734
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
722
735
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
723
736
|
const modHold = modAttack + voiceParams.modHold;
|
|
724
737
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -733,8 +746,7 @@ class MidyGMLite {
|
|
|
733
746
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
734
747
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
735
748
|
}
|
|
736
|
-
setFilterEnvelope(note) {
|
|
737
|
-
const now = this.audioContext.currentTime;
|
|
749
|
+
setFilterEnvelope(note, scheduleTime) {
|
|
738
750
|
const { voiceParams, startTime } = note;
|
|
739
751
|
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
740
752
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
@@ -748,14 +760,14 @@ class MidyGMLite {
|
|
|
748
760
|
const modHold = modAttack + voiceParams.modHold;
|
|
749
761
|
const modDecay = modHold + voiceParams.modDecay;
|
|
750
762
|
note.filterNode.frequency
|
|
751
|
-
.cancelScheduledValues(
|
|
763
|
+
.cancelScheduledValues(scheduleTime)
|
|
752
764
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
753
765
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
754
766
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
755
767
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
756
768
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
757
769
|
}
|
|
758
|
-
startModulation(channel, note,
|
|
770
|
+
startModulation(channel, note, scheduleTime) {
|
|
759
771
|
const { voiceParams } = note;
|
|
760
772
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
761
773
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -764,10 +776,10 @@ class MidyGMLite {
|
|
|
764
776
|
gain: voiceParams.modLfoToFilterFc,
|
|
765
777
|
});
|
|
766
778
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
767
|
-
this.setModLfoToPitch(channel, note);
|
|
779
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
768
780
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
769
|
-
this.setModLfoToVolume(note);
|
|
770
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
781
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
782
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
771
783
|
note.modulationLFO.connect(note.filterDepth);
|
|
772
784
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
773
785
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -794,6 +806,7 @@ class MidyGMLite {
|
|
|
794
806
|
}
|
|
795
807
|
}
|
|
796
808
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
809
|
+
const now = this.audioContext.currentTime;
|
|
797
810
|
const state = channel.state;
|
|
798
811
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
799
812
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -805,11 +818,11 @@ class MidyGMLite {
|
|
|
805
818
|
type: "lowpass",
|
|
806
819
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
807
820
|
});
|
|
808
|
-
this.setVolumeEnvelope(note);
|
|
809
|
-
this.setFilterEnvelope(note);
|
|
810
|
-
this.setPitchEnvelope(note);
|
|
821
|
+
this.setVolumeEnvelope(note, now);
|
|
822
|
+
this.setFilterEnvelope(note, now);
|
|
823
|
+
this.setPitchEnvelope(note, now);
|
|
811
824
|
if (0 < state.modulationDepth) {
|
|
812
|
-
this.startModulation(channel, note,
|
|
825
|
+
this.startModulation(channel, note, now);
|
|
813
826
|
}
|
|
814
827
|
note.bufferSource.connect(note.filterNode);
|
|
815
828
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
@@ -836,7 +849,7 @@ class MidyGMLite {
|
|
|
836
849
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
837
850
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
838
851
|
if (!prevNote.ending) {
|
|
839
|
-
this.
|
|
852
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
840
853
|
startTime, undefined, // portamentoNoteNumber
|
|
841
854
|
true);
|
|
842
855
|
}
|
|
@@ -851,9 +864,9 @@ class MidyGMLite {
|
|
|
851
864
|
scheduledNotes.set(noteNumber, [note]);
|
|
852
865
|
}
|
|
853
866
|
}
|
|
854
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
855
|
-
|
|
856
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
867
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
868
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
869
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
857
870
|
}
|
|
858
871
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
859
872
|
const note = scheduledNotes[index];
|
|
@@ -880,7 +893,7 @@ class MidyGMLite {
|
|
|
880
893
|
note.bufferSource.stop(stopTime);
|
|
881
894
|
});
|
|
882
895
|
}
|
|
883
|
-
|
|
896
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
884
897
|
const channel = this.channels[channelNumber];
|
|
885
898
|
if (!force && 0.5 < channel.state.sustainPedal)
|
|
886
899
|
return;
|
|
@@ -902,127 +915,119 @@ class MidyGMLite {
|
|
|
902
915
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
903
916
|
}
|
|
904
917
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
return this.
|
|
918
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
919
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
920
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
908
921
|
}
|
|
909
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
922
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
910
923
|
const velocity = halfVelocity * 2;
|
|
911
924
|
const channel = this.channels[channelNumber];
|
|
912
925
|
const promises = [];
|
|
913
|
-
channel
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (!note)
|
|
918
|
-
continue;
|
|
919
|
-
const { noteNumber } = note;
|
|
920
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
921
|
-
promises.push(promise);
|
|
922
|
-
}
|
|
926
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
927
|
+
const { noteNumber } = note;
|
|
928
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
929
|
+
promises.push(promise);
|
|
923
930
|
});
|
|
924
931
|
return promises;
|
|
925
932
|
}
|
|
926
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
933
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
927
934
|
const channelNumber = statusByte & 0x0F;
|
|
928
935
|
const messageType = statusByte & 0xF0;
|
|
929
936
|
switch (messageType) {
|
|
930
937
|
case 0x80:
|
|
931
|
-
return this.
|
|
938
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
932
939
|
case 0x90:
|
|
933
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
940
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
934
941
|
case 0xB0:
|
|
935
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
942
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
936
943
|
case 0xC0:
|
|
937
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
944
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
938
945
|
case 0xE0:
|
|
939
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
946
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
940
947
|
default:
|
|
941
948
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
942
949
|
}
|
|
943
950
|
}
|
|
944
|
-
handleProgramChange(channelNumber, program) {
|
|
951
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
945
952
|
const channel = this.channels[channelNumber];
|
|
946
953
|
channel.program = program;
|
|
947
954
|
}
|
|
948
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
955
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
949
956
|
const pitchBend = msb * 128 + lsb;
|
|
950
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
957
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
951
958
|
}
|
|
952
|
-
setPitchBend(channelNumber, value) {
|
|
959
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
960
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
953
961
|
const channel = this.channels[channelNumber];
|
|
954
962
|
const state = channel.state;
|
|
955
963
|
const prev = state.pitchWheel * 2 - 1;
|
|
956
964
|
const next = (value - 8192) / 8192;
|
|
957
965
|
state.pitchWheel = value / 16383;
|
|
958
966
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
959
|
-
this.updateChannelDetune(channel);
|
|
960
|
-
this.applyVoiceParams(channel, 14);
|
|
967
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
968
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
961
969
|
}
|
|
962
|
-
setModLfoToPitch(channel, note) {
|
|
963
|
-
const now = this.audioContext.currentTime;
|
|
970
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
964
971
|
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
965
972
|
const baseDepth = Math.abs(modLfoToPitch) +
|
|
966
973
|
channel.state.modulationDepth;
|
|
967
974
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
968
975
|
note.modulationDepth.gain
|
|
969
|
-
.cancelScheduledValues(
|
|
970
|
-
.setValueAtTime(modulationDepth,
|
|
976
|
+
.cancelScheduledValues(scheduleTime)
|
|
977
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
971
978
|
}
|
|
972
|
-
setModLfoToFilterFc(note) {
|
|
973
|
-
const now = this.audioContext.currentTime;
|
|
979
|
+
setModLfoToFilterFc(note, scheduleTime) {
|
|
974
980
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
975
981
|
note.filterDepth.gain
|
|
976
|
-
.cancelScheduledValues(
|
|
977
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
982
|
+
.cancelScheduledValues(scheduleTime)
|
|
983
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
978
984
|
}
|
|
979
|
-
setModLfoToVolume(note) {
|
|
980
|
-
const now = this.audioContext.currentTime;
|
|
985
|
+
setModLfoToVolume(note, scheduleTime) {
|
|
981
986
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
982
987
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
983
988
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
|
|
984
989
|
note.volumeDepth.gain
|
|
985
|
-
.cancelScheduledValues(
|
|
986
|
-
.setValueAtTime(volumeDepth,
|
|
990
|
+
.cancelScheduledValues(scheduleTime)
|
|
991
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
987
992
|
}
|
|
988
|
-
setDelayModLFO(note) {
|
|
989
|
-
const now = this.audioContext.currentTime;
|
|
993
|
+
setDelayModLFO(note, scheduleTime) {
|
|
990
994
|
const startTime = note.startTime;
|
|
991
|
-
if (startTime <
|
|
995
|
+
if (startTime < scheduleTime)
|
|
992
996
|
return;
|
|
993
|
-
note.modulationLFO.stop(
|
|
997
|
+
note.modulationLFO.stop(scheduleTime);
|
|
994
998
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
995
999
|
note.modulationLFO.connect(note.filterDepth);
|
|
996
1000
|
}
|
|
997
|
-
setFreqModLFO(note) {
|
|
998
|
-
const now = this.audioContext.currentTime;
|
|
1001
|
+
setFreqModLFO(note, scheduleTime) {
|
|
999
1002
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1000
1003
|
note.modulationLFO.frequency
|
|
1001
|
-
.cancelScheduledValues(
|
|
1002
|
-
.setValueAtTime(freqModLFO,
|
|
1004
|
+
.cancelScheduledValues(scheduleTime)
|
|
1005
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1003
1006
|
}
|
|
1004
1007
|
createVoiceParamsHandlers() {
|
|
1005
1008
|
return {
|
|
1006
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1009
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1007
1010
|
if (0 < channel.state.modulationDepth) {
|
|
1008
|
-
this.setModLfoToPitch(channel, note);
|
|
1011
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1009
1012
|
}
|
|
1010
1013
|
},
|
|
1011
|
-
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
1012
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1013
|
-
if (0 < channel.state.modulationDepth)
|
|
1014
|
-
this.setModLfoToFilterFc(note);
|
|
1014
|
+
vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1015
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1016
|
+
if (0 < channel.state.modulationDepth) {
|
|
1017
|
+
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1018
|
+
}
|
|
1015
1019
|
},
|
|
1016
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1017
|
-
if (0 < channel.state.modulationDepth)
|
|
1018
|
-
this.setModLfoToVolume(note);
|
|
1020
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1021
|
+
if (0 < channel.state.modulationDepth) {
|
|
1022
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1023
|
+
}
|
|
1019
1024
|
},
|
|
1020
|
-
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1021
|
-
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
1022
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1023
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1024
|
-
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
1025
|
-
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
1025
|
+
chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1026
|
+
reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1027
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1028
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1029
|
+
delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1030
|
+
freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
|
|
1026
1031
|
};
|
|
1027
1032
|
}
|
|
1028
1033
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1032,7 +1037,7 @@ class MidyGMLite {
|
|
|
1032
1037
|
state[3] = noteNumber / 127;
|
|
1033
1038
|
return state;
|
|
1034
1039
|
}
|
|
1035
|
-
applyVoiceParams(channel, controllerType) {
|
|
1040
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1036
1041
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1037
1042
|
for (let i = 0; i < noteList.length; i++) {
|
|
1038
1043
|
const note = noteList[i];
|
|
@@ -1048,7 +1053,7 @@ class MidyGMLite {
|
|
|
1048
1053
|
continue;
|
|
1049
1054
|
note.voiceParams[key] = value;
|
|
1050
1055
|
if (key in this.voiceParamsHandlers) {
|
|
1051
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1056
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1052
1057
|
}
|
|
1053
1058
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1054
1059
|
if (appliedFilterEnvelope)
|
|
@@ -1060,8 +1065,8 @@ class MidyGMLite {
|
|
|
1060
1065
|
if (key in voiceParams)
|
|
1061
1066
|
noteVoiceParams[key] = voiceParams[key];
|
|
1062
1067
|
}
|
|
1063
|
-
this.setFilterEnvelope(note);
|
|
1064
|
-
this.setPitchEnvelope(note);
|
|
1068
|
+
this.setFilterEnvelope(note, scheduleTime);
|
|
1069
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1065
1070
|
}
|
|
1066
1071
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1067
1072
|
if (appliedVolumeEnvelope)
|
|
@@ -1073,7 +1078,7 @@ class MidyGMLite {
|
|
|
1073
1078
|
if (key in voiceParams)
|
|
1074
1079
|
noteVoiceParams[key] = voiceParams[key];
|
|
1075
1080
|
}
|
|
1076
|
-
this.setVolumeEnvelope(channel, note);
|
|
1081
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1077
1082
|
}
|
|
1078
1083
|
}
|
|
1079
1084
|
}
|
|
@@ -1095,44 +1100,39 @@ class MidyGMLite {
|
|
|
1095
1100
|
123: this.allNotesOff,
|
|
1096
1101
|
};
|
|
1097
1102
|
}
|
|
1098
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1103
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1099
1104
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1100
1105
|
if (handler) {
|
|
1101
|
-
handler.call(this, channelNumber, value);
|
|
1106
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1102
1107
|
const channel = this.channels[channelNumber];
|
|
1103
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1108
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1104
1109
|
}
|
|
1105
1110
|
else {
|
|
1106
1111
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1107
1112
|
}
|
|
1108
1113
|
}
|
|
1109
|
-
updateModulation(channel) {
|
|
1110
|
-
|
|
1114
|
+
updateModulation(channel, scheduleTime) {
|
|
1115
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1111
1116
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
else {
|
|
1121
|
-
this.setPitchEnvelope(note);
|
|
1122
|
-
this.startModulation(channel, note, now);
|
|
1123
|
-
}
|
|
1117
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1118
|
+
if (note.modulationDepth) {
|
|
1119
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1123
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1124
1124
|
}
|
|
1125
1125
|
});
|
|
1126
1126
|
}
|
|
1127
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1127
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1128
1128
|
const channel = this.channels[channelNumber];
|
|
1129
1129
|
channel.state.modulationDepth = modulation / 127;
|
|
1130
|
-
this.updateModulation(channel);
|
|
1130
|
+
this.updateModulation(channel, scheduleTime);
|
|
1131
1131
|
}
|
|
1132
|
-
setVolume(channelNumber, volume) {
|
|
1132
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1133
1133
|
const channel = this.channels[channelNumber];
|
|
1134
1134
|
channel.state.volume = volume / 127;
|
|
1135
|
-
this.updateChannelVolume(channel);
|
|
1135
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1136
1136
|
}
|
|
1137
1137
|
panToGain(pan) {
|
|
1138
1138
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1141,36 +1141,36 @@ class MidyGMLite {
|
|
|
1141
1141
|
gainRight: Math.sin(theta),
|
|
1142
1142
|
};
|
|
1143
1143
|
}
|
|
1144
|
-
setPan(channelNumber, pan) {
|
|
1144
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1145
1145
|
const channel = this.channels[channelNumber];
|
|
1146
1146
|
channel.state.pan = pan / 127;
|
|
1147
|
-
this.updateChannelVolume(channel);
|
|
1147
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1148
1148
|
}
|
|
1149
|
-
setExpression(channelNumber, expression) {
|
|
1149
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1150
1150
|
const channel = this.channels[channelNumber];
|
|
1151
1151
|
channel.state.expression = expression / 127;
|
|
1152
|
-
this.updateChannelVolume(channel);
|
|
1152
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1153
1153
|
}
|
|
1154
|
-
dataEntryLSB(channelNumber, value) {
|
|
1154
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1155
1155
|
this.channels[channelNumber].dataLSB = value;
|
|
1156
|
-
this.handleRPN(channelNumber);
|
|
1156
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1157
1157
|
}
|
|
1158
|
-
updateChannelVolume(channel) {
|
|
1159
|
-
const now = this.audioContext.currentTime;
|
|
1158
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1160
1159
|
const state = channel.state;
|
|
1161
1160
|
const volume = state.volume * state.expression;
|
|
1162
1161
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1163
1162
|
channel.gainL.gain
|
|
1164
|
-
.cancelScheduledValues(
|
|
1165
|
-
.setValueAtTime(volume * gainLeft,
|
|
1163
|
+
.cancelScheduledValues(scheduleTime)
|
|
1164
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1166
1165
|
channel.gainR.gain
|
|
1167
|
-
.cancelScheduledValues(
|
|
1168
|
-
.setValueAtTime(volume * gainRight,
|
|
1166
|
+
.cancelScheduledValues(scheduleTime)
|
|
1167
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1169
1168
|
}
|
|
1170
|
-
setSustainPedal(channelNumber, value) {
|
|
1169
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1170
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1171
1171
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1172
1172
|
if (value < 64) {
|
|
1173
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1173
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1174
1174
|
}
|
|
1175
1175
|
}
|
|
1176
1176
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
@@ -1191,12 +1191,12 @@ class MidyGMLite {
|
|
|
1191
1191
|
channel.dataLSB = minLSB;
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
|
-
handleRPN(channelNumber) {
|
|
1194
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1195
1195
|
const channel = this.channels[channelNumber];
|
|
1196
1196
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1197
1197
|
switch (rpn) {
|
|
1198
1198
|
case 0:
|
|
1199
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1199
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1200
1200
|
break;
|
|
1201
1201
|
default:
|
|
1202
1202
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1208,28 +1208,30 @@ class MidyGMLite {
|
|
|
1208
1208
|
setRPNLSB(channelNumber, value) {
|
|
1209
1209
|
this.channels[channelNumber].rpnLSB = value;
|
|
1210
1210
|
}
|
|
1211
|
-
dataEntryMSB(channelNumber, value) {
|
|
1211
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1212
1212
|
this.channels[channelNumber].dataMSB = value;
|
|
1213
|
-
this.handleRPN(channelNumber);
|
|
1213
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1214
1214
|
}
|
|
1215
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1215
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1216
1216
|
const channel = this.channels[channelNumber];
|
|
1217
1217
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1218
1218
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1219
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1219
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1220
1220
|
}
|
|
1221
|
-
setPitchBendRange(channelNumber, value) {
|
|
1221
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1222
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1222
1223
|
const channel = this.channels[channelNumber];
|
|
1223
1224
|
const state = channel.state;
|
|
1224
1225
|
const prev = state.pitchWheelSensitivity;
|
|
1225
1226
|
const next = value / 128;
|
|
1226
1227
|
state.pitchWheelSensitivity = next;
|
|
1227
1228
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1228
|
-
this.updateChannelDetune(channel);
|
|
1229
|
-
this.applyVoiceParams(channel, 16);
|
|
1229
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1230
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
1230
1231
|
}
|
|
1231
|
-
allSoundOff(channelNumber) {
|
|
1232
|
-
|
|
1232
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1233
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1234
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
1233
1235
|
}
|
|
1234
1236
|
resetAllControllers(channelNumber) {
|
|
1235
1237
|
const stateTypes = [
|
|
@@ -1253,10 +1255,11 @@ class MidyGMLite {
|
|
|
1253
1255
|
channel[type] = this.constructor.channelSettings[type];
|
|
1254
1256
|
}
|
|
1255
1257
|
}
|
|
1256
|
-
allNotesOff(channelNumber) {
|
|
1257
|
-
|
|
1258
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1259
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1260
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1258
1261
|
}
|
|
1259
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1262
|
+
handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
|
|
1260
1263
|
switch (data[2]) {
|
|
1261
1264
|
case 9:
|
|
1262
1265
|
switch (data[3]) {
|
|
@@ -1280,12 +1283,12 @@ class MidyGMLite {
|
|
|
1280
1283
|
}
|
|
1281
1284
|
this.channels[9].bank = 128;
|
|
1282
1285
|
}
|
|
1283
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
1286
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1284
1287
|
switch (data[2]) {
|
|
1285
1288
|
case 4:
|
|
1286
1289
|
switch (data[3]) {
|
|
1287
1290
|
case 1:
|
|
1288
|
-
return this.handleMasterVolumeSysEx(data);
|
|
1291
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
1289
1292
|
default:
|
|
1290
1293
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1291
1294
|
}
|
|
@@ -1294,42 +1297,40 @@ class MidyGMLite {
|
|
|
1294
1297
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1295
1298
|
}
|
|
1296
1299
|
}
|
|
1297
|
-
handleMasterVolumeSysEx(data) {
|
|
1300
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
1298
1301
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1299
|
-
this.setMasterVolume(volume);
|
|
1302
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
1300
1303
|
}
|
|
1301
|
-
setMasterVolume(volume) {
|
|
1304
|
+
setMasterVolume(volume, scheduleTime) {
|
|
1305
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1302
1306
|
if (volume < 0 && 1 < volume) {
|
|
1303
1307
|
console.error("Master Volume is out of range");
|
|
1304
1308
|
}
|
|
1305
1309
|
else {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1310
|
+
this.masterVolume.gain
|
|
1311
|
+
.cancelScheduledValues(scheduleTime)
|
|
1312
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
1309
1313
|
}
|
|
1310
1314
|
}
|
|
1311
|
-
|
|
1312
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1313
|
-
}
|
|
1314
|
-
handleSysEx(data) {
|
|
1315
|
+
handleSysEx(data, scheduleTime) {
|
|
1315
1316
|
switch (data[0]) {
|
|
1316
1317
|
case 126:
|
|
1317
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
1318
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
1318
1319
|
case 127:
|
|
1319
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
1320
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
1320
1321
|
default:
|
|
1321
|
-
|
|
1322
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1322
1323
|
}
|
|
1323
1324
|
}
|
|
1324
|
-
scheduleTask(callback,
|
|
1325
|
+
scheduleTask(callback, scheduleTime) {
|
|
1325
1326
|
return new Promise((resolve) => {
|
|
1326
1327
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
1327
1328
|
bufferSource.onended = () => {
|
|
1328
1329
|
callback();
|
|
1329
1330
|
resolve();
|
|
1330
1331
|
};
|
|
1331
|
-
bufferSource.start(
|
|
1332
|
-
bufferSource.stop(
|
|
1332
|
+
bufferSource.start(scheduleTime);
|
|
1333
|
+
bufferSource.stop(scheduleTime);
|
|
1333
1334
|
});
|
|
1334
1335
|
}
|
|
1335
1336
|
}
|