@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.js
CHANGED
|
@@ -587,7 +587,8 @@ export class Midy {
|
|
|
587
587
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
588
588
|
if (portamentoTarget)
|
|
589
589
|
portamentoTarget.portamento = true;
|
|
590
|
-
const notePromise = this.
|
|
590
|
+
const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
591
|
+
portamentoTarget?.noteNumber);
|
|
591
592
|
if (notePromise) {
|
|
592
593
|
this.notePromises.push(notePromise);
|
|
593
594
|
}
|
|
@@ -640,10 +641,11 @@ export class Midy {
|
|
|
640
641
|
resolve();
|
|
641
642
|
return;
|
|
642
643
|
}
|
|
643
|
-
const
|
|
644
|
+
const now = this.audioContext.currentTime;
|
|
645
|
+
const t = now + offset;
|
|
644
646
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
645
647
|
if (this.isPausing) {
|
|
646
|
-
await this.stopNotes(0, true);
|
|
648
|
+
await this.stopNotes(0, true, now);
|
|
647
649
|
this.notePromises = [];
|
|
648
650
|
resolve();
|
|
649
651
|
this.isPausing = false;
|
|
@@ -651,7 +653,7 @@ export class Midy {
|
|
|
651
653
|
return;
|
|
652
654
|
}
|
|
653
655
|
else if (this.isStopping) {
|
|
654
|
-
await this.stopNotes(0, true);
|
|
656
|
+
await this.stopNotes(0, true, now);
|
|
655
657
|
this.notePromises = [];
|
|
656
658
|
this.exclusiveClassMap.clear();
|
|
657
659
|
this.audioBufferCache.clear();
|
|
@@ -661,7 +663,7 @@ export class Midy {
|
|
|
661
663
|
return;
|
|
662
664
|
}
|
|
663
665
|
else if (this.isSeeking) {
|
|
664
|
-
this.stopNotes(0, true);
|
|
666
|
+
this.stopNotes(0, true, now);
|
|
665
667
|
this.exclusiveClassMap.clear();
|
|
666
668
|
this.startTime = this.audioContext.currentTime;
|
|
667
669
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -670,7 +672,6 @@ export class Midy {
|
|
|
670
672
|
await schedulePlayback();
|
|
671
673
|
}
|
|
672
674
|
else {
|
|
673
|
-
const now = this.audioContext.currentTime;
|
|
674
675
|
const waitTime = now + this.noteCheckInterval;
|
|
675
676
|
await this.scheduleTask(() => { }, waitTime);
|
|
676
677
|
await schedulePlayback();
|
|
@@ -790,25 +791,26 @@ export class Midy {
|
|
|
790
791
|
}
|
|
791
792
|
return { instruments, timeline };
|
|
792
793
|
}
|
|
793
|
-
|
|
794
|
-
const now = this.audioContext.currentTime;
|
|
794
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
795
795
|
const channel = this.channels[channelNumber];
|
|
796
|
+
const promises = [];
|
|
796
797
|
channel.scheduledNotes.forEach((noteList) => {
|
|
797
798
|
for (let i = 0; i < noteList.length; i++) {
|
|
798
799
|
const note = noteList[i];
|
|
799
800
|
if (!note)
|
|
800
801
|
continue;
|
|
801
|
-
const promise = this.
|
|
802
|
-
force);
|
|
802
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
803
803
|
this.notePromises.push(promise);
|
|
804
|
+
promises.push(promise);
|
|
804
805
|
}
|
|
805
806
|
});
|
|
806
807
|
channel.scheduledNotes.clear();
|
|
807
|
-
|
|
808
|
+
return Promise.all(promises);
|
|
808
809
|
}
|
|
809
|
-
stopNotes(velocity, force) {
|
|
810
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
811
|
+
const promises = [];
|
|
810
812
|
for (let i = 0; i < this.channels.length; i++) {
|
|
811
|
-
this.stopChannelNotes(i, velocity, force);
|
|
813
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
812
814
|
}
|
|
813
815
|
return Promise.all(this.notePromises);
|
|
814
816
|
}
|
|
@@ -868,22 +870,22 @@ export class Midy {
|
|
|
868
870
|
}
|
|
869
871
|
});
|
|
870
872
|
}
|
|
871
|
-
getActiveNotes(channel,
|
|
873
|
+
getActiveNotes(channel, scheduleTime) {
|
|
872
874
|
const activeNotes = new SparseMap(128);
|
|
873
875
|
channel.scheduledNotes.forEach((noteList) => {
|
|
874
|
-
const activeNote = this.getActiveNote(noteList,
|
|
876
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
875
877
|
if (activeNote) {
|
|
876
878
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
877
879
|
}
|
|
878
880
|
});
|
|
879
881
|
return activeNotes;
|
|
880
882
|
}
|
|
881
|
-
getActiveNote(noteList,
|
|
883
|
+
getActiveNote(noteList, scheduleTime) {
|
|
882
884
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
883
885
|
const note = noteList[i];
|
|
884
886
|
if (!note)
|
|
885
887
|
return;
|
|
886
|
-
if (
|
|
888
|
+
if (scheduleTime < note.startTime)
|
|
887
889
|
continue;
|
|
888
890
|
return (note.ending) ? null : note;
|
|
889
891
|
}
|
|
@@ -1043,44 +1045,36 @@ export class Midy {
|
|
|
1043
1045
|
calcNoteDetune(channel, note) {
|
|
1044
1046
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1045
1047
|
}
|
|
1046
|
-
updateChannelDetune(channel) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const note = noteList[i];
|
|
1050
|
-
if (!note)
|
|
1051
|
-
continue;
|
|
1052
|
-
this.updateDetune(channel, note);
|
|
1053
|
-
}
|
|
1048
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1049
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1050
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1054
1051
|
});
|
|
1055
1052
|
}
|
|
1056
|
-
updateDetune(channel, note) {
|
|
1057
|
-
const now = this.audioContext.currentTime;
|
|
1053
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1058
1054
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1059
1055
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1060
1056
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1061
1057
|
note.bufferSource.detune
|
|
1062
|
-
.cancelScheduledValues(
|
|
1063
|
-
.setValueAtTime(detune,
|
|
1058
|
+
.cancelScheduledValues(scheduleTime)
|
|
1059
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1064
1060
|
}
|
|
1065
1061
|
getPortamentoTime(channel) {
|
|
1066
1062
|
const factor = 5 * Math.log(10) / 127;
|
|
1067
1063
|
const time = channel.state.portamentoTime;
|
|
1068
1064
|
return Math.log(time) / factor;
|
|
1069
1065
|
}
|
|
1070
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1071
|
-
const now = this.audioContext.currentTime;
|
|
1066
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1072
1067
|
const { voiceParams, startTime } = note;
|
|
1073
1068
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1074
1069
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1075
1070
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1076
1071
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1077
1072
|
note.volumeEnvelopeNode.gain
|
|
1078
|
-
.cancelScheduledValues(
|
|
1073
|
+
.cancelScheduledValues(scheduleTime)
|
|
1079
1074
|
.setValueAtTime(0, volDelay)
|
|
1080
1075
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1081
1076
|
}
|
|
1082
|
-
setVolumeEnvelope(channel, note) {
|
|
1083
|
-
const now = this.audioContext.currentTime;
|
|
1077
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1084
1078
|
const state = channel.state;
|
|
1085
1079
|
const { voiceParams, startTime } = note;
|
|
1086
1080
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
@@ -1091,7 +1085,7 @@ export class Midy {
|
|
|
1091
1085
|
const volHold = volAttack + voiceParams.volHold;
|
|
1092
1086
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
1093
1087
|
note.volumeEnvelopeNode.gain
|
|
1094
|
-
.cancelScheduledValues(
|
|
1088
|
+
.cancelScheduledValues(scheduleTime)
|
|
1095
1089
|
.setValueAtTime(0, startTime)
|
|
1096
1090
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1097
1091
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
@@ -1099,7 +1093,6 @@ export class Midy {
|
|
|
1099
1093
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1100
1094
|
}
|
|
1101
1095
|
setPitchEnvelope(note, scheduleTime) {
|
|
1102
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1103
1096
|
const { voiceParams } = note;
|
|
1104
1097
|
const baseRate = voiceParams.playbackRate;
|
|
1105
1098
|
note.bufferSource.playbackRate
|
|
@@ -1126,8 +1119,7 @@ export class Midy {
|
|
|
1126
1119
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1127
1120
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1128
1121
|
}
|
|
1129
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1130
|
-
const now = this.audioContext.currentTime;
|
|
1122
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1131
1123
|
const state = channel.state;
|
|
1132
1124
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1133
1125
|
const softPedalFactor = 1 -
|
|
@@ -1143,13 +1135,12 @@ export class Midy {
|
|
|
1143
1135
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1144
1136
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1145
1137
|
note.filterNode.frequency
|
|
1146
|
-
.cancelScheduledValues(
|
|
1138
|
+
.cancelScheduledValues(scheduleTime)
|
|
1147
1139
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1148
1140
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1149
1141
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1150
1142
|
}
|
|
1151
|
-
setFilterEnvelope(channel, note) {
|
|
1152
|
-
const now = this.audioContext.currentTime;
|
|
1143
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1153
1144
|
const state = channel.state;
|
|
1154
1145
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1155
1146
|
const softPedalFactor = 1 -
|
|
@@ -1170,14 +1161,14 @@ export class Midy {
|
|
|
1170
1161
|
const modHold = modAttack + voiceParams.modHold;
|
|
1171
1162
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1172
1163
|
note.filterNode.frequency
|
|
1173
|
-
.cancelScheduledValues(
|
|
1164
|
+
.cancelScheduledValues(scheduleTime)
|
|
1174
1165
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1175
1166
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1176
1167
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1177
1168
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1178
1169
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1179
1170
|
}
|
|
1180
|
-
startModulation(channel, note,
|
|
1171
|
+
startModulation(channel, note, scheduleTime) {
|
|
1181
1172
|
const { voiceParams } = note;
|
|
1182
1173
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1183
1174
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1186,10 +1177,10 @@ export class Midy {
|
|
|
1186
1177
|
gain: voiceParams.modLfoToFilterFc,
|
|
1187
1178
|
});
|
|
1188
1179
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1189
|
-
this.setModLfoToPitch(channel, note);
|
|
1180
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1190
1181
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1191
|
-
this.setModLfoToVolume(channel, note);
|
|
1192
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1182
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1183
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1193
1184
|
note.modulationLFO.connect(note.filterDepth);
|
|
1194
1185
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1195
1186
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1197,15 +1188,15 @@ export class Midy {
|
|
|
1197
1188
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1198
1189
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1199
1190
|
}
|
|
1200
|
-
startVibrato(channel, note,
|
|
1191
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1201
1192
|
const { voiceParams } = note;
|
|
1202
1193
|
const state = channel.state;
|
|
1203
1194
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1204
1195
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1205
1196
|
});
|
|
1206
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1197
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1207
1198
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1208
|
-
this.setVibLfoToPitch(channel, note);
|
|
1199
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1209
1200
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1210
1201
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1211
1202
|
}
|
|
@@ -1228,6 +1219,7 @@ export class Midy {
|
|
|
1228
1219
|
}
|
|
1229
1220
|
}
|
|
1230
1221
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1222
|
+
const now = this.audioContext.currentTime;
|
|
1231
1223
|
const state = channel.state;
|
|
1232
1224
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1233
1225
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1244,20 +1236,20 @@ export class Midy {
|
|
|
1244
1236
|
});
|
|
1245
1237
|
if (portamento) {
|
|
1246
1238
|
note.portamento = true;
|
|
1247
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1248
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1239
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1240
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1249
1241
|
}
|
|
1250
1242
|
else {
|
|
1251
1243
|
note.portamento = false;
|
|
1252
|
-
this.setVolumeEnvelope(channel, note);
|
|
1253
|
-
this.setFilterEnvelope(channel, note);
|
|
1244
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1245
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1254
1246
|
}
|
|
1255
1247
|
if (0 < state.vibratoDepth) {
|
|
1256
|
-
this.startVibrato(channel, note,
|
|
1248
|
+
this.startVibrato(channel, note, now);
|
|
1257
1249
|
}
|
|
1258
|
-
this.setPitchEnvelope(note);
|
|
1250
|
+
this.setPitchEnvelope(note, now);
|
|
1259
1251
|
if (0 < state.modulationDepth) {
|
|
1260
|
-
this.startModulation(channel, note,
|
|
1252
|
+
this.startModulation(channel, note, now);
|
|
1261
1253
|
}
|
|
1262
1254
|
if (this.mono && channel.currentBufferSource) {
|
|
1263
1255
|
channel.currentBufferSource.stop(startTime);
|
|
@@ -1269,10 +1261,10 @@ export class Midy {
|
|
|
1269
1261
|
note.volumeNode.connect(note.gainL);
|
|
1270
1262
|
note.volumeNode.connect(note.gainR);
|
|
1271
1263
|
if (0 < channel.chorusSendLevel) {
|
|
1272
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1264
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1273
1265
|
}
|
|
1274
1266
|
if (0 < channel.reverbSendLevel) {
|
|
1275
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1267
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1276
1268
|
}
|
|
1277
1269
|
note.bufferSource.start(startTime);
|
|
1278
1270
|
return note;
|
|
@@ -1309,9 +1301,9 @@ export class Midy {
|
|
|
1309
1301
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1310
1302
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1311
1303
|
if (!prevNote.ending) {
|
|
1312
|
-
this.
|
|
1313
|
-
startTime,
|
|
1314
|
-
|
|
1304
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1305
|
+
startTime, true, // force
|
|
1306
|
+
undefined);
|
|
1315
1307
|
}
|
|
1316
1308
|
}
|
|
1317
1309
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1324,9 +1316,9 @@ export class Midy {
|
|
|
1324
1316
|
scheduledNotes.set(noteNumber, [note]);
|
|
1325
1317
|
}
|
|
1326
1318
|
}
|
|
1327
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1328
|
-
|
|
1329
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
1319
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1320
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1321
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1330
1322
|
}
|
|
1331
1323
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1332
1324
|
const note = scheduledNotes[index];
|
|
@@ -1366,7 +1358,7 @@ export class Midy {
|
|
|
1366
1358
|
note.bufferSource.stop(stopTime);
|
|
1367
1359
|
});
|
|
1368
1360
|
}
|
|
1369
|
-
|
|
1361
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1370
1362
|
const channel = this.channels[channelNumber];
|
|
1371
1363
|
const state = channel.state;
|
|
1372
1364
|
if (!force) {
|
|
@@ -1406,24 +1398,19 @@ export class Midy {
|
|
|
1406
1398
|
}
|
|
1407
1399
|
}
|
|
1408
1400
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
return this.
|
|
1401
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1402
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1403
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
|
|
1404
|
+
undefined);
|
|
1412
1405
|
}
|
|
1413
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1406
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1414
1407
|
const velocity = halfVelocity * 2;
|
|
1415
1408
|
const channel = this.channels[channelNumber];
|
|
1416
1409
|
const promises = [];
|
|
1417
|
-
channel
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
if (!note)
|
|
1422
|
-
continue;
|
|
1423
|
-
const { noteNumber } = note;
|
|
1424
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1425
|
-
promises.push(promise);
|
|
1426
|
-
}
|
|
1410
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1411
|
+
const { noteNumber } = note;
|
|
1412
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1413
|
+
promises.push(promise);
|
|
1427
1414
|
});
|
|
1428
1415
|
return promises;
|
|
1429
1416
|
}
|
|
@@ -1434,55 +1421,51 @@ export class Midy {
|
|
|
1434
1421
|
channel.state.sostenutoPedal = 0;
|
|
1435
1422
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1436
1423
|
const { noteNumber } = activeNote;
|
|
1437
|
-
const promise = this.
|
|
1424
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1438
1425
|
promises.push(promise);
|
|
1439
1426
|
});
|
|
1440
1427
|
channel.sostenutoNotes.clear();
|
|
1441
1428
|
return promises;
|
|
1442
1429
|
}
|
|
1443
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1430
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1444
1431
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
1445
1432
|
const messageType = statusByte & 0xF0;
|
|
1446
1433
|
switch (messageType) {
|
|
1447
1434
|
case 0x80:
|
|
1448
|
-
return this.
|
|
1435
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1449
1436
|
case 0x90:
|
|
1450
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1437
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1451
1438
|
case 0xA0:
|
|
1452
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
1439
|
+
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1453
1440
|
case 0xB0:
|
|
1454
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1441
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1455
1442
|
case 0xC0:
|
|
1456
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1443
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1457
1444
|
case 0xD0:
|
|
1458
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1445
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1459
1446
|
case 0xE0:
|
|
1460
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1447
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1461
1448
|
default:
|
|
1462
1449
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1463
1450
|
}
|
|
1464
1451
|
}
|
|
1465
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure,
|
|
1466
|
-
if (!startTime)
|
|
1467
|
-
startTime = this.audioContext.currentTime;
|
|
1452
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1468
1453
|
const channel = this.channels[channelNumber];
|
|
1469
1454
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1470
1455
|
const table = channel.polyphonicKeyPressureTable;
|
|
1471
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1456
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1472
1457
|
if (activeNotes.has(noteNumber)) {
|
|
1473
1458
|
const note = activeNotes.get(noteNumber);
|
|
1474
1459
|
this.setControllerParameters(channel, note, table);
|
|
1475
1460
|
}
|
|
1476
1461
|
// this.applyVoiceParams(channel, 10);
|
|
1477
1462
|
}
|
|
1478
|
-
handleProgramChange(channelNumber, program) {
|
|
1463
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1479
1464
|
const channel = this.channels[channelNumber];
|
|
1480
1465
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1481
1466
|
channel.program = program;
|
|
1482
1467
|
}
|
|
1483
|
-
handleChannelPressure(channelNumber, value,
|
|
1484
|
-
if (!startTime)
|
|
1485
|
-
startTime = this.audioContext.currentTime;
|
|
1468
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1486
1469
|
const channel = this.channels[channelNumber];
|
|
1487
1470
|
const prev = channel.state.channelPressure;
|
|
1488
1471
|
const next = value / 127;
|
|
@@ -1492,72 +1475,68 @@ export class Midy {
|
|
|
1492
1475
|
channel.detune += pressureDepth * (next - prev);
|
|
1493
1476
|
}
|
|
1494
1477
|
const table = channel.channelPressureTable;
|
|
1495
|
-
this.getActiveNotes(channel,
|
|
1478
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1496
1479
|
this.setControllerParameters(channel, note, table);
|
|
1497
1480
|
});
|
|
1498
1481
|
// this.applyVoiceParams(channel, 13);
|
|
1499
1482
|
}
|
|
1500
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1483
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1501
1484
|
const pitchBend = msb * 128 + lsb;
|
|
1502
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1485
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1503
1486
|
}
|
|
1504
|
-
setPitchBend(channelNumber, value) {
|
|
1487
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1488
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1505
1489
|
const channel = this.channels[channelNumber];
|
|
1506
1490
|
const state = channel.state;
|
|
1507
1491
|
const prev = state.pitchWheel * 2 - 1;
|
|
1508
1492
|
const next = (value - 8192) / 8192;
|
|
1509
1493
|
state.pitchWheel = value / 16383;
|
|
1510
1494
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1511
|
-
this.updateChannelDetune(channel);
|
|
1512
|
-
this.applyVoiceParams(channel, 14);
|
|
1495
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1496
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1513
1497
|
}
|
|
1514
|
-
setModLfoToPitch(channel, note) {
|
|
1515
|
-
const now = this.audioContext.currentTime;
|
|
1498
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1516
1499
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1517
1500
|
this.getLFOPitchDepth(channel, note);
|
|
1518
1501
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1519
1502
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1520
1503
|
note.modulationDepth.gain
|
|
1521
|
-
.cancelScheduledValues(
|
|
1522
|
-
.setValueAtTime(modulationDepth,
|
|
1504
|
+
.cancelScheduledValues(scheduleTime)
|
|
1505
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1523
1506
|
}
|
|
1524
|
-
setVibLfoToPitch(channel, note) {
|
|
1525
|
-
const now = this.audioContext.currentTime;
|
|
1507
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1526
1508
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1527
1509
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1528
1510
|
2;
|
|
1529
1511
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1530
1512
|
note.vibratoDepth.gain
|
|
1531
|
-
.cancelScheduledValues(
|
|
1532
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1513
|
+
.cancelScheduledValues(scheduleTime)
|
|
1514
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1533
1515
|
}
|
|
1534
|
-
setModLfoToFilterFc(channel, note) {
|
|
1535
|
-
const now = this.audioContext.currentTime;
|
|
1516
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1536
1517
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1537
1518
|
this.getLFOFilterDepth(channel, note);
|
|
1538
1519
|
note.filterDepth.gain
|
|
1539
|
-
.cancelScheduledValues(
|
|
1540
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1520
|
+
.cancelScheduledValues(scheduleTime)
|
|
1521
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1541
1522
|
}
|
|
1542
|
-
setModLfoToVolume(channel, note) {
|
|
1543
|
-
const now = this.audioContext.currentTime;
|
|
1523
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1544
1524
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1545
1525
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1546
1526
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1547
1527
|
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1548
1528
|
note.volumeDepth.gain
|
|
1549
|
-
.cancelScheduledValues(
|
|
1550
|
-
.setValueAtTime(volumeDepth,
|
|
1529
|
+
.cancelScheduledValues(scheduleTime)
|
|
1530
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1551
1531
|
}
|
|
1552
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1532
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1553
1533
|
if (0 < prevValue) {
|
|
1554
1534
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1555
|
-
const now = this.audioContext.currentTime;
|
|
1556
1535
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1557
1536
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1558
1537
|
note.reverbEffectsSend.gain
|
|
1559
|
-
.cancelScheduledValues(
|
|
1560
|
-
.setValueAtTime(value,
|
|
1538
|
+
.cancelScheduledValues(scheduleTime)
|
|
1539
|
+
.setValueAtTime(value, scheduleTime);
|
|
1561
1540
|
}
|
|
1562
1541
|
else {
|
|
1563
1542
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1575,15 +1554,14 @@ export class Midy {
|
|
|
1575
1554
|
}
|
|
1576
1555
|
}
|
|
1577
1556
|
}
|
|
1578
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1557
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1579
1558
|
if (0 < prevValue) {
|
|
1580
1559
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1581
|
-
const now = this.audioContext.currentTime;
|
|
1582
1560
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1583
1561
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1584
1562
|
note.chorusEffectsSend.gain
|
|
1585
|
-
.cancelScheduledValues(
|
|
1586
|
-
.setValueAtTime(value,
|
|
1563
|
+
.cancelScheduledValues(scheduleTime)
|
|
1564
|
+
.setValueAtTime(value, scheduleTime);
|
|
1587
1565
|
}
|
|
1588
1566
|
else {
|
|
1589
1567
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1601,75 +1579,71 @@ export class Midy {
|
|
|
1601
1579
|
}
|
|
1602
1580
|
}
|
|
1603
1581
|
}
|
|
1604
|
-
setDelayModLFO(note) {
|
|
1605
|
-
const now = this.audioContext.currentTime;
|
|
1582
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1606
1583
|
const startTime = note.startTime;
|
|
1607
|
-
if (startTime <
|
|
1584
|
+
if (startTime < scheduleTime)
|
|
1608
1585
|
return;
|
|
1609
|
-
note.modulationLFO.stop(
|
|
1586
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1610
1587
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1611
1588
|
note.modulationLFO.connect(note.filterDepth);
|
|
1612
1589
|
}
|
|
1613
|
-
setFreqModLFO(note) {
|
|
1614
|
-
const now = this.audioContext.currentTime;
|
|
1590
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1615
1591
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1616
1592
|
note.modulationLFO.frequency
|
|
1617
|
-
.cancelScheduledValues(
|
|
1618
|
-
.setValueAtTime(freqModLFO,
|
|
1593
|
+
.cancelScheduledValues(scheduleTime)
|
|
1594
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1619
1595
|
}
|
|
1620
|
-
setFreqVibLFO(channel, note) {
|
|
1621
|
-
const now = this.audioContext.currentTime;
|
|
1596
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1622
1597
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1623
1598
|
note.vibratoLFO.frequency
|
|
1624
|
-
.cancelScheduledValues(
|
|
1625
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1599
|
+
.cancelScheduledValues(scheduleTime)
|
|
1600
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1626
1601
|
}
|
|
1627
1602
|
createVoiceParamsHandlers() {
|
|
1628
1603
|
return {
|
|
1629
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1604
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1630
1605
|
if (0 < channel.state.modulationDepth) {
|
|
1631
|
-
this.setModLfoToPitch(channel, note);
|
|
1606
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1632
1607
|
}
|
|
1633
1608
|
},
|
|
1634
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1609
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1635
1610
|
if (0 < channel.state.vibratoDepth) {
|
|
1636
|
-
this.setVibLfoToPitch(channel, note);
|
|
1611
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1637
1612
|
}
|
|
1638
1613
|
},
|
|
1639
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1614
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1640
1615
|
if (0 < channel.state.modulationDepth) {
|
|
1641
|
-
this.setModLfoToFilterFc(channel, note);
|
|
1616
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1642
1617
|
}
|
|
1643
1618
|
},
|
|
1644
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1619
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1645
1620
|
if (0 < channel.state.modulationDepth) {
|
|
1646
|
-
this.setModLfoToVolume(channel, note);
|
|
1621
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1647
1622
|
}
|
|
1648
1623
|
},
|
|
1649
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1650
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1624
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1625
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1651
1626
|
},
|
|
1652
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1653
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1627
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1628
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1654
1629
|
},
|
|
1655
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1656
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1657
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1630
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1631
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1632
|
+
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1658
1633
|
if (0 < channel.state.vibratoDepth) {
|
|
1659
|
-
const now = this.audioContext.currentTime;
|
|
1660
1634
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1661
1635
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1662
|
-
if (
|
|
1636
|
+
if (scheduleTime < prevStartTime)
|
|
1663
1637
|
return;
|
|
1664
1638
|
const value = note.voiceParams.delayVibLFO;
|
|
1665
1639
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1666
|
-
note.vibratoLFO.stop(
|
|
1640
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1667
1641
|
note.vibratoLFO.start(startTime);
|
|
1668
1642
|
}
|
|
1669
1643
|
},
|
|
1670
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1644
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1671
1645
|
if (0 < channel.state.vibratoDepth) {
|
|
1672
|
-
this.setFreqVibLFO(channel, note);
|
|
1646
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1673
1647
|
}
|
|
1674
1648
|
},
|
|
1675
1649
|
};
|
|
@@ -1681,7 +1655,7 @@ export class Midy {
|
|
|
1681
1655
|
state[3] = noteNumber / 127;
|
|
1682
1656
|
return state;
|
|
1683
1657
|
}
|
|
1684
|
-
applyVoiceParams(channel, controllerType) {
|
|
1658
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1685
1659
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1686
1660
|
for (let i = 0; i < noteList.length; i++) {
|
|
1687
1661
|
const note = noteList[i];
|
|
@@ -1697,7 +1671,7 @@ export class Midy {
|
|
|
1697
1671
|
continue;
|
|
1698
1672
|
note.voiceParams[key] = value;
|
|
1699
1673
|
if (key in this.voiceParamsHandlers) {
|
|
1700
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1674
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1701
1675
|
}
|
|
1702
1676
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1703
1677
|
if (appliedFilterEnvelope)
|
|
@@ -1710,12 +1684,12 @@ export class Midy {
|
|
|
1710
1684
|
noteVoiceParams[key] = voiceParams[key];
|
|
1711
1685
|
}
|
|
1712
1686
|
if (note.portamento) {
|
|
1713
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1687
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1714
1688
|
}
|
|
1715
1689
|
else {
|
|
1716
|
-
this.setFilterEnvelope(channel, note);
|
|
1690
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1717
1691
|
}
|
|
1718
|
-
this.setPitchEnvelope(note);
|
|
1692
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1719
1693
|
}
|
|
1720
1694
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1721
1695
|
if (appliedVolumeEnvelope)
|
|
@@ -1727,7 +1701,7 @@ export class Midy {
|
|
|
1727
1701
|
if (key in voiceParams)
|
|
1728
1702
|
noteVoiceParams[key] = voiceParams[key];
|
|
1729
1703
|
}
|
|
1730
|
-
this.setVolumeEnvelope(channel, note);
|
|
1704
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1731
1705
|
}
|
|
1732
1706
|
}
|
|
1733
1707
|
}
|
|
@@ -1771,12 +1745,12 @@ export class Midy {
|
|
|
1771
1745
|
127: this.polyOn,
|
|
1772
1746
|
};
|
|
1773
1747
|
}
|
|
1774
|
-
handleControlChange(channelNumber, controllerType, value,
|
|
1748
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1775
1749
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1776
1750
|
if (handler) {
|
|
1777
|
-
handler.call(this, channelNumber, value,
|
|
1751
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1778
1752
|
const channel = this.channels[channelNumber];
|
|
1779
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1753
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1780
1754
|
this.applyControlTable(channel, controllerType);
|
|
1781
1755
|
}
|
|
1782
1756
|
else {
|
|
@@ -1862,48 +1836,46 @@ export class Midy {
|
|
|
1862
1836
|
setBankLSB(channelNumber, lsb) {
|
|
1863
1837
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1864
1838
|
}
|
|
1865
|
-
dataEntryLSB(channelNumber, value) {
|
|
1839
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1866
1840
|
this.channels[channelNumber].dataLSB = value;
|
|
1867
|
-
this.handleRPN(channelNumber,
|
|
1841
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1868
1842
|
}
|
|
1869
|
-
updateChannelVolume(channel) {
|
|
1870
|
-
const now = this.audioContext.currentTime;
|
|
1843
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1871
1844
|
const state = channel.state;
|
|
1872
1845
|
const volume = state.volume * state.expression;
|
|
1873
1846
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1874
1847
|
channel.gainL.gain
|
|
1875
|
-
.cancelScheduledValues(
|
|
1876
|
-
.setValueAtTime(volume * gainLeft,
|
|
1848
|
+
.cancelScheduledValues(scheduleTime)
|
|
1849
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1877
1850
|
channel.gainR.gain
|
|
1878
|
-
.cancelScheduledValues(
|
|
1879
|
-
.setValueAtTime(volume * gainRight,
|
|
1851
|
+
.cancelScheduledValues(scheduleTime)
|
|
1852
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1880
1853
|
}
|
|
1881
|
-
setSustainPedal(channelNumber, value) {
|
|
1854
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1855
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1882
1856
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1883
1857
|
if (value < 64) {
|
|
1884
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1858
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1885
1859
|
}
|
|
1886
1860
|
}
|
|
1887
1861
|
setPortamento(channelNumber, value) {
|
|
1888
1862
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1889
1863
|
}
|
|
1890
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1864
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1891
1865
|
const channel = this.channels[channelNumber];
|
|
1892
1866
|
channel.state.sostenutoPedal = value / 127;
|
|
1893
1867
|
if (64 <= value) {
|
|
1894
|
-
|
|
1895
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1868
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1896
1869
|
}
|
|
1897
1870
|
else {
|
|
1898
1871
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
1899
1872
|
}
|
|
1900
1873
|
}
|
|
1901
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1874
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1902
1875
|
const channel = this.channels[channelNumber];
|
|
1903
1876
|
channel.state.softPedal = softPedal / 127;
|
|
1904
1877
|
}
|
|
1905
|
-
setFilterResonance(channelNumber, filterResonance) {
|
|
1906
|
-
const now = this.audioContext.currentTime;
|
|
1878
|
+
setFilterResonance(channelNumber, filterResonance, scheduleTime) {
|
|
1907
1879
|
const channel = this.channels[channelNumber];
|
|
1908
1880
|
const state = channel.state;
|
|
1909
1881
|
state.filterResonance = filterResonance / 64;
|
|
@@ -1913,16 +1885,15 @@ export class Midy {
|
|
|
1913
1885
|
if (!note)
|
|
1914
1886
|
continue;
|
|
1915
1887
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1916
|
-
note.filterNode.Q.setValueAtTime(Q,
|
|
1888
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
1917
1889
|
}
|
|
1918
1890
|
});
|
|
1919
1891
|
}
|
|
1920
|
-
setReleaseTime(channelNumber, releaseTime) {
|
|
1892
|
+
setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
|
|
1921
1893
|
const channel = this.channels[channelNumber];
|
|
1922
1894
|
channel.state.releaseTime = releaseTime / 64;
|
|
1923
1895
|
}
|
|
1924
|
-
setAttackTime(channelNumber, attackTime) {
|
|
1925
|
-
const now = this.audioContext.currentTime;
|
|
1896
|
+
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
1926
1897
|
const channel = this.channels[channelNumber];
|
|
1927
1898
|
channel.state.attackTime = attackTime / 64;
|
|
1928
1899
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1930,13 +1901,13 @@ export class Midy {
|
|
|
1930
1901
|
const note = noteList[i];
|
|
1931
1902
|
if (!note)
|
|
1932
1903
|
continue;
|
|
1933
|
-
if (note.startTime <
|
|
1904
|
+
if (note.startTime < scheduleTime)
|
|
1934
1905
|
continue;
|
|
1935
1906
|
this.setVolumeEnvelope(channel, note);
|
|
1936
1907
|
}
|
|
1937
1908
|
});
|
|
1938
1909
|
}
|
|
1939
|
-
setBrightness(channelNumber, brightness) {
|
|
1910
|
+
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
1940
1911
|
const channel = this.channels[channelNumber];
|
|
1941
1912
|
channel.state.brightness = brightness / 64;
|
|
1942
1913
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1945,7 +1916,7 @@ export class Midy {
|
|
|
1945
1916
|
if (!note)
|
|
1946
1917
|
continue;
|
|
1947
1918
|
if (note.portamento) {
|
|
1948
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1919
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1949
1920
|
}
|
|
1950
1921
|
else {
|
|
1951
1922
|
this.setFilterEnvelope(channel, note);
|
|
@@ -1953,7 +1924,7 @@ export class Midy {
|
|
|
1953
1924
|
}
|
|
1954
1925
|
});
|
|
1955
1926
|
}
|
|
1956
|
-
setDecayTime(channelNumber, dacayTime) {
|
|
1927
|
+
setDecayTime(channelNumber, dacayTime, scheduleTime) {
|
|
1957
1928
|
const channel = this.channels[channelNumber];
|
|
1958
1929
|
channel.state.decayTime = dacayTime / 64;
|
|
1959
1930
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1961,11 +1932,11 @@ export class Midy {
|
|
|
1961
1932
|
const note = noteList[i];
|
|
1962
1933
|
if (!note)
|
|
1963
1934
|
continue;
|
|
1964
|
-
this.setVolumeEnvelope(channel, note);
|
|
1935
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1965
1936
|
}
|
|
1966
1937
|
});
|
|
1967
1938
|
}
|
|
1968
|
-
setVibratoRate(channelNumber, vibratoRate) {
|
|
1939
|
+
setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
|
|
1969
1940
|
const channel = this.channels[channelNumber];
|
|
1970
1941
|
channel.state.vibratoRate = vibratoRate / 64;
|
|
1971
1942
|
if (channel.vibratoDepth <= 0)
|
|
@@ -1975,11 +1946,11 @@ export class Midy {
|
|
|
1975
1946
|
const note = noteList[i];
|
|
1976
1947
|
if (!note)
|
|
1977
1948
|
continue;
|
|
1978
|
-
this.setVibLfoToPitch(channel, note);
|
|
1949
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1979
1950
|
}
|
|
1980
1951
|
});
|
|
1981
1952
|
}
|
|
1982
|
-
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1953
|
+
setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
|
|
1983
1954
|
const channel = this.channels[channelNumber];
|
|
1984
1955
|
const prev = channel.state.vibratoDepth;
|
|
1985
1956
|
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
@@ -1989,7 +1960,7 @@ export class Midy {
|
|
|
1989
1960
|
const note = noteList[i];
|
|
1990
1961
|
if (!note)
|
|
1991
1962
|
continue;
|
|
1992
|
-
this.setFreqVibLFO(channel, note);
|
|
1963
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1993
1964
|
}
|
|
1994
1965
|
});
|
|
1995
1966
|
}
|
|
@@ -1999,7 +1970,7 @@ export class Midy {
|
|
|
1999
1970
|
const note = noteList[i];
|
|
2000
1971
|
if (!note)
|
|
2001
1972
|
continue;
|
|
2002
|
-
this.startVibrato(channel, note,
|
|
1973
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
2003
1974
|
}
|
|
2004
1975
|
});
|
|
2005
1976
|
}
|
|
@@ -2013,21 +1984,21 @@ export class Midy {
|
|
|
2013
1984
|
const note = noteList[i];
|
|
2014
1985
|
if (!note)
|
|
2015
1986
|
continue;
|
|
2016
|
-
this.startVibrato(channel, note,
|
|
1987
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
2017
1988
|
}
|
|
2018
1989
|
});
|
|
2019
1990
|
}
|
|
2020
1991
|
}
|
|
2021
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1992
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
2022
1993
|
const channel = this.channels[channelNumber];
|
|
2023
1994
|
const state = channel.state;
|
|
2024
1995
|
const reverbEffect = this.reverbEffect;
|
|
2025
1996
|
if (0 < state.reverbSendLevel) {
|
|
2026
1997
|
if (0 < reverbSendLevel) {
|
|
2027
|
-
const now = this.audioContext.currentTime;
|
|
2028
1998
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2029
|
-
reverbEffect.input.gain
|
|
2030
|
-
|
|
1999
|
+
reverbEffect.input.gain
|
|
2000
|
+
.cancelScheduledValues(scheduleTime)
|
|
2001
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2031
2002
|
}
|
|
2032
2003
|
else {
|
|
2033
2004
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -2044,31 +2015,31 @@ export class Midy {
|
|
|
2044
2015
|
}
|
|
2045
2016
|
else {
|
|
2046
2017
|
if (0 < reverbSendLevel) {
|
|
2047
|
-
const now = this.audioContext.currentTime;
|
|
2048
2018
|
channel.scheduledNotes.forEach((noteList) => {
|
|
2049
2019
|
for (let i = 0; i < noteList.length; i++) {
|
|
2050
2020
|
const note = noteList[i];
|
|
2051
2021
|
if (!note)
|
|
2052
2022
|
continue;
|
|
2053
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
2023
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2054
2024
|
}
|
|
2055
2025
|
});
|
|
2056
2026
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2057
|
-
reverbEffect.input.gain
|
|
2058
|
-
|
|
2027
|
+
reverbEffect.input.gain
|
|
2028
|
+
.cancelScheduledValues(scheduleTime)
|
|
2029
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2059
2030
|
}
|
|
2060
2031
|
}
|
|
2061
2032
|
}
|
|
2062
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
2033
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2063
2034
|
const channel = this.channels[channelNumber];
|
|
2064
2035
|
const state = channel.state;
|
|
2065
2036
|
const chorusEffect = this.chorusEffect;
|
|
2066
2037
|
if (0 < state.chorusSendLevel) {
|
|
2067
2038
|
if (0 < chorusSendLevel) {
|
|
2068
|
-
const now = this.audioContext.currentTime;
|
|
2069
2039
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2070
|
-
chorusEffect.input.gain
|
|
2071
|
-
|
|
2040
|
+
chorusEffect.input.gain
|
|
2041
|
+
.cancelScheduledValues(scheduleTime)
|
|
2042
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2072
2043
|
}
|
|
2073
2044
|
else {
|
|
2074
2045
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -2085,18 +2056,18 @@ export class Midy {
|
|
|
2085
2056
|
}
|
|
2086
2057
|
else {
|
|
2087
2058
|
if (0 < chorusSendLevel) {
|
|
2088
|
-
const now = this.audioContext.currentTime;
|
|
2089
2059
|
channel.scheduledNotes.forEach((noteList) => {
|
|
2090
2060
|
for (let i = 0; i < noteList.length; i++) {
|
|
2091
2061
|
const note = noteList[i];
|
|
2092
2062
|
if (!note)
|
|
2093
2063
|
continue;
|
|
2094
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
2064
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2095
2065
|
}
|
|
2096
2066
|
});
|
|
2097
2067
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2098
|
-
chorusEffect.input.gain
|
|
2099
|
-
|
|
2068
|
+
chorusEffect.input.gain
|
|
2069
|
+
.cancelScheduledValues(scheduleTime)
|
|
2070
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2100
2071
|
}
|
|
2101
2072
|
}
|
|
2102
2073
|
}
|
|
@@ -2126,13 +2097,13 @@ export class Midy {
|
|
|
2126
2097
|
channel.dataMSB = minMSB;
|
|
2127
2098
|
}
|
|
2128
2099
|
}
|
|
2129
|
-
handleRPN(channelNumber, value) {
|
|
2100
|
+
handleRPN(channelNumber, value, scheduleTime) {
|
|
2130
2101
|
const channel = this.channels[channelNumber];
|
|
2131
2102
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
2132
2103
|
switch (rpn) {
|
|
2133
2104
|
case 0:
|
|
2134
2105
|
channel.dataLSB += value;
|
|
2135
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
2106
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
2136
2107
|
break;
|
|
2137
2108
|
case 1:
|
|
2138
2109
|
channel.dataLSB += value;
|
|
@@ -2164,25 +2135,26 @@ export class Midy {
|
|
|
2164
2135
|
setRPNLSB(channelNumber, value) {
|
|
2165
2136
|
this.channels[channelNumber].rpnLSB = value;
|
|
2166
2137
|
}
|
|
2167
|
-
dataEntryMSB(channelNumber, value) {
|
|
2138
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2168
2139
|
this.channels[channelNumber].dataMSB = value;
|
|
2169
|
-
this.handleRPN(channelNumber,
|
|
2140
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
2170
2141
|
}
|
|
2171
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
2142
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2172
2143
|
const channel = this.channels[channelNumber];
|
|
2173
2144
|
this.limitData(channel, 0, 127, 0, 99);
|
|
2174
2145
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2175
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
2146
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2176
2147
|
}
|
|
2177
|
-
setPitchBendRange(channelNumber, value) {
|
|
2148
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
2149
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2178
2150
|
const channel = this.channels[channelNumber];
|
|
2179
2151
|
const state = channel.state;
|
|
2180
2152
|
const prev = state.pitchWheelSensitivity;
|
|
2181
2153
|
const next = value / 128;
|
|
2182
2154
|
state.pitchWheelSensitivity = next;
|
|
2183
2155
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2184
|
-
this.updateChannelDetune(channel);
|
|
2185
|
-
this.applyVoiceParams(channel, 16);
|
|
2156
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2157
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2186
2158
|
}
|
|
2187
2159
|
handleFineTuningRPN(channelNumber) {
|
|
2188
2160
|
const channel = this.channels[channelNumber];
|
|
@@ -2223,8 +2195,9 @@ export class Midy {
|
|
|
2223
2195
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2224
2196
|
this.updateModulation(channel);
|
|
2225
2197
|
}
|
|
2226
|
-
allSoundOff(channelNumber) {
|
|
2227
|
-
|
|
2198
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2199
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2200
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2228
2201
|
}
|
|
2229
2202
|
resetAllControllers(channelNumber) {
|
|
2230
2203
|
const stateTypes = [
|
|
@@ -2252,8 +2225,9 @@ export class Midy {
|
|
|
2252
2225
|
channel[type] = this.constructor.channelSettings[type];
|
|
2253
2226
|
}
|
|
2254
2227
|
}
|
|
2255
|
-
allNotesOff(channelNumber) {
|
|
2256
|
-
|
|
2228
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2229
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2230
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2257
2231
|
}
|
|
2258
2232
|
omniOff() {
|
|
2259
2233
|
this.omni = false;
|
|
@@ -2267,16 +2241,16 @@ export class Midy {
|
|
|
2267
2241
|
polyOn() {
|
|
2268
2242
|
this.mono = false;
|
|
2269
2243
|
}
|
|
2270
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2244
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2271
2245
|
switch (data[2]) {
|
|
2272
2246
|
case 8:
|
|
2273
2247
|
switch (data[3]) {
|
|
2274
2248
|
case 8:
|
|
2275
2249
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2276
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2250
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2277
2251
|
case 9:
|
|
2278
2252
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2279
|
-
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
|
|
2253
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false, scheduleTime);
|
|
2280
2254
|
default:
|
|
2281
2255
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2282
2256
|
}
|
|
@@ -2319,18 +2293,18 @@ export class Midy {
|
|
|
2319
2293
|
this.channels[9].bankMSB = 120;
|
|
2320
2294
|
this.channels[9].bank = 120 * 128;
|
|
2321
2295
|
}
|
|
2322
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2296
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2323
2297
|
switch (data[2]) {
|
|
2324
2298
|
case 4:
|
|
2325
2299
|
switch (data[3]) {
|
|
2326
2300
|
case 1:
|
|
2327
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2301
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2328
2302
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2329
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2303
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2330
2304
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2331
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2305
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2332
2306
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2333
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2307
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2334
2308
|
default:
|
|
2335
2309
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2336
2310
|
}
|
|
@@ -2338,10 +2312,10 @@ export class Midy {
|
|
|
2338
2312
|
case 8:
|
|
2339
2313
|
switch (data[3]) {
|
|
2340
2314
|
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2341
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
|
|
2315
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true, scheduleTime);
|
|
2342
2316
|
case 9:
|
|
2343
2317
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2344
|
-
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
|
|
2318
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true, scheduleTime);
|
|
2345
2319
|
default:
|
|
2346
2320
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2347
2321
|
}
|
|
@@ -2361,7 +2335,7 @@ export class Midy {
|
|
|
2361
2335
|
case 10:
|
|
2362
2336
|
switch (data[3]) {
|
|
2363
2337
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2364
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2338
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2365
2339
|
default:
|
|
2366
2340
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2367
2341
|
}
|
|
@@ -2370,49 +2344,50 @@ export class Midy {
|
|
|
2370
2344
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2371
2345
|
}
|
|
2372
2346
|
}
|
|
2373
|
-
handleMasterVolumeSysEx(data) {
|
|
2347
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2374
2348
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2375
|
-
this.setMasterVolume(volume);
|
|
2349
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2376
2350
|
}
|
|
2377
|
-
setMasterVolume(volume) {
|
|
2351
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2352
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2378
2353
|
if (volume < 0 && 1 < volume) {
|
|
2379
2354
|
console.error("Master Volume is out of range");
|
|
2380
2355
|
}
|
|
2381
2356
|
else {
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2357
|
+
this.masterVolume.gain
|
|
2358
|
+
.cancelScheduledValues(scheduleTime)
|
|
2359
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2385
2360
|
}
|
|
2386
2361
|
}
|
|
2387
|
-
handleMasterFineTuningSysEx(data) {
|
|
2362
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2388
2363
|
const fineTuning = data[5] * 128 + data[4];
|
|
2389
|
-
this.setMasterFineTuning(fineTuning);
|
|
2364
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2390
2365
|
}
|
|
2391
|
-
setMasterFineTuning(value) {
|
|
2366
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2392
2367
|
const prev = this.masterFineTuning;
|
|
2393
2368
|
const next = (value - 8192) / 8.192; // cent
|
|
2394
2369
|
this.masterFineTuning = next;
|
|
2395
2370
|
channel.detune += next - prev;
|
|
2396
|
-
this.updateChannelDetune(channel);
|
|
2371
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2397
2372
|
}
|
|
2398
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2373
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2399
2374
|
const coarseTuning = data[4];
|
|
2400
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2375
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2401
2376
|
}
|
|
2402
|
-
setMasterCoarseTuning(value) {
|
|
2377
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2403
2378
|
const prev = this.masterCoarseTuning;
|
|
2404
2379
|
const next = (value - 64) * 100; // cent
|
|
2405
2380
|
this.masterCoarseTuning = next;
|
|
2406
2381
|
channel.detune += next - prev;
|
|
2407
|
-
this.updateChannelDetune(channel);
|
|
2382
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2408
2383
|
}
|
|
2409
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2384
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2410
2385
|
if (data[7] === 1) {
|
|
2411
2386
|
switch (data[8]) {
|
|
2412
2387
|
case 1:
|
|
2413
2388
|
return this.handleReverbParameterSysEx(data);
|
|
2414
2389
|
case 2:
|
|
2415
|
-
return this.handleChorusParameterSysEx(data);
|
|
2390
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2416
2391
|
default:
|
|
2417
2392
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2418
2393
|
}
|
|
@@ -2491,88 +2466,84 @@ export class Midy {
|
|
|
2491
2466
|
calcDelay(rt60, feedback) {
|
|
2492
2467
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2493
2468
|
}
|
|
2494
|
-
handleChorusParameterSysEx(data) {
|
|
2469
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2495
2470
|
switch (data[9]) {
|
|
2496
2471
|
case 0:
|
|
2497
|
-
return this.setChorusType(data[10]);
|
|
2472
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2498
2473
|
case 1:
|
|
2499
|
-
return this.setChorusModRate(data[10]);
|
|
2474
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2500
2475
|
case 2:
|
|
2501
|
-
return this.setChorusModDepth(data[10]);
|
|
2476
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2502
2477
|
case 3:
|
|
2503
|
-
return this.setChorusFeedback(data[10]);
|
|
2478
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2504
2479
|
case 4:
|
|
2505
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2480
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2506
2481
|
}
|
|
2507
2482
|
}
|
|
2508
|
-
setChorusType(type) {
|
|
2483
|
+
setChorusType(type, scheduleTime) {
|
|
2509
2484
|
switch (type) {
|
|
2510
2485
|
case 0:
|
|
2511
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2486
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2512
2487
|
case 1:
|
|
2513
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2488
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2514
2489
|
case 2:
|
|
2515
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2490
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2516
2491
|
case 3:
|
|
2517
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2492
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2518
2493
|
case 4:
|
|
2519
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2494
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2520
2495
|
case 5:
|
|
2521
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2496
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2522
2497
|
default:
|
|
2523
2498
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2524
2499
|
}
|
|
2525
2500
|
}
|
|
2526
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2527
|
-
this.setChorusModRate(modRate);
|
|
2528
|
-
this.setChorusModDepth(modDepth);
|
|
2529
|
-
this.setChorusFeedback(feedback);
|
|
2530
|
-
this.setChorusSendToReverb(sendToReverb);
|
|
2501
|
+
setChorusParameter(modRate, modDepth, feedback, sendToReverb, scheduleTime) {
|
|
2502
|
+
this.setChorusModRate(modRate, scheduleTime);
|
|
2503
|
+
this.setChorusModDepth(modDepth, scheduleTime);
|
|
2504
|
+
this.setChorusFeedback(feedback, scheduleTime);
|
|
2505
|
+
this.setChorusSendToReverb(sendToReverb, scheduleTime);
|
|
2531
2506
|
}
|
|
2532
|
-
setChorusModRate(value) {
|
|
2533
|
-
const now = this.audioContext.currentTime;
|
|
2507
|
+
setChorusModRate(value, scheduleTime) {
|
|
2534
2508
|
const modRate = this.getChorusModRate(value);
|
|
2535
2509
|
this.chorus.modRate = modRate;
|
|
2536
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2510
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2537
2511
|
}
|
|
2538
2512
|
getChorusModRate(value) {
|
|
2539
2513
|
return value * 0.122; // Hz
|
|
2540
2514
|
}
|
|
2541
|
-
setChorusModDepth(value) {
|
|
2542
|
-
const now = this.audioContext.currentTime;
|
|
2515
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2543
2516
|
const modDepth = this.getChorusModDepth(value);
|
|
2544
2517
|
this.chorus.modDepth = modDepth;
|
|
2545
2518
|
this.chorusEffect.lfoGain.gain
|
|
2546
|
-
.cancelScheduledValues(
|
|
2547
|
-
.setValueAtTime(modDepth / 2,
|
|
2519
|
+
.cancelScheduledValues(scheduleTime)
|
|
2520
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2548
2521
|
}
|
|
2549
2522
|
getChorusModDepth(value) {
|
|
2550
2523
|
return (value + 1) / 3200; // second
|
|
2551
2524
|
}
|
|
2552
|
-
setChorusFeedback(value) {
|
|
2553
|
-
const now = this.audioContext.currentTime;
|
|
2525
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2554
2526
|
const feedback = this.getChorusFeedback(value);
|
|
2555
2527
|
this.chorus.feedback = feedback;
|
|
2556
2528
|
const chorusEffect = this.chorusEffect;
|
|
2557
2529
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2558
2530
|
chorusEffect.feedbackGains[i].gain
|
|
2559
|
-
.cancelScheduledValues(
|
|
2560
|
-
.setValueAtTime(feedback,
|
|
2531
|
+
.cancelScheduledValues(scheduleTime)
|
|
2532
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2561
2533
|
}
|
|
2562
2534
|
}
|
|
2563
2535
|
getChorusFeedback(value) {
|
|
2564
2536
|
return value * 0.00763;
|
|
2565
2537
|
}
|
|
2566
|
-
setChorusSendToReverb(value) {
|
|
2538
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2567
2539
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2568
2540
|
const sendGain = this.chorusEffect.sendGain;
|
|
2569
2541
|
if (0 < this.chorus.sendToReverb) {
|
|
2570
2542
|
this.chorus.sendToReverb = sendToReverb;
|
|
2571
2543
|
if (0 < sendToReverb) {
|
|
2572
|
-
const now = this.audioContext.currentTime;
|
|
2573
2544
|
sendGain.gain
|
|
2574
|
-
.cancelScheduledValues(
|
|
2575
|
-
.setValueAtTime(sendToReverb,
|
|
2545
|
+
.cancelScheduledValues(scheduleTime)
|
|
2546
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2576
2547
|
}
|
|
2577
2548
|
else {
|
|
2578
2549
|
sendGain.disconnect();
|
|
@@ -2581,11 +2552,10 @@ export class Midy {
|
|
|
2581
2552
|
else {
|
|
2582
2553
|
this.chorus.sendToReverb = sendToReverb;
|
|
2583
2554
|
if (0 < sendToReverb) {
|
|
2584
|
-
const now = this.audioContext.currentTime;
|
|
2585
2555
|
sendGain.connect(this.reverbEffect.input);
|
|
2586
2556
|
sendGain.gain
|
|
2587
|
-
.cancelScheduledValues(
|
|
2588
|
-
.setValueAtTime(sendToReverb,
|
|
2557
|
+
.cancelScheduledValues(scheduleTime)
|
|
2558
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2589
2559
|
}
|
|
2590
2560
|
}
|
|
2591
2561
|
}
|
|
@@ -2611,7 +2581,7 @@ export class Midy {
|
|
|
2611
2581
|
}
|
|
2612
2582
|
return bitmap;
|
|
2613
2583
|
}
|
|
2614
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2584
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2615
2585
|
if (data.length < 19) {
|
|
2616
2586
|
console.error("Data length is too short");
|
|
2617
2587
|
return;
|
|
@@ -2626,10 +2596,10 @@ export class Midy {
|
|
|
2626
2596
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2627
2597
|
}
|
|
2628
2598
|
if (realtime)
|
|
2629
|
-
this.updateChannelDetune(channel);
|
|
2599
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2630
2600
|
}
|
|
2631
2601
|
}
|
|
2632
|
-
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
|
|
2602
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2633
2603
|
if (data.length < 31) {
|
|
2634
2604
|
console.error("Data length is too short");
|
|
2635
2605
|
return;
|
|
@@ -2648,7 +2618,7 @@ export class Midy {
|
|
|
2648
2618
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2649
2619
|
}
|
|
2650
2620
|
if (realtime)
|
|
2651
|
-
this.updateChannelDetune(channel);
|
|
2621
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2652
2622
|
}
|
|
2653
2623
|
}
|
|
2654
2624
|
getPitchControl(channel, note) {
|
|
@@ -2707,7 +2677,7 @@ export class Midy {
|
|
|
2707
2677
|
if (table[5] !== 0)
|
|
2708
2678
|
this.setModLfoToVolume(channel, note);
|
|
2709
2679
|
}
|
|
2710
|
-
|
|
2680
|
+
handlePressureSysEx(data, tableName) {
|
|
2711
2681
|
const channelNumber = data[4];
|
|
2712
2682
|
const table = this.channels[channelNumber][tableName];
|
|
2713
2683
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2755,7 +2725,7 @@ export class Midy {
|
|
|
2755
2725
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2756
2726
|
return (controlValue + 64) / 64;
|
|
2757
2727
|
}
|
|
2758
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2728
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2759
2729
|
const channelNumber = data[4];
|
|
2760
2730
|
const keyNumber = data[5];
|
|
2761
2731
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2765,30 +2735,27 @@ export class Midy {
|
|
|
2765
2735
|
const index = keyNumber * 128 + controllerType;
|
|
2766
2736
|
table[index] = value - 64;
|
|
2767
2737
|
}
|
|
2768
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2738
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2769
2739
|
}
|
|
2770
|
-
|
|
2771
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2772
|
-
}
|
|
2773
|
-
handleSysEx(data) {
|
|
2740
|
+
handleSysEx(data, scheduleTime) {
|
|
2774
2741
|
switch (data[0]) {
|
|
2775
2742
|
case 126:
|
|
2776
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2743
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2777
2744
|
case 127:
|
|
2778
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2745
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2779
2746
|
default:
|
|
2780
|
-
|
|
2747
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2781
2748
|
}
|
|
2782
2749
|
}
|
|
2783
|
-
scheduleTask(callback,
|
|
2750
|
+
scheduleTask(callback, scheduleTime) {
|
|
2784
2751
|
return new Promise((resolve) => {
|
|
2785
2752
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2786
2753
|
bufferSource.onended = () => {
|
|
2787
2754
|
callback();
|
|
2788
2755
|
resolve();
|
|
2789
2756
|
};
|
|
2790
|
-
bufferSource.start(
|
|
2791
|
-
bufferSource.stop(
|
|
2757
|
+
bufferSource.start(scheduleTime);
|
|
2758
|
+
bufferSource.stop(scheduleTime);
|
|
2792
2759
|
});
|
|
2793
2760
|
}
|
|
2794
2761
|
}
|