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