@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-GM2.js
CHANGED
|
@@ -69,31 +69,37 @@ 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,
|
|
75
81
|
writable: true,
|
|
76
82
|
value: void 0
|
|
77
83
|
});
|
|
78
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
79
85
|
enumerable: true,
|
|
80
86
|
configurable: true,
|
|
81
87
|
writable: true,
|
|
82
88
|
value: void 0
|
|
83
89
|
});
|
|
84
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "volumeNode", {
|
|
85
91
|
enumerable: true,
|
|
86
92
|
configurable: true,
|
|
87
93
|
writable: true,
|
|
88
94
|
value: void 0
|
|
89
95
|
});
|
|
90
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "gainL", {
|
|
91
97
|
enumerable: true,
|
|
92
98
|
configurable: true,
|
|
93
99
|
writable: true,
|
|
94
100
|
value: void 0
|
|
95
101
|
});
|
|
96
|
-
Object.defineProperty(this, "
|
|
102
|
+
Object.defineProperty(this, "gainR", {
|
|
97
103
|
enumerable: true,
|
|
98
104
|
configurable: true,
|
|
99
105
|
writable: true,
|
|
@@ -495,6 +501,9 @@ class MidyGM2 {
|
|
|
495
501
|
...this.setChannelAudioNodes(audioContext),
|
|
496
502
|
scheduledNotes: new SparseMap(128),
|
|
497
503
|
sostenutoNotes: new SparseMap(128),
|
|
504
|
+
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
505
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
506
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
498
507
|
};
|
|
499
508
|
});
|
|
500
509
|
return channels;
|
|
@@ -561,10 +570,11 @@ class MidyGM2 {
|
|
|
561
570
|
const event = this.timeline[queueIndex];
|
|
562
571
|
if (event.startTime > t + this.lookAhead)
|
|
563
572
|
break;
|
|
573
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
564
574
|
switch (event.type) {
|
|
565
575
|
case "noteOn":
|
|
566
576
|
if (event.velocity !== 0) {
|
|
567
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
577
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
568
578
|
break;
|
|
569
579
|
}
|
|
570
580
|
/* falls through */
|
|
@@ -572,26 +582,27 @@ class MidyGM2 {
|
|
|
572
582
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
573
583
|
if (portamentoTarget)
|
|
574
584
|
portamentoTarget.portamento = true;
|
|
575
|
-
const notePromise = this.
|
|
585
|
+
const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
586
|
+
portamentoTarget?.noteNumber);
|
|
576
587
|
if (notePromise) {
|
|
577
588
|
this.notePromises.push(notePromise);
|
|
578
589
|
}
|
|
579
590
|
break;
|
|
580
591
|
}
|
|
581
592
|
case "controller":
|
|
582
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
593
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
583
594
|
break;
|
|
584
595
|
case "programChange":
|
|
585
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
596
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
586
597
|
break;
|
|
587
598
|
case "channelAftertouch":
|
|
588
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
599
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
589
600
|
break;
|
|
590
601
|
case "pitchBend":
|
|
591
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
602
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
592
603
|
break;
|
|
593
604
|
case "sysEx":
|
|
594
|
-
this.handleSysEx(event.data);
|
|
605
|
+
this.handleSysEx(event.data, startTime);
|
|
595
606
|
}
|
|
596
607
|
queueIndex++;
|
|
597
608
|
}
|
|
@@ -622,10 +633,11 @@ class MidyGM2 {
|
|
|
622
633
|
resolve();
|
|
623
634
|
return;
|
|
624
635
|
}
|
|
625
|
-
const
|
|
636
|
+
const now = this.audioContext.currentTime;
|
|
637
|
+
const t = now + offset;
|
|
626
638
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
627
639
|
if (this.isPausing) {
|
|
628
|
-
await this.stopNotes(0, true);
|
|
640
|
+
await this.stopNotes(0, true, now);
|
|
629
641
|
this.notePromises = [];
|
|
630
642
|
resolve();
|
|
631
643
|
this.isPausing = false;
|
|
@@ -633,7 +645,7 @@ class MidyGM2 {
|
|
|
633
645
|
return;
|
|
634
646
|
}
|
|
635
647
|
else if (this.isStopping) {
|
|
636
|
-
await this.stopNotes(0, true);
|
|
648
|
+
await this.stopNotes(0, true, now);
|
|
637
649
|
this.notePromises = [];
|
|
638
650
|
this.exclusiveClassMap.clear();
|
|
639
651
|
this.audioBufferCache.clear();
|
|
@@ -643,7 +655,7 @@ class MidyGM2 {
|
|
|
643
655
|
return;
|
|
644
656
|
}
|
|
645
657
|
else if (this.isSeeking) {
|
|
646
|
-
this.stopNotes(0, true);
|
|
658
|
+
this.stopNotes(0, true, now);
|
|
647
659
|
this.exclusiveClassMap.clear();
|
|
648
660
|
this.startTime = this.audioContext.currentTime;
|
|
649
661
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -652,7 +664,6 @@ class MidyGM2 {
|
|
|
652
664
|
await schedulePlayback();
|
|
653
665
|
}
|
|
654
666
|
else {
|
|
655
|
-
const now = this.audioContext.currentTime;
|
|
656
667
|
const waitTime = now + this.noteCheckInterval;
|
|
657
668
|
await this.scheduleTask(() => { }, waitTime);
|
|
658
669
|
await schedulePlayback();
|
|
@@ -772,25 +783,26 @@ class MidyGM2 {
|
|
|
772
783
|
}
|
|
773
784
|
return { instruments, timeline };
|
|
774
785
|
}
|
|
775
|
-
|
|
776
|
-
const now = this.audioContext.currentTime;
|
|
786
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
777
787
|
const channel = this.channels[channelNumber];
|
|
788
|
+
const promises = [];
|
|
778
789
|
channel.scheduledNotes.forEach((noteList) => {
|
|
779
790
|
for (let i = 0; i < noteList.length; i++) {
|
|
780
791
|
const note = noteList[i];
|
|
781
792
|
if (!note)
|
|
782
793
|
continue;
|
|
783
|
-
const promise = this.
|
|
784
|
-
force);
|
|
794
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
785
795
|
this.notePromises.push(promise);
|
|
796
|
+
promises.push(promise);
|
|
786
797
|
}
|
|
787
798
|
});
|
|
788
799
|
channel.scheduledNotes.clear();
|
|
789
|
-
|
|
800
|
+
return Promise.all(promises);
|
|
790
801
|
}
|
|
791
|
-
stopNotes(velocity, force) {
|
|
802
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
803
|
+
const promises = [];
|
|
792
804
|
for (let i = 0; i < this.channels.length; i++) {
|
|
793
|
-
this.stopChannelNotes(i, velocity, force);
|
|
805
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
794
806
|
}
|
|
795
807
|
return Promise.all(this.notePromises);
|
|
796
808
|
}
|
|
@@ -838,22 +850,34 @@ class MidyGM2 {
|
|
|
838
850
|
const now = this.audioContext.currentTime;
|
|
839
851
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
840
852
|
}
|
|
841
|
-
|
|
853
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
854
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
855
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
856
|
+
const note = noteList[i];
|
|
857
|
+
if (!note)
|
|
858
|
+
continue;
|
|
859
|
+
if (scheduleTime < note.startTime)
|
|
860
|
+
continue;
|
|
861
|
+
callback(note);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
getActiveNotes(channel, scheduleTime) {
|
|
842
866
|
const activeNotes = new SparseMap(128);
|
|
843
867
|
channel.scheduledNotes.forEach((noteList) => {
|
|
844
|
-
const activeNote = this.getActiveNote(noteList,
|
|
868
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
845
869
|
if (activeNote) {
|
|
846
870
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
847
871
|
}
|
|
848
872
|
});
|
|
849
873
|
return activeNotes;
|
|
850
874
|
}
|
|
851
|
-
getActiveNote(noteList,
|
|
875
|
+
getActiveNote(noteList, scheduleTime) {
|
|
852
876
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
853
877
|
const note = noteList[i];
|
|
854
878
|
if (!note)
|
|
855
879
|
return;
|
|
856
|
-
if (
|
|
880
|
+
if (scheduleTime < note.startTime)
|
|
857
881
|
continue;
|
|
858
882
|
return (note.ending) ? null : note;
|
|
859
883
|
}
|
|
@@ -1013,73 +1037,64 @@ class MidyGM2 {
|
|
|
1013
1037
|
calcNoteDetune(channel, note) {
|
|
1014
1038
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1015
1039
|
}
|
|
1016
|
-
updateChannelDetune(channel) {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const note = noteList[i];
|
|
1020
|
-
if (!note)
|
|
1021
|
-
continue;
|
|
1022
|
-
this.updateDetune(channel, note, 0);
|
|
1023
|
-
}
|
|
1040
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1041
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1042
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1024
1043
|
});
|
|
1025
1044
|
}
|
|
1026
|
-
updateDetune(channel, note,
|
|
1027
|
-
const now = this.audioContext.currentTime;
|
|
1045
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1028
1046
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1029
|
-
const detune = channel.detune + noteDetune
|
|
1047
|
+
const detune = channel.detune + noteDetune;
|
|
1030
1048
|
note.bufferSource.detune
|
|
1031
|
-
.cancelScheduledValues(
|
|
1032
|
-
.setValueAtTime(detune,
|
|
1049
|
+
.cancelScheduledValues(scheduleTime)
|
|
1050
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1033
1051
|
}
|
|
1034
1052
|
getPortamentoTime(channel) {
|
|
1035
1053
|
const factor = 5 * Math.log(10) / 127;
|
|
1036
1054
|
const time = channel.state.portamentoTime;
|
|
1037
1055
|
return Math.log(time) / factor;
|
|
1038
1056
|
}
|
|
1039
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1040
|
-
const now = this.audioContext.currentTime;
|
|
1057
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1041
1058
|
const { voiceParams, startTime } = note;
|
|
1042
1059
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1043
1060
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1044
1061
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1045
1062
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1046
1063
|
note.volumeEnvelopeNode.gain
|
|
1047
|
-
.cancelScheduledValues(
|
|
1064
|
+
.cancelScheduledValues(scheduleTime)
|
|
1048
1065
|
.setValueAtTime(0, volDelay)
|
|
1049
1066
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1050
1067
|
}
|
|
1051
|
-
setVolumeEnvelope(note,
|
|
1052
|
-
const now = this.audioContext.currentTime;
|
|
1068
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1053
1069
|
const { voiceParams, startTime } = note;
|
|
1054
1070
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1055
|
-
(1 +
|
|
1071
|
+
(1 + this.getAmplitudeControl(channel));
|
|
1056
1072
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1057
1073
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1058
1074
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
1059
1075
|
const volHold = volAttack + voiceParams.volHold;
|
|
1060
1076
|
const volDecay = volHold + voiceParams.volDecay;
|
|
1061
1077
|
note.volumeEnvelopeNode.gain
|
|
1062
|
-
.cancelScheduledValues(
|
|
1078
|
+
.cancelScheduledValues(scheduleTime)
|
|
1063
1079
|
.setValueAtTime(0, startTime)
|
|
1064
1080
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1065
1081
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
1066
1082
|
.setValueAtTime(attackVolume, volHold)
|
|
1067
1083
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1068
1084
|
}
|
|
1069
|
-
setPitchEnvelope(note) {
|
|
1070
|
-
const now = this.audioContext.currentTime;
|
|
1085
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1071
1086
|
const { voiceParams } = note;
|
|
1072
1087
|
const baseRate = voiceParams.playbackRate;
|
|
1073
1088
|
note.bufferSource.playbackRate
|
|
1074
|
-
.cancelScheduledValues(
|
|
1075
|
-
.setValueAtTime(baseRate,
|
|
1089
|
+
.cancelScheduledValues(scheduleTime)
|
|
1090
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1076
1091
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1077
1092
|
if (modEnvToPitch === 0)
|
|
1078
1093
|
return;
|
|
1079
1094
|
const basePitch = this.rateToCent(baseRate);
|
|
1080
1095
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1081
1096
|
const peekRate = this.centToRate(peekPitch);
|
|
1082
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1097
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1083
1098
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1084
1099
|
const modHold = modAttack + voiceParams.modHold;
|
|
1085
1100
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1094,8 +1109,7 @@ class MidyGM2 {
|
|
|
1094
1109
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1095
1110
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1096
1111
|
}
|
|
1097
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1098
|
-
const now = this.audioContext.currentTime;
|
|
1112
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1099
1113
|
const state = channel.state;
|
|
1100
1114
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1101
1115
|
const softPedalFactor = 1 -
|
|
@@ -1110,18 +1124,18 @@ class MidyGM2 {
|
|
|
1110
1124
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1111
1125
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1112
1126
|
note.filterNode.frequency
|
|
1113
|
-
.cancelScheduledValues(
|
|
1127
|
+
.cancelScheduledValues(scheduleTime)
|
|
1114
1128
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1115
1129
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1116
1130
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1117
1131
|
}
|
|
1118
|
-
setFilterEnvelope(channel, note,
|
|
1119
|
-
const now = this.audioContext.currentTime;
|
|
1132
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1120
1133
|
const state = channel.state;
|
|
1121
1134
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1122
1135
|
const softPedalFactor = 1 -
|
|
1123
1136
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1124
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1137
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1138
|
+
this.getFilterCutoffControl(channel);
|
|
1125
1139
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1126
1140
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1127
1141
|
softPedalFactor;
|
|
@@ -1135,14 +1149,14 @@ class MidyGM2 {
|
|
|
1135
1149
|
const modHold = modAttack + voiceParams.modHold;
|
|
1136
1150
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1137
1151
|
note.filterNode.frequency
|
|
1138
|
-
.cancelScheduledValues(
|
|
1152
|
+
.cancelScheduledValues(scheduleTime)
|
|
1139
1153
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1140
1154
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1141
1155
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1142
1156
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1143
1157
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1144
1158
|
}
|
|
1145
|
-
startModulation(channel, note,
|
|
1159
|
+
startModulation(channel, note, scheduleTime) {
|
|
1146
1160
|
const { voiceParams } = note;
|
|
1147
1161
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1148
1162
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1151,10 +1165,10 @@ class MidyGM2 {
|
|
|
1151
1165
|
gain: voiceParams.modLfoToFilterFc,
|
|
1152
1166
|
});
|
|
1153
1167
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1154
|
-
this.setModLfoToPitch(channel, note,
|
|
1168
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1155
1169
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1156
|
-
this.setModLfoToVolume(note,
|
|
1157
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1170
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1171
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1158
1172
|
note.modulationLFO.connect(note.filterDepth);
|
|
1159
1173
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1160
1174
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1162,15 +1176,15 @@ class MidyGM2 {
|
|
|
1162
1176
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1163
1177
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1164
1178
|
}
|
|
1165
|
-
startVibrato(channel, note,
|
|
1179
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1166
1180
|
const { voiceParams } = note;
|
|
1167
1181
|
const state = channel.state;
|
|
1168
1182
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1169
1183
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1170
1184
|
});
|
|
1171
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1185
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1172
1186
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1173
|
-
this.setVibLfoToPitch(channel, note);
|
|
1187
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1174
1188
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1175
1189
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1176
1190
|
}
|
|
@@ -1193,6 +1207,7 @@ class MidyGM2 {
|
|
|
1193
1207
|
}
|
|
1194
1208
|
}
|
|
1195
1209
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1210
|
+
const now = this.audioContext.currentTime;
|
|
1196
1211
|
const state = channel.state;
|
|
1197
1212
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1198
1213
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1209,20 +1224,20 @@ class MidyGM2 {
|
|
|
1209
1224
|
});
|
|
1210
1225
|
if (portamento) {
|
|
1211
1226
|
note.portamento = true;
|
|
1212
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1213
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1227
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1228
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1214
1229
|
}
|
|
1215
1230
|
else {
|
|
1216
1231
|
note.portamento = false;
|
|
1217
|
-
this.setVolumeEnvelope(note,
|
|
1218
|
-
this.setFilterEnvelope(channel, note,
|
|
1232
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1233
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1219
1234
|
}
|
|
1220
1235
|
if (0 < state.vibratoDepth) {
|
|
1221
|
-
this.startVibrato(channel, note,
|
|
1236
|
+
this.startVibrato(channel, note, now);
|
|
1222
1237
|
}
|
|
1223
|
-
this.setPitchEnvelope(note);
|
|
1238
|
+
this.setPitchEnvelope(note, now);
|
|
1224
1239
|
if (0 < state.modulationDepth) {
|
|
1225
|
-
this.startModulation(channel, note,
|
|
1240
|
+
this.startModulation(channel, note, now);
|
|
1226
1241
|
}
|
|
1227
1242
|
if (this.mono && channel.currentBufferSource) {
|
|
1228
1243
|
channel.currentBufferSource.stop(startTime);
|
|
@@ -1234,10 +1249,10 @@ class MidyGM2 {
|
|
|
1234
1249
|
note.volumeNode.connect(note.gainL);
|
|
1235
1250
|
note.volumeNode.connect(note.gainR);
|
|
1236
1251
|
if (0 < channel.chorusSendLevel) {
|
|
1237
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1252
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1238
1253
|
}
|
|
1239
1254
|
if (0 < channel.reverbSendLevel) {
|
|
1240
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1255
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1241
1256
|
}
|
|
1242
1257
|
note.bufferSource.start(startTime);
|
|
1243
1258
|
return note;
|
|
@@ -1274,9 +1289,9 @@ class MidyGM2 {
|
|
|
1274
1289
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1275
1290
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1276
1291
|
if (!prevNote.ending) {
|
|
1277
|
-
this.
|
|
1278
|
-
startTime,
|
|
1279
|
-
|
|
1292
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1293
|
+
startTime, true, // force
|
|
1294
|
+
undefined);
|
|
1280
1295
|
}
|
|
1281
1296
|
}
|
|
1282
1297
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1289,9 +1304,9 @@ class MidyGM2 {
|
|
|
1289
1304
|
scheduledNotes.set(noteNumber, [note]);
|
|
1290
1305
|
}
|
|
1291
1306
|
}
|
|
1292
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1293
|
-
|
|
1294
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
1307
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1308
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1309
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1295
1310
|
}
|
|
1296
1311
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1297
1312
|
const note = scheduledNotes[index];
|
|
@@ -1331,7 +1346,7 @@ class MidyGM2 {
|
|
|
1331
1346
|
note.bufferSource.stop(stopTime);
|
|
1332
1347
|
});
|
|
1333
1348
|
}
|
|
1334
|
-
|
|
1349
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1335
1350
|
const channel = this.channels[channelNumber];
|
|
1336
1351
|
const state = channel.state;
|
|
1337
1352
|
if (!force) {
|
|
@@ -1370,24 +1385,19 @@ class MidyGM2 {
|
|
|
1370
1385
|
}
|
|
1371
1386
|
}
|
|
1372
1387
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
return this.
|
|
1388
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1389
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1390
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
|
|
1391
|
+
undefined);
|
|
1376
1392
|
}
|
|
1377
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1393
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1378
1394
|
const velocity = halfVelocity * 2;
|
|
1379
1395
|
const channel = this.channels[channelNumber];
|
|
1380
1396
|
const promises = [];
|
|
1381
|
-
channel
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
if (!note)
|
|
1386
|
-
continue;
|
|
1387
|
-
const { noteNumber } = note;
|
|
1388
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1389
|
-
promises.push(promise);
|
|
1390
|
-
}
|
|
1397
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1398
|
+
const { noteNumber } = note;
|
|
1399
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1400
|
+
promises.push(promise);
|
|
1391
1401
|
});
|
|
1392
1402
|
return promises;
|
|
1393
1403
|
}
|
|
@@ -1398,39 +1408,38 @@ class MidyGM2 {
|
|
|
1398
1408
|
channel.state.sostenutoPedal = 0;
|
|
1399
1409
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1400
1410
|
const { noteNumber } = activeNote;
|
|
1401
|
-
const promise = this.
|
|
1411
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1402
1412
|
promises.push(promise);
|
|
1403
1413
|
});
|
|
1404
1414
|
channel.sostenutoNotes.clear();
|
|
1405
1415
|
return promises;
|
|
1406
1416
|
}
|
|
1407
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1417
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1408
1418
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
1409
1419
|
const messageType = statusByte & 0xF0;
|
|
1410
1420
|
switch (messageType) {
|
|
1411
1421
|
case 0x80:
|
|
1412
|
-
return this.
|
|
1422
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1413
1423
|
case 0x90:
|
|
1414
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1424
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1415
1425
|
case 0xB0:
|
|
1416
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1426
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1417
1427
|
case 0xC0:
|
|
1418
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1428
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1419
1429
|
case 0xD0:
|
|
1420
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1430
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1421
1431
|
case 0xE0:
|
|
1422
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1432
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1423
1433
|
default:
|
|
1424
1434
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1425
1435
|
}
|
|
1426
1436
|
}
|
|
1427
|
-
handleProgramChange(channelNumber, program) {
|
|
1437
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1428
1438
|
const channel = this.channels[channelNumber];
|
|
1429
1439
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1430
1440
|
channel.program = program;
|
|
1431
1441
|
}
|
|
1432
|
-
handleChannelPressure(channelNumber, value) {
|
|
1433
|
-
const now = this.audioContext.currentTime;
|
|
1442
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1434
1443
|
const channel = this.channels[channelNumber];
|
|
1435
1444
|
const prev = channel.state.channelPressure;
|
|
1436
1445
|
const next = value / 127;
|
|
@@ -1440,69 +1449,68 @@ class MidyGM2 {
|
|
|
1440
1449
|
channel.detune += pressureDepth * (next - prev);
|
|
1441
1450
|
}
|
|
1442
1451
|
const table = channel.channelPressureTable;
|
|
1443
|
-
this.getActiveNotes(channel,
|
|
1444
|
-
this.
|
|
1452
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1453
|
+
this.setControllerParameters(channel, note, table);
|
|
1445
1454
|
});
|
|
1446
1455
|
// this.applyVoiceParams(channel, 13);
|
|
1447
1456
|
}
|
|
1448
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1457
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1449
1458
|
const pitchBend = msb * 128 + lsb;
|
|
1450
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1459
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1451
1460
|
}
|
|
1452
|
-
setPitchBend(channelNumber, value) {
|
|
1461
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1462
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1453
1463
|
const channel = this.channels[channelNumber];
|
|
1454
1464
|
const state = channel.state;
|
|
1455
1465
|
const prev = state.pitchWheel * 2 - 1;
|
|
1456
1466
|
const next = (value - 8192) / 8192;
|
|
1457
1467
|
state.pitchWheel = value / 16383;
|
|
1458
1468
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1459
|
-
this.updateChannelDetune(channel);
|
|
1460
|
-
this.applyVoiceParams(channel, 14);
|
|
1469
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1470
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1461
1471
|
}
|
|
1462
|
-
setModLfoToPitch(channel, note,
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1472
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1473
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1474
|
+
this.getLFOPitchDepth(channel);
|
|
1465
1475
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1466
1476
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1467
1477
|
note.modulationDepth.gain
|
|
1468
|
-
.cancelScheduledValues(
|
|
1469
|
-
.setValueAtTime(modulationDepth,
|
|
1478
|
+
.cancelScheduledValues(scheduleTime)
|
|
1479
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1470
1480
|
}
|
|
1471
|
-
setVibLfoToPitch(channel, note) {
|
|
1472
|
-
const now = this.audioContext.currentTime;
|
|
1481
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1473
1482
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1474
1483
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1475
1484
|
2;
|
|
1476
1485
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1477
1486
|
note.vibratoDepth.gain
|
|
1478
|
-
.cancelScheduledValues(
|
|
1479
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1487
|
+
.cancelScheduledValues(scheduleTime)
|
|
1488
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1480
1489
|
}
|
|
1481
|
-
setModLfoToFilterFc(note,
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1490
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1491
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1492
|
+
this.getLFOFilterDepth(channel);
|
|
1484
1493
|
note.filterDepth.gain
|
|
1485
|
-
.cancelScheduledValues(
|
|
1486
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1494
|
+
.cancelScheduledValues(scheduleTime)
|
|
1495
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1487
1496
|
}
|
|
1488
|
-
setModLfoToVolume(note,
|
|
1489
|
-
const now = this.audioContext.currentTime;
|
|
1497
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1490
1498
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1491
1499
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1492
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1500
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1501
|
+
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1493
1502
|
note.volumeDepth.gain
|
|
1494
|
-
.cancelScheduledValues(
|
|
1495
|
-
.setValueAtTime(volumeDepth,
|
|
1503
|
+
.cancelScheduledValues(scheduleTime)
|
|
1504
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1496
1505
|
}
|
|
1497
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1506
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1498
1507
|
if (0 < prevValue) {
|
|
1499
1508
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1500
|
-
const now = this.audioContext.currentTime;
|
|
1501
1509
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1502
1510
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1503
1511
|
note.reverbEffectsSend.gain
|
|
1504
|
-
.cancelScheduledValues(
|
|
1505
|
-
.setValueAtTime(value,
|
|
1512
|
+
.cancelScheduledValues(scheduleTime)
|
|
1513
|
+
.setValueAtTime(value, scheduleTime);
|
|
1506
1514
|
}
|
|
1507
1515
|
else {
|
|
1508
1516
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1520,15 +1528,14 @@ class MidyGM2 {
|
|
|
1520
1528
|
}
|
|
1521
1529
|
}
|
|
1522
1530
|
}
|
|
1523
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1531
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1524
1532
|
if (0 < prevValue) {
|
|
1525
1533
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1526
|
-
const now = this.audioContext.currentTime;
|
|
1527
1534
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1528
1535
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1529
1536
|
note.chorusEffectsSend.gain
|
|
1530
|
-
.cancelScheduledValues(
|
|
1531
|
-
.setValueAtTime(value,
|
|
1537
|
+
.cancelScheduledValues(scheduleTime)
|
|
1538
|
+
.setValueAtTime(value, scheduleTime);
|
|
1532
1539
|
}
|
|
1533
1540
|
else {
|
|
1534
1541
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1546,75 +1553,71 @@ class MidyGM2 {
|
|
|
1546
1553
|
}
|
|
1547
1554
|
}
|
|
1548
1555
|
}
|
|
1549
|
-
setDelayModLFO(note) {
|
|
1550
|
-
const now = this.audioContext.currentTime;
|
|
1556
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1551
1557
|
const startTime = note.startTime;
|
|
1552
|
-
if (startTime <
|
|
1558
|
+
if (startTime < scheduleTime)
|
|
1553
1559
|
return;
|
|
1554
|
-
note.modulationLFO.stop(
|
|
1560
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1555
1561
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1556
1562
|
note.modulationLFO.connect(note.filterDepth);
|
|
1557
1563
|
}
|
|
1558
|
-
setFreqModLFO(note) {
|
|
1559
|
-
const now = this.audioContext.currentTime;
|
|
1564
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1560
1565
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1561
1566
|
note.modulationLFO.frequency
|
|
1562
|
-
.cancelScheduledValues(
|
|
1563
|
-
.setValueAtTime(freqModLFO,
|
|
1567
|
+
.cancelScheduledValues(scheduleTime)
|
|
1568
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1564
1569
|
}
|
|
1565
|
-
setFreqVibLFO(channel, note) {
|
|
1566
|
-
const now = this.audioContext.currentTime;
|
|
1570
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1567
1571
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1568
1572
|
note.vibratoLFO.frequency
|
|
1569
|
-
.cancelScheduledValues(
|
|
1570
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1573
|
+
.cancelScheduledValues(scheduleTime)
|
|
1574
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1571
1575
|
}
|
|
1572
1576
|
createVoiceParamsHandlers() {
|
|
1573
1577
|
return {
|
|
1574
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1578
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1575
1579
|
if (0 < channel.state.modulationDepth) {
|
|
1576
|
-
this.setModLfoToPitch(channel, note,
|
|
1580
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1577
1581
|
}
|
|
1578
1582
|
},
|
|
1579
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1583
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1580
1584
|
if (0 < channel.state.vibratoDepth) {
|
|
1581
|
-
this.setVibLfoToPitch(channel, note);
|
|
1585
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1582
1586
|
}
|
|
1583
1587
|
},
|
|
1584
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1588
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1585
1589
|
if (0 < channel.state.modulationDepth) {
|
|
1586
|
-
this.setModLfoToFilterFc(note,
|
|
1590
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1587
1591
|
}
|
|
1588
1592
|
},
|
|
1589
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1593
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1590
1594
|
if (0 < channel.state.modulationDepth) {
|
|
1591
|
-
this.setModLfoToVolume(note,
|
|
1595
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1592
1596
|
}
|
|
1593
1597
|
},
|
|
1594
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1595
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1598
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1599
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1596
1600
|
},
|
|
1597
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1598
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1601
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1602
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1599
1603
|
},
|
|
1600
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1601
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1602
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1604
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1605
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1606
|
+
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1603
1607
|
if (0 < channel.state.vibratoDepth) {
|
|
1604
|
-
const now = this.audioContext.currentTime;
|
|
1605
1608
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1606
1609
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1607
|
-
if (
|
|
1610
|
+
if (scheduleTime < prevStartTime)
|
|
1608
1611
|
return;
|
|
1609
1612
|
const value = note.voiceParams.delayVibLFO;
|
|
1610
1613
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1611
|
-
note.vibratoLFO.stop(
|
|
1614
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1612
1615
|
note.vibratoLFO.start(startTime);
|
|
1613
1616
|
}
|
|
1614
1617
|
},
|
|
1615
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1618
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1616
1619
|
if (0 < channel.state.vibratoDepth) {
|
|
1617
|
-
this.setFreqVibLFO(channel, note);
|
|
1620
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1618
1621
|
}
|
|
1619
1622
|
},
|
|
1620
1623
|
};
|
|
@@ -1626,7 +1629,7 @@ class MidyGM2 {
|
|
|
1626
1629
|
state[3] = noteNumber / 127;
|
|
1627
1630
|
return state;
|
|
1628
1631
|
}
|
|
1629
|
-
applyVoiceParams(channel, controllerType) {
|
|
1632
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1630
1633
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1631
1634
|
for (let i = 0; i < noteList.length; i++) {
|
|
1632
1635
|
const note = noteList[i];
|
|
@@ -1642,7 +1645,7 @@ class MidyGM2 {
|
|
|
1642
1645
|
continue;
|
|
1643
1646
|
note.voiceParams[key] = value;
|
|
1644
1647
|
if (key in this.voiceParamsHandlers) {
|
|
1645
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1648
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1646
1649
|
}
|
|
1647
1650
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1648
1651
|
if (appliedFilterEnvelope)
|
|
@@ -1655,12 +1658,12 @@ class MidyGM2 {
|
|
|
1655
1658
|
noteVoiceParams[key] = voiceParams[key];
|
|
1656
1659
|
}
|
|
1657
1660
|
if (note.portamento) {
|
|
1658
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1661
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1659
1662
|
}
|
|
1660
1663
|
else {
|
|
1661
|
-
this.setFilterEnvelope(channel, note,
|
|
1664
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1662
1665
|
}
|
|
1663
|
-
this.setPitchEnvelope(note);
|
|
1666
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1664
1667
|
}
|
|
1665
1668
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1666
1669
|
if (appliedVolumeEnvelope)
|
|
@@ -1672,7 +1675,7 @@ class MidyGM2 {
|
|
|
1672
1675
|
if (key in voiceParams)
|
|
1673
1676
|
noteVoiceParams[key] = voiceParams[key];
|
|
1674
1677
|
}
|
|
1675
|
-
this.setVolumeEnvelope(note,
|
|
1678
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1676
1679
|
}
|
|
1677
1680
|
}
|
|
1678
1681
|
}
|
|
@@ -1706,12 +1709,12 @@ class MidyGM2 {
|
|
|
1706
1709
|
127: this.polyOn,
|
|
1707
1710
|
};
|
|
1708
1711
|
}
|
|
1709
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1712
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1710
1713
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1711
1714
|
if (handler) {
|
|
1712
|
-
handler.call(this, channelNumber, value);
|
|
1715
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1713
1716
|
const channel = this.channels[channelNumber];
|
|
1714
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1717
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1715
1718
|
this.applyControlTable(channel, controllerType);
|
|
1716
1719
|
}
|
|
1717
1720
|
else {
|
|
@@ -1721,55 +1724,45 @@ class MidyGM2 {
|
|
|
1721
1724
|
setBankMSB(channelNumber, msb) {
|
|
1722
1725
|
this.channels[channelNumber].bankMSB = msb;
|
|
1723
1726
|
}
|
|
1724
|
-
updateModulation(channel) {
|
|
1725
|
-
|
|
1727
|
+
updateModulation(channel, scheduleTime) {
|
|
1728
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1726
1729
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
}
|
|
1735
|
-
else {
|
|
1736
|
-
this.setPitchEnvelope(note);
|
|
1737
|
-
this.startModulation(channel, note, now);
|
|
1738
|
-
}
|
|
1730
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1731
|
+
if (note.modulationDepth) {
|
|
1732
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1736
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1739
1737
|
}
|
|
1740
1738
|
});
|
|
1741
1739
|
}
|
|
1742
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1740
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1743
1741
|
const channel = this.channels[channelNumber];
|
|
1744
1742
|
channel.state.modulationDepth = modulation / 127;
|
|
1745
|
-
this.updateModulation(channel);
|
|
1743
|
+
this.updateModulation(channel, scheduleTime);
|
|
1746
1744
|
}
|
|
1747
1745
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1748
1746
|
const channel = this.channels[channelNumber];
|
|
1749
1747
|
const factor = 5 * Math.log(10) / 127;
|
|
1750
1748
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1751
1749
|
}
|
|
1752
|
-
setKeyBasedVolume(channel) {
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
if (!note)
|
|
1758
|
-
continue;
|
|
1759
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1760
|
-
if (keyBasedValue === 0)
|
|
1761
|
-
continue;
|
|
1750
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1751
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1752
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1753
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1754
|
+
if (keyBasedValue !== 0) {
|
|
1762
1755
|
note.volumeNode.gain
|
|
1763
|
-
.cancelScheduledValues(
|
|
1764
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1756
|
+
.cancelScheduledValues(scheduleTime)
|
|
1757
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1765
1758
|
}
|
|
1766
1759
|
});
|
|
1767
1760
|
}
|
|
1768
|
-
setVolume(channelNumber, volume) {
|
|
1761
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1769
1762
|
const channel = this.channels[channelNumber];
|
|
1770
1763
|
channel.state.volume = volume / 127;
|
|
1771
|
-
this.updateChannelVolume(channel);
|
|
1772
|
-
this.setKeyBasedVolume(channel);
|
|
1764
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1765
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1773
1766
|
}
|
|
1774
1767
|
panToGain(pan) {
|
|
1775
1768
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1778,90 +1771,84 @@ class MidyGM2 {
|
|
|
1778
1771
|
gainRight: Math.sin(theta),
|
|
1779
1772
|
};
|
|
1780
1773
|
}
|
|
1781
|
-
setKeyBasedPan(channel) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
if (!note)
|
|
1787
|
-
continue;
|
|
1788
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1789
|
-
if (keyBasedValue === 0)
|
|
1790
|
-
continue;
|
|
1774
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1775
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1776
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1777
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1778
|
+
if (keyBasedValue !== 0) {
|
|
1791
1779
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1792
1780
|
note.gainL.gain
|
|
1793
|
-
.cancelScheduledValues(
|
|
1794
|
-
.setValueAtTime(gainLeft,
|
|
1781
|
+
.cancelScheduledValues(scheduleTime)
|
|
1782
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1795
1783
|
note.gainR.gain
|
|
1796
|
-
.cancelScheduledValues(
|
|
1797
|
-
.setValueAtTime(gainRight,
|
|
1784
|
+
.cancelScheduledValues(scheduleTime)
|
|
1785
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1798
1786
|
}
|
|
1799
1787
|
});
|
|
1800
1788
|
}
|
|
1801
|
-
setPan(channelNumber, pan) {
|
|
1789
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1802
1790
|
const channel = this.channels[channelNumber];
|
|
1803
1791
|
channel.state.pan = pan / 127;
|
|
1804
|
-
this.updateChannelVolume(channel);
|
|
1805
|
-
this.setKeyBasedPan(channel);
|
|
1792
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1793
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1806
1794
|
}
|
|
1807
|
-
setExpression(channelNumber, expression) {
|
|
1795
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1808
1796
|
const channel = this.channels[channelNumber];
|
|
1809
1797
|
channel.state.expression = expression / 127;
|
|
1810
|
-
this.updateChannelVolume(channel);
|
|
1798
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1811
1799
|
}
|
|
1812
1800
|
setBankLSB(channelNumber, lsb) {
|
|
1813
1801
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1814
1802
|
}
|
|
1815
|
-
dataEntryLSB(channelNumber, value) {
|
|
1803
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1816
1804
|
this.channels[channelNumber].dataLSB = value;
|
|
1817
|
-
this.handleRPN(channelNumber);
|
|
1805
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1818
1806
|
}
|
|
1819
|
-
updateChannelVolume(channel) {
|
|
1820
|
-
const now = this.audioContext.currentTime;
|
|
1807
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1821
1808
|
const state = channel.state;
|
|
1822
1809
|
const volume = state.volume * state.expression;
|
|
1823
1810
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1824
1811
|
channel.gainL.gain
|
|
1825
|
-
.cancelScheduledValues(
|
|
1826
|
-
.setValueAtTime(volume * gainLeft,
|
|
1812
|
+
.cancelScheduledValues(scheduleTime)
|
|
1813
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1827
1814
|
channel.gainR.gain
|
|
1828
|
-
.cancelScheduledValues(
|
|
1829
|
-
.setValueAtTime(volume * gainRight,
|
|
1815
|
+
.cancelScheduledValues(scheduleTime)
|
|
1816
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1830
1817
|
}
|
|
1831
|
-
setSustainPedal(channelNumber, value) {
|
|
1818
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1819
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1832
1820
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1833
1821
|
if (value < 64) {
|
|
1834
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1822
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1835
1823
|
}
|
|
1836
1824
|
}
|
|
1837
1825
|
setPortamento(channelNumber, value) {
|
|
1838
1826
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1839
1827
|
}
|
|
1840
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1828
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1841
1829
|
const channel = this.channels[channelNumber];
|
|
1842
1830
|
channel.state.sostenutoPedal = value / 127;
|
|
1843
1831
|
if (64 <= value) {
|
|
1844
|
-
|
|
1845
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1832
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1846
1833
|
}
|
|
1847
1834
|
else {
|
|
1848
1835
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
1849
1836
|
}
|
|
1850
1837
|
}
|
|
1851
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1838
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1852
1839
|
const channel = this.channels[channelNumber];
|
|
1853
1840
|
channel.state.softPedal = softPedal / 127;
|
|
1854
1841
|
}
|
|
1855
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1842
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1856
1843
|
const channel = this.channels[channelNumber];
|
|
1857
1844
|
const state = channel.state;
|
|
1858
1845
|
const reverbEffect = this.reverbEffect;
|
|
1859
1846
|
if (0 < state.reverbSendLevel) {
|
|
1860
1847
|
if (0 < reverbSendLevel) {
|
|
1861
|
-
const now = this.audioContext.currentTime;
|
|
1862
1848
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1863
|
-
reverbEffect.input.gain
|
|
1864
|
-
|
|
1849
|
+
reverbEffect.input.gain
|
|
1850
|
+
.cancelScheduledValues(scheduleTime)
|
|
1851
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1865
1852
|
}
|
|
1866
1853
|
else {
|
|
1867
1854
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1878,31 +1865,31 @@ class MidyGM2 {
|
|
|
1878
1865
|
}
|
|
1879
1866
|
else {
|
|
1880
1867
|
if (0 < reverbSendLevel) {
|
|
1881
|
-
const now = this.audioContext.currentTime;
|
|
1882
1868
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1883
1869
|
for (let i = 0; i < noteList.length; i++) {
|
|
1884
1870
|
const note = noteList[i];
|
|
1885
1871
|
if (!note)
|
|
1886
1872
|
continue;
|
|
1887
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1873
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1888
1874
|
}
|
|
1889
1875
|
});
|
|
1890
1876
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1891
|
-
reverbEffect.input.gain
|
|
1892
|
-
|
|
1877
|
+
reverbEffect.input.gain
|
|
1878
|
+
.cancelScheduledValues(scheduleTime)
|
|
1879
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1893
1880
|
}
|
|
1894
1881
|
}
|
|
1895
1882
|
}
|
|
1896
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1883
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
1897
1884
|
const channel = this.channels[channelNumber];
|
|
1898
1885
|
const state = channel.state;
|
|
1899
1886
|
const chorusEffect = this.chorusEffect;
|
|
1900
1887
|
if (0 < state.chorusSendLevel) {
|
|
1901
1888
|
if (0 < chorusSendLevel) {
|
|
1902
|
-
const now = this.audioContext.currentTime;
|
|
1903
1889
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1904
|
-
chorusEffect.input.gain
|
|
1905
|
-
|
|
1890
|
+
chorusEffect.input.gain
|
|
1891
|
+
.cancelScheduledValues(scheduleTime)
|
|
1892
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1906
1893
|
}
|
|
1907
1894
|
else {
|
|
1908
1895
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1919,18 +1906,18 @@ class MidyGM2 {
|
|
|
1919
1906
|
}
|
|
1920
1907
|
else {
|
|
1921
1908
|
if (0 < chorusSendLevel) {
|
|
1922
|
-
const now = this.audioContext.currentTime;
|
|
1923
1909
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1924
1910
|
for (let i = 0; i < noteList.length; i++) {
|
|
1925
1911
|
const note = noteList[i];
|
|
1926
1912
|
if (!note)
|
|
1927
1913
|
continue;
|
|
1928
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1914
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1929
1915
|
}
|
|
1930
1916
|
});
|
|
1931
1917
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1932
|
-
chorusEffect.input.gain
|
|
1933
|
-
|
|
1918
|
+
chorusEffect.input.gain
|
|
1919
|
+
.cancelScheduledValues(scheduleTime)
|
|
1920
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1934
1921
|
}
|
|
1935
1922
|
}
|
|
1936
1923
|
}
|
|
@@ -1960,12 +1947,12 @@ class MidyGM2 {
|
|
|
1960
1947
|
channel.dataMSB = minMSB;
|
|
1961
1948
|
}
|
|
1962
1949
|
}
|
|
1963
|
-
handleRPN(channelNumber) {
|
|
1950
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1964
1951
|
const channel = this.channels[channelNumber];
|
|
1965
1952
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1966
1953
|
switch (rpn) {
|
|
1967
1954
|
case 0:
|
|
1968
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1955
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1969
1956
|
break;
|
|
1970
1957
|
case 1:
|
|
1971
1958
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1986,25 +1973,26 @@ class MidyGM2 {
|
|
|
1986
1973
|
setRPNLSB(channelNumber, value) {
|
|
1987
1974
|
this.channels[channelNumber].rpnLSB = value;
|
|
1988
1975
|
}
|
|
1989
|
-
dataEntryMSB(channelNumber, value) {
|
|
1976
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1990
1977
|
this.channels[channelNumber].dataMSB = value;
|
|
1991
|
-
this.handleRPN(channelNumber);
|
|
1978
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1992
1979
|
}
|
|
1993
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1980
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1994
1981
|
const channel = this.channels[channelNumber];
|
|
1995
1982
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1996
1983
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1997
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1984
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1998
1985
|
}
|
|
1999
|
-
setPitchBendRange(channelNumber, value) {
|
|
1986
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1987
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2000
1988
|
const channel = this.channels[channelNumber];
|
|
2001
1989
|
const state = channel.state;
|
|
2002
1990
|
const prev = state.pitchWheelSensitivity;
|
|
2003
1991
|
const next = value / 128;
|
|
2004
1992
|
state.pitchWheelSensitivity = next;
|
|
2005
1993
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2006
|
-
this.updateChannelDetune(channel);
|
|
2007
|
-
this.applyVoiceParams(channel, 16);
|
|
1994
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1995
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2008
1996
|
}
|
|
2009
1997
|
handleFineTuningRPN(channelNumber) {
|
|
2010
1998
|
const channel = this.channels[channelNumber];
|
|
@@ -2045,8 +2033,9 @@ class MidyGM2 {
|
|
|
2045
2033
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2046
2034
|
this.updateModulation(channel);
|
|
2047
2035
|
}
|
|
2048
|
-
allSoundOff(channelNumber) {
|
|
2049
|
-
|
|
2036
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2037
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2038
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2050
2039
|
}
|
|
2051
2040
|
resetAllControllers(channelNumber) {
|
|
2052
2041
|
const stateTypes = [
|
|
@@ -2074,8 +2063,9 @@ class MidyGM2 {
|
|
|
2074
2063
|
channel[type] = this.constructor.channelSettings[type];
|
|
2075
2064
|
}
|
|
2076
2065
|
}
|
|
2077
|
-
allNotesOff(channelNumber) {
|
|
2078
|
-
|
|
2066
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2067
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2068
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2079
2069
|
}
|
|
2080
2070
|
omniOff() {
|
|
2081
2071
|
this.omni = false;
|
|
@@ -2089,13 +2079,13 @@ class MidyGM2 {
|
|
|
2089
2079
|
polyOn() {
|
|
2090
2080
|
this.mono = false;
|
|
2091
2081
|
}
|
|
2092
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2082
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2093
2083
|
switch (data[2]) {
|
|
2094
2084
|
case 8:
|
|
2095
2085
|
switch (data[3]) {
|
|
2096
2086
|
case 8:
|
|
2097
2087
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2098
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2088
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2099
2089
|
default:
|
|
2100
2090
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2101
2091
|
}
|
|
@@ -2138,18 +2128,18 @@ class MidyGM2 {
|
|
|
2138
2128
|
this.channels[9].bankMSB = 120;
|
|
2139
2129
|
this.channels[9].bank = 120 * 128;
|
|
2140
2130
|
}
|
|
2141
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2131
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2142
2132
|
switch (data[2]) {
|
|
2143
2133
|
case 4:
|
|
2144
2134
|
switch (data[3]) {
|
|
2145
2135
|
case 1:
|
|
2146
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2136
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2147
2137
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2148
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2138
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2149
2139
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2150
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2140
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2151
2141
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2152
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2142
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2153
2143
|
default:
|
|
2154
2144
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2155
2145
|
}
|
|
@@ -2167,7 +2157,7 @@ class MidyGM2 {
|
|
|
2167
2157
|
case 10:
|
|
2168
2158
|
switch (data[3]) {
|
|
2169
2159
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2170
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2160
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2171
2161
|
default:
|
|
2172
2162
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2173
2163
|
}
|
|
@@ -2176,49 +2166,50 @@ class MidyGM2 {
|
|
|
2176
2166
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2177
2167
|
}
|
|
2178
2168
|
}
|
|
2179
|
-
handleMasterVolumeSysEx(data) {
|
|
2169
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2180
2170
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2181
|
-
this.setMasterVolume(volume);
|
|
2171
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2182
2172
|
}
|
|
2183
|
-
setMasterVolume(volume) {
|
|
2173
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2174
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2184
2175
|
if (volume < 0 && 1 < volume) {
|
|
2185
2176
|
console.error("Master Volume is out of range");
|
|
2186
2177
|
}
|
|
2187
2178
|
else {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2179
|
+
this.masterVolume.gain
|
|
2180
|
+
.cancelScheduledValues(scheduleTime)
|
|
2181
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2191
2182
|
}
|
|
2192
2183
|
}
|
|
2193
|
-
handleMasterFineTuningSysEx(data) {
|
|
2184
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2194
2185
|
const fineTuning = data[5] * 128 + data[4];
|
|
2195
|
-
this.setMasterFineTuning(fineTuning);
|
|
2186
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2196
2187
|
}
|
|
2197
|
-
setMasterFineTuning(value) {
|
|
2188
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2198
2189
|
const prev = this.masterFineTuning;
|
|
2199
2190
|
const next = (value - 8192) / 8.192; // cent
|
|
2200
2191
|
this.masterFineTuning = next;
|
|
2201
2192
|
channel.detune += next - prev;
|
|
2202
|
-
this.updateChannelDetune(channel);
|
|
2193
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2203
2194
|
}
|
|
2204
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2195
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2205
2196
|
const coarseTuning = data[4];
|
|
2206
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2197
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2207
2198
|
}
|
|
2208
|
-
setMasterCoarseTuning(value) {
|
|
2199
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2209
2200
|
const prev = this.masterCoarseTuning;
|
|
2210
2201
|
const next = (value - 64) * 100; // cent
|
|
2211
2202
|
this.masterCoarseTuning = next;
|
|
2212
2203
|
channel.detune += next - prev;
|
|
2213
|
-
this.updateChannelDetune(channel);
|
|
2204
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2214
2205
|
}
|
|
2215
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2206
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2216
2207
|
if (data[7] === 1) {
|
|
2217
2208
|
switch (data[8]) {
|
|
2218
2209
|
case 1:
|
|
2219
2210
|
return this.handleReverbParameterSysEx(data);
|
|
2220
2211
|
case 2:
|
|
2221
|
-
return this.handleChorusParameterSysEx(data);
|
|
2212
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2222
2213
|
default:
|
|
2223
2214
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2224
2215
|
}
|
|
@@ -2297,88 +2288,84 @@ class MidyGM2 {
|
|
|
2297
2288
|
calcDelay(rt60, feedback) {
|
|
2298
2289
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2299
2290
|
}
|
|
2300
|
-
handleChorusParameterSysEx(data) {
|
|
2291
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2301
2292
|
switch (data[9]) {
|
|
2302
2293
|
case 0:
|
|
2303
|
-
return this.setChorusType(data[10]);
|
|
2294
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2304
2295
|
case 1:
|
|
2305
|
-
return this.setChorusModRate(data[10]);
|
|
2296
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2306
2297
|
case 2:
|
|
2307
|
-
return this.setChorusModDepth(data[10]);
|
|
2298
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2308
2299
|
case 3:
|
|
2309
|
-
return this.setChorusFeedback(data[10]);
|
|
2300
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2310
2301
|
case 4:
|
|
2311
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2302
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2312
2303
|
}
|
|
2313
2304
|
}
|
|
2314
|
-
setChorusType(type) {
|
|
2305
|
+
setChorusType(type, scheduleTime) {
|
|
2315
2306
|
switch (type) {
|
|
2316
2307
|
case 0:
|
|
2317
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2308
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2318
2309
|
case 1:
|
|
2319
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2310
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2320
2311
|
case 2:
|
|
2321
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2312
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2322
2313
|
case 3:
|
|
2323
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2314
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2324
2315
|
case 4:
|
|
2325
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2316
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2326
2317
|
case 5:
|
|
2327
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2318
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2328
2319
|
default:
|
|
2329
2320
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2330
2321
|
}
|
|
2331
2322
|
}
|
|
2332
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2333
|
-
this.setChorusModRate(modRate);
|
|
2334
|
-
this.setChorusModDepth(modDepth);
|
|
2335
|
-
this.setChorusFeedback(feedback);
|
|
2336
|
-
this.setChorusSendToReverb(sendToReverb);
|
|
2323
|
+
setChorusParameter(modRate, modDepth, feedback, sendToReverb, scheduleTime) {
|
|
2324
|
+
this.setChorusModRate(modRate, scheduleTime);
|
|
2325
|
+
this.setChorusModDepth(modDepth, scheduleTime);
|
|
2326
|
+
this.setChorusFeedback(feedback, scheduleTime);
|
|
2327
|
+
this.setChorusSendToReverb(sendToReverb, scheduleTime);
|
|
2337
2328
|
}
|
|
2338
|
-
setChorusModRate(value) {
|
|
2339
|
-
const now = this.audioContext.currentTime;
|
|
2329
|
+
setChorusModRate(value, scheduleTime) {
|
|
2340
2330
|
const modRate = this.getChorusModRate(value);
|
|
2341
2331
|
this.chorus.modRate = modRate;
|
|
2342
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2332
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2343
2333
|
}
|
|
2344
2334
|
getChorusModRate(value) {
|
|
2345
2335
|
return value * 0.122; // Hz
|
|
2346
2336
|
}
|
|
2347
|
-
setChorusModDepth(value) {
|
|
2348
|
-
const now = this.audioContext.currentTime;
|
|
2337
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2349
2338
|
const modDepth = this.getChorusModDepth(value);
|
|
2350
2339
|
this.chorus.modDepth = modDepth;
|
|
2351
2340
|
this.chorusEffect.lfoGain.gain
|
|
2352
|
-
.cancelScheduledValues(
|
|
2353
|
-
.setValueAtTime(modDepth / 2,
|
|
2341
|
+
.cancelScheduledValues(scheduleTime)
|
|
2342
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2354
2343
|
}
|
|
2355
2344
|
getChorusModDepth(value) {
|
|
2356
2345
|
return (value + 1) / 3200; // second
|
|
2357
2346
|
}
|
|
2358
|
-
setChorusFeedback(value) {
|
|
2359
|
-
const now = this.audioContext.currentTime;
|
|
2347
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2360
2348
|
const feedback = this.getChorusFeedback(value);
|
|
2361
2349
|
this.chorus.feedback = feedback;
|
|
2362
2350
|
const chorusEffect = this.chorusEffect;
|
|
2363
2351
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2364
2352
|
chorusEffect.feedbackGains[i].gain
|
|
2365
|
-
.cancelScheduledValues(
|
|
2366
|
-
.setValueAtTime(feedback,
|
|
2353
|
+
.cancelScheduledValues(scheduleTime)
|
|
2354
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2367
2355
|
}
|
|
2368
2356
|
}
|
|
2369
2357
|
getChorusFeedback(value) {
|
|
2370
2358
|
return value * 0.00763;
|
|
2371
2359
|
}
|
|
2372
|
-
setChorusSendToReverb(value) {
|
|
2360
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2373
2361
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2374
2362
|
const sendGain = this.chorusEffect.sendGain;
|
|
2375
2363
|
if (0 < this.chorus.sendToReverb) {
|
|
2376
2364
|
this.chorus.sendToReverb = sendToReverb;
|
|
2377
2365
|
if (0 < sendToReverb) {
|
|
2378
|
-
const now = this.audioContext.currentTime;
|
|
2379
2366
|
sendGain.gain
|
|
2380
|
-
.cancelScheduledValues(
|
|
2381
|
-
.setValueAtTime(sendToReverb,
|
|
2367
|
+
.cancelScheduledValues(scheduleTime)
|
|
2368
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2382
2369
|
}
|
|
2383
2370
|
else {
|
|
2384
2371
|
sendGain.disconnect();
|
|
@@ -2387,11 +2374,10 @@ class MidyGM2 {
|
|
|
2387
2374
|
else {
|
|
2388
2375
|
this.chorus.sendToReverb = sendToReverb;
|
|
2389
2376
|
if (0 < sendToReverb) {
|
|
2390
|
-
const now = this.audioContext.currentTime;
|
|
2391
2377
|
sendGain.connect(this.reverbEffect.input);
|
|
2392
2378
|
sendGain.gain
|
|
2393
|
-
.cancelScheduledValues(
|
|
2394
|
-
.setValueAtTime(sendToReverb,
|
|
2379
|
+
.cancelScheduledValues(scheduleTime)
|
|
2380
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2395
2381
|
}
|
|
2396
2382
|
}
|
|
2397
2383
|
}
|
|
@@ -2417,7 +2403,7 @@ class MidyGM2 {
|
|
|
2417
2403
|
}
|
|
2418
2404
|
return bitmap;
|
|
2419
2405
|
}
|
|
2420
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2406
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2421
2407
|
if (data.length < 19) {
|
|
2422
2408
|
console.error("Data length is too short");
|
|
2423
2409
|
return;
|
|
@@ -2432,47 +2418,51 @@ class MidyGM2 {
|
|
|
2432
2418
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2433
2419
|
}
|
|
2434
2420
|
if (realtime)
|
|
2435
|
-
this.updateChannelDetune(channel);
|
|
2421
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2436
2422
|
}
|
|
2437
2423
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
if (!note.portamento) {
|
|
2443
|
-
if (table[1] !== 64) {
|
|
2444
|
-
const channelPressure = channel.channelPressureTable[1] *
|
|
2445
|
-
channel.state.channelPressure;
|
|
2446
|
-
const pressure = (channelPressure - 64) * 15;
|
|
2447
|
-
this.setFilterEnvelope(channel, note, pressure);
|
|
2448
|
-
}
|
|
2449
|
-
if (table[2] !== 64) {
|
|
2450
|
-
const channelPressure = channel.channelPressureTable[2] *
|
|
2451
|
-
channel.state.channelPressure;
|
|
2452
|
-
const pressure = channelPressure / 64;
|
|
2453
|
-
this.setVolumeEnvelope(note, pressure);
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
if (table[3] !== 0) {
|
|
2457
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2458
|
-
channel.state.channelPressure;
|
|
2459
|
-
const pressure = channelPressure / 127 * 600;
|
|
2460
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2461
|
-
}
|
|
2462
|
-
if (table[4] !== 0) {
|
|
2463
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2464
|
-
channel.state.channelPressure;
|
|
2465
|
-
const pressure = channelPressure / 127 * 2400;
|
|
2466
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2467
|
-
}
|
|
2468
|
-
if (table[5] !== 0) {
|
|
2469
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2470
|
-
channel.state.channelPressure;
|
|
2471
|
-
const pressure = channelPressure / 127;
|
|
2472
|
-
this.setModLfoToVolume(note, pressure);
|
|
2473
|
-
}
|
|
2424
|
+
getFilterCutoffControl(channel) {
|
|
2425
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2426
|
+
channel.state.channelPressure;
|
|
2427
|
+
return channelPressure * 15;
|
|
2474
2428
|
}
|
|
2475
|
-
|
|
2429
|
+
getAmplitudeControl(channel) {
|
|
2430
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2431
|
+
channel.state.channelPressure;
|
|
2432
|
+
return channelPressure / 64;
|
|
2433
|
+
}
|
|
2434
|
+
getLFOPitchDepth(channel) {
|
|
2435
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2436
|
+
channel.state.channelPressure;
|
|
2437
|
+
return channelPressure / 127 * 600;
|
|
2438
|
+
}
|
|
2439
|
+
getLFOFilterDepth(channel) {
|
|
2440
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2441
|
+
channel.state.channelPressure;
|
|
2442
|
+
return channelPressure / 127 * 2400;
|
|
2443
|
+
}
|
|
2444
|
+
getLFOAmplitudeDepth(channel) {
|
|
2445
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2446
|
+
channel.state.channelPressure;
|
|
2447
|
+
return channelPressure / 127;
|
|
2448
|
+
}
|
|
2449
|
+
setControllerParameters(channel, note, table) {
|
|
2450
|
+
if (table[0] !== 64)
|
|
2451
|
+
this.updateDetune(channel, note);
|
|
2452
|
+
if (!note.portamento) {
|
|
2453
|
+
if (table[1] !== 64)
|
|
2454
|
+
this.setFilterEnvelope(channel, note);
|
|
2455
|
+
if (table[2] !== 64)
|
|
2456
|
+
this.setVolumeEnvelope(channel, note);
|
|
2457
|
+
}
|
|
2458
|
+
if (table[3] !== 0)
|
|
2459
|
+
this.setModLfoToPitch(channel, note);
|
|
2460
|
+
if (table[4] !== 0)
|
|
2461
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2462
|
+
if (table[5] !== 0)
|
|
2463
|
+
this.setModLfoToVolume(channel, note);
|
|
2464
|
+
}
|
|
2465
|
+
handlePressureSysEx(data, tableName) {
|
|
2476
2466
|
const channelNumber = data[4];
|
|
2477
2467
|
const table = this.channels[channelNumber][tableName];
|
|
2478
2468
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2501,7 +2491,7 @@ class MidyGM2 {
|
|
|
2501
2491
|
const note = noteList[i];
|
|
2502
2492
|
if (!note)
|
|
2503
2493
|
continue;
|
|
2504
|
-
this.
|
|
2494
|
+
this.setControllerParameters(channel, note, table);
|
|
2505
2495
|
}
|
|
2506
2496
|
});
|
|
2507
2497
|
}
|
|
@@ -2520,7 +2510,7 @@ class MidyGM2 {
|
|
|
2520
2510
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2521
2511
|
return (controlValue + 64) / 64;
|
|
2522
2512
|
}
|
|
2523
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2513
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2524
2514
|
const channelNumber = data[4];
|
|
2525
2515
|
const keyNumber = data[5];
|
|
2526
2516
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2530,30 +2520,27 @@ class MidyGM2 {
|
|
|
2530
2520
|
const index = keyNumber * 128 + controllerType;
|
|
2531
2521
|
table[index] = value - 64;
|
|
2532
2522
|
}
|
|
2533
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2534
|
-
}
|
|
2535
|
-
handleExclusiveMessage(data) {
|
|
2536
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2523
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2537
2524
|
}
|
|
2538
|
-
handleSysEx(data) {
|
|
2525
|
+
handleSysEx(data, scheduleTime) {
|
|
2539
2526
|
switch (data[0]) {
|
|
2540
2527
|
case 126:
|
|
2541
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2528
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2542
2529
|
case 127:
|
|
2543
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2530
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2544
2531
|
default:
|
|
2545
|
-
|
|
2532
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2546
2533
|
}
|
|
2547
2534
|
}
|
|
2548
|
-
scheduleTask(callback,
|
|
2535
|
+
scheduleTask(callback, scheduleTime) {
|
|
2549
2536
|
return new Promise((resolve) => {
|
|
2550
2537
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2551
2538
|
bufferSource.onended = () => {
|
|
2552
2539
|
callback();
|
|
2553
2540
|
resolve();
|
|
2554
2541
|
};
|
|
2555
|
-
bufferSource.start(
|
|
2556
|
-
bufferSource.stop(
|
|
2542
|
+
bufferSource.start(scheduleTime);
|
|
2543
|
+
bufferSource.stop(scheduleTime);
|
|
2557
2544
|
});
|
|
2558
2545
|
}
|
|
2559
2546
|
}
|
|
@@ -2565,9 +2552,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2565
2552
|
value: {
|
|
2566
2553
|
currentBufferSource: null,
|
|
2567
2554
|
detune: 0,
|
|
2568
|
-
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
2569
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2570
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2571
2555
|
program: 0,
|
|
2572
2556
|
bank: 121 * 128,
|
|
2573
2557
|
bankMSB: 121,
|