@marmooo/midy 0.2.6 → 0.2.8
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 +18 -13
- package/esm/midy-GM1.d.ts +73 -74
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +207 -218
- package/esm/midy-GM2.d.ts +125 -127
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +358 -418
- package/esm/midy-GMLite.d.ts +69 -70
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +195 -207
- package/esm/midy.d.ts +148 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +404 -500
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +73 -74
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +207 -218
- package/script/midy-GM2.d.ts +125 -127
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +358 -418
- package/script/midy-GMLite.d.ts +69 -70
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +195 -207
- package/script/midy.d.ts +148 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +404 -500
package/script/midy-GM2.js
CHANGED
|
@@ -287,18 +287,6 @@ class MidyGM2 {
|
|
|
287
287
|
delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
|
|
288
288
|
}
|
|
289
289
|
});
|
|
290
|
-
Object.defineProperty(this, "mono", {
|
|
291
|
-
enumerable: true,
|
|
292
|
-
configurable: true,
|
|
293
|
-
writable: true,
|
|
294
|
-
value: false
|
|
295
|
-
}); // CC#124, CC#125
|
|
296
|
-
Object.defineProperty(this, "omni", {
|
|
297
|
-
enumerable: true,
|
|
298
|
-
configurable: true,
|
|
299
|
-
writable: true,
|
|
300
|
-
value: false
|
|
301
|
-
}); // CC#126, CC#127
|
|
302
290
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
303
291
|
enumerable: true,
|
|
304
292
|
configurable: true,
|
|
@@ -500,6 +488,7 @@ class MidyGM2 {
|
|
|
500
488
|
controlTable: this.initControlTable(),
|
|
501
489
|
...this.setChannelAudioNodes(audioContext),
|
|
502
490
|
scheduledNotes: new SparseMap(128),
|
|
491
|
+
sustainNotes: [],
|
|
503
492
|
sostenutoNotes: new SparseMap(128),
|
|
504
493
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
505
494
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
@@ -582,14 +571,15 @@ class MidyGM2 {
|
|
|
582
571
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
583
572
|
if (portamentoTarget)
|
|
584
573
|
portamentoTarget.portamento = true;
|
|
585
|
-
const notePromise = this.
|
|
574
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
575
|
+
portamentoTarget?.noteNumber);
|
|
586
576
|
if (notePromise) {
|
|
587
577
|
this.notePromises.push(notePromise);
|
|
588
578
|
}
|
|
589
579
|
break;
|
|
590
580
|
}
|
|
591
581
|
case "controller":
|
|
592
|
-
this.handleControlChange(
|
|
582
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
593
583
|
break;
|
|
594
584
|
case "programChange":
|
|
595
585
|
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
@@ -632,10 +622,11 @@ class MidyGM2 {
|
|
|
632
622
|
resolve();
|
|
633
623
|
return;
|
|
634
624
|
}
|
|
635
|
-
const
|
|
625
|
+
const now = this.audioContext.currentTime;
|
|
626
|
+
const t = now + offset;
|
|
636
627
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
637
628
|
if (this.isPausing) {
|
|
638
|
-
await this.stopNotes(0, true);
|
|
629
|
+
await this.stopNotes(0, true, now);
|
|
639
630
|
this.notePromises = [];
|
|
640
631
|
resolve();
|
|
641
632
|
this.isPausing = false;
|
|
@@ -643,7 +634,7 @@ class MidyGM2 {
|
|
|
643
634
|
return;
|
|
644
635
|
}
|
|
645
636
|
else if (this.isStopping) {
|
|
646
|
-
await this.stopNotes(0, true);
|
|
637
|
+
await this.stopNotes(0, true, now);
|
|
647
638
|
this.notePromises = [];
|
|
648
639
|
this.exclusiveClassMap.clear();
|
|
649
640
|
this.audioBufferCache.clear();
|
|
@@ -653,7 +644,7 @@ class MidyGM2 {
|
|
|
653
644
|
return;
|
|
654
645
|
}
|
|
655
646
|
else if (this.isSeeking) {
|
|
656
|
-
this.stopNotes(0, true);
|
|
647
|
+
this.stopNotes(0, true, now);
|
|
657
648
|
this.exclusiveClassMap.clear();
|
|
658
649
|
this.startTime = this.audioContext.currentTime;
|
|
659
650
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -662,7 +653,6 @@ class MidyGM2 {
|
|
|
662
653
|
await schedulePlayback();
|
|
663
654
|
}
|
|
664
655
|
else {
|
|
665
|
-
const now = this.audioContext.currentTime;
|
|
666
656
|
const waitTime = now + this.noteCheckInterval;
|
|
667
657
|
await this.scheduleTask(() => { }, waitTime);
|
|
668
658
|
await schedulePlayback();
|
|
@@ -782,25 +772,21 @@ class MidyGM2 {
|
|
|
782
772
|
}
|
|
783
773
|
return { instruments, timeline };
|
|
784
774
|
}
|
|
785
|
-
|
|
786
|
-
const now = this.audioContext.currentTime;
|
|
775
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
787
776
|
const channel = this.channels[channelNumber];
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, undefined, // portamentoNoteNumber
|
|
794
|
-
force);
|
|
795
|
-
this.notePromises.push(promise);
|
|
796
|
-
}
|
|
777
|
+
const promises = [];
|
|
778
|
+
this.processScheduledNotes(channel, (note) => {
|
|
779
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
780
|
+
this.notePromises.push(promise);
|
|
781
|
+
promises.push(promise);
|
|
797
782
|
});
|
|
798
783
|
channel.scheduledNotes.clear();
|
|
799
|
-
|
|
784
|
+
return Promise.all(promises);
|
|
800
785
|
}
|
|
801
|
-
stopNotes(velocity, force) {
|
|
786
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
787
|
+
const promises = [];
|
|
802
788
|
for (let i = 0; i < this.channels.length; i++) {
|
|
803
|
-
this.stopChannelNotes(i, velocity, force);
|
|
789
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
804
790
|
}
|
|
805
791
|
return Promise.all(this.notePromises);
|
|
806
792
|
}
|
|
@@ -848,34 +834,32 @@ class MidyGM2 {
|
|
|
848
834
|
const now = this.audioContext.currentTime;
|
|
849
835
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
850
836
|
}
|
|
851
|
-
processScheduledNotes(channel,
|
|
837
|
+
processScheduledNotes(channel, callback) {
|
|
852
838
|
channel.scheduledNotes.forEach((noteList) => {
|
|
853
839
|
for (let i = 0; i < noteList.length; i++) {
|
|
854
840
|
const note = noteList[i];
|
|
855
841
|
if (!note)
|
|
856
842
|
continue;
|
|
857
|
-
if (scheduleTime < note.startTime)
|
|
858
|
-
continue;
|
|
859
843
|
callback(note);
|
|
860
844
|
}
|
|
861
845
|
});
|
|
862
846
|
}
|
|
863
|
-
getActiveNotes(channel,
|
|
847
|
+
getActiveNotes(channel, scheduleTime) {
|
|
864
848
|
const activeNotes = new SparseMap(128);
|
|
865
849
|
channel.scheduledNotes.forEach((noteList) => {
|
|
866
|
-
const activeNote = this.getActiveNote(noteList,
|
|
850
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
867
851
|
if (activeNote) {
|
|
868
852
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
869
853
|
}
|
|
870
854
|
});
|
|
871
855
|
return activeNotes;
|
|
872
856
|
}
|
|
873
|
-
getActiveNote(noteList,
|
|
857
|
+
getActiveNote(noteList, scheduleTime) {
|
|
874
858
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
875
859
|
const note = noteList[i];
|
|
876
860
|
if (!note)
|
|
877
861
|
return;
|
|
878
|
-
if (
|
|
862
|
+
if (scheduleTime < note.startTime)
|
|
879
863
|
continue;
|
|
880
864
|
return (note.ending) ? null : note;
|
|
881
865
|
}
|
|
@@ -1035,43 +1019,35 @@ class MidyGM2 {
|
|
|
1035
1019
|
calcNoteDetune(channel, note) {
|
|
1036
1020
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1037
1021
|
}
|
|
1038
|
-
updateChannelDetune(channel) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const note = noteList[i];
|
|
1042
|
-
if (!note)
|
|
1043
|
-
continue;
|
|
1044
|
-
this.updateDetune(channel, note);
|
|
1045
|
-
}
|
|
1022
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1023
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1024
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1046
1025
|
});
|
|
1047
1026
|
}
|
|
1048
|
-
updateDetune(channel, note) {
|
|
1049
|
-
const now = this.audioContext.currentTime;
|
|
1027
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1050
1028
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1051
1029
|
const detune = channel.detune + noteDetune;
|
|
1052
1030
|
note.bufferSource.detune
|
|
1053
|
-
.cancelScheduledValues(
|
|
1054
|
-
.setValueAtTime(detune,
|
|
1031
|
+
.cancelScheduledValues(scheduleTime)
|
|
1032
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1055
1033
|
}
|
|
1056
1034
|
getPortamentoTime(channel) {
|
|
1057
1035
|
const factor = 5 * Math.log(10) / 127;
|
|
1058
1036
|
const time = channel.state.portamentoTime;
|
|
1059
1037
|
return Math.log(time) / factor;
|
|
1060
1038
|
}
|
|
1061
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1062
|
-
const now = this.audioContext.currentTime;
|
|
1039
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1063
1040
|
const { voiceParams, startTime } = note;
|
|
1064
1041
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1065
1042
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1066
1043
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1067
1044
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1068
1045
|
note.volumeEnvelopeNode.gain
|
|
1069
|
-
.cancelScheduledValues(
|
|
1046
|
+
.cancelScheduledValues(scheduleTime)
|
|
1070
1047
|
.setValueAtTime(0, volDelay)
|
|
1071
1048
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1072
1049
|
}
|
|
1073
|
-
setVolumeEnvelope(channel, note) {
|
|
1074
|
-
const now = this.audioContext.currentTime;
|
|
1050
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1075
1051
|
const { voiceParams, startTime } = note;
|
|
1076
1052
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1077
1053
|
(1 + this.getAmplitudeControl(channel));
|
|
@@ -1081,7 +1057,7 @@ class MidyGM2 {
|
|
|
1081
1057
|
const volHold = volAttack + voiceParams.volHold;
|
|
1082
1058
|
const volDecay = volHold + voiceParams.volDecay;
|
|
1083
1059
|
note.volumeEnvelopeNode.gain
|
|
1084
|
-
.cancelScheduledValues(
|
|
1060
|
+
.cancelScheduledValues(scheduleTime)
|
|
1085
1061
|
.setValueAtTime(0, startTime)
|
|
1086
1062
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1087
1063
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -1089,7 +1065,6 @@ class MidyGM2 {
|
|
|
1089
1065
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1090
1066
|
}
|
|
1091
1067
|
setPitchEnvelope(note, scheduleTime) {
|
|
1092
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1093
1068
|
const { voiceParams } = note;
|
|
1094
1069
|
const baseRate = voiceParams.playbackRate;
|
|
1095
1070
|
note.bufferSource.playbackRate
|
|
@@ -1116,8 +1091,7 @@ class MidyGM2 {
|
|
|
1116
1091
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1117
1092
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1118
1093
|
}
|
|
1119
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1120
|
-
const now = this.audioContext.currentTime;
|
|
1094
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1121
1095
|
const state = channel.state;
|
|
1122
1096
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1123
1097
|
const softPedalFactor = 1 -
|
|
@@ -1132,13 +1106,12 @@ class MidyGM2 {
|
|
|
1132
1106
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1133
1107
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1134
1108
|
note.filterNode.frequency
|
|
1135
|
-
.cancelScheduledValues(
|
|
1109
|
+
.cancelScheduledValues(scheduleTime)
|
|
1136
1110
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1137
1111
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1138
1112
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1139
1113
|
}
|
|
1140
|
-
setFilterEnvelope(channel, note) {
|
|
1141
|
-
const now = this.audioContext.currentTime;
|
|
1114
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1142
1115
|
const state = channel.state;
|
|
1143
1116
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1144
1117
|
const softPedalFactor = 1 -
|
|
@@ -1158,14 +1131,14 @@ class MidyGM2 {
|
|
|
1158
1131
|
const modHold = modAttack + voiceParams.modHold;
|
|
1159
1132
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1160
1133
|
note.filterNode.frequency
|
|
1161
|
-
.cancelScheduledValues(
|
|
1134
|
+
.cancelScheduledValues(scheduleTime)
|
|
1162
1135
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1163
1136
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1164
1137
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1165
1138
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1166
1139
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1167
1140
|
}
|
|
1168
|
-
startModulation(channel, note,
|
|
1141
|
+
startModulation(channel, note, scheduleTime) {
|
|
1169
1142
|
const { voiceParams } = note;
|
|
1170
1143
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1171
1144
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1174,10 +1147,10 @@ class MidyGM2 {
|
|
|
1174
1147
|
gain: voiceParams.modLfoToFilterFc,
|
|
1175
1148
|
});
|
|
1176
1149
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1177
|
-
this.setModLfoToPitch(channel, note);
|
|
1150
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1178
1151
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1179
|
-
this.setModLfoToVolume(
|
|
1180
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1152
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1153
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1181
1154
|
note.modulationLFO.connect(note.filterDepth);
|
|
1182
1155
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1183
1156
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1185,15 +1158,15 @@ class MidyGM2 {
|
|
|
1185
1158
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1186
1159
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1187
1160
|
}
|
|
1188
|
-
startVibrato(channel, note,
|
|
1161
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1189
1162
|
const { voiceParams } = note;
|
|
1190
1163
|
const state = channel.state;
|
|
1191
1164
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1192
1165
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1193
1166
|
});
|
|
1194
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1167
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1195
1168
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1196
|
-
this.setVibLfoToPitch(channel, note);
|
|
1169
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1197
1170
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1198
1171
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1199
1172
|
}
|
|
@@ -1216,6 +1189,7 @@ class MidyGM2 {
|
|
|
1216
1189
|
}
|
|
1217
1190
|
}
|
|
1218
1191
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1192
|
+
const now = this.audioContext.currentTime;
|
|
1219
1193
|
const state = channel.state;
|
|
1220
1194
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1221
1195
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1232,22 +1206,22 @@ class MidyGM2 {
|
|
|
1232
1206
|
});
|
|
1233
1207
|
if (portamento) {
|
|
1234
1208
|
note.portamento = true;
|
|
1235
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1236
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1209
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1210
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1237
1211
|
}
|
|
1238
1212
|
else {
|
|
1239
1213
|
note.portamento = false;
|
|
1240
|
-
this.setVolumeEnvelope(channel, note);
|
|
1241
|
-
this.setFilterEnvelope(channel, note);
|
|
1214
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1215
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1242
1216
|
}
|
|
1243
1217
|
if (0 < state.vibratoDepth) {
|
|
1244
|
-
this.startVibrato(channel, note,
|
|
1218
|
+
this.startVibrato(channel, note, now);
|
|
1245
1219
|
}
|
|
1246
|
-
this.setPitchEnvelope(note);
|
|
1220
|
+
this.setPitchEnvelope(note, now);
|
|
1247
1221
|
if (0 < state.modulationDepth) {
|
|
1248
|
-
this.startModulation(channel, note,
|
|
1222
|
+
this.startModulation(channel, note, now);
|
|
1249
1223
|
}
|
|
1250
|
-
if (
|
|
1224
|
+
if (channel.mono && channel.currentBufferSource) {
|
|
1251
1225
|
channel.currentBufferSource.stop(startTime);
|
|
1252
1226
|
channel.currentBufferSource = note.bufferSource;
|
|
1253
1227
|
}
|
|
@@ -1257,10 +1231,10 @@ class MidyGM2 {
|
|
|
1257
1231
|
note.volumeNode.connect(note.gainL);
|
|
1258
1232
|
note.volumeNode.connect(note.gainR);
|
|
1259
1233
|
if (0 < channel.chorusSendLevel) {
|
|
1260
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1234
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1261
1235
|
}
|
|
1262
1236
|
if (0 < channel.reverbSendLevel) {
|
|
1263
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1237
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1264
1238
|
}
|
|
1265
1239
|
note.bufferSource.start(startTime);
|
|
1266
1240
|
return note;
|
|
@@ -1288,8 +1262,8 @@ class MidyGM2 {
|
|
|
1288
1262
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1289
1263
|
note.gainL.connect(channel.gainL);
|
|
1290
1264
|
note.gainR.connect(channel.gainR);
|
|
1291
|
-
if (channel.state.
|
|
1292
|
-
channel.
|
|
1265
|
+
if (0.5 <= channel.state.sustainPedal) {
|
|
1266
|
+
channel.sustainNotes.push(note);
|
|
1293
1267
|
}
|
|
1294
1268
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1295
1269
|
if (exclusiveClass !== 0) {
|
|
@@ -1297,9 +1271,9 @@ class MidyGM2 {
|
|
|
1297
1271
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1298
1272
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1299
1273
|
if (!prevNote.ending) {
|
|
1300
|
-
this.
|
|
1301
|
-
startTime,
|
|
1302
|
-
|
|
1274
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1275
|
+
startTime, true, // force
|
|
1276
|
+
undefined);
|
|
1303
1277
|
}
|
|
1304
1278
|
}
|
|
1305
1279
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1312,9 +1286,9 @@ class MidyGM2 {
|
|
|
1312
1286
|
scheduledNotes.set(noteNumber, [note]);
|
|
1313
1287
|
}
|
|
1314
1288
|
}
|
|
1315
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1316
|
-
|
|
1317
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
1289
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1290
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1291
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1318
1292
|
}
|
|
1319
1293
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1320
1294
|
const note = scheduledNotes[index];
|
|
@@ -1354,11 +1328,11 @@ class MidyGM2 {
|
|
|
1354
1328
|
note.bufferSource.stop(stopTime);
|
|
1355
1329
|
});
|
|
1356
1330
|
}
|
|
1357
|
-
|
|
1331
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1358
1332
|
const channel = this.channels[channelNumber];
|
|
1359
1333
|
const state = channel.state;
|
|
1360
1334
|
if (!force) {
|
|
1361
|
-
if (0.5
|
|
1335
|
+
if (0.5 <= state.sustainPedal)
|
|
1362
1336
|
return;
|
|
1363
1337
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1364
1338
|
return;
|
|
@@ -1393,68 +1367,60 @@ class MidyGM2 {
|
|
|
1393
1367
|
}
|
|
1394
1368
|
}
|
|
1395
1369
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
return this.
|
|
1370
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1371
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1372
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
|
|
1373
|
+
undefined);
|
|
1399
1374
|
}
|
|
1400
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1375
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1401
1376
|
const velocity = halfVelocity * 2;
|
|
1402
1377
|
const channel = this.channels[channelNumber];
|
|
1403
1378
|
const promises = [];
|
|
1404
|
-
channel.
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
continue;
|
|
1410
|
-
const { noteNumber } = note;
|
|
1411
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1412
|
-
promises.push(promise);
|
|
1413
|
-
}
|
|
1414
|
-
});
|
|
1379
|
+
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1380
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1381
|
+
promises.push(promise);
|
|
1382
|
+
}
|
|
1383
|
+
channel.sustainNotes = [];
|
|
1415
1384
|
return promises;
|
|
1416
1385
|
}
|
|
1417
|
-
releaseSostenutoPedal(channelNumber, halfVelocity) {
|
|
1386
|
+
releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1418
1387
|
const velocity = halfVelocity * 2;
|
|
1419
1388
|
const channel = this.channels[channelNumber];
|
|
1420
1389
|
const promises = [];
|
|
1421
1390
|
channel.state.sostenutoPedal = 0;
|
|
1422
|
-
channel.sostenutoNotes.forEach((
|
|
1423
|
-
const
|
|
1424
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1391
|
+
channel.sostenutoNotes.forEach((note) => {
|
|
1392
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1425
1393
|
promises.push(promise);
|
|
1426
1394
|
});
|
|
1427
1395
|
channel.sostenutoNotes.clear();
|
|
1428
1396
|
return promises;
|
|
1429
1397
|
}
|
|
1430
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1431
|
-
const channelNumber =
|
|
1398
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1399
|
+
const channelNumber = statusByte & 0x0F;
|
|
1432
1400
|
const messageType = statusByte & 0xF0;
|
|
1433
1401
|
switch (messageType) {
|
|
1434
1402
|
case 0x80:
|
|
1435
|
-
return this.
|
|
1403
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1436
1404
|
case 0x90:
|
|
1437
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1405
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1438
1406
|
case 0xB0:
|
|
1439
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1407
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1440
1408
|
case 0xC0:
|
|
1441
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1409
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1442
1410
|
case 0xD0:
|
|
1443
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1411
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1444
1412
|
case 0xE0:
|
|
1445
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1413
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1446
1414
|
default:
|
|
1447
1415
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1448
1416
|
}
|
|
1449
1417
|
}
|
|
1450
|
-
handleProgramChange(channelNumber, program) {
|
|
1418
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1451
1419
|
const channel = this.channels[channelNumber];
|
|
1452
1420
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1453
1421
|
channel.program = program;
|
|
1454
1422
|
}
|
|
1455
|
-
handleChannelPressure(channelNumber, value,
|
|
1456
|
-
if (!startTime)
|
|
1457
|
-
startTime = this.audioContext.currentTime;
|
|
1423
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1458
1424
|
const channel = this.channels[channelNumber];
|
|
1459
1425
|
const prev = channel.state.channelPressure;
|
|
1460
1426
|
const next = value / 127;
|
|
@@ -1464,72 +1430,68 @@ class MidyGM2 {
|
|
|
1464
1430
|
channel.detune += pressureDepth * (next - prev);
|
|
1465
1431
|
}
|
|
1466
1432
|
const table = channel.channelPressureTable;
|
|
1467
|
-
this.getActiveNotes(channel,
|
|
1433
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1468
1434
|
this.setControllerParameters(channel, note, table);
|
|
1469
1435
|
});
|
|
1470
1436
|
// this.applyVoiceParams(channel, 13);
|
|
1471
1437
|
}
|
|
1472
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1438
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1473
1439
|
const pitchBend = msb * 128 + lsb;
|
|
1474
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1440
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1475
1441
|
}
|
|
1476
|
-
setPitchBend(channelNumber, value) {
|
|
1442
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1443
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1477
1444
|
const channel = this.channels[channelNumber];
|
|
1478
1445
|
const state = channel.state;
|
|
1479
1446
|
const prev = state.pitchWheel * 2 - 1;
|
|
1480
1447
|
const next = (value - 8192) / 8192;
|
|
1481
1448
|
state.pitchWheel = value / 16383;
|
|
1482
1449
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1483
|
-
this.updateChannelDetune(channel);
|
|
1484
|
-
this.applyVoiceParams(channel, 14);
|
|
1450
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1451
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1485
1452
|
}
|
|
1486
|
-
setModLfoToPitch(channel, note) {
|
|
1487
|
-
const now = this.audioContext.currentTime;
|
|
1453
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1488
1454
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1489
1455
|
this.getLFOPitchDepth(channel);
|
|
1490
1456
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1491
1457
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1492
1458
|
note.modulationDepth.gain
|
|
1493
|
-
.cancelScheduledValues(
|
|
1494
|
-
.setValueAtTime(modulationDepth,
|
|
1459
|
+
.cancelScheduledValues(scheduleTime)
|
|
1460
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1495
1461
|
}
|
|
1496
|
-
setVibLfoToPitch(channel, note) {
|
|
1497
|
-
const now = this.audioContext.currentTime;
|
|
1462
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1498
1463
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1499
1464
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1500
1465
|
2;
|
|
1501
1466
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1502
1467
|
note.vibratoDepth.gain
|
|
1503
|
-
.cancelScheduledValues(
|
|
1504
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1468
|
+
.cancelScheduledValues(scheduleTime)
|
|
1469
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1505
1470
|
}
|
|
1506
|
-
setModLfoToFilterFc(channel, note) {
|
|
1507
|
-
const now = this.audioContext.currentTime;
|
|
1471
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1508
1472
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1509
1473
|
this.getLFOFilterDepth(channel);
|
|
1510
1474
|
note.filterDepth.gain
|
|
1511
|
-
.cancelScheduledValues(
|
|
1512
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1475
|
+
.cancelScheduledValues(scheduleTime)
|
|
1476
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1513
1477
|
}
|
|
1514
|
-
setModLfoToVolume(channel, note) {
|
|
1515
|
-
const now = this.audioContext.currentTime;
|
|
1478
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1516
1479
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1517
1480
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1518
1481
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1519
1482
|
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1520
1483
|
note.volumeDepth.gain
|
|
1521
|
-
.cancelScheduledValues(
|
|
1522
|
-
.setValueAtTime(volumeDepth,
|
|
1484
|
+
.cancelScheduledValues(scheduleTime)
|
|
1485
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1523
1486
|
}
|
|
1524
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1487
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1525
1488
|
if (0 < prevValue) {
|
|
1526
1489
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1527
|
-
const now = this.audioContext.currentTime;
|
|
1528
1490
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1529
1491
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1530
1492
|
note.reverbEffectsSend.gain
|
|
1531
|
-
.cancelScheduledValues(
|
|
1532
|
-
.setValueAtTime(value,
|
|
1493
|
+
.cancelScheduledValues(scheduleTime)
|
|
1494
|
+
.setValueAtTime(value, scheduleTime);
|
|
1533
1495
|
}
|
|
1534
1496
|
else {
|
|
1535
1497
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1547,15 +1509,14 @@ class MidyGM2 {
|
|
|
1547
1509
|
}
|
|
1548
1510
|
}
|
|
1549
1511
|
}
|
|
1550
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1512
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1551
1513
|
if (0 < prevValue) {
|
|
1552
1514
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1553
|
-
const now = this.audioContext.currentTime;
|
|
1554
1515
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1555
1516
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1556
1517
|
note.chorusEffectsSend.gain
|
|
1557
|
-
.cancelScheduledValues(
|
|
1558
|
-
.setValueAtTime(value,
|
|
1518
|
+
.cancelScheduledValues(scheduleTime)
|
|
1519
|
+
.setValueAtTime(value, scheduleTime);
|
|
1559
1520
|
}
|
|
1560
1521
|
else {
|
|
1561
1522
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1573,75 +1534,71 @@ class MidyGM2 {
|
|
|
1573
1534
|
}
|
|
1574
1535
|
}
|
|
1575
1536
|
}
|
|
1576
|
-
setDelayModLFO(note) {
|
|
1577
|
-
const now = this.audioContext.currentTime;
|
|
1537
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1578
1538
|
const startTime = note.startTime;
|
|
1579
|
-
if (startTime <
|
|
1539
|
+
if (startTime < scheduleTime)
|
|
1580
1540
|
return;
|
|
1581
|
-
note.modulationLFO.stop(
|
|
1541
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1582
1542
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1583
1543
|
note.modulationLFO.connect(note.filterDepth);
|
|
1584
1544
|
}
|
|
1585
|
-
setFreqModLFO(note) {
|
|
1586
|
-
const now = this.audioContext.currentTime;
|
|
1545
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1587
1546
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1588
1547
|
note.modulationLFO.frequency
|
|
1589
|
-
.cancelScheduledValues(
|
|
1590
|
-
.setValueAtTime(freqModLFO,
|
|
1548
|
+
.cancelScheduledValues(scheduleTime)
|
|
1549
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1591
1550
|
}
|
|
1592
|
-
setFreqVibLFO(channel, note) {
|
|
1593
|
-
const now = this.audioContext.currentTime;
|
|
1551
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1594
1552
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1595
1553
|
note.vibratoLFO.frequency
|
|
1596
|
-
.cancelScheduledValues(
|
|
1597
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1554
|
+
.cancelScheduledValues(scheduleTime)
|
|
1555
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1598
1556
|
}
|
|
1599
1557
|
createVoiceParamsHandlers() {
|
|
1600
1558
|
return {
|
|
1601
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1559
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1602
1560
|
if (0 < channel.state.modulationDepth) {
|
|
1603
|
-
this.setModLfoToPitch(channel, note);
|
|
1561
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1604
1562
|
}
|
|
1605
1563
|
},
|
|
1606
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1564
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1607
1565
|
if (0 < channel.state.vibratoDepth) {
|
|
1608
|
-
this.setVibLfoToPitch(channel, note);
|
|
1566
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1609
1567
|
}
|
|
1610
1568
|
},
|
|
1611
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1569
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1612
1570
|
if (0 < channel.state.modulationDepth) {
|
|
1613
|
-
this.setModLfoToFilterFc(channel, note);
|
|
1571
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1614
1572
|
}
|
|
1615
1573
|
},
|
|
1616
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1574
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1617
1575
|
if (0 < channel.state.modulationDepth) {
|
|
1618
|
-
this.setModLfoToVolume(channel, note);
|
|
1576
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1619
1577
|
}
|
|
1620
1578
|
},
|
|
1621
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1622
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1579
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1580
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1623
1581
|
},
|
|
1624
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1625
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1582
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1583
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1626
1584
|
},
|
|
1627
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1628
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1629
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1585
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1586
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1587
|
+
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1630
1588
|
if (0 < channel.state.vibratoDepth) {
|
|
1631
|
-
const now = this.audioContext.currentTime;
|
|
1632
1589
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1633
1590
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1634
|
-
if (
|
|
1591
|
+
if (scheduleTime < prevStartTime)
|
|
1635
1592
|
return;
|
|
1636
1593
|
const value = note.voiceParams.delayVibLFO;
|
|
1637
1594
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1638
|
-
note.vibratoLFO.stop(
|
|
1595
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1639
1596
|
note.vibratoLFO.start(startTime);
|
|
1640
1597
|
}
|
|
1641
1598
|
},
|
|
1642
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1599
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1643
1600
|
if (0 < channel.state.vibratoDepth) {
|
|
1644
|
-
this.setFreqVibLFO(channel, note);
|
|
1601
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1645
1602
|
}
|
|
1646
1603
|
},
|
|
1647
1604
|
};
|
|
@@ -1653,54 +1610,49 @@ class MidyGM2 {
|
|
|
1653
1610
|
state[3] = noteNumber / 127;
|
|
1654
1611
|
return state;
|
|
1655
1612
|
}
|
|
1656
|
-
applyVoiceParams(channel, controllerType) {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1613
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1614
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1615
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1616
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1617
|
+
let appliedFilterEnvelope = false;
|
|
1618
|
+
let appliedVolumeEnvelope = false;
|
|
1619
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1620
|
+
const prevValue = note.voiceParams[key];
|
|
1621
|
+
if (value === prevValue)
|
|
1661
1622
|
continue;
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
if (value === prevValue)
|
|
1623
|
+
note.voiceParams[key] = value;
|
|
1624
|
+
if (key in this.voiceParamsHandlers) {
|
|
1625
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1626
|
+
}
|
|
1627
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1628
|
+
if (appliedFilterEnvelope)
|
|
1669
1629
|
continue;
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1630
|
+
appliedFilterEnvelope = true;
|
|
1631
|
+
const noteVoiceParams = note.voiceParams;
|
|
1632
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1633
|
+
const key = filterEnvelopeKeys[i];
|
|
1634
|
+
if (key in voiceParams)
|
|
1635
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1673
1636
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
continue;
|
|
1677
|
-
appliedFilterEnvelope = true;
|
|
1678
|
-
const noteVoiceParams = note.voiceParams;
|
|
1679
|
-
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1680
|
-
const key = filterEnvelopeKeys[i];
|
|
1681
|
-
if (key in voiceParams)
|
|
1682
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1683
|
-
}
|
|
1684
|
-
if (note.portamento) {
|
|
1685
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1686
|
-
}
|
|
1687
|
-
else {
|
|
1688
|
-
this.setFilterEnvelope(channel, note);
|
|
1689
|
-
}
|
|
1690
|
-
this.setPitchEnvelope(note);
|
|
1637
|
+
if (note.portamento) {
|
|
1638
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1691
1639
|
}
|
|
1692
|
-
else
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1640
|
+
else {
|
|
1641
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1642
|
+
}
|
|
1643
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1644
|
+
}
|
|
1645
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1646
|
+
if (appliedVolumeEnvelope)
|
|
1647
|
+
continue;
|
|
1648
|
+
appliedVolumeEnvelope = true;
|
|
1649
|
+
const noteVoiceParams = note.voiceParams;
|
|
1650
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1651
|
+
const key = volumeEnvelopeKeys[i];
|
|
1652
|
+
if (key in voiceParams)
|
|
1653
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1703
1654
|
}
|
|
1655
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1704
1656
|
}
|
|
1705
1657
|
}
|
|
1706
1658
|
});
|
|
@@ -1733,12 +1685,12 @@ class MidyGM2 {
|
|
|
1733
1685
|
127: this.polyOn,
|
|
1734
1686
|
};
|
|
1735
1687
|
}
|
|
1736
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1688
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1737
1689
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1738
1690
|
if (handler) {
|
|
1739
|
-
handler.call(this, channelNumber, value,
|
|
1691
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1740
1692
|
const channel = this.channels[channelNumber];
|
|
1741
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1693
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1742
1694
|
this.applyControlTable(channel, controllerType);
|
|
1743
1695
|
}
|
|
1744
1696
|
else {
|
|
@@ -1749,9 +1701,8 @@ class MidyGM2 {
|
|
|
1749
1701
|
this.channels[channelNumber].bankMSB = msb;
|
|
1750
1702
|
}
|
|
1751
1703
|
updateModulation(channel, scheduleTime) {
|
|
1752
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1753
1704
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1754
|
-
this.processScheduledNotes(channel,
|
|
1705
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1755
1706
|
if (note.modulationDepth) {
|
|
1756
1707
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1757
1708
|
}
|
|
@@ -1762,6 +1713,7 @@ class MidyGM2 {
|
|
|
1762
1713
|
});
|
|
1763
1714
|
}
|
|
1764
1715
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1716
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1765
1717
|
const channel = this.channels[channelNumber];
|
|
1766
1718
|
channel.state.modulationDepth = modulation / 127;
|
|
1767
1719
|
this.updateModulation(channel, scheduleTime);
|
|
@@ -1772,8 +1724,7 @@ class MidyGM2 {
|
|
|
1772
1724
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1773
1725
|
}
|
|
1774
1726
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1775
|
-
|
|
1776
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1727
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1777
1728
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1778
1729
|
if (keyBasedValue !== 0) {
|
|
1779
1730
|
note.volumeNode.gain
|
|
@@ -1783,6 +1734,7 @@ class MidyGM2 {
|
|
|
1783
1734
|
});
|
|
1784
1735
|
}
|
|
1785
1736
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1737
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1786
1738
|
const channel = this.channels[channelNumber];
|
|
1787
1739
|
channel.state.volume = volume / 127;
|
|
1788
1740
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1796,8 +1748,7 @@ class MidyGM2 {
|
|
|
1796
1748
|
};
|
|
1797
1749
|
}
|
|
1798
1750
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1799
|
-
|
|
1800
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1751
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1801
1752
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1802
1753
|
if (keyBasedValue !== 0) {
|
|
1803
1754
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
@@ -1811,12 +1762,14 @@ class MidyGM2 {
|
|
|
1811
1762
|
});
|
|
1812
1763
|
}
|
|
1813
1764
|
setPan(channelNumber, pan, scheduleTime) {
|
|
1765
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1814
1766
|
const channel = this.channels[channelNumber];
|
|
1815
1767
|
channel.state.pan = pan / 127;
|
|
1816
1768
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1817
1769
|
this.setKeyBasedPan(channel, scheduleTime);
|
|
1818
1770
|
}
|
|
1819
1771
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1772
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1820
1773
|
const channel = this.channels[channelNumber];
|
|
1821
1774
|
channel.state.expression = expression / 127;
|
|
1822
1775
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -1824,125 +1777,113 @@ class MidyGM2 {
|
|
|
1824
1777
|
setBankLSB(channelNumber, lsb) {
|
|
1825
1778
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1826
1779
|
}
|
|
1827
|
-
dataEntryLSB(channelNumber, value) {
|
|
1780
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1828
1781
|
this.channels[channelNumber].dataLSB = value;
|
|
1829
|
-
this.handleRPN(channelNumber);
|
|
1782
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1830
1783
|
}
|
|
1831
|
-
updateChannelVolume(channel) {
|
|
1832
|
-
const now = this.audioContext.currentTime;
|
|
1784
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1833
1785
|
const state = channel.state;
|
|
1834
1786
|
const volume = state.volume * state.expression;
|
|
1835
1787
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1836
1788
|
channel.gainL.gain
|
|
1837
|
-
.cancelScheduledValues(
|
|
1838
|
-
.setValueAtTime(volume * gainLeft,
|
|
1789
|
+
.cancelScheduledValues(scheduleTime)
|
|
1790
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1839
1791
|
channel.gainR.gain
|
|
1840
|
-
.cancelScheduledValues(
|
|
1841
|
-
.setValueAtTime(volume * gainRight,
|
|
1792
|
+
.cancelScheduledValues(scheduleTime)
|
|
1793
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1842
1794
|
}
|
|
1843
|
-
setSustainPedal(channelNumber, value) {
|
|
1844
|
-
this.
|
|
1845
|
-
|
|
1846
|
-
|
|
1795
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1796
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1797
|
+
const channel = this.channels[channelNumber];
|
|
1798
|
+
channel.state.sustainPedal = value / 127;
|
|
1799
|
+
if (64 <= value) {
|
|
1800
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1801
|
+
channel.sustainNotes.push(note);
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
else {
|
|
1805
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1847
1806
|
}
|
|
1848
1807
|
}
|
|
1849
1808
|
setPortamento(channelNumber, value) {
|
|
1850
1809
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1851
1810
|
}
|
|
1852
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1811
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1812
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1853
1813
|
const channel = this.channels[channelNumber];
|
|
1854
1814
|
channel.state.sostenutoPedal = value / 127;
|
|
1855
1815
|
if (64 <= value) {
|
|
1856
|
-
|
|
1857
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1816
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1858
1817
|
}
|
|
1859
1818
|
else {
|
|
1860
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1819
|
+
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1861
1820
|
}
|
|
1862
1821
|
}
|
|
1863
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1822
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1864
1823
|
const channel = this.channels[channelNumber];
|
|
1865
1824
|
channel.state.softPedal = softPedal / 127;
|
|
1866
1825
|
}
|
|
1867
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1826
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1827
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1868
1828
|
const channel = this.channels[channelNumber];
|
|
1869
1829
|
const state = channel.state;
|
|
1870
1830
|
const reverbEffect = this.reverbEffect;
|
|
1871
1831
|
if (0 < state.reverbSendLevel) {
|
|
1872
1832
|
if (0 < reverbSendLevel) {
|
|
1873
|
-
const now = this.audioContext.currentTime;
|
|
1874
1833
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1875
|
-
reverbEffect.input.gain
|
|
1876
|
-
|
|
1834
|
+
reverbEffect.input.gain
|
|
1835
|
+
.cancelScheduledValues(scheduleTime)
|
|
1836
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1877
1837
|
}
|
|
1878
1838
|
else {
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
continue;
|
|
1884
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1885
|
-
continue;
|
|
1886
|
-
note.reverbEffectsSend.disconnect();
|
|
1887
|
-
}
|
|
1839
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1840
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1841
|
+
return false;
|
|
1842
|
+
note.reverbEffectsSend.disconnect();
|
|
1888
1843
|
});
|
|
1889
1844
|
}
|
|
1890
1845
|
}
|
|
1891
1846
|
else {
|
|
1892
1847
|
if (0 < reverbSendLevel) {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1896
|
-
const note = noteList[i];
|
|
1897
|
-
if (!note)
|
|
1898
|
-
continue;
|
|
1899
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1900
|
-
}
|
|
1848
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1849
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1901
1850
|
});
|
|
1902
1851
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1903
|
-
reverbEffect.input.gain
|
|
1904
|
-
|
|
1852
|
+
reverbEffect.input.gain
|
|
1853
|
+
.cancelScheduledValues(scheduleTime)
|
|
1854
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1905
1855
|
}
|
|
1906
1856
|
}
|
|
1907
1857
|
}
|
|
1908
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1858
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
1859
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1909
1860
|
const channel = this.channels[channelNumber];
|
|
1910
1861
|
const state = channel.state;
|
|
1911
1862
|
const chorusEffect = this.chorusEffect;
|
|
1912
1863
|
if (0 < state.chorusSendLevel) {
|
|
1913
1864
|
if (0 < chorusSendLevel) {
|
|
1914
|
-
const now = this.audioContext.currentTime;
|
|
1915
1865
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1916
|
-
chorusEffect.input.gain
|
|
1917
|
-
|
|
1866
|
+
chorusEffect.input.gain
|
|
1867
|
+
.cancelScheduledValues(scheduleTime)
|
|
1868
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1918
1869
|
}
|
|
1919
1870
|
else {
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
continue;
|
|
1925
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1926
|
-
continue;
|
|
1927
|
-
note.chorusEffectsSend.disconnect();
|
|
1928
|
-
}
|
|
1871
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1872
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1873
|
+
return false;
|
|
1874
|
+
note.chorusEffectsSend.disconnect();
|
|
1929
1875
|
});
|
|
1930
1876
|
}
|
|
1931
1877
|
}
|
|
1932
1878
|
else {
|
|
1933
1879
|
if (0 < chorusSendLevel) {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1937
|
-
const note = noteList[i];
|
|
1938
|
-
if (!note)
|
|
1939
|
-
continue;
|
|
1940
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1941
|
-
}
|
|
1880
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1881
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1942
1882
|
});
|
|
1943
1883
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1944
|
-
chorusEffect.input.gain
|
|
1945
|
-
|
|
1884
|
+
chorusEffect.input.gain
|
|
1885
|
+
.cancelScheduledValues(scheduleTime)
|
|
1886
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1946
1887
|
}
|
|
1947
1888
|
}
|
|
1948
1889
|
}
|
|
@@ -1972,21 +1913,21 @@ class MidyGM2 {
|
|
|
1972
1913
|
channel.dataMSB = minMSB;
|
|
1973
1914
|
}
|
|
1974
1915
|
}
|
|
1975
|
-
handleRPN(channelNumber) {
|
|
1916
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1976
1917
|
const channel = this.channels[channelNumber];
|
|
1977
1918
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1978
1919
|
switch (rpn) {
|
|
1979
1920
|
case 0:
|
|
1980
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1921
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1981
1922
|
break;
|
|
1982
1923
|
case 1:
|
|
1983
|
-
this.handleFineTuningRPN(channelNumber);
|
|
1924
|
+
this.handleFineTuningRPN(channelNumber, scheduleTime);
|
|
1984
1925
|
break;
|
|
1985
1926
|
case 2:
|
|
1986
|
-
this.handleCoarseTuningRPN(channelNumber);
|
|
1927
|
+
this.handleCoarseTuningRPN(channelNumber, scheduleTime);
|
|
1987
1928
|
break;
|
|
1988
1929
|
case 5:
|
|
1989
|
-
this.handleModulationDepthRangeRPN(channelNumber);
|
|
1930
|
+
this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
|
|
1990
1931
|
break;
|
|
1991
1932
|
default:
|
|
1992
1933
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
@@ -1998,67 +1939,72 @@ class MidyGM2 {
|
|
|
1998
1939
|
setRPNLSB(channelNumber, value) {
|
|
1999
1940
|
this.channels[channelNumber].rpnLSB = value;
|
|
2000
1941
|
}
|
|
2001
|
-
dataEntryMSB(channelNumber, value) {
|
|
1942
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2002
1943
|
this.channels[channelNumber].dataMSB = value;
|
|
2003
|
-
this.handleRPN(channelNumber);
|
|
1944
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
2004
1945
|
}
|
|
2005
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1946
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2006
1947
|
const channel = this.channels[channelNumber];
|
|
2007
1948
|
this.limitData(channel, 0, 127, 0, 99);
|
|
2008
1949
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2009
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1950
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2010
1951
|
}
|
|
2011
|
-
setPitchBendRange(channelNumber, value) {
|
|
1952
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1953
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2012
1954
|
const channel = this.channels[channelNumber];
|
|
2013
1955
|
const state = channel.state;
|
|
2014
1956
|
const prev = state.pitchWheelSensitivity;
|
|
2015
1957
|
const next = value / 128;
|
|
2016
1958
|
state.pitchWheelSensitivity = next;
|
|
2017
1959
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2018
|
-
this.updateChannelDetune(channel);
|
|
2019
|
-
this.applyVoiceParams(channel, 16);
|
|
1960
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1961
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2020
1962
|
}
|
|
2021
|
-
handleFineTuningRPN(channelNumber) {
|
|
1963
|
+
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
2022
1964
|
const channel = this.channels[channelNumber];
|
|
2023
1965
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2024
1966
|
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
2025
|
-
this.setFineTuning(channelNumber, fineTuning);
|
|
1967
|
+
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2026
1968
|
}
|
|
2027
|
-
setFineTuning(channelNumber, value) {
|
|
1969
|
+
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1970
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2028
1971
|
const channel = this.channels[channelNumber];
|
|
2029
1972
|
const prev = channel.fineTuning;
|
|
2030
1973
|
const next = (value - 8192) / 8.192; // cent
|
|
2031
1974
|
channel.fineTuning = next;
|
|
2032
1975
|
channel.detune += next - prev;
|
|
2033
|
-
this.updateChannelDetune(channel);
|
|
1976
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2034
1977
|
}
|
|
2035
|
-
handleCoarseTuningRPN(channelNumber) {
|
|
1978
|
+
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2036
1979
|
const channel = this.channels[channelNumber];
|
|
2037
1980
|
this.limitDataMSB(channel, 0, 127);
|
|
2038
1981
|
const coarseTuning = channel.dataMSB;
|
|
2039
|
-
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1982
|
+
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2040
1983
|
}
|
|
2041
|
-
setCoarseTuning(channelNumber, value) {
|
|
1984
|
+
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1985
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2042
1986
|
const channel = this.channels[channelNumber];
|
|
2043
1987
|
const prev = channel.coarseTuning;
|
|
2044
1988
|
const next = (value - 64) * 100; // cent
|
|
2045
1989
|
channel.coarseTuning = next;
|
|
2046
1990
|
channel.detune += next - prev;
|
|
2047
|
-
this.updateChannelDetune(channel);
|
|
1991
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2048
1992
|
}
|
|
2049
|
-
handleModulationDepthRangeRPN(channelNumber) {
|
|
1993
|
+
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2050
1994
|
const channel = this.channels[channelNumber];
|
|
2051
1995
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2052
1996
|
const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
|
|
2053
|
-
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
1997
|
+
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
2054
1998
|
}
|
|
2055
|
-
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1999
|
+
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2000
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2056
2001
|
const channel = this.channels[channelNumber];
|
|
2057
2002
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2058
|
-
this.updateModulation(channel);
|
|
2003
|
+
this.updateModulation(channel, scheduleTime);
|
|
2059
2004
|
}
|
|
2060
|
-
allSoundOff(channelNumber) {
|
|
2061
|
-
|
|
2005
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2006
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2007
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2062
2008
|
}
|
|
2063
2009
|
resetAllControllers(channelNumber) {
|
|
2064
2010
|
const stateTypes = [
|
|
@@ -2086,28 +2032,33 @@ class MidyGM2 {
|
|
|
2086
2032
|
channel[type] = this.constructor.channelSettings[type];
|
|
2087
2033
|
}
|
|
2088
2034
|
}
|
|
2089
|
-
allNotesOff(channelNumber) {
|
|
2090
|
-
|
|
2035
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2036
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2037
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2091
2038
|
}
|
|
2092
|
-
omniOff() {
|
|
2093
|
-
this.
|
|
2039
|
+
omniOff(channelNumber, value, scheduleTime) {
|
|
2040
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2094
2041
|
}
|
|
2095
|
-
omniOn() {
|
|
2096
|
-
this.
|
|
2042
|
+
omniOn(channelNumber, value, scheduleTime) {
|
|
2043
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2097
2044
|
}
|
|
2098
|
-
monoOn() {
|
|
2099
|
-
|
|
2045
|
+
monoOn(channelNumber, value, scheduleTime) {
|
|
2046
|
+
const channel = this.channels[channelNumber];
|
|
2047
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2048
|
+
channel.mono = true;
|
|
2100
2049
|
}
|
|
2101
|
-
polyOn() {
|
|
2102
|
-
|
|
2050
|
+
polyOn(channelNumber, value, scheduleTime) {
|
|
2051
|
+
const channel = this.channels[channelNumber];
|
|
2052
|
+
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
2053
|
+
channel.mono = false;
|
|
2103
2054
|
}
|
|
2104
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2055
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2105
2056
|
switch (data[2]) {
|
|
2106
2057
|
case 8:
|
|
2107
2058
|
switch (data[3]) {
|
|
2108
2059
|
case 8:
|
|
2109
2060
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2110
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2061
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2111
2062
|
default:
|
|
2112
2063
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2113
2064
|
}
|
|
@@ -2150,18 +2101,18 @@ class MidyGM2 {
|
|
|
2150
2101
|
this.channels[9].bankMSB = 120;
|
|
2151
2102
|
this.channels[9].bank = 120 * 128;
|
|
2152
2103
|
}
|
|
2153
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2104
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2154
2105
|
switch (data[2]) {
|
|
2155
2106
|
case 4:
|
|
2156
2107
|
switch (data[3]) {
|
|
2157
2108
|
case 1:
|
|
2158
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2109
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2159
2110
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2160
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2111
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2161
2112
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2162
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2113
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2163
2114
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2164
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2115
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2165
2116
|
default:
|
|
2166
2117
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2167
2118
|
}
|
|
@@ -2179,7 +2130,7 @@ class MidyGM2 {
|
|
|
2179
2130
|
case 10:
|
|
2180
2131
|
switch (data[3]) {
|
|
2181
2132
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2182
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2133
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2183
2134
|
default:
|
|
2184
2135
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2185
2136
|
}
|
|
@@ -2188,49 +2139,50 @@ class MidyGM2 {
|
|
|
2188
2139
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2189
2140
|
}
|
|
2190
2141
|
}
|
|
2191
|
-
handleMasterVolumeSysEx(data) {
|
|
2142
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2192
2143
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2193
|
-
this.setMasterVolume(volume);
|
|
2144
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2194
2145
|
}
|
|
2195
|
-
setMasterVolume(volume) {
|
|
2146
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2147
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2196
2148
|
if (volume < 0 && 1 < volume) {
|
|
2197
2149
|
console.error("Master Volume is out of range");
|
|
2198
2150
|
}
|
|
2199
2151
|
else {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2152
|
+
this.masterVolume.gain
|
|
2153
|
+
.cancelScheduledValues(scheduleTime)
|
|
2154
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2203
2155
|
}
|
|
2204
2156
|
}
|
|
2205
|
-
handleMasterFineTuningSysEx(data) {
|
|
2157
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2206
2158
|
const fineTuning = data[5] * 128 + data[4];
|
|
2207
|
-
this.setMasterFineTuning(fineTuning);
|
|
2159
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2208
2160
|
}
|
|
2209
|
-
setMasterFineTuning(value) {
|
|
2161
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2210
2162
|
const prev = this.masterFineTuning;
|
|
2211
2163
|
const next = (value - 8192) / 8.192; // cent
|
|
2212
2164
|
this.masterFineTuning = next;
|
|
2213
2165
|
channel.detune += next - prev;
|
|
2214
|
-
this.updateChannelDetune(channel);
|
|
2166
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2215
2167
|
}
|
|
2216
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2168
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2217
2169
|
const coarseTuning = data[4];
|
|
2218
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2170
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2219
2171
|
}
|
|
2220
|
-
setMasterCoarseTuning(value) {
|
|
2172
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2221
2173
|
const prev = this.masterCoarseTuning;
|
|
2222
2174
|
const next = (value - 64) * 100; // cent
|
|
2223
2175
|
this.masterCoarseTuning = next;
|
|
2224
2176
|
channel.detune += next - prev;
|
|
2225
|
-
this.updateChannelDetune(channel);
|
|
2177
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2226
2178
|
}
|
|
2227
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2179
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2228
2180
|
if (data[7] === 1) {
|
|
2229
2181
|
switch (data[8]) {
|
|
2230
2182
|
case 1:
|
|
2231
2183
|
return this.handleReverbParameterSysEx(data);
|
|
2232
2184
|
case 2:
|
|
2233
|
-
return this.handleChorusParameterSysEx(data);
|
|
2185
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2234
2186
|
default:
|
|
2235
2187
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2236
2188
|
}
|
|
@@ -2309,88 +2261,84 @@ class MidyGM2 {
|
|
|
2309
2261
|
calcDelay(rt60, feedback) {
|
|
2310
2262
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2311
2263
|
}
|
|
2312
|
-
handleChorusParameterSysEx(data) {
|
|
2264
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2313
2265
|
switch (data[9]) {
|
|
2314
2266
|
case 0:
|
|
2315
|
-
return this.setChorusType(data[10]);
|
|
2267
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2316
2268
|
case 1:
|
|
2317
|
-
return this.setChorusModRate(data[10]);
|
|
2269
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2318
2270
|
case 2:
|
|
2319
|
-
return this.setChorusModDepth(data[10]);
|
|
2271
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2320
2272
|
case 3:
|
|
2321
|
-
return this.setChorusFeedback(data[10]);
|
|
2273
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2322
2274
|
case 4:
|
|
2323
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2275
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2324
2276
|
}
|
|
2325
2277
|
}
|
|
2326
|
-
setChorusType(type) {
|
|
2278
|
+
setChorusType(type, scheduleTime) {
|
|
2327
2279
|
switch (type) {
|
|
2328
2280
|
case 0:
|
|
2329
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2281
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2330
2282
|
case 1:
|
|
2331
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2283
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2332
2284
|
case 2:
|
|
2333
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2285
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2334
2286
|
case 3:
|
|
2335
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2287
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2336
2288
|
case 4:
|
|
2337
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2289
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2338
2290
|
case 5:
|
|
2339
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2291
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2340
2292
|
default:
|
|
2341
2293
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2342
2294
|
}
|
|
2343
2295
|
}
|
|
2344
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2345
|
-
this.setChorusModRate(modRate);
|
|
2346
|
-
this.setChorusModDepth(modDepth);
|
|
2347
|
-
this.setChorusFeedback(feedback);
|
|
2348
|
-
this.setChorusSendToReverb(sendToReverb);
|
|
2296
|
+
setChorusParameter(modRate, modDepth, feedback, sendToReverb, scheduleTime) {
|
|
2297
|
+
this.setChorusModRate(modRate, scheduleTime);
|
|
2298
|
+
this.setChorusModDepth(modDepth, scheduleTime);
|
|
2299
|
+
this.setChorusFeedback(feedback, scheduleTime);
|
|
2300
|
+
this.setChorusSendToReverb(sendToReverb, scheduleTime);
|
|
2349
2301
|
}
|
|
2350
|
-
setChorusModRate(value) {
|
|
2351
|
-
const now = this.audioContext.currentTime;
|
|
2302
|
+
setChorusModRate(value, scheduleTime) {
|
|
2352
2303
|
const modRate = this.getChorusModRate(value);
|
|
2353
2304
|
this.chorus.modRate = modRate;
|
|
2354
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2305
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2355
2306
|
}
|
|
2356
2307
|
getChorusModRate(value) {
|
|
2357
2308
|
return value * 0.122; // Hz
|
|
2358
2309
|
}
|
|
2359
|
-
setChorusModDepth(value) {
|
|
2360
|
-
const now = this.audioContext.currentTime;
|
|
2310
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2361
2311
|
const modDepth = this.getChorusModDepth(value);
|
|
2362
2312
|
this.chorus.modDepth = modDepth;
|
|
2363
2313
|
this.chorusEffect.lfoGain.gain
|
|
2364
|
-
.cancelScheduledValues(
|
|
2365
|
-
.setValueAtTime(modDepth / 2,
|
|
2314
|
+
.cancelScheduledValues(scheduleTime)
|
|
2315
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2366
2316
|
}
|
|
2367
2317
|
getChorusModDepth(value) {
|
|
2368
2318
|
return (value + 1) / 3200; // second
|
|
2369
2319
|
}
|
|
2370
|
-
setChorusFeedback(value) {
|
|
2371
|
-
const now = this.audioContext.currentTime;
|
|
2320
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2372
2321
|
const feedback = this.getChorusFeedback(value);
|
|
2373
2322
|
this.chorus.feedback = feedback;
|
|
2374
2323
|
const chorusEffect = this.chorusEffect;
|
|
2375
2324
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2376
2325
|
chorusEffect.feedbackGains[i].gain
|
|
2377
|
-
.cancelScheduledValues(
|
|
2378
|
-
.setValueAtTime(feedback,
|
|
2326
|
+
.cancelScheduledValues(scheduleTime)
|
|
2327
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2379
2328
|
}
|
|
2380
2329
|
}
|
|
2381
2330
|
getChorusFeedback(value) {
|
|
2382
2331
|
return value * 0.00763;
|
|
2383
2332
|
}
|
|
2384
|
-
setChorusSendToReverb(value) {
|
|
2333
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2385
2334
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2386
2335
|
const sendGain = this.chorusEffect.sendGain;
|
|
2387
2336
|
if (0 < this.chorus.sendToReverb) {
|
|
2388
2337
|
this.chorus.sendToReverb = sendToReverb;
|
|
2389
2338
|
if (0 < sendToReverb) {
|
|
2390
|
-
const now = this.audioContext.currentTime;
|
|
2391
2339
|
sendGain.gain
|
|
2392
|
-
.cancelScheduledValues(
|
|
2393
|
-
.setValueAtTime(sendToReverb,
|
|
2340
|
+
.cancelScheduledValues(scheduleTime)
|
|
2341
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2394
2342
|
}
|
|
2395
2343
|
else {
|
|
2396
2344
|
sendGain.disconnect();
|
|
@@ -2399,11 +2347,10 @@ class MidyGM2 {
|
|
|
2399
2347
|
else {
|
|
2400
2348
|
this.chorus.sendToReverb = sendToReverb;
|
|
2401
2349
|
if (0 < sendToReverb) {
|
|
2402
|
-
const now = this.audioContext.currentTime;
|
|
2403
2350
|
sendGain.connect(this.reverbEffect.input);
|
|
2404
2351
|
sendGain.gain
|
|
2405
|
-
.cancelScheduledValues(
|
|
2406
|
-
.setValueAtTime(sendToReverb,
|
|
2352
|
+
.cancelScheduledValues(scheduleTime)
|
|
2353
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2407
2354
|
}
|
|
2408
2355
|
}
|
|
2409
2356
|
}
|
|
@@ -2429,7 +2376,7 @@ class MidyGM2 {
|
|
|
2429
2376
|
}
|
|
2430
2377
|
return bitmap;
|
|
2431
2378
|
}
|
|
2432
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2379
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2433
2380
|
if (data.length < 19) {
|
|
2434
2381
|
console.error("Data length is too short");
|
|
2435
2382
|
return;
|
|
@@ -2444,7 +2391,7 @@ class MidyGM2 {
|
|
|
2444
2391
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2445
2392
|
}
|
|
2446
2393
|
if (realtime)
|
|
2447
|
-
this.updateChannelDetune(channel);
|
|
2394
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2448
2395
|
}
|
|
2449
2396
|
}
|
|
2450
2397
|
getFilterCutoffControl(channel) {
|
|
@@ -2488,7 +2435,7 @@ class MidyGM2 {
|
|
|
2488
2435
|
if (table[5] !== 0)
|
|
2489
2436
|
this.setModLfoToVolume(channel, note);
|
|
2490
2437
|
}
|
|
2491
|
-
|
|
2438
|
+
handlePressureSysEx(data, tableName) {
|
|
2492
2439
|
const channelNumber = data[4];
|
|
2493
2440
|
const table = this.channels[channelNumber][tableName];
|
|
2494
2441
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2512,13 +2459,8 @@ class MidyGM2 {
|
|
|
2512
2459
|
const slotSize = 6;
|
|
2513
2460
|
const offset = controllerType * slotSize;
|
|
2514
2461
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
const note = noteList[i];
|
|
2518
|
-
if (!note)
|
|
2519
|
-
continue;
|
|
2520
|
-
this.setControllerParameters(channel, note, table);
|
|
2521
|
-
}
|
|
2462
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2463
|
+
this.setControllerParameters(channel, note, table);
|
|
2522
2464
|
});
|
|
2523
2465
|
}
|
|
2524
2466
|
handleControlChangeSysEx(data) {
|
|
@@ -2536,7 +2478,7 @@ class MidyGM2 {
|
|
|
2536
2478
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2537
2479
|
return (controlValue + 64) / 64;
|
|
2538
2480
|
}
|
|
2539
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2481
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2540
2482
|
const channelNumber = data[4];
|
|
2541
2483
|
const keyNumber = data[5];
|
|
2542
2484
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2546,30 +2488,27 @@ class MidyGM2 {
|
|
|
2546
2488
|
const index = keyNumber * 128 + controllerType;
|
|
2547
2489
|
table[index] = value - 64;
|
|
2548
2490
|
}
|
|
2549
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2491
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2550
2492
|
}
|
|
2551
|
-
|
|
2552
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2553
|
-
}
|
|
2554
|
-
handleSysEx(data) {
|
|
2493
|
+
handleSysEx(data, scheduleTime) {
|
|
2555
2494
|
switch (data[0]) {
|
|
2556
2495
|
case 126:
|
|
2557
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2496
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2558
2497
|
case 127:
|
|
2559
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2498
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2560
2499
|
default:
|
|
2561
|
-
|
|
2500
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2562
2501
|
}
|
|
2563
2502
|
}
|
|
2564
|
-
scheduleTask(callback,
|
|
2503
|
+
scheduleTask(callback, scheduleTime) {
|
|
2565
2504
|
return new Promise((resolve) => {
|
|
2566
2505
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2567
2506
|
bufferSource.onended = () => {
|
|
2568
2507
|
callback();
|
|
2569
2508
|
resolve();
|
|
2570
2509
|
};
|
|
2571
|
-
bufferSource.start(
|
|
2572
|
-
bufferSource.stop(
|
|
2510
|
+
bufferSource.start(scheduleTime);
|
|
2511
|
+
bufferSource.stop(scheduleTime);
|
|
2573
2512
|
});
|
|
2574
2513
|
}
|
|
2575
2514
|
}
|
|
@@ -2589,6 +2528,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2589
2528
|
dataLSB: 0,
|
|
2590
2529
|
rpnMSB: 127,
|
|
2591
2530
|
rpnLSB: 127,
|
|
2531
|
+
mono: false, // CC#124, CC#125
|
|
2592
2532
|
fineTuning: 0, // cb
|
|
2593
2533
|
coarseTuning: 0, // cb
|
|
2594
2534
|
modulationDepthRange: 50, // cent
|