@marmooo/midy 0.2.6 → 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 +68 -69
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +143 -156
- package/esm/midy-GM2.d.ts +104 -105
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +251 -280
- package/esm/midy-GMLite.d.ts +68 -69
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +143 -156
- package/esm/midy.d.ts +127 -128
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +274 -307
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +68 -69
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +143 -156
- package/script/midy-GM2.d.ts +104 -105
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +251 -280
- package/script/midy-GMLite.d.ts +68 -69
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +143 -156
- package/script/midy.d.ts +127 -128
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +274 -307
package/script/midy-GM2.js
CHANGED
|
@@ -582,7 +582,8 @@ class MidyGM2 {
|
|
|
582
582
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
583
583
|
if (portamentoTarget)
|
|
584
584
|
portamentoTarget.portamento = true;
|
|
585
|
-
const notePromise = this.
|
|
585
|
+
const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
586
|
+
portamentoTarget?.noteNumber);
|
|
586
587
|
if (notePromise) {
|
|
587
588
|
this.notePromises.push(notePromise);
|
|
588
589
|
}
|
|
@@ -632,10 +633,11 @@ class MidyGM2 {
|
|
|
632
633
|
resolve();
|
|
633
634
|
return;
|
|
634
635
|
}
|
|
635
|
-
const
|
|
636
|
+
const now = this.audioContext.currentTime;
|
|
637
|
+
const t = now + offset;
|
|
636
638
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
637
639
|
if (this.isPausing) {
|
|
638
|
-
await this.stopNotes(0, true);
|
|
640
|
+
await this.stopNotes(0, true, now);
|
|
639
641
|
this.notePromises = [];
|
|
640
642
|
resolve();
|
|
641
643
|
this.isPausing = false;
|
|
@@ -643,7 +645,7 @@ class MidyGM2 {
|
|
|
643
645
|
return;
|
|
644
646
|
}
|
|
645
647
|
else if (this.isStopping) {
|
|
646
|
-
await this.stopNotes(0, true);
|
|
648
|
+
await this.stopNotes(0, true, now);
|
|
647
649
|
this.notePromises = [];
|
|
648
650
|
this.exclusiveClassMap.clear();
|
|
649
651
|
this.audioBufferCache.clear();
|
|
@@ -653,7 +655,7 @@ class MidyGM2 {
|
|
|
653
655
|
return;
|
|
654
656
|
}
|
|
655
657
|
else if (this.isSeeking) {
|
|
656
|
-
this.stopNotes(0, true);
|
|
658
|
+
this.stopNotes(0, true, now);
|
|
657
659
|
this.exclusiveClassMap.clear();
|
|
658
660
|
this.startTime = this.audioContext.currentTime;
|
|
659
661
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -662,7 +664,6 @@ class MidyGM2 {
|
|
|
662
664
|
await schedulePlayback();
|
|
663
665
|
}
|
|
664
666
|
else {
|
|
665
|
-
const now = this.audioContext.currentTime;
|
|
666
667
|
const waitTime = now + this.noteCheckInterval;
|
|
667
668
|
await this.scheduleTask(() => { }, waitTime);
|
|
668
669
|
await schedulePlayback();
|
|
@@ -782,25 +783,26 @@ class MidyGM2 {
|
|
|
782
783
|
}
|
|
783
784
|
return { instruments, timeline };
|
|
784
785
|
}
|
|
785
|
-
|
|
786
|
-
const now = this.audioContext.currentTime;
|
|
786
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
787
787
|
const channel = this.channels[channelNumber];
|
|
788
|
+
const promises = [];
|
|
788
789
|
channel.scheduledNotes.forEach((noteList) => {
|
|
789
790
|
for (let i = 0; i < noteList.length; i++) {
|
|
790
791
|
const note = noteList[i];
|
|
791
792
|
if (!note)
|
|
792
793
|
continue;
|
|
793
|
-
const promise = this.
|
|
794
|
-
force);
|
|
794
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
795
795
|
this.notePromises.push(promise);
|
|
796
|
+
promises.push(promise);
|
|
796
797
|
}
|
|
797
798
|
});
|
|
798
799
|
channel.scheduledNotes.clear();
|
|
799
|
-
|
|
800
|
+
return Promise.all(promises);
|
|
800
801
|
}
|
|
801
|
-
stopNotes(velocity, force) {
|
|
802
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
803
|
+
const promises = [];
|
|
802
804
|
for (let i = 0; i < this.channels.length; i++) {
|
|
803
|
-
this.stopChannelNotes(i, velocity, force);
|
|
805
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
804
806
|
}
|
|
805
807
|
return Promise.all(this.notePromises);
|
|
806
808
|
}
|
|
@@ -860,22 +862,22 @@ class MidyGM2 {
|
|
|
860
862
|
}
|
|
861
863
|
});
|
|
862
864
|
}
|
|
863
|
-
getActiveNotes(channel,
|
|
865
|
+
getActiveNotes(channel, scheduleTime) {
|
|
864
866
|
const activeNotes = new SparseMap(128);
|
|
865
867
|
channel.scheduledNotes.forEach((noteList) => {
|
|
866
|
-
const activeNote = this.getActiveNote(noteList,
|
|
868
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
867
869
|
if (activeNote) {
|
|
868
870
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
869
871
|
}
|
|
870
872
|
});
|
|
871
873
|
return activeNotes;
|
|
872
874
|
}
|
|
873
|
-
getActiveNote(noteList,
|
|
875
|
+
getActiveNote(noteList, scheduleTime) {
|
|
874
876
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
875
877
|
const note = noteList[i];
|
|
876
878
|
if (!note)
|
|
877
879
|
return;
|
|
878
|
-
if (
|
|
880
|
+
if (scheduleTime < note.startTime)
|
|
879
881
|
continue;
|
|
880
882
|
return (note.ending) ? null : note;
|
|
881
883
|
}
|
|
@@ -1035,43 +1037,35 @@ class MidyGM2 {
|
|
|
1035
1037
|
calcNoteDetune(channel, note) {
|
|
1036
1038
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1037
1039
|
}
|
|
1038
|
-
updateChannelDetune(channel) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const note = noteList[i];
|
|
1042
|
-
if (!note)
|
|
1043
|
-
continue;
|
|
1044
|
-
this.updateDetune(channel, note);
|
|
1045
|
-
}
|
|
1040
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1041
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1042
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1046
1043
|
});
|
|
1047
1044
|
}
|
|
1048
|
-
updateDetune(channel, note) {
|
|
1049
|
-
const now = this.audioContext.currentTime;
|
|
1045
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1050
1046
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1051
1047
|
const detune = channel.detune + noteDetune;
|
|
1052
1048
|
note.bufferSource.detune
|
|
1053
|
-
.cancelScheduledValues(
|
|
1054
|
-
.setValueAtTime(detune,
|
|
1049
|
+
.cancelScheduledValues(scheduleTime)
|
|
1050
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1055
1051
|
}
|
|
1056
1052
|
getPortamentoTime(channel) {
|
|
1057
1053
|
const factor = 5 * Math.log(10) / 127;
|
|
1058
1054
|
const time = channel.state.portamentoTime;
|
|
1059
1055
|
return Math.log(time) / factor;
|
|
1060
1056
|
}
|
|
1061
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1062
|
-
const now = this.audioContext.currentTime;
|
|
1057
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1063
1058
|
const { voiceParams, startTime } = note;
|
|
1064
1059
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1065
1060
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1066
1061
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1067
1062
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1068
1063
|
note.volumeEnvelopeNode.gain
|
|
1069
|
-
.cancelScheduledValues(
|
|
1064
|
+
.cancelScheduledValues(scheduleTime)
|
|
1070
1065
|
.setValueAtTime(0, volDelay)
|
|
1071
1066
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1072
1067
|
}
|
|
1073
|
-
setVolumeEnvelope(channel, note) {
|
|
1074
|
-
const now = this.audioContext.currentTime;
|
|
1068
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1075
1069
|
const { voiceParams, startTime } = note;
|
|
1076
1070
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1077
1071
|
(1 + this.getAmplitudeControl(channel));
|
|
@@ -1081,7 +1075,7 @@ class MidyGM2 {
|
|
|
1081
1075
|
const volHold = volAttack + voiceParams.volHold;
|
|
1082
1076
|
const volDecay = volHold + voiceParams.volDecay;
|
|
1083
1077
|
note.volumeEnvelopeNode.gain
|
|
1084
|
-
.cancelScheduledValues(
|
|
1078
|
+
.cancelScheduledValues(scheduleTime)
|
|
1085
1079
|
.setValueAtTime(0, startTime)
|
|
1086
1080
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1087
1081
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -1089,7 +1083,6 @@ class MidyGM2 {
|
|
|
1089
1083
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1090
1084
|
}
|
|
1091
1085
|
setPitchEnvelope(note, scheduleTime) {
|
|
1092
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1093
1086
|
const { voiceParams } = note;
|
|
1094
1087
|
const baseRate = voiceParams.playbackRate;
|
|
1095
1088
|
note.bufferSource.playbackRate
|
|
@@ -1116,8 +1109,7 @@ class MidyGM2 {
|
|
|
1116
1109
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1117
1110
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1118
1111
|
}
|
|
1119
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1120
|
-
const now = this.audioContext.currentTime;
|
|
1112
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1121
1113
|
const state = channel.state;
|
|
1122
1114
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1123
1115
|
const softPedalFactor = 1 -
|
|
@@ -1132,13 +1124,12 @@ class MidyGM2 {
|
|
|
1132
1124
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1133
1125
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1134
1126
|
note.filterNode.frequency
|
|
1135
|
-
.cancelScheduledValues(
|
|
1127
|
+
.cancelScheduledValues(scheduleTime)
|
|
1136
1128
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1137
1129
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1138
1130
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1139
1131
|
}
|
|
1140
|
-
setFilterEnvelope(channel, note) {
|
|
1141
|
-
const now = this.audioContext.currentTime;
|
|
1132
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1142
1133
|
const state = channel.state;
|
|
1143
1134
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1144
1135
|
const softPedalFactor = 1 -
|
|
@@ -1158,14 +1149,14 @@ class MidyGM2 {
|
|
|
1158
1149
|
const modHold = modAttack + voiceParams.modHold;
|
|
1159
1150
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1160
1151
|
note.filterNode.frequency
|
|
1161
|
-
.cancelScheduledValues(
|
|
1152
|
+
.cancelScheduledValues(scheduleTime)
|
|
1162
1153
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1163
1154
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1164
1155
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1165
1156
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1166
1157
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1167
1158
|
}
|
|
1168
|
-
startModulation(channel, note,
|
|
1159
|
+
startModulation(channel, note, scheduleTime) {
|
|
1169
1160
|
const { voiceParams } = note;
|
|
1170
1161
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1171
1162
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1174,10 +1165,10 @@ class MidyGM2 {
|
|
|
1174
1165
|
gain: voiceParams.modLfoToFilterFc,
|
|
1175
1166
|
});
|
|
1176
1167
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1177
|
-
this.setModLfoToPitch(channel, note);
|
|
1168
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1178
1169
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1179
|
-
this.setModLfoToVolume(
|
|
1180
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1170
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1171
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1181
1172
|
note.modulationLFO.connect(note.filterDepth);
|
|
1182
1173
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1183
1174
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1185,15 +1176,15 @@ class MidyGM2 {
|
|
|
1185
1176
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1186
1177
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1187
1178
|
}
|
|
1188
|
-
startVibrato(channel, note,
|
|
1179
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1189
1180
|
const { voiceParams } = note;
|
|
1190
1181
|
const state = channel.state;
|
|
1191
1182
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1192
1183
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1193
1184
|
});
|
|
1194
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1185
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1195
1186
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1196
|
-
this.setVibLfoToPitch(channel, note);
|
|
1187
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1197
1188
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1198
1189
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1199
1190
|
}
|
|
@@ -1216,6 +1207,7 @@ class MidyGM2 {
|
|
|
1216
1207
|
}
|
|
1217
1208
|
}
|
|
1218
1209
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1210
|
+
const now = this.audioContext.currentTime;
|
|
1219
1211
|
const state = channel.state;
|
|
1220
1212
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1221
1213
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1232,20 +1224,20 @@ class MidyGM2 {
|
|
|
1232
1224
|
});
|
|
1233
1225
|
if (portamento) {
|
|
1234
1226
|
note.portamento = true;
|
|
1235
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1236
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1227
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1228
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1237
1229
|
}
|
|
1238
1230
|
else {
|
|
1239
1231
|
note.portamento = false;
|
|
1240
|
-
this.setVolumeEnvelope(channel, note);
|
|
1241
|
-
this.setFilterEnvelope(channel, note);
|
|
1232
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1233
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1242
1234
|
}
|
|
1243
1235
|
if (0 < state.vibratoDepth) {
|
|
1244
|
-
this.startVibrato(channel, note,
|
|
1236
|
+
this.startVibrato(channel, note, now);
|
|
1245
1237
|
}
|
|
1246
|
-
this.setPitchEnvelope(note);
|
|
1238
|
+
this.setPitchEnvelope(note, now);
|
|
1247
1239
|
if (0 < state.modulationDepth) {
|
|
1248
|
-
this.startModulation(channel, note,
|
|
1240
|
+
this.startModulation(channel, note, now);
|
|
1249
1241
|
}
|
|
1250
1242
|
if (this.mono && channel.currentBufferSource) {
|
|
1251
1243
|
channel.currentBufferSource.stop(startTime);
|
|
@@ -1257,10 +1249,10 @@ class MidyGM2 {
|
|
|
1257
1249
|
note.volumeNode.connect(note.gainL);
|
|
1258
1250
|
note.volumeNode.connect(note.gainR);
|
|
1259
1251
|
if (0 < channel.chorusSendLevel) {
|
|
1260
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1252
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1261
1253
|
}
|
|
1262
1254
|
if (0 < channel.reverbSendLevel) {
|
|
1263
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1255
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1264
1256
|
}
|
|
1265
1257
|
note.bufferSource.start(startTime);
|
|
1266
1258
|
return note;
|
|
@@ -1297,9 +1289,9 @@ class MidyGM2 {
|
|
|
1297
1289
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1298
1290
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1299
1291
|
if (!prevNote.ending) {
|
|
1300
|
-
this.
|
|
1301
|
-
startTime,
|
|
1302
|
-
|
|
1292
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1293
|
+
startTime, true, // force
|
|
1294
|
+
undefined);
|
|
1303
1295
|
}
|
|
1304
1296
|
}
|
|
1305
1297
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1312,9 +1304,9 @@ class MidyGM2 {
|
|
|
1312
1304
|
scheduledNotes.set(noteNumber, [note]);
|
|
1313
1305
|
}
|
|
1314
1306
|
}
|
|
1315
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1316
|
-
|
|
1317
|
-
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);
|
|
1318
1310
|
}
|
|
1319
1311
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1320
1312
|
const note = scheduledNotes[index];
|
|
@@ -1354,7 +1346,7 @@ class MidyGM2 {
|
|
|
1354
1346
|
note.bufferSource.stop(stopTime);
|
|
1355
1347
|
});
|
|
1356
1348
|
}
|
|
1357
|
-
|
|
1349
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1358
1350
|
const channel = this.channels[channelNumber];
|
|
1359
1351
|
const state = channel.state;
|
|
1360
1352
|
if (!force) {
|
|
@@ -1393,24 +1385,19 @@ class MidyGM2 {
|
|
|
1393
1385
|
}
|
|
1394
1386
|
}
|
|
1395
1387
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
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);
|
|
1399
1392
|
}
|
|
1400
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1393
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1401
1394
|
const velocity = halfVelocity * 2;
|
|
1402
1395
|
const channel = this.channels[channelNumber];
|
|
1403
1396
|
const promises = [];
|
|
1404
|
-
channel
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
if (!note)
|
|
1409
|
-
continue;
|
|
1410
|
-
const { noteNumber } = note;
|
|
1411
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1412
|
-
promises.push(promise);
|
|
1413
|
-
}
|
|
1397
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1398
|
+
const { noteNumber } = note;
|
|
1399
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1400
|
+
promises.push(promise);
|
|
1414
1401
|
});
|
|
1415
1402
|
return promises;
|
|
1416
1403
|
}
|
|
@@ -1421,40 +1408,38 @@ class MidyGM2 {
|
|
|
1421
1408
|
channel.state.sostenutoPedal = 0;
|
|
1422
1409
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1423
1410
|
const { noteNumber } = activeNote;
|
|
1424
|
-
const promise = this.
|
|
1411
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1425
1412
|
promises.push(promise);
|
|
1426
1413
|
});
|
|
1427
1414
|
channel.sostenutoNotes.clear();
|
|
1428
1415
|
return promises;
|
|
1429
1416
|
}
|
|
1430
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1417
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1431
1418
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
1432
1419
|
const messageType = statusByte & 0xF0;
|
|
1433
1420
|
switch (messageType) {
|
|
1434
1421
|
case 0x80:
|
|
1435
|
-
return this.
|
|
1422
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1436
1423
|
case 0x90:
|
|
1437
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1424
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1438
1425
|
case 0xB0:
|
|
1439
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1426
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1440
1427
|
case 0xC0:
|
|
1441
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1428
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1442
1429
|
case 0xD0:
|
|
1443
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1430
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1444
1431
|
case 0xE0:
|
|
1445
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1432
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1446
1433
|
default:
|
|
1447
1434
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1448
1435
|
}
|
|
1449
1436
|
}
|
|
1450
|
-
handleProgramChange(channelNumber, program) {
|
|
1437
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1451
1438
|
const channel = this.channels[channelNumber];
|
|
1452
1439
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1453
1440
|
channel.program = program;
|
|
1454
1441
|
}
|
|
1455
|
-
handleChannelPressure(channelNumber, value,
|
|
1456
|
-
if (!startTime)
|
|
1457
|
-
startTime = this.audioContext.currentTime;
|
|
1442
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1458
1443
|
const channel = this.channels[channelNumber];
|
|
1459
1444
|
const prev = channel.state.channelPressure;
|
|
1460
1445
|
const next = value / 127;
|
|
@@ -1464,72 +1449,68 @@ class MidyGM2 {
|
|
|
1464
1449
|
channel.detune += pressureDepth * (next - prev);
|
|
1465
1450
|
}
|
|
1466
1451
|
const table = channel.channelPressureTable;
|
|
1467
|
-
this.getActiveNotes(channel,
|
|
1452
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1468
1453
|
this.setControllerParameters(channel, note, table);
|
|
1469
1454
|
});
|
|
1470
1455
|
// this.applyVoiceParams(channel, 13);
|
|
1471
1456
|
}
|
|
1472
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1457
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1473
1458
|
const pitchBend = msb * 128 + lsb;
|
|
1474
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1459
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1475
1460
|
}
|
|
1476
|
-
setPitchBend(channelNumber, value) {
|
|
1461
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1462
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1477
1463
|
const channel = this.channels[channelNumber];
|
|
1478
1464
|
const state = channel.state;
|
|
1479
1465
|
const prev = state.pitchWheel * 2 - 1;
|
|
1480
1466
|
const next = (value - 8192) / 8192;
|
|
1481
1467
|
state.pitchWheel = value / 16383;
|
|
1482
1468
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1483
|
-
this.updateChannelDetune(channel);
|
|
1484
|
-
this.applyVoiceParams(channel, 14);
|
|
1469
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1470
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1485
1471
|
}
|
|
1486
|
-
setModLfoToPitch(channel, note) {
|
|
1487
|
-
const now = this.audioContext.currentTime;
|
|
1472
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1488
1473
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1489
1474
|
this.getLFOPitchDepth(channel);
|
|
1490
1475
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1491
1476
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1492
1477
|
note.modulationDepth.gain
|
|
1493
|
-
.cancelScheduledValues(
|
|
1494
|
-
.setValueAtTime(modulationDepth,
|
|
1478
|
+
.cancelScheduledValues(scheduleTime)
|
|
1479
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1495
1480
|
}
|
|
1496
|
-
setVibLfoToPitch(channel, note) {
|
|
1497
|
-
const now = this.audioContext.currentTime;
|
|
1481
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1498
1482
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1499
1483
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1500
1484
|
2;
|
|
1501
1485
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1502
1486
|
note.vibratoDepth.gain
|
|
1503
|
-
.cancelScheduledValues(
|
|
1504
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1487
|
+
.cancelScheduledValues(scheduleTime)
|
|
1488
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1505
1489
|
}
|
|
1506
|
-
setModLfoToFilterFc(channel, note) {
|
|
1507
|
-
const now = this.audioContext.currentTime;
|
|
1490
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1508
1491
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1509
1492
|
this.getLFOFilterDepth(channel);
|
|
1510
1493
|
note.filterDepth.gain
|
|
1511
|
-
.cancelScheduledValues(
|
|
1512
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1494
|
+
.cancelScheduledValues(scheduleTime)
|
|
1495
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1513
1496
|
}
|
|
1514
|
-
setModLfoToVolume(channel, note) {
|
|
1515
|
-
const now = this.audioContext.currentTime;
|
|
1497
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1516
1498
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1517
1499
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1518
1500
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1519
1501
|
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1520
1502
|
note.volumeDepth.gain
|
|
1521
|
-
.cancelScheduledValues(
|
|
1522
|
-
.setValueAtTime(volumeDepth,
|
|
1503
|
+
.cancelScheduledValues(scheduleTime)
|
|
1504
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1523
1505
|
}
|
|
1524
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1506
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1525
1507
|
if (0 < prevValue) {
|
|
1526
1508
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1527
|
-
const now = this.audioContext.currentTime;
|
|
1528
1509
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1529
1510
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1530
1511
|
note.reverbEffectsSend.gain
|
|
1531
|
-
.cancelScheduledValues(
|
|
1532
|
-
.setValueAtTime(value,
|
|
1512
|
+
.cancelScheduledValues(scheduleTime)
|
|
1513
|
+
.setValueAtTime(value, scheduleTime);
|
|
1533
1514
|
}
|
|
1534
1515
|
else {
|
|
1535
1516
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1547,15 +1528,14 @@ class MidyGM2 {
|
|
|
1547
1528
|
}
|
|
1548
1529
|
}
|
|
1549
1530
|
}
|
|
1550
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1531
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1551
1532
|
if (0 < prevValue) {
|
|
1552
1533
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1553
|
-
const now = this.audioContext.currentTime;
|
|
1554
1534
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1555
1535
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1556
1536
|
note.chorusEffectsSend.gain
|
|
1557
|
-
.cancelScheduledValues(
|
|
1558
|
-
.setValueAtTime(value,
|
|
1537
|
+
.cancelScheduledValues(scheduleTime)
|
|
1538
|
+
.setValueAtTime(value, scheduleTime);
|
|
1559
1539
|
}
|
|
1560
1540
|
else {
|
|
1561
1541
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1573,75 +1553,71 @@ class MidyGM2 {
|
|
|
1573
1553
|
}
|
|
1574
1554
|
}
|
|
1575
1555
|
}
|
|
1576
|
-
setDelayModLFO(note) {
|
|
1577
|
-
const now = this.audioContext.currentTime;
|
|
1556
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1578
1557
|
const startTime = note.startTime;
|
|
1579
|
-
if (startTime <
|
|
1558
|
+
if (startTime < scheduleTime)
|
|
1580
1559
|
return;
|
|
1581
|
-
note.modulationLFO.stop(
|
|
1560
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1582
1561
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1583
1562
|
note.modulationLFO.connect(note.filterDepth);
|
|
1584
1563
|
}
|
|
1585
|
-
setFreqModLFO(note) {
|
|
1586
|
-
const now = this.audioContext.currentTime;
|
|
1564
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1587
1565
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1588
1566
|
note.modulationLFO.frequency
|
|
1589
|
-
.cancelScheduledValues(
|
|
1590
|
-
.setValueAtTime(freqModLFO,
|
|
1567
|
+
.cancelScheduledValues(scheduleTime)
|
|
1568
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1591
1569
|
}
|
|
1592
|
-
setFreqVibLFO(channel, note) {
|
|
1593
|
-
const now = this.audioContext.currentTime;
|
|
1570
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1594
1571
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1595
1572
|
note.vibratoLFO.frequency
|
|
1596
|
-
.cancelScheduledValues(
|
|
1597
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1573
|
+
.cancelScheduledValues(scheduleTime)
|
|
1574
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1598
1575
|
}
|
|
1599
1576
|
createVoiceParamsHandlers() {
|
|
1600
1577
|
return {
|
|
1601
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1578
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1602
1579
|
if (0 < channel.state.modulationDepth) {
|
|
1603
|
-
this.setModLfoToPitch(channel, note);
|
|
1580
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1604
1581
|
}
|
|
1605
1582
|
},
|
|
1606
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1583
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1607
1584
|
if (0 < channel.state.vibratoDepth) {
|
|
1608
|
-
this.setVibLfoToPitch(channel, note);
|
|
1585
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1609
1586
|
}
|
|
1610
1587
|
},
|
|
1611
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1588
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1612
1589
|
if (0 < channel.state.modulationDepth) {
|
|
1613
|
-
this.setModLfoToFilterFc(channel, note);
|
|
1590
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1614
1591
|
}
|
|
1615
1592
|
},
|
|
1616
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1593
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1617
1594
|
if (0 < channel.state.modulationDepth) {
|
|
1618
|
-
this.setModLfoToVolume(channel, note);
|
|
1595
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1619
1596
|
}
|
|
1620
1597
|
},
|
|
1621
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1622
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1598
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1599
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1623
1600
|
},
|
|
1624
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1625
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1601
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1602
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1626
1603
|
},
|
|
1627
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1628
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1629
|
-
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) => {
|
|
1630
1607
|
if (0 < channel.state.vibratoDepth) {
|
|
1631
|
-
const now = this.audioContext.currentTime;
|
|
1632
1608
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1633
1609
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1634
|
-
if (
|
|
1610
|
+
if (scheduleTime < prevStartTime)
|
|
1635
1611
|
return;
|
|
1636
1612
|
const value = note.voiceParams.delayVibLFO;
|
|
1637
1613
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1638
|
-
note.vibratoLFO.stop(
|
|
1614
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1639
1615
|
note.vibratoLFO.start(startTime);
|
|
1640
1616
|
}
|
|
1641
1617
|
},
|
|
1642
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1618
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1643
1619
|
if (0 < channel.state.vibratoDepth) {
|
|
1644
|
-
this.setFreqVibLFO(channel, note);
|
|
1620
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1645
1621
|
}
|
|
1646
1622
|
},
|
|
1647
1623
|
};
|
|
@@ -1653,7 +1629,7 @@ class MidyGM2 {
|
|
|
1653
1629
|
state[3] = noteNumber / 127;
|
|
1654
1630
|
return state;
|
|
1655
1631
|
}
|
|
1656
|
-
applyVoiceParams(channel, controllerType) {
|
|
1632
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1657
1633
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1658
1634
|
for (let i = 0; i < noteList.length; i++) {
|
|
1659
1635
|
const note = noteList[i];
|
|
@@ -1669,7 +1645,7 @@ class MidyGM2 {
|
|
|
1669
1645
|
continue;
|
|
1670
1646
|
note.voiceParams[key] = value;
|
|
1671
1647
|
if (key in this.voiceParamsHandlers) {
|
|
1672
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1648
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1673
1649
|
}
|
|
1674
1650
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1675
1651
|
if (appliedFilterEnvelope)
|
|
@@ -1682,12 +1658,12 @@ class MidyGM2 {
|
|
|
1682
1658
|
noteVoiceParams[key] = voiceParams[key];
|
|
1683
1659
|
}
|
|
1684
1660
|
if (note.portamento) {
|
|
1685
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1661
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1686
1662
|
}
|
|
1687
1663
|
else {
|
|
1688
|
-
this.setFilterEnvelope(channel, note);
|
|
1664
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1689
1665
|
}
|
|
1690
|
-
this.setPitchEnvelope(note);
|
|
1666
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1691
1667
|
}
|
|
1692
1668
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1693
1669
|
if (appliedVolumeEnvelope)
|
|
@@ -1699,7 +1675,7 @@ class MidyGM2 {
|
|
|
1699
1675
|
if (key in voiceParams)
|
|
1700
1676
|
noteVoiceParams[key] = voiceParams[key];
|
|
1701
1677
|
}
|
|
1702
|
-
this.setVolumeEnvelope(channel, note);
|
|
1678
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1703
1679
|
}
|
|
1704
1680
|
}
|
|
1705
1681
|
}
|
|
@@ -1733,12 +1709,12 @@ class MidyGM2 {
|
|
|
1733
1709
|
127: this.polyOn,
|
|
1734
1710
|
};
|
|
1735
1711
|
}
|
|
1736
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1712
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1737
1713
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1738
1714
|
if (handler) {
|
|
1739
|
-
handler.call(this, channelNumber, value,
|
|
1715
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1740
1716
|
const channel = this.channels[channelNumber];
|
|
1741
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1717
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1742
1718
|
this.applyControlTable(channel, controllerType);
|
|
1743
1719
|
}
|
|
1744
1720
|
else {
|
|
@@ -1824,56 +1800,55 @@ class MidyGM2 {
|
|
|
1824
1800
|
setBankLSB(channelNumber, lsb) {
|
|
1825
1801
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1826
1802
|
}
|
|
1827
|
-
dataEntryLSB(channelNumber, value) {
|
|
1803
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1828
1804
|
this.channels[channelNumber].dataLSB = value;
|
|
1829
|
-
this.handleRPN(channelNumber);
|
|
1805
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1830
1806
|
}
|
|
1831
|
-
updateChannelVolume(channel) {
|
|
1832
|
-
const now = this.audioContext.currentTime;
|
|
1807
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1833
1808
|
const state = channel.state;
|
|
1834
1809
|
const volume = state.volume * state.expression;
|
|
1835
1810
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1836
1811
|
channel.gainL.gain
|
|
1837
|
-
.cancelScheduledValues(
|
|
1838
|
-
.setValueAtTime(volume * gainLeft,
|
|
1812
|
+
.cancelScheduledValues(scheduleTime)
|
|
1813
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1839
1814
|
channel.gainR.gain
|
|
1840
|
-
.cancelScheduledValues(
|
|
1841
|
-
.setValueAtTime(volume * gainRight,
|
|
1815
|
+
.cancelScheduledValues(scheduleTime)
|
|
1816
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1842
1817
|
}
|
|
1843
|
-
setSustainPedal(channelNumber, value) {
|
|
1818
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1819
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1844
1820
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1845
1821
|
if (value < 64) {
|
|
1846
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1822
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1847
1823
|
}
|
|
1848
1824
|
}
|
|
1849
1825
|
setPortamento(channelNumber, value) {
|
|
1850
1826
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1851
1827
|
}
|
|
1852
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1828
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1853
1829
|
const channel = this.channels[channelNumber];
|
|
1854
1830
|
channel.state.sostenutoPedal = value / 127;
|
|
1855
1831
|
if (64 <= value) {
|
|
1856
|
-
|
|
1857
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1832
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1858
1833
|
}
|
|
1859
1834
|
else {
|
|
1860
1835
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
1861
1836
|
}
|
|
1862
1837
|
}
|
|
1863
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1838
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1864
1839
|
const channel = this.channels[channelNumber];
|
|
1865
1840
|
channel.state.softPedal = softPedal / 127;
|
|
1866
1841
|
}
|
|
1867
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1842
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1868
1843
|
const channel = this.channels[channelNumber];
|
|
1869
1844
|
const state = channel.state;
|
|
1870
1845
|
const reverbEffect = this.reverbEffect;
|
|
1871
1846
|
if (0 < state.reverbSendLevel) {
|
|
1872
1847
|
if (0 < reverbSendLevel) {
|
|
1873
|
-
const now = this.audioContext.currentTime;
|
|
1874
1848
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1875
|
-
reverbEffect.input.gain
|
|
1876
|
-
|
|
1849
|
+
reverbEffect.input.gain
|
|
1850
|
+
.cancelScheduledValues(scheduleTime)
|
|
1851
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1877
1852
|
}
|
|
1878
1853
|
else {
|
|
1879
1854
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1890,31 +1865,31 @@ class MidyGM2 {
|
|
|
1890
1865
|
}
|
|
1891
1866
|
else {
|
|
1892
1867
|
if (0 < reverbSendLevel) {
|
|
1893
|
-
const now = this.audioContext.currentTime;
|
|
1894
1868
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1895
1869
|
for (let i = 0; i < noteList.length; i++) {
|
|
1896
1870
|
const note = noteList[i];
|
|
1897
1871
|
if (!note)
|
|
1898
1872
|
continue;
|
|
1899
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1873
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1900
1874
|
}
|
|
1901
1875
|
});
|
|
1902
1876
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1903
|
-
reverbEffect.input.gain
|
|
1904
|
-
|
|
1877
|
+
reverbEffect.input.gain
|
|
1878
|
+
.cancelScheduledValues(scheduleTime)
|
|
1879
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1905
1880
|
}
|
|
1906
1881
|
}
|
|
1907
1882
|
}
|
|
1908
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1883
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
1909
1884
|
const channel = this.channels[channelNumber];
|
|
1910
1885
|
const state = channel.state;
|
|
1911
1886
|
const chorusEffect = this.chorusEffect;
|
|
1912
1887
|
if (0 < state.chorusSendLevel) {
|
|
1913
1888
|
if (0 < chorusSendLevel) {
|
|
1914
|
-
const now = this.audioContext.currentTime;
|
|
1915
1889
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1916
|
-
chorusEffect.input.gain
|
|
1917
|
-
|
|
1890
|
+
chorusEffect.input.gain
|
|
1891
|
+
.cancelScheduledValues(scheduleTime)
|
|
1892
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1918
1893
|
}
|
|
1919
1894
|
else {
|
|
1920
1895
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1931,18 +1906,18 @@ class MidyGM2 {
|
|
|
1931
1906
|
}
|
|
1932
1907
|
else {
|
|
1933
1908
|
if (0 < chorusSendLevel) {
|
|
1934
|
-
const now = this.audioContext.currentTime;
|
|
1935
1909
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1936
1910
|
for (let i = 0; i < noteList.length; i++) {
|
|
1937
1911
|
const note = noteList[i];
|
|
1938
1912
|
if (!note)
|
|
1939
1913
|
continue;
|
|
1940
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1914
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1941
1915
|
}
|
|
1942
1916
|
});
|
|
1943
1917
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1944
|
-
chorusEffect.input.gain
|
|
1945
|
-
|
|
1918
|
+
chorusEffect.input.gain
|
|
1919
|
+
.cancelScheduledValues(scheduleTime)
|
|
1920
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1946
1921
|
}
|
|
1947
1922
|
}
|
|
1948
1923
|
}
|
|
@@ -1972,12 +1947,12 @@ class MidyGM2 {
|
|
|
1972
1947
|
channel.dataMSB = minMSB;
|
|
1973
1948
|
}
|
|
1974
1949
|
}
|
|
1975
|
-
handleRPN(channelNumber) {
|
|
1950
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1976
1951
|
const channel = this.channels[channelNumber];
|
|
1977
1952
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1978
1953
|
switch (rpn) {
|
|
1979
1954
|
case 0:
|
|
1980
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1955
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1981
1956
|
break;
|
|
1982
1957
|
case 1:
|
|
1983
1958
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1998,25 +1973,26 @@ class MidyGM2 {
|
|
|
1998
1973
|
setRPNLSB(channelNumber, value) {
|
|
1999
1974
|
this.channels[channelNumber].rpnLSB = value;
|
|
2000
1975
|
}
|
|
2001
|
-
dataEntryMSB(channelNumber, value) {
|
|
1976
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2002
1977
|
this.channels[channelNumber].dataMSB = value;
|
|
2003
|
-
this.handleRPN(channelNumber);
|
|
1978
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
2004
1979
|
}
|
|
2005
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1980
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2006
1981
|
const channel = this.channels[channelNumber];
|
|
2007
1982
|
this.limitData(channel, 0, 127, 0, 99);
|
|
2008
1983
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2009
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1984
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2010
1985
|
}
|
|
2011
|
-
setPitchBendRange(channelNumber, value) {
|
|
1986
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1987
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2012
1988
|
const channel = this.channels[channelNumber];
|
|
2013
1989
|
const state = channel.state;
|
|
2014
1990
|
const prev = state.pitchWheelSensitivity;
|
|
2015
1991
|
const next = value / 128;
|
|
2016
1992
|
state.pitchWheelSensitivity = next;
|
|
2017
1993
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2018
|
-
this.updateChannelDetune(channel);
|
|
2019
|
-
this.applyVoiceParams(channel, 16);
|
|
1994
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1995
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2020
1996
|
}
|
|
2021
1997
|
handleFineTuningRPN(channelNumber) {
|
|
2022
1998
|
const channel = this.channels[channelNumber];
|
|
@@ -2057,8 +2033,9 @@ class MidyGM2 {
|
|
|
2057
2033
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2058
2034
|
this.updateModulation(channel);
|
|
2059
2035
|
}
|
|
2060
|
-
allSoundOff(channelNumber) {
|
|
2061
|
-
|
|
2036
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2037
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2038
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2062
2039
|
}
|
|
2063
2040
|
resetAllControllers(channelNumber) {
|
|
2064
2041
|
const stateTypes = [
|
|
@@ -2086,8 +2063,9 @@ class MidyGM2 {
|
|
|
2086
2063
|
channel[type] = this.constructor.channelSettings[type];
|
|
2087
2064
|
}
|
|
2088
2065
|
}
|
|
2089
|
-
allNotesOff(channelNumber) {
|
|
2090
|
-
|
|
2066
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2067
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2068
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2091
2069
|
}
|
|
2092
2070
|
omniOff() {
|
|
2093
2071
|
this.omni = false;
|
|
@@ -2101,13 +2079,13 @@ class MidyGM2 {
|
|
|
2101
2079
|
polyOn() {
|
|
2102
2080
|
this.mono = false;
|
|
2103
2081
|
}
|
|
2104
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2082
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2105
2083
|
switch (data[2]) {
|
|
2106
2084
|
case 8:
|
|
2107
2085
|
switch (data[3]) {
|
|
2108
2086
|
case 8:
|
|
2109
2087
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2110
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2088
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2111
2089
|
default:
|
|
2112
2090
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2113
2091
|
}
|
|
@@ -2150,18 +2128,18 @@ class MidyGM2 {
|
|
|
2150
2128
|
this.channels[9].bankMSB = 120;
|
|
2151
2129
|
this.channels[9].bank = 120 * 128;
|
|
2152
2130
|
}
|
|
2153
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2131
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2154
2132
|
switch (data[2]) {
|
|
2155
2133
|
case 4:
|
|
2156
2134
|
switch (data[3]) {
|
|
2157
2135
|
case 1:
|
|
2158
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2136
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2159
2137
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2160
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2138
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2161
2139
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2162
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2140
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2163
2141
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2164
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2142
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2165
2143
|
default:
|
|
2166
2144
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2167
2145
|
}
|
|
@@ -2179,7 +2157,7 @@ class MidyGM2 {
|
|
|
2179
2157
|
case 10:
|
|
2180
2158
|
switch (data[3]) {
|
|
2181
2159
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2182
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2160
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2183
2161
|
default:
|
|
2184
2162
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2185
2163
|
}
|
|
@@ -2188,49 +2166,50 @@ class MidyGM2 {
|
|
|
2188
2166
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2189
2167
|
}
|
|
2190
2168
|
}
|
|
2191
|
-
handleMasterVolumeSysEx(data) {
|
|
2169
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2192
2170
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2193
|
-
this.setMasterVolume(volume);
|
|
2171
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2194
2172
|
}
|
|
2195
|
-
setMasterVolume(volume) {
|
|
2173
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2174
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2196
2175
|
if (volume < 0 && 1 < volume) {
|
|
2197
2176
|
console.error("Master Volume is out of range");
|
|
2198
2177
|
}
|
|
2199
2178
|
else {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2179
|
+
this.masterVolume.gain
|
|
2180
|
+
.cancelScheduledValues(scheduleTime)
|
|
2181
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2203
2182
|
}
|
|
2204
2183
|
}
|
|
2205
|
-
handleMasterFineTuningSysEx(data) {
|
|
2184
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2206
2185
|
const fineTuning = data[5] * 128 + data[4];
|
|
2207
|
-
this.setMasterFineTuning(fineTuning);
|
|
2186
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2208
2187
|
}
|
|
2209
|
-
setMasterFineTuning(value) {
|
|
2188
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2210
2189
|
const prev = this.masterFineTuning;
|
|
2211
2190
|
const next = (value - 8192) / 8.192; // cent
|
|
2212
2191
|
this.masterFineTuning = next;
|
|
2213
2192
|
channel.detune += next - prev;
|
|
2214
|
-
this.updateChannelDetune(channel);
|
|
2193
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2215
2194
|
}
|
|
2216
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2195
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2217
2196
|
const coarseTuning = data[4];
|
|
2218
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2197
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2219
2198
|
}
|
|
2220
|
-
setMasterCoarseTuning(value) {
|
|
2199
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2221
2200
|
const prev = this.masterCoarseTuning;
|
|
2222
2201
|
const next = (value - 64) * 100; // cent
|
|
2223
2202
|
this.masterCoarseTuning = next;
|
|
2224
2203
|
channel.detune += next - prev;
|
|
2225
|
-
this.updateChannelDetune(channel);
|
|
2204
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2226
2205
|
}
|
|
2227
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2206
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2228
2207
|
if (data[7] === 1) {
|
|
2229
2208
|
switch (data[8]) {
|
|
2230
2209
|
case 1:
|
|
2231
2210
|
return this.handleReverbParameterSysEx(data);
|
|
2232
2211
|
case 2:
|
|
2233
|
-
return this.handleChorusParameterSysEx(data);
|
|
2212
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2234
2213
|
default:
|
|
2235
2214
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2236
2215
|
}
|
|
@@ -2309,88 +2288,84 @@ class MidyGM2 {
|
|
|
2309
2288
|
calcDelay(rt60, feedback) {
|
|
2310
2289
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2311
2290
|
}
|
|
2312
|
-
handleChorusParameterSysEx(data) {
|
|
2291
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2313
2292
|
switch (data[9]) {
|
|
2314
2293
|
case 0:
|
|
2315
|
-
return this.setChorusType(data[10]);
|
|
2294
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2316
2295
|
case 1:
|
|
2317
|
-
return this.setChorusModRate(data[10]);
|
|
2296
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2318
2297
|
case 2:
|
|
2319
|
-
return this.setChorusModDepth(data[10]);
|
|
2298
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2320
2299
|
case 3:
|
|
2321
|
-
return this.setChorusFeedback(data[10]);
|
|
2300
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2322
2301
|
case 4:
|
|
2323
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2302
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2324
2303
|
}
|
|
2325
2304
|
}
|
|
2326
|
-
setChorusType(type) {
|
|
2305
|
+
setChorusType(type, scheduleTime) {
|
|
2327
2306
|
switch (type) {
|
|
2328
2307
|
case 0:
|
|
2329
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2308
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2330
2309
|
case 1:
|
|
2331
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2310
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2332
2311
|
case 2:
|
|
2333
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2312
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2334
2313
|
case 3:
|
|
2335
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2314
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2336
2315
|
case 4:
|
|
2337
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2316
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2338
2317
|
case 5:
|
|
2339
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2318
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2340
2319
|
default:
|
|
2341
2320
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2342
2321
|
}
|
|
2343
2322
|
}
|
|
2344
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2345
|
-
this.setChorusModRate(modRate);
|
|
2346
|
-
this.setChorusModDepth(modDepth);
|
|
2347
|
-
this.setChorusFeedback(feedback);
|
|
2348
|
-
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);
|
|
2349
2328
|
}
|
|
2350
|
-
setChorusModRate(value) {
|
|
2351
|
-
const now = this.audioContext.currentTime;
|
|
2329
|
+
setChorusModRate(value, scheduleTime) {
|
|
2352
2330
|
const modRate = this.getChorusModRate(value);
|
|
2353
2331
|
this.chorus.modRate = modRate;
|
|
2354
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2332
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2355
2333
|
}
|
|
2356
2334
|
getChorusModRate(value) {
|
|
2357
2335
|
return value * 0.122; // Hz
|
|
2358
2336
|
}
|
|
2359
|
-
setChorusModDepth(value) {
|
|
2360
|
-
const now = this.audioContext.currentTime;
|
|
2337
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2361
2338
|
const modDepth = this.getChorusModDepth(value);
|
|
2362
2339
|
this.chorus.modDepth = modDepth;
|
|
2363
2340
|
this.chorusEffect.lfoGain.gain
|
|
2364
|
-
.cancelScheduledValues(
|
|
2365
|
-
.setValueAtTime(modDepth / 2,
|
|
2341
|
+
.cancelScheduledValues(scheduleTime)
|
|
2342
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2366
2343
|
}
|
|
2367
2344
|
getChorusModDepth(value) {
|
|
2368
2345
|
return (value + 1) / 3200; // second
|
|
2369
2346
|
}
|
|
2370
|
-
setChorusFeedback(value) {
|
|
2371
|
-
const now = this.audioContext.currentTime;
|
|
2347
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2372
2348
|
const feedback = this.getChorusFeedback(value);
|
|
2373
2349
|
this.chorus.feedback = feedback;
|
|
2374
2350
|
const chorusEffect = this.chorusEffect;
|
|
2375
2351
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2376
2352
|
chorusEffect.feedbackGains[i].gain
|
|
2377
|
-
.cancelScheduledValues(
|
|
2378
|
-
.setValueAtTime(feedback,
|
|
2353
|
+
.cancelScheduledValues(scheduleTime)
|
|
2354
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2379
2355
|
}
|
|
2380
2356
|
}
|
|
2381
2357
|
getChorusFeedback(value) {
|
|
2382
2358
|
return value * 0.00763;
|
|
2383
2359
|
}
|
|
2384
|
-
setChorusSendToReverb(value) {
|
|
2360
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2385
2361
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2386
2362
|
const sendGain = this.chorusEffect.sendGain;
|
|
2387
2363
|
if (0 < this.chorus.sendToReverb) {
|
|
2388
2364
|
this.chorus.sendToReverb = sendToReverb;
|
|
2389
2365
|
if (0 < sendToReverb) {
|
|
2390
|
-
const now = this.audioContext.currentTime;
|
|
2391
2366
|
sendGain.gain
|
|
2392
|
-
.cancelScheduledValues(
|
|
2393
|
-
.setValueAtTime(sendToReverb,
|
|
2367
|
+
.cancelScheduledValues(scheduleTime)
|
|
2368
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2394
2369
|
}
|
|
2395
2370
|
else {
|
|
2396
2371
|
sendGain.disconnect();
|
|
@@ -2399,11 +2374,10 @@ class MidyGM2 {
|
|
|
2399
2374
|
else {
|
|
2400
2375
|
this.chorus.sendToReverb = sendToReverb;
|
|
2401
2376
|
if (0 < sendToReverb) {
|
|
2402
|
-
const now = this.audioContext.currentTime;
|
|
2403
2377
|
sendGain.connect(this.reverbEffect.input);
|
|
2404
2378
|
sendGain.gain
|
|
2405
|
-
.cancelScheduledValues(
|
|
2406
|
-
.setValueAtTime(sendToReverb,
|
|
2379
|
+
.cancelScheduledValues(scheduleTime)
|
|
2380
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2407
2381
|
}
|
|
2408
2382
|
}
|
|
2409
2383
|
}
|
|
@@ -2429,7 +2403,7 @@ class MidyGM2 {
|
|
|
2429
2403
|
}
|
|
2430
2404
|
return bitmap;
|
|
2431
2405
|
}
|
|
2432
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2406
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2433
2407
|
if (data.length < 19) {
|
|
2434
2408
|
console.error("Data length is too short");
|
|
2435
2409
|
return;
|
|
@@ -2444,7 +2418,7 @@ class MidyGM2 {
|
|
|
2444
2418
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2445
2419
|
}
|
|
2446
2420
|
if (realtime)
|
|
2447
|
-
this.updateChannelDetune(channel);
|
|
2421
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2448
2422
|
}
|
|
2449
2423
|
}
|
|
2450
2424
|
getFilterCutoffControl(channel) {
|
|
@@ -2488,7 +2462,7 @@ class MidyGM2 {
|
|
|
2488
2462
|
if (table[5] !== 0)
|
|
2489
2463
|
this.setModLfoToVolume(channel, note);
|
|
2490
2464
|
}
|
|
2491
|
-
|
|
2465
|
+
handlePressureSysEx(data, tableName) {
|
|
2492
2466
|
const channelNumber = data[4];
|
|
2493
2467
|
const table = this.channels[channelNumber][tableName];
|
|
2494
2468
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2536,7 +2510,7 @@ class MidyGM2 {
|
|
|
2536
2510
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2537
2511
|
return (controlValue + 64) / 64;
|
|
2538
2512
|
}
|
|
2539
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2513
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2540
2514
|
const channelNumber = data[4];
|
|
2541
2515
|
const keyNumber = data[5];
|
|
2542
2516
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2546,30 +2520,27 @@ class MidyGM2 {
|
|
|
2546
2520
|
const index = keyNumber * 128 + controllerType;
|
|
2547
2521
|
table[index] = value - 64;
|
|
2548
2522
|
}
|
|
2549
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2523
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2550
2524
|
}
|
|
2551
|
-
|
|
2552
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2553
|
-
}
|
|
2554
|
-
handleSysEx(data) {
|
|
2525
|
+
handleSysEx(data, scheduleTime) {
|
|
2555
2526
|
switch (data[0]) {
|
|
2556
2527
|
case 126:
|
|
2557
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2528
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2558
2529
|
case 127:
|
|
2559
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2530
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2560
2531
|
default:
|
|
2561
|
-
|
|
2532
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2562
2533
|
}
|
|
2563
2534
|
}
|
|
2564
|
-
scheduleTask(callback,
|
|
2535
|
+
scheduleTask(callback, scheduleTime) {
|
|
2565
2536
|
return new Promise((resolve) => {
|
|
2566
2537
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2567
2538
|
bufferSource.onended = () => {
|
|
2568
2539
|
callback();
|
|
2569
2540
|
resolve();
|
|
2570
2541
|
};
|
|
2571
|
-
bufferSource.start(
|
|
2572
|
-
bufferSource.stop(
|
|
2542
|
+
bufferSource.start(scheduleTime);
|
|
2543
|
+
bufferSource.stop(scheduleTime);
|
|
2573
2544
|
});
|
|
2574
2545
|
}
|
|
2575
2546
|
}
|