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