@marmooo/midy 0.2.7 → 0.2.9

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/script/midy.js CHANGED
@@ -249,6 +249,12 @@ const volumeEnvelopeKeys = [
249
249
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
250
250
  class Midy {
251
251
  constructor(audioContext, options = this.defaultOptions) {
252
+ Object.defineProperty(this, "mode", {
253
+ enumerable: true,
254
+ configurable: true,
255
+ writable: true,
256
+ value: "GM2"
257
+ });
252
258
  Object.defineProperty(this, "ticksPerBeat", {
253
259
  enumerable: true,
254
260
  configurable: true,
@@ -294,18 +300,6 @@ class Midy {
294
300
  delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
295
301
  }
296
302
  });
297
- Object.defineProperty(this, "mono", {
298
- enumerable: true,
299
- configurable: true,
300
- writable: true,
301
- value: false
302
- }); // CC#124, CC#125
303
- Object.defineProperty(this, "omni", {
304
- enumerable: true,
305
- configurable: true,
306
- writable: true,
307
- value: false
308
- }); // CC#126, CC#127
309
303
  Object.defineProperty(this, "noteCheckInterval", {
310
304
  enumerable: true,
311
305
  configurable: true,
@@ -439,6 +433,11 @@ class Midy {
439
433
  this.audioContext = audioContext;
440
434
  this.options = { ...this.defaultOptions, ...options };
441
435
  this.masterVolume = new GainNode(audioContext);
436
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
437
+ this.schedulerBuffer = new AudioBuffer({
438
+ length: 1,
439
+ sampleRate: audioContext.sampleRate,
440
+ });
442
441
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
443
442
  this.controlChangeHandlers = this.createControlChangeHandlers();
444
443
  this.channels = this.createChannels(audioContext);
@@ -447,6 +446,7 @@ class Midy {
447
446
  this.chorusEffect.output.connect(this.masterVolume);
448
447
  this.reverbEffect.output.connect(this.masterVolume);
449
448
  this.masterVolume.connect(audioContext.destination);
449
+ this.scheduler.connect(audioContext.destination);
450
450
  this.GM2SystemOn();
451
451
  }
452
452
  initSoundFontTable() {
@@ -507,6 +507,7 @@ class Midy {
507
507
  controlTable: this.initControlTable(),
508
508
  ...this.setChannelAudioNodes(audioContext),
509
509
  scheduledNotes: new SparseMap(128),
510
+ sustainNotes: [],
510
511
  sostenutoNotes: new SparseMap(128),
511
512
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
512
513
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
@@ -545,10 +546,24 @@ class Midy {
545
546
  return audioBuffer;
546
547
  }
547
548
  }
548
- createNoteBufferNode(audioBuffer, voiceParams) {
549
+ calcLoopMode(channel, note, voiceParams) {
550
+ if (channel.isDrum) {
551
+ const noteNumber = note.noteNumber;
552
+ if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
553
+ return true;
554
+ }
555
+ else {
556
+ return false;
557
+ }
558
+ }
559
+ else {
560
+ return voiceParams.sampleModes % 2 !== 0;
561
+ }
562
+ }
563
+ createBufferSource(channel, note, voiceParams, audioBuffer) {
549
564
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
550
565
  bufferSource.buffer = audioBuffer;
551
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
566
+ bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
552
567
  if (bufferSource.loop) {
553
568
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
554
569
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -590,7 +605,7 @@ class Midy {
590
605
  const portamentoTarget = this.findPortamentoTarget(queueIndex);
591
606
  if (portamentoTarget)
592
607
  portamentoTarget.portamento = true;
593
- const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
608
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
594
609
  portamentoTarget?.noteNumber);
595
610
  if (notePromise) {
596
611
  this.notePromises.push(notePromise);
@@ -601,7 +616,7 @@ class Midy {
601
616
  this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
602
617
  break;
603
618
  case "controller":
604
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
619
+ this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
605
620
  break;
606
621
  case "programChange":
607
622
  this.handleProgramChange(event.channel, event.programNumber, startTime);
@@ -797,15 +812,10 @@ class Midy {
797
812
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
798
813
  const channel = this.channels[channelNumber];
799
814
  const promises = [];
800
- channel.scheduledNotes.forEach((noteList) => {
801
- for (let i = 0; i < noteList.length; i++) {
802
- const note = noteList[i];
803
- if (!note)
804
- continue;
805
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
806
- this.notePromises.push(promise);
807
- promises.push(promise);
808
- }
815
+ this.processScheduledNotes(channel, (note) => {
816
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
817
+ this.notePromises.push(promise);
818
+ promises.push(promise);
809
819
  });
810
820
  channel.scheduledNotes.clear();
811
821
  return Promise.all(promises);
@@ -861,14 +871,12 @@ class Midy {
861
871
  const now = this.audioContext.currentTime;
862
872
  return this.resumeTime + now - this.startTime - this.startDelay;
863
873
  }
864
- processScheduledNotes(channel, scheduleTime, callback) {
874
+ processScheduledNotes(channel, callback) {
865
875
  channel.scheduledNotes.forEach((noteList) => {
866
876
  for (let i = 0; i < noteList.length; i++) {
867
877
  const note = noteList[i];
868
878
  if (!note)
869
879
  continue;
870
- if (scheduleTime < note.startTime)
871
- continue;
872
880
  callback(note);
873
881
  }
874
882
  });
@@ -1035,7 +1043,9 @@ class Midy {
1035
1043
  return 8.176 * this.centToRate(cent);
1036
1044
  }
1037
1045
  calcChannelDetune(channel) {
1038
- const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
1046
+ const masterTuning = channel.isDrum
1047
+ ? 0
1048
+ : this.masterCoarseTuning + this.masterFineTuning;
1039
1049
  const channelTuning = channel.coarseTuning + channel.fineTuning;
1040
1050
  const tuning = masterTuning + channelTuning;
1041
1051
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
@@ -1049,7 +1059,7 @@ class Midy {
1049
1059
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1050
1060
  }
1051
1061
  updateChannelDetune(channel, scheduleTime) {
1052
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1062
+ this.processScheduledNotes(channel, (note) => {
1053
1063
  this.updateDetune(channel, note, scheduleTime);
1054
1064
  });
1055
1065
  }
@@ -1062,9 +1072,8 @@ class Midy {
1062
1072
  .setValueAtTime(detune, scheduleTime);
1063
1073
  }
1064
1074
  getPortamentoTime(channel) {
1065
- const factor = 5 * Math.log(10) / 127;
1066
- const time = channel.state.portamentoTime;
1067
- return Math.log(time) / factor;
1075
+ const factor = 5 * Math.log(10) * 127;
1076
+ return channel.state.portamentoTime * factor;
1068
1077
  }
1069
1078
  setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
1070
1079
  const { voiceParams, startTime } = note;
@@ -1228,7 +1237,7 @@ class Midy {
1228
1237
  const voiceParams = voice.getAllParams(controllerState);
1229
1238
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1230
1239
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1231
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
1240
+ note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
1232
1241
  note.volumeNode = new GainNode(this.audioContext);
1233
1242
  note.gainL = new GainNode(this.audioContext);
1234
1243
  note.gainR = new GainNode(this.audioContext);
@@ -1254,7 +1263,7 @@ class Midy {
1254
1263
  if (0 < state.modulationDepth) {
1255
1264
  this.startModulation(channel, note, now);
1256
1265
  }
1257
- if (this.mono && channel.currentBufferSource) {
1266
+ if (channel.mono && channel.currentBufferSource) {
1258
1267
  channel.currentBufferSource.stop(startTime);
1259
1268
  channel.currentBufferSource = note.bufferSource;
1260
1269
  }
@@ -1272,14 +1281,21 @@ class Midy {
1272
1281
  note.bufferSource.start(startTime);
1273
1282
  return note;
1274
1283
  }
1275
- calcBank(channel, channelNumber) {
1276
- if (channel.bankMSB === 121) {
1277
- return 0;
1278
- }
1279
- if (channelNumber % 9 <= 1 && channel.bankMSB === 120) {
1280
- return 128;
1284
+ calcBank(channel) {
1285
+ switch (this.mode) {
1286
+ case "GM1":
1287
+ if (channel.isDrum)
1288
+ return 128;
1289
+ return 0;
1290
+ case "GM2":
1291
+ if (channel.bankMSB === 121)
1292
+ return 0;
1293
+ if (channel.isDrum)
1294
+ return 128;
1295
+ return channel.bank;
1296
+ default:
1297
+ return channel.bank;
1281
1298
  }
1282
- return channel.bank;
1283
1299
  }
1284
1300
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1285
1301
  const channel = this.channels[channelNumber];
@@ -1295,15 +1311,15 @@ class Midy {
1295
1311
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1296
1312
  note.gainL.connect(channel.gainL);
1297
1313
  note.gainR.connect(channel.gainR);
1298
- if (channel.state.sostenutoPedal) {
1299
- channel.sostenutoNotes.set(noteNumber, note);
1314
+ if (0.5 <= channel.state.sustainPedal) {
1315
+ channel.sustainNotes.push(note);
1300
1316
  }
1301
1317
  const exclusiveClass = note.voiceParams.exclusiveClass;
1302
1318
  if (exclusiveClass !== 0) {
1303
1319
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1304
1320
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1305
1321
  const [prevNote, prevChannelNumber] = prevEntry;
1306
- if (!prevNote.ending) {
1322
+ if (prevNote && !prevNote.ending) {
1307
1323
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1308
1324
  startTime, true, // force
1309
1325
  undefined);
@@ -1365,7 +1381,7 @@ class Midy {
1365
1381
  const channel = this.channels[channelNumber];
1366
1382
  const state = channel.state;
1367
1383
  if (!force) {
1368
- if (0.5 < state.sustainPedal)
1384
+ if (0.5 <= state.sustainPedal)
1369
1385
  return;
1370
1386
  if (channel.sostenutoNotes.has(noteNumber))
1371
1387
  return;
@@ -1410,28 +1426,27 @@ class Midy {
1410
1426
  const velocity = halfVelocity * 2;
1411
1427
  const channel = this.channels[channelNumber];
1412
1428
  const promises = [];
1413
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1414
- const { noteNumber } = note;
1415
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1429
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
1430
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1416
1431
  promises.push(promise);
1417
- });
1432
+ }
1433
+ channel.sustainNotes = [];
1418
1434
  return promises;
1419
1435
  }
1420
- releaseSostenutoPedal(channelNumber, halfVelocity) {
1436
+ releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
1421
1437
  const velocity = halfVelocity * 2;
1422
1438
  const channel = this.channels[channelNumber];
1423
1439
  const promises = [];
1424
1440
  channel.state.sostenutoPedal = 0;
1425
- channel.sostenutoNotes.forEach((activeNote) => {
1426
- const { noteNumber } = activeNote;
1427
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1441
+ channel.sostenutoNotes.forEach((note) => {
1442
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1428
1443
  promises.push(promise);
1429
1444
  });
1430
1445
  channel.sostenutoNotes.clear();
1431
1446
  return promises;
1432
1447
  }
1433
1448
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1434
- const channelNumber = omni ? 0 : statusByte & 0x0F;
1449
+ const channelNumber = statusByte & 0x0F;
1435
1450
  const messageType = statusByte & 0xF0;
1436
1451
  switch (messageType) {
1437
1452
  case 0x80:
@@ -1467,9 +1482,21 @@ class Midy {
1467
1482
  const channel = this.channels[channelNumber];
1468
1483
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1469
1484
  channel.program = program;
1485
+ if (this.mode === "GM2") {
1486
+ switch (channel.bankMSB) {
1487
+ case 120:
1488
+ channel.isDrum = true;
1489
+ break;
1490
+ case 121:
1491
+ channel.isDrum = false;
1492
+ break;
1493
+ }
1494
+ }
1470
1495
  }
1471
1496
  handleChannelPressure(channelNumber, value, scheduleTime) {
1472
1497
  const channel = this.channels[channelNumber];
1498
+ if (channel.isDrum)
1499
+ return;
1473
1500
  const prev = channel.state.channelPressure;
1474
1501
  const next = value / 127;
1475
1502
  channel.state.channelPressure = next;
@@ -1488,8 +1515,10 @@ class Midy {
1488
1515
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
1489
1516
  }
1490
1517
  setPitchBend(channelNumber, value, scheduleTime) {
1491
- scheduleTime ??= this.audioContext.currentTime;
1492
1518
  const channel = this.channels[channelNumber];
1519
+ if (channel.isDrum)
1520
+ return;
1521
+ scheduleTime ??= this.audioContext.currentTime;
1493
1522
  const state = channel.state;
1494
1523
  const prev = state.pitchWheel * 2 - 1;
1495
1524
  const next = (value - 8192) / 8192;
@@ -1659,53 +1688,48 @@ class Midy {
1659
1688
  return state;
1660
1689
  }
1661
1690
  applyVoiceParams(channel, controllerType, scheduleTime) {
1662
- channel.scheduledNotes.forEach((noteList) => {
1663
- for (let i = 0; i < noteList.length; i++) {
1664
- const note = noteList[i];
1665
- if (!note)
1691
+ this.processScheduledNotes(channel, (note) => {
1692
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1693
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1694
+ let appliedFilterEnvelope = false;
1695
+ let appliedVolumeEnvelope = false;
1696
+ for (const [key, value] of Object.entries(voiceParams)) {
1697
+ const prevValue = note.voiceParams[key];
1698
+ if (value === prevValue)
1666
1699
  continue;
1667
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1668
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1669
- let appliedFilterEnvelope = false;
1670
- let appliedVolumeEnvelope = false;
1671
- for (const [key, value] of Object.entries(voiceParams)) {
1672
- const prevValue = note.voiceParams[key];
1673
- if (value === prevValue)
1700
+ note.voiceParams[key] = value;
1701
+ if (key in this.voiceParamsHandlers) {
1702
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1703
+ }
1704
+ else if (filterEnvelopeKeySet.has(key)) {
1705
+ if (appliedFilterEnvelope)
1674
1706
  continue;
1675
- note.voiceParams[key] = value;
1676
- if (key in this.voiceParamsHandlers) {
1677
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1707
+ appliedFilterEnvelope = true;
1708
+ const noteVoiceParams = note.voiceParams;
1709
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1710
+ const key = filterEnvelopeKeys[i];
1711
+ if (key in voiceParams)
1712
+ noteVoiceParams[key] = voiceParams[key];
1678
1713
  }
1679
- else if (filterEnvelopeKeySet.has(key)) {
1680
- if (appliedFilterEnvelope)
1681
- continue;
1682
- appliedFilterEnvelope = true;
1683
- const noteVoiceParams = note.voiceParams;
1684
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1685
- const key = filterEnvelopeKeys[i];
1686
- if (key in voiceParams)
1687
- noteVoiceParams[key] = voiceParams[key];
1688
- }
1689
- if (note.portamento) {
1690
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1691
- }
1692
- else {
1693
- this.setFilterEnvelope(channel, note, scheduleTime);
1694
- }
1695
- this.setPitchEnvelope(note, scheduleTime);
1714
+ if (note.portamento) {
1715
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1696
1716
  }
1697
- else if (volumeEnvelopeKeySet.has(key)) {
1698
- if (appliedVolumeEnvelope)
1699
- continue;
1700
- appliedVolumeEnvelope = true;
1701
- const noteVoiceParams = note.voiceParams;
1702
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1703
- const key = volumeEnvelopeKeys[i];
1704
- if (key in voiceParams)
1705
- noteVoiceParams[key] = voiceParams[key];
1706
- }
1707
- this.setVolumeEnvelope(channel, note, scheduleTime);
1717
+ else {
1718
+ this.setFilterEnvelope(channel, note, scheduleTime);
1708
1719
  }
1720
+ this.setPitchEnvelope(note, scheduleTime);
1721
+ }
1722
+ else if (volumeEnvelopeKeySet.has(key)) {
1723
+ if (appliedVolumeEnvelope)
1724
+ continue;
1725
+ appliedVolumeEnvelope = true;
1726
+ const noteVoiceParams = note.voiceParams;
1727
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1728
+ const key = volumeEnvelopeKeys[i];
1729
+ if (key in voiceParams)
1730
+ noteVoiceParams[key] = voiceParams[key];
1731
+ }
1732
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1709
1733
  }
1710
1734
  }
1711
1735
  });
@@ -1764,9 +1788,8 @@ class Midy {
1764
1788
  this.channels[channelNumber].bankMSB = msb;
1765
1789
  }
1766
1790
  updateModulation(channel, scheduleTime) {
1767
- scheduleTime ??= this.audioContext.currentTime;
1768
1791
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1769
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1792
+ this.processScheduledNotes(channel, (note) => {
1770
1793
  if (note.modulationDepth) {
1771
1794
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1772
1795
  }
@@ -1778,17 +1801,18 @@ class Midy {
1778
1801
  }
1779
1802
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1780
1803
  const channel = this.channels[channelNumber];
1804
+ if (channel.isDrum)
1805
+ return;
1806
+ scheduleTime ??= this.audioContext.currentTime;
1781
1807
  channel.state.modulationDepth = modulation / 127;
1782
1808
  this.updateModulation(channel, scheduleTime);
1783
1809
  }
1784
1810
  setPortamentoTime(channelNumber, portamentoTime) {
1785
1811
  const channel = this.channels[channelNumber];
1786
- const factor = 5 * Math.log(10) / 127;
1787
- channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1812
+ channel.state.portamentoTime = portamentoTime / 127;
1788
1813
  }
1789
1814
  setKeyBasedVolume(channel, scheduleTime) {
1790
- scheduleTime ??= this.audioContext.currentTime;
1791
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1815
+ this.processScheduledNotes(channel, (note) => {
1792
1816
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1793
1817
  if (keyBasedValue !== 0) {
1794
1818
  note.volumeNode.gain
@@ -1798,6 +1822,7 @@ class Midy {
1798
1822
  });
1799
1823
  }
1800
1824
  setVolume(channelNumber, volume, scheduleTime) {
1825
+ scheduleTime ??= this.audioContext.currentTime;
1801
1826
  const channel = this.channels[channelNumber];
1802
1827
  channel.state.volume = volume / 127;
1803
1828
  this.updateChannelVolume(channel, scheduleTime);
@@ -1811,8 +1836,7 @@ class Midy {
1811
1836
  };
1812
1837
  }
1813
1838
  setKeyBasedPan(channel, scheduleTime) {
1814
- scheduleTime ??= this.audioContext.currentTime;
1815
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1839
+ this.processScheduledNotes(channel, (note) => {
1816
1840
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1817
1841
  if (keyBasedValue !== 0) {
1818
1842
  const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
@@ -1826,12 +1850,14 @@ class Midy {
1826
1850
  });
1827
1851
  }
1828
1852
  setPan(channelNumber, pan, scheduleTime) {
1853
+ scheduleTime ??= this.audioContext.currentTime;
1829
1854
  const channel = this.channels[channelNumber];
1830
1855
  channel.state.pan = pan / 127;
1831
1856
  this.updateChannelVolume(channel, scheduleTime);
1832
1857
  this.setKeyBasedPan(channel, scheduleTime);
1833
1858
  }
1834
1859
  setExpression(channelNumber, expression, scheduleTime) {
1860
+ scheduleTime ??= this.audioContext.currentTime;
1835
1861
  const channel = this.channels[channelNumber];
1836
1862
  channel.state.expression = expression / 127;
1837
1863
  this.updateChannelVolume(channel, scheduleTime);
@@ -1855,144 +1881,156 @@ class Midy {
1855
1881
  .setValueAtTime(volume * gainRight, scheduleTime);
1856
1882
  }
1857
1883
  setSustainPedal(channelNumber, value, scheduleTime) {
1884
+ const channel = this.channels[channelNumber];
1885
+ if (channel.isDrum)
1886
+ return;
1858
1887
  scheduleTime ??= this.audioContext.currentTime;
1859
- this.channels[channelNumber].state.sustainPedal = value / 127;
1860
- if (value < 64) {
1888
+ channel.state.sustainPedal = value / 127;
1889
+ if (64 <= value) {
1890
+ this.processScheduledNotes(channel, (note) => {
1891
+ channel.sustainNotes.push(note);
1892
+ });
1893
+ }
1894
+ else {
1861
1895
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1862
1896
  }
1863
1897
  }
1864
1898
  setPortamento(channelNumber, value) {
1865
- this.channels[channelNumber].state.portamento = value / 127;
1899
+ const channel = this.channels[channelNumber];
1900
+ if (channel.isDrum)
1901
+ return;
1902
+ channel.state.portamento = value / 127;
1866
1903
  }
1867
1904
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1868
1905
  const channel = this.channels[channelNumber];
1906
+ if (channel.isDrum)
1907
+ return;
1908
+ scheduleTime ??= this.audioContext.currentTime;
1869
1909
  channel.state.sostenutoPedal = value / 127;
1870
1910
  if (64 <= value) {
1871
1911
  channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
1872
1912
  }
1873
1913
  else {
1874
- this.releaseSostenutoPedal(channelNumber, value);
1914
+ this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
1875
1915
  }
1876
1916
  }
1877
- setSoftPedal(channelNumber, softPedal, _scheduleTime) {
1917
+ setSoftPedal(channelNumber, softPedal, scheduleTime) {
1878
1918
  const channel = this.channels[channelNumber];
1919
+ if (channel.isDrum)
1920
+ return;
1921
+ scheduleTime ??= this.audioContext.currentTime;
1879
1922
  channel.state.softPedal = softPedal / 127;
1923
+ this.processScheduledNotes(channel, (note) => {
1924
+ if (note.portamento) {
1925
+ this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1926
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1927
+ }
1928
+ else {
1929
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1930
+ this.setFilterEnvelope(channel, note, scheduleTime);
1931
+ }
1932
+ });
1880
1933
  }
1881
1934
  setFilterResonance(channelNumber, filterResonance, scheduleTime) {
1882
1935
  const channel = this.channels[channelNumber];
1936
+ if (channel.isDrum)
1937
+ return;
1938
+ scheduleTime ??= this.audioContext.currentTime;
1883
1939
  const state = channel.state;
1884
1940
  state.filterResonance = filterResonance / 64;
1885
- channel.scheduledNotes.forEach((noteList) => {
1886
- for (let i = 0; i < noteList.length; i++) {
1887
- const note = noteList[i];
1888
- if (!note)
1889
- continue;
1890
- const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1891
- note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1892
- }
1941
+ this.processScheduledNotes(channel, (note) => {
1942
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1943
+ note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1893
1944
  });
1894
1945
  }
1895
1946
  setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
1896
1947
  const channel = this.channels[channelNumber];
1948
+ if (channel.isDrum)
1949
+ return;
1950
+ scheduleTime ??= this.audioContext.currentTime;
1897
1951
  channel.state.releaseTime = releaseTime / 64;
1898
1952
  }
1899
1953
  setAttackTime(channelNumber, attackTime, scheduleTime) {
1900
1954
  const channel = this.channels[channelNumber];
1955
+ if (channel.isDrum)
1956
+ return;
1957
+ scheduleTime ??= this.audioContext.currentTime;
1901
1958
  channel.state.attackTime = attackTime / 64;
1902
- channel.scheduledNotes.forEach((noteList) => {
1903
- for (let i = 0; i < noteList.length; i++) {
1904
- const note = noteList[i];
1905
- if (!note)
1906
- continue;
1907
- if (note.startTime < scheduleTime)
1908
- continue;
1909
- this.setVolumeEnvelope(channel, note);
1910
- }
1959
+ this.processScheduledNotes(channel, (note) => {
1960
+ if (note.startTime < scheduleTime)
1961
+ return false;
1962
+ this.setVolumeEnvelope(channel, note);
1911
1963
  });
1912
1964
  }
1913
1965
  setBrightness(channelNumber, brightness, scheduleTime) {
1914
1966
  const channel = this.channels[channelNumber];
1967
+ if (channel.isDrum)
1968
+ return;
1969
+ scheduleTime ??= this.audioContext.currentTime;
1915
1970
  channel.state.brightness = brightness / 64;
1916
- channel.scheduledNotes.forEach((noteList) => {
1917
- for (let i = 0; i < noteList.length; i++) {
1918
- const note = noteList[i];
1919
- if (!note)
1920
- continue;
1921
- if (note.portamento) {
1922
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1923
- }
1924
- else {
1925
- this.setFilterEnvelope(channel, note);
1926
- }
1971
+ this.processScheduledNotes(channel, (note) => {
1972
+ if (note.portamento) {
1973
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1974
+ }
1975
+ else {
1976
+ this.setFilterEnvelope(channel, note);
1927
1977
  }
1928
1978
  });
1929
1979
  }
1930
1980
  setDecayTime(channelNumber, dacayTime, scheduleTime) {
1931
1981
  const channel = this.channels[channelNumber];
1982
+ if (channel.isDrum)
1983
+ return;
1984
+ scheduleTime ??= this.audioContext.currentTime;
1932
1985
  channel.state.decayTime = dacayTime / 64;
1933
- channel.scheduledNotes.forEach((noteList) => {
1934
- for (let i = 0; i < noteList.length; i++) {
1935
- const note = noteList[i];
1936
- if (!note)
1937
- continue;
1938
- this.setVolumeEnvelope(channel, note, scheduleTime);
1939
- }
1986
+ this.processScheduledNotes(channel, (note) => {
1987
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1940
1988
  });
1941
1989
  }
1942
1990
  setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
1943
1991
  const channel = this.channels[channelNumber];
1992
+ if (channel.isDrum)
1993
+ return;
1994
+ scheduleTime ??= this.audioContext.currentTime;
1944
1995
  channel.state.vibratoRate = vibratoRate / 64;
1945
1996
  if (channel.vibratoDepth <= 0)
1946
1997
  return;
1947
- channel.scheduledNotes.forEach((noteList) => {
1948
- for (let i = 0; i < noteList.length; i++) {
1949
- const note = noteList[i];
1950
- if (!note)
1951
- continue;
1952
- this.setVibLfoToPitch(channel, note, scheduleTime);
1953
- }
1998
+ this.processScheduledNotes(channel, (note) => {
1999
+ this.setVibLfoToPitch(channel, note, scheduleTime);
1954
2000
  });
1955
2001
  }
1956
2002
  setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
1957
2003
  const channel = this.channels[channelNumber];
2004
+ if (channel.isDrum)
2005
+ return;
2006
+ scheduleTime ??= this.audioContext.currentTime;
1958
2007
  const prev = channel.state.vibratoDepth;
1959
2008
  channel.state.vibratoDepth = vibratoDepth / 64;
1960
2009
  if (0 < prev) {
1961
- channel.scheduledNotes.forEach((noteList) => {
1962
- for (let i = 0; i < noteList.length; i++) {
1963
- const note = noteList[i];
1964
- if (!note)
1965
- continue;
1966
- this.setFreqVibLFO(channel, note, scheduleTime);
1967
- }
2010
+ this.processScheduledNotes(channel, (note) => {
2011
+ this.setFreqVibLFO(channel, note, scheduleTime);
1968
2012
  });
1969
2013
  }
1970
2014
  else {
1971
- channel.scheduledNotes.forEach((noteList) => {
1972
- for (let i = 0; i < noteList.length; i++) {
1973
- const note = noteList[i];
1974
- if (!note)
1975
- continue;
1976
- this.startVibrato(channel, note, scheduleTime);
1977
- }
2015
+ this.processScheduledNotes(channel, (note) => {
2016
+ this.startVibrato(channel, note, scheduleTime);
1978
2017
  });
1979
2018
  }
1980
2019
  }
1981
2020
  setVibratoDelay(channelNumber, vibratoDelay) {
1982
2021
  const channel = this.channels[channelNumber];
2022
+ if (channel.isDrum)
2023
+ return;
2024
+ scheduleTime ??= this.audioContext.currentTime;
1983
2025
  channel.state.vibratoDelay = vibratoDelay / 64;
1984
2026
  if (0 < channel.state.vibratoDepth) {
1985
- channel.scheduledNotes.forEach((noteList) => {
1986
- for (let i = 0; i < noteList.length; i++) {
1987
- const note = noteList[i];
1988
- if (!note)
1989
- continue;
1990
- this.startVibrato(channel, note, scheduleTime);
1991
- }
2027
+ this.processScheduledNotes(channel, (note) => {
2028
+ this.startVibrato(channel, note, scheduleTime);
1992
2029
  });
1993
2030
  }
1994
2031
  }
1995
2032
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
2033
+ scheduleTime ??= this.audioContext.currentTime;
1996
2034
  const channel = this.channels[channelNumber];
1997
2035
  const state = channel.state;
1998
2036
  const reverbEffect = this.reverbEffect;
@@ -2004,27 +2042,17 @@ class Midy {
2004
2042
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2005
2043
  }
2006
2044
  else {
2007
- channel.scheduledNotes.forEach((noteList) => {
2008
- for (let i = 0; i < noteList.length; i++) {
2009
- const note = noteList[i];
2010
- if (!note)
2011
- continue;
2012
- if (note.voiceParams.reverbEffectsSend <= 0)
2013
- continue;
2014
- note.reverbEffectsSend.disconnect();
2015
- }
2045
+ this.processScheduledNotes(channel, (note) => {
2046
+ if (note.voiceParams.reverbEffectsSend <= 0)
2047
+ return false;
2048
+ note.reverbEffectsSend.disconnect();
2016
2049
  });
2017
2050
  }
2018
2051
  }
2019
2052
  else {
2020
2053
  if (0 < reverbSendLevel) {
2021
- channel.scheduledNotes.forEach((noteList) => {
2022
- for (let i = 0; i < noteList.length; i++) {
2023
- const note = noteList[i];
2024
- if (!note)
2025
- continue;
2026
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2027
- }
2054
+ this.processScheduledNotes(channel, (note) => {
2055
+ this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2028
2056
  });
2029
2057
  state.reverbSendLevel = reverbSendLevel / 127;
2030
2058
  reverbEffect.input.gain
@@ -2034,6 +2062,7 @@ class Midy {
2034
2062
  }
2035
2063
  }
2036
2064
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2065
+ scheduleTime ??= this.audioContext.currentTime;
2037
2066
  const channel = this.channels[channelNumber];
2038
2067
  const state = channel.state;
2039
2068
  const chorusEffect = this.chorusEffect;
@@ -2045,27 +2074,17 @@ class Midy {
2045
2074
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2046
2075
  }
2047
2076
  else {
2048
- channel.scheduledNotes.forEach((noteList) => {
2049
- for (let i = 0; i < noteList.length; i++) {
2050
- const note = noteList[i];
2051
- if (!note)
2052
- continue;
2053
- if (note.voiceParams.chorusEffectsSend <= 0)
2054
- continue;
2055
- note.chorusEffectsSend.disconnect();
2056
- }
2077
+ this.processScheduledNotes(channel, (note) => {
2078
+ if (note.voiceParams.chorusEffectsSend <= 0)
2079
+ return false;
2080
+ note.chorusEffectsSend.disconnect();
2057
2081
  });
2058
2082
  }
2059
2083
  }
2060
2084
  else {
2061
2085
  if (0 < chorusSendLevel) {
2062
- channel.scheduledNotes.forEach((noteList) => {
2063
- for (let i = 0; i < noteList.length; i++) {
2064
- const note = noteList[i];
2065
- if (!note)
2066
- continue;
2067
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2068
- }
2086
+ this.processScheduledNotes(channel, (note) => {
2087
+ this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2069
2088
  });
2070
2089
  state.chorusSendLevel = chorusSendLevel / 127;
2071
2090
  chorusEffect.input.gain
@@ -2110,15 +2129,15 @@ class Midy {
2110
2129
  break;
2111
2130
  case 1:
2112
2131
  channel.dataLSB += value;
2113
- this.handleFineTuningRPN(channelNumber);
2132
+ this.handleFineTuningRPN(channelNumber, scheduleTime);
2114
2133
  break;
2115
2134
  case 2:
2116
2135
  channel.dataMSB += value;
2117
- this.handleCoarseTuningRPN(channelNumber);
2136
+ this.handleCoarseTuningRPN(channelNumber, scheduleTime);
2118
2137
  break;
2119
2138
  case 5:
2120
2139
  channel.dataLSB += value;
2121
- this.handleModulationDepthRangeRPN(channelNumber);
2140
+ this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
2122
2141
  break;
2123
2142
  default:
2124
2143
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -2149,8 +2168,10 @@ class Midy {
2149
2168
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
2150
2169
  }
2151
2170
  setPitchBendRange(channelNumber, value, scheduleTime) {
2152
- scheduleTime ??= this.audioContext.currentTime;
2153
2171
  const channel = this.channels[channelNumber];
2172
+ if (channel.isDrum)
2173
+ return;
2174
+ scheduleTime ??= this.audioContext.currentTime;
2154
2175
  const state = channel.state;
2155
2176
  const prev = state.pitchWheelSensitivity;
2156
2177
  const next = value / 128;
@@ -2159,44 +2180,53 @@ class Midy {
2159
2180
  this.updateChannelDetune(channel, scheduleTime);
2160
2181
  this.applyVoiceParams(channel, 16, scheduleTime);
2161
2182
  }
2162
- handleFineTuningRPN(channelNumber) {
2183
+ handleFineTuningRPN(channelNumber, scheduleTime) {
2163
2184
  const channel = this.channels[channelNumber];
2164
2185
  this.limitData(channel, 0, 127, 0, 127);
2165
2186
  const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
2166
- this.setFineTuning(channelNumber, fineTuning);
2187
+ this.setFineTuning(channelNumber, fineTuning, scheduleTime);
2167
2188
  }
2168
- setFineTuning(channelNumber, value) {
2189
+ setFineTuning(channelNumber, value, scheduleTime) {
2169
2190
  const channel = this.channels[channelNumber];
2191
+ if (channel.isDrum)
2192
+ return;
2193
+ scheduleTime ??= this.audioContext.currentTime;
2170
2194
  const prev = channel.fineTuning;
2171
2195
  const next = (value - 8192) / 8.192; // cent
2172
2196
  channel.fineTuning = next;
2173
2197
  channel.detune += next - prev;
2174
- this.updateChannelDetune(channel);
2198
+ this.updateChannelDetune(channel, scheduleTime);
2175
2199
  }
2176
- handleCoarseTuningRPN(channelNumber) {
2200
+ handleCoarseTuningRPN(channelNumber, scheduleTime) {
2177
2201
  const channel = this.channels[channelNumber];
2178
2202
  this.limitDataMSB(channel, 0, 127);
2179
2203
  const coarseTuning = channel.dataMSB;
2180
- this.setCoarseTuning(channelNumber, coarseTuning);
2204
+ this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
2181
2205
  }
2182
- setCoarseTuning(channelNumber, value) {
2206
+ setCoarseTuning(channelNumber, value, scheduleTime) {
2183
2207
  const channel = this.channels[channelNumber];
2208
+ if (channel.isDrum)
2209
+ return;
2210
+ scheduleTime ??= this.audioContext.currentTime;
2184
2211
  const prev = channel.coarseTuning;
2185
2212
  const next = (value - 64) * 100; // cent
2186
2213
  channel.coarseTuning = next;
2187
2214
  channel.detune += next - prev;
2188
- this.updateChannelDetune(channel);
2215
+ this.updateChannelDetune(channel, scheduleTime);
2189
2216
  }
2190
- handleModulationDepthRangeRPN(channelNumber) {
2217
+ handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
2191
2218
  const channel = this.channels[channelNumber];
2192
2219
  this.limitData(channel, 0, 127, 0, 127);
2193
2220
  const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
2194
- this.setModulationDepthRange(channelNumber, modulationDepthRange);
2221
+ this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
2195
2222
  }
2196
- setModulationDepthRange(channelNumber, modulationDepthRange) {
2223
+ setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2197
2224
  const channel = this.channels[channelNumber];
2225
+ if (channel.isDrum)
2226
+ return;
2227
+ scheduleTime ??= this.audioContext.currentTime;
2198
2228
  channel.modulationDepthRange = modulationDepthRange;
2199
- this.updateModulation(channel);
2229
+ this.updateModulation(channel, scheduleTime);
2200
2230
  }
2201
2231
  allSoundOff(channelNumber, _value, scheduleTime) {
2202
2232
  scheduleTime ??= this.audioContext.currentTime;
@@ -2232,17 +2262,21 @@ class Midy {
2232
2262
  scheduleTime ??= this.audioContext.currentTime;
2233
2263
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2234
2264
  }
2235
- omniOff() {
2236
- this.omni = false;
2265
+ omniOff(channelNumber, value, scheduleTime) {
2266
+ this.allNotesOff(channelNumber, value, scheduleTime);
2237
2267
  }
2238
- omniOn() {
2239
- this.omni = true;
2268
+ omniOn(channelNumber, value, scheduleTime) {
2269
+ this.allNotesOff(channelNumber, value, scheduleTime);
2240
2270
  }
2241
- monoOn() {
2242
- this.mono = true;
2271
+ monoOn(channelNumber, value, scheduleTime) {
2272
+ const channel = this.channels[channelNumber];
2273
+ this.allNotesOff(channelNumber, value, scheduleTime);
2274
+ channel.mono = true;
2243
2275
  }
2244
- polyOn() {
2245
- this.mono = false;
2276
+ polyOn(channelNumber, value, scheduleTime) {
2277
+ const channel = this.channels[channelNumber];
2278
+ this.allNotesOff(channelNumber, value, scheduleTime);
2279
+ channel.mono = false;
2246
2280
  }
2247
2281
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
2248
2282
  switch (data[2]) {
@@ -2261,12 +2295,12 @@ class Midy {
2261
2295
  case 9:
2262
2296
  switch (data[3]) {
2263
2297
  case 1:
2264
- this.GM1SystemOn();
2298
+ this.GM1SystemOn(scheduleTime);
2265
2299
  break;
2266
2300
  case 2: // GM System Off
2267
2301
  break;
2268
2302
  case 3:
2269
- this.GM2SystemOn();
2303
+ this.GM2SystemOn(scheduleTime);
2270
2304
  break;
2271
2305
  default:
2272
2306
  console.warn(`Unsupported Exclusive Message: ${data}`);
@@ -2276,25 +2310,35 @@ class Midy {
2276
2310
  console.warn(`Unsupported Exclusive Message: ${data}`);
2277
2311
  }
2278
2312
  }
2279
- GM1SystemOn() {
2313
+ GM1SystemOn(scheduleTime) {
2314
+ scheduleTime ??= this.audioContext.currentTime;
2315
+ this.mode = "GM1";
2280
2316
  for (let i = 0; i < this.channels.length; i++) {
2317
+ this.allSoundOff(i, 0, scheduleTime);
2281
2318
  const channel = this.channels[i];
2282
2319
  channel.bankMSB = 0;
2283
2320
  channel.bankLSB = 0;
2284
2321
  channel.bank = 0;
2322
+ channel.isDrum = false;
2285
2323
  }
2286
2324
  this.channels[9].bankMSB = 1;
2287
2325
  this.channels[9].bank = 128;
2326
+ this.channels[9].isDrum = true;
2288
2327
  }
2289
- GM2SystemOn() {
2328
+ GM2SystemOn(scheduleTime) {
2329
+ scheduleTime ??= this.audioContext.currentTime;
2330
+ this.mode = "GM2";
2290
2331
  for (let i = 0; i < this.channels.length; i++) {
2332
+ this.allSoundOff(i, 0, scheduleTime);
2291
2333
  const channel = this.channels[i];
2292
2334
  channel.bankMSB = 121;
2293
2335
  channel.bankLSB = 0;
2294
2336
  channel.bank = 121 * 128;
2337
+ channel.isDrum = false;
2295
2338
  }
2296
2339
  this.channels[9].bankMSB = 120;
2297
2340
  this.channels[9].bank = 120 * 128;
2341
+ this.channels[9].isDrum = true;
2298
2342
  }
2299
2343
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
2300
2344
  switch (data[2]) {
@@ -2370,8 +2414,14 @@ class Midy {
2370
2414
  const prev = this.masterFineTuning;
2371
2415
  const next = (value - 8192) / 8.192; // cent
2372
2416
  this.masterFineTuning = next;
2373
- channel.detune += next - prev;
2374
- this.updateChannelDetune(channel, scheduleTime);
2417
+ const detuneChange = next - prev;
2418
+ for (let i = 0; i < this.channels.length; i++) {
2419
+ const channel = this.channels[i];
2420
+ if (channel.isDrum)
2421
+ continue;
2422
+ channel.detune += detuneChange;
2423
+ this.updateChannelDetune(channel, scheduleTime);
2424
+ }
2375
2425
  }
2376
2426
  handleMasterCoarseTuningSysEx(data, scheduleTime) {
2377
2427
  const coarseTuning = data[4];
@@ -2381,8 +2431,14 @@ class Midy {
2381
2431
  const prev = this.masterCoarseTuning;
2382
2432
  const next = (value - 64) * 100; // cent
2383
2433
  this.masterCoarseTuning = next;
2384
- channel.detune += next - prev;
2385
- this.updateChannelDetune(channel, scheduleTime);
2434
+ const detuneChange = next - prev;
2435
+ for (let i = 0; i < this.channels.length; i++) {
2436
+ const channel = this.channels[i];
2437
+ if (channel.isDrum)
2438
+ continue;
2439
+ channel.detune += detuneChange;
2440
+ this.updateChannelDetune(channel, scheduleTime);
2441
+ }
2386
2442
  }
2387
2443
  handleGlobalParameterControlSysEx(data, scheduleTime) {
2388
2444
  if (data[7] === 1) {
@@ -2594,6 +2650,8 @@ class Midy {
2594
2650
  if (!channelBitmap[i])
2595
2651
  continue;
2596
2652
  const channel = this.channels[i];
2653
+ if (channel.isDrum)
2654
+ continue;
2597
2655
  for (let j = 0; j < 12; j++) {
2598
2656
  const centValue = data[j + 7] - 64;
2599
2657
  channel.scaleOctaveTuningTable[j] = centValue;
@@ -2612,6 +2670,8 @@ class Midy {
2612
2670
  if (!channelBitmap[i])
2613
2671
  continue;
2614
2672
  const channel = this.channels[i];
2673
+ if (channel.isDrum)
2674
+ continue;
2615
2675
  for (let j = 0; j < 12; j++) {
2616
2676
  const index = 7 + j * 2;
2617
2677
  const msb = data[index] & 0x7F;
@@ -2682,7 +2742,10 @@ class Midy {
2682
2742
  }
2683
2743
  handlePressureSysEx(data, tableName) {
2684
2744
  const channelNumber = data[4];
2685
- const table = this.channels[channelNumber][tableName];
2745
+ const channel = this.channels[channelNumber];
2746
+ if (channel.isDrum)
2747
+ return;
2748
+ const table = channel[tableName];
2686
2749
  for (let i = 5; i < data.length - 1; i += 2) {
2687
2750
  const pp = data[i];
2688
2751
  const rr = data[i + 1];
@@ -2704,19 +2767,17 @@ class Midy {
2704
2767
  const slotSize = 6;
2705
2768
  const offset = controllerType * slotSize;
2706
2769
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2707
- channel.scheduledNotes.forEach((noteList) => {
2708
- for (let i = 0; i < noteList.length; i++) {
2709
- const note = noteList[i];
2710
- if (!note)
2711
- continue;
2712
- this.setControllerParameters(channel, note, table);
2713
- }
2770
+ this.processScheduledNotes(channel, (note) => {
2771
+ this.setControllerParameters(channel, note, table);
2714
2772
  });
2715
2773
  }
2716
2774
  handleControlChangeSysEx(data) {
2717
2775
  const channelNumber = data[4];
2776
+ const channel = this.channels[channelNumber];
2777
+ if (channel.isDrum)
2778
+ return;
2718
2779
  const controllerType = data[5];
2719
- const table = this.channels[channelNumber].controlTable[controllerType];
2780
+ const table = channel.controlTable[controllerType];
2720
2781
  for (let i = 6; i < data.length - 1; i += 2) {
2721
2782
  const pp = data[i];
2722
2783
  const rr = data[i + 1];
@@ -2730,8 +2791,11 @@ class Midy {
2730
2791
  }
2731
2792
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2732
2793
  const channelNumber = data[4];
2794
+ const channel = this.channels[channelNumber];
2795
+ if (channel.isDrum)
2796
+ return;
2733
2797
  const keyNumber = data[5];
2734
- const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
2798
+ const table = channel.keyBasedInstrumentControlTable;
2735
2799
  for (let i = 6; i < data.length - 1; i += 2) {
2736
2800
  const controllerType = data[i];
2737
2801
  const value = data[i + 1];
@@ -2752,13 +2816,20 @@ class Midy {
2752
2816
  }
2753
2817
  scheduleTask(callback, scheduleTime) {
2754
2818
  return new Promise((resolve) => {
2755
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
2819
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
2820
+ buffer: this.schedulerBuffer,
2821
+ });
2822
+ bufferSource.connect(this.scheduler);
2756
2823
  bufferSource.onended = () => {
2757
- callback();
2758
- resolve();
2824
+ try {
2825
+ callback();
2826
+ }
2827
+ finally {
2828
+ bufferSource.disconnect();
2829
+ resolve();
2830
+ }
2759
2831
  };
2760
2832
  bufferSource.start(scheduleTime);
2761
- bufferSource.stop(scheduleTime);
2762
2833
  });
2763
2834
  }
2764
2835
  }
@@ -2769,6 +2840,7 @@ Object.defineProperty(Midy, "channelSettings", {
2769
2840
  writable: true,
2770
2841
  value: {
2771
2842
  currentBufferSource: null,
2843
+ isDrum: false,
2772
2844
  detune: 0,
2773
2845
  program: 0,
2774
2846
  bank: 121 * 128,
@@ -2778,8 +2850,9 @@ Object.defineProperty(Midy, "channelSettings", {
2778
2850
  dataLSB: 0,
2779
2851
  rpnMSB: 127,
2780
2852
  rpnLSB: 127,
2853
+ mono: false, // CC#124, CC#125
2854
+ modulationDepthRange: 50, // cent
2781
2855
  fineTuning: 0, // cb
2782
2856
  coarseTuning: 0, // cb
2783
- modulationDepthRange: 50, // cent
2784
2857
  }
2785
2858
  });