@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/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.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
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 t = this.audioContext.currentTime + offset;
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
- async stopChannelNotes(channelNumber, velocity, force) {
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.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, undefined, // portamentoNoteNumber
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
- await Promise.all(this.notePromises);
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, time) {
873
+ getActiveNotes(channel, scheduleTime) {
872
874
  const activeNotes = new SparseMap(128);
873
875
  channel.scheduledNotes.forEach((noteList) => {
874
- const activeNote = this.getActiveNote(noteList, time);
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, time) {
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 (time < note.startTime)
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
- channel.scheduledNotes.forEach((noteList) => {
1048
- for (let i = 0; i < noteList.length; i++) {
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(now)
1063
- .setValueAtTime(detune, now);
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(now)
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(now)
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(now)
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(now)
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, startTime) {
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, startTime) {
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, startTime);
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, startTime);
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.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1313
- startTime, undefined, // portamentoNoteNumber
1314
- true);
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, portamento) {
1328
- const now = this.audioContext.currentTime;
1329
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
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
- scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
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
- releaseNote(channelNumber, noteNumber, velocity, portamentoNoteNumber) {
1410
- const now = this.audioContext.currentTime;
1411
- return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, portamentoNoteNumber, false);
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.state.sustainPedal = halfVelocity;
1418
- channel.scheduledNotes.forEach((noteList) => {
1419
- for (let i = 0; i < noteList.length; i++) {
1420
- const note = noteList[i];
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.releaseNote(channelNumber, noteNumber, velocity);
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.releaseNote(channelNumber, data1, data2);
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, startTime) {
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, startTime);
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, startTime) {
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, startTime).forEach((note) => {
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(now)
1522
- .setValueAtTime(modulationDepth, now);
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(now)
1532
- .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
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(now)
1540
- .setValueAtTime(modLfoToFilterFc, now);
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(now)
1550
- .setValueAtTime(volumeDepth, now);
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(now)
1560
- .setValueAtTime(value, now);
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(now)
1586
- .setValueAtTime(value, now);
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 < now)
1584
+ if (startTime < scheduleTime)
1608
1585
  return;
1609
- note.modulationLFO.stop(now);
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(now)
1618
- .setValueAtTime(freqModLFO, now);
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(now)
1625
- .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
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 (now < prevStartTime)
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(now);
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, startTime) {
1748
+ handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1775
1749
  const handler = this.controlChangeHandlers[controllerType];
1776
1750
  if (handler) {
1777
- handler.call(this, channelNumber, value, startTime);
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, 0);
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(now)
1876
- .setValueAtTime(volume * gainLeft, now);
1848
+ .cancelScheduledValues(scheduleTime)
1849
+ .setValueAtTime(volume * gainLeft, scheduleTime);
1877
1850
  channel.gainR.gain
1878
- .cancelScheduledValues(now)
1879
- .setValueAtTime(volume * gainRight, now);
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
- const now = this.audioContext.currentTime;
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, now);
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 < now)
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, note.startTime);
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, note.startTime);
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.cancelScheduledValues(now);
2030
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
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.cancelScheduledValues(now);
2058
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
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.cancelScheduledValues(now);
2071
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
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.cancelScheduledValues(now);
2099
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
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, 0);
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
- return this.stopChannelNotes(channelNumber, 0, true);
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
- return this.stopChannelNotes(channelNumber, 0, false);
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
- const now = this.audioContext.currentTime;
2383
- this.masterVolume.gain.cancelScheduledValues(now);
2384
- this.masterVolume.gain.setValueAtTime(volume * volume, now);
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, now);
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(now)
2547
- .setValueAtTime(modDepth / 2, now);
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(now)
2560
- .setValueAtTime(feedback, now);
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(now)
2575
- .setValueAtTime(sendToReverb, now);
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(now)
2588
- .setValueAtTime(sendToReverb, now);
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
- handleChannelPressureSysEx(data, tableName) {
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
- handleExclusiveMessage(data) {
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
- return this.handleExclusiveMessage(data);
2747
+ console.warn(`Unsupported Exclusive Message: ${data}`);
2781
2748
  }
2782
2749
  }
2783
- scheduleTask(callback, startTime) {
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(startTime);
2791
- bufferSource.stop(startTime);
2757
+ bufferSource.start(scheduleTime);
2758
+ bufferSource.stop(scheduleTime);
2792
2759
  });
2793
2760
  }
2794
2761
  }