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