@marmooo/midy 0.3.3 → 0.3.4

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.
@@ -47,24 +47,6 @@ class Note {
47
47
  writable: true,
48
48
  value: void 0
49
49
  });
50
- Object.defineProperty(this, "volumeNode", {
51
- enumerable: true,
52
- configurable: true,
53
- writable: true,
54
- value: void 0
55
- });
56
- Object.defineProperty(this, "gainL", {
57
- enumerable: true,
58
- configurable: true,
59
- writable: true,
60
- value: void 0
61
- });
62
- Object.defineProperty(this, "gainR", {
63
- enumerable: true,
64
- configurable: true,
65
- writable: true,
66
- value: void 0
67
- });
68
50
  Object.defineProperty(this, "modulationLFO", {
69
51
  enumerable: true,
70
52
  configurable: true,
@@ -202,6 +184,16 @@ class ControllerState {
202
184
  }
203
185
  }
204
186
  }
187
+ const volumeEnvelopeKeys = [
188
+ "volDelay",
189
+ "volAttack",
190
+ "volHold",
191
+ "volDecay",
192
+ "volSustain",
193
+ "volRelease",
194
+ "initialAttenuation",
195
+ ];
196
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
205
197
  const filterEnvelopeKeys = [
206
198
  "modEnvToPitch",
207
199
  "initialFilterFc",
@@ -211,22 +203,20 @@ const filterEnvelopeKeys = [
211
203
  "modHold",
212
204
  "modDecay",
213
205
  "modSustain",
214
- "modRelease",
215
- "playbackRate",
216
206
  ];
217
207
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
218
- const volumeEnvelopeKeys = [
219
- "volDelay",
220
- "volAttack",
221
- "volHold",
222
- "volDecay",
223
- "volSustain",
224
- "volRelease",
225
- "initialAttenuation",
208
+ const pitchEnvelopeKeys = [
209
+ "modEnvToPitch",
210
+ "modDelay",
211
+ "modAttack",
212
+ "modHold",
213
+ "modDecay",
214
+ "modSustain",
215
+ "playbackRate",
226
216
  ];
227
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
217
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
228
218
  class MidyGM2 {
229
- constructor(audioContext, options = this.defaultOptions) {
219
+ constructor(audioContext) {
230
220
  Object.defineProperty(this, "mode", {
231
221
  enumerable: true,
232
222
  configurable: true,
@@ -250,6 +240,7 @@ class MidyGM2 {
250
240
  configurable: true,
251
241
  writable: true,
252
242
  value: {
243
+ algorithm: "SchroederReverb",
253
244
  time: this.getReverbTime(64),
254
245
  feedback: 0.8,
255
246
  }
@@ -398,30 +389,7 @@ class MidyGM2 {
398
389
  writable: true,
399
390
  value: new Array(this.numChannels * drumExclusiveClassCount)
400
391
  });
401
- Object.defineProperty(this, "defaultOptions", {
402
- enumerable: true,
403
- configurable: true,
404
- writable: true,
405
- value: {
406
- reverbAlgorithm: (audioContext) => {
407
- const { time: rt60, feedback } = this.reverb;
408
- // const delay = this.calcDelay(rt60, feedback);
409
- // const impulse = this.createConvolutionReverbImpulse(
410
- // audioContext,
411
- // rt60,
412
- // delay,
413
- // );
414
- // return this.createConvolutionReverb(audioContext, impulse);
415
- const combFeedbacks = this.generateDistributedArray(feedback, 4);
416
- const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
417
- const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
418
- const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
419
- return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
420
- },
421
- }
422
- });
423
392
  this.audioContext = audioContext;
424
- this.options = { ...this.defaultOptions, ...options };
425
393
  this.masterVolume = new GainNode(audioContext);
426
394
  this.scheduler = new GainNode(audioContext, { gain: 0 });
427
395
  this.schedulerBuffer = new AudioBuffer({
@@ -431,7 +399,7 @@ class MidyGM2 {
431
399
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
432
400
  this.controlChangeHandlers = this.createControlChangeHandlers();
433
401
  this.channels = this.createChannels(audioContext);
434
- this.reverbEffect = this.options.reverbAlgorithm(audioContext);
402
+ this.reverbEffect = this.createReverbEffect(audioContext);
435
403
  this.chorusEffect = this.createChorusEffect(audioContext);
436
404
  this.chorusEffect.output.connect(this.masterVolume);
437
405
  this.reverbEffect.output.connect(this.masterVolume);
@@ -495,7 +463,7 @@ class MidyGM2 {
495
463
  this.timeline = midiData.timeline;
496
464
  this.totalTime = this.calcTotalTime();
497
465
  }
498
- setChannelAudioNodes(audioContext) {
466
+ createChannelAudioNodes(audioContext) {
499
467
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
500
468
  const gainL = new GainNode(audioContext, { gain: gainLeft });
501
469
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -510,10 +478,9 @@ class MidyGM2 {
510
478
  };
511
479
  }
512
480
  resetChannelTable(channel) {
513
- this.resetControlTable(channel.controlTable);
481
+ channel.controlTable.fill(-1);
514
482
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
515
- channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
516
- channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
483
+ channel.channelPressureTable.fill(-1);
517
484
  channel.keyBasedInstrumentControlTable.fill(-1);
518
485
  }
519
486
  createChannels(audioContext) {
@@ -523,14 +490,16 @@ class MidyGM2 {
523
490
  isDrum: false,
524
491
  state: new ControllerState(),
525
492
  ...this.constructor.channelSettings,
526
- ...this.setChannelAudioNodes(audioContext),
493
+ ...this.createChannelAudioNodes(audioContext),
527
494
  scheduledNotes: [],
528
495
  sustainNotes: [],
529
496
  sostenutoNotes: [],
530
497
  controlTable: this.initControlTable(),
531
498
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
532
- channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
499
+ channelPressureTable: new Int8Array(6).fill(-1),
533
500
  keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
501
+ keyBasedGainLs: new Array(128),
502
+ keyBasedGainRs: new Array(128),
534
503
  };
535
504
  });
536
505
  return channels;
@@ -572,10 +541,9 @@ class MidyGM2 {
572
541
  createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
573
542
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
574
543
  bufferSource.buffer = audioBuffer;
575
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
576
- if (channel.isDrum) {
577
- bufferSource.loop = this.isLoopDrum(channel, noteNumber);
578
- }
544
+ bufferSource.loop = channel.isDrum
545
+ ? this.isLoopDrum(channel, noteNumber)
546
+ : (voiceParams.sampleModes % 2 !== 0);
579
547
  if (bufferSource.loop) {
580
548
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
581
549
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -816,7 +784,7 @@ class MidyGM2 {
816
784
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
817
785
  const channel = this.channels[channelNumber];
818
786
  const promises = [];
819
- this.processScheduledNotes(channel, (note) => {
787
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
820
788
  const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
821
789
  this.notePromises.push(promise);
822
790
  promises.push(promise);
@@ -875,7 +843,7 @@ class MidyGM2 {
875
843
  const now = this.audioContext.currentTime;
876
844
  return this.resumeTime + now - this.startTime - this.startDelay;
877
845
  }
878
- processScheduledNotes(channel, callback) {
846
+ processScheduledNotes(channel, scheduleTime, callback) {
879
847
  const scheduledNotes = channel.scheduledNotes;
880
848
  for (let i = 0; i < scheduledNotes.length; i++) {
881
849
  const note = scheduledNotes[i];
@@ -883,6 +851,8 @@ class MidyGM2 {
883
851
  continue;
884
852
  if (note.ending)
885
853
  continue;
854
+ if (note.startTime < scheduleTime)
855
+ continue;
886
856
  callback(note);
887
857
  }
888
858
  }
@@ -895,7 +865,7 @@ class MidyGM2 {
895
865
  if (note.ending)
896
866
  continue;
897
867
  if (scheduleTime < note.startTime)
898
- continue;
868
+ break;
899
869
  callback(note);
900
870
  }
901
871
  }
@@ -984,6 +954,22 @@ class MidyGM2 {
984
954
  const output = allpasses.at(-1);
985
955
  return { input, output };
986
956
  }
957
+ createReverbEffect(audioContext) {
958
+ const { algorithm, time: rt60, feedback } = this.reverb;
959
+ switch (algorithm) {
960
+ case "ConvolutionReverb": {
961
+ const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
962
+ return this.createConvolutionReverb(audioContext, impulse);
963
+ }
964
+ case "SchroederReverb": {
965
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
966
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
967
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
968
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
969
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
970
+ }
971
+ }
972
+ }
987
973
  createChorusEffect(audioContext) {
988
974
  const input = new GainNode(audioContext);
989
975
  const output = new GainNode(audioContext);
@@ -1048,15 +1034,22 @@ class MidyGM2 {
1048
1034
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
1049
1035
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
1050
1036
  const pitch = pitchWheel * pitchWheelSensitivity;
1051
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1052
- const pressure = pressureDepth * channel.state.channelPressure;
1053
- return tuning + pitch + pressure;
1037
+ const channelPressureRaw = channel.channelPressureTable[0];
1038
+ if (0 <= channelPressureRaw) {
1039
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1040
+ const channelPressure = channelPressureDepth *
1041
+ channel.state.channelPressure;
1042
+ return tuning + pitch + channelPressure;
1043
+ }
1044
+ else {
1045
+ return tuning + pitch;
1046
+ }
1054
1047
  }
1055
1048
  calcNoteDetune(channel, note) {
1056
1049
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1057
1050
  }
1058
1051
  updateChannelDetune(channel, scheduleTime) {
1059
- this.processScheduledNotes(channel, (note) => {
1052
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1060
1053
  this.updateDetune(channel, note, scheduleTime);
1061
1054
  });
1062
1055
  }
@@ -1303,9 +1296,6 @@ class MidyGM2 {
1303
1296
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1304
1297
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1305
1298
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1306
- note.volumeNode = new GainNode(this.audioContext);
1307
- note.gainL = new GainNode(this.audioContext);
1308
- note.gainR = new GainNode(this.audioContext);
1309
1299
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1310
1300
  note.filterNode = new BiquadFilterNode(this.audioContext, {
1311
1301
  type: "lowpass",
@@ -1338,9 +1328,6 @@ class MidyGM2 {
1338
1328
  }
1339
1329
  note.bufferSource.connect(note.filterNode);
1340
1330
  note.filterNode.connect(note.volumeEnvelopeNode);
1341
- note.volumeEnvelopeNode.connect(note.volumeNode);
1342
- note.volumeNode.connect(note.gainL);
1343
- note.volumeNode.connect(note.gainR);
1344
1331
  if (0 < state.chorusSendLevel) {
1345
1332
  this.setChorusEffectsSend(channel, note, 0, now);
1346
1333
  }
@@ -1399,14 +1386,7 @@ class MidyGM2 {
1399
1386
  }
1400
1387
  this.drumExclusiveClassNotes[index] = note;
1401
1388
  }
1402
- isDrumNoteOffException(channel, noteNumber) {
1403
- if (!channel.isDrum)
1404
- return false;
1405
- const programNumber = channel.programNumber;
1406
- return !((programNumber === 48 && noteNumber === 88) ||
1407
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1408
- }
1409
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1389
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1410
1390
  const channel = this.channels[channelNumber];
1411
1391
  const bankNumber = this.calcBank(channel, channelNumber);
1412
1392
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -1418,9 +1398,18 @@ class MidyGM2 {
1418
1398
  return;
1419
1399
  const isSF3 = soundFont.parsed.info.version.major === 3;
1420
1400
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1421
- note.noteOffEvent = noteOffEvent;
1422
- note.gainL.connect(channel.gainL);
1423
- note.gainR.connect(channel.gainR);
1401
+ if (channel.isDrum) {
1402
+ const audioContext = this.audioContext;
1403
+ const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1404
+ channel.keyBasedGainLs[noteNumber] = gainL;
1405
+ channel.keyBasedGainRs[noteNumber] = gainR;
1406
+ note.volumeEnvelopeNode.connect(gainL);
1407
+ note.volumeEnvelopeNode.connect(gainR);
1408
+ }
1409
+ else {
1410
+ note.volumeEnvelopeNode.connect(channel.gainL);
1411
+ note.volumeEnvelopeNode.connect(channel.gainR);
1412
+ }
1424
1413
  if (0.5 <= channel.state.sustainPedal) {
1425
1414
  channel.sustainNotes.push(note);
1426
1415
  }
@@ -1438,9 +1427,6 @@ class MidyGM2 {
1438
1427
  note.bufferSource.disconnect();
1439
1428
  note.filterNode.disconnect();
1440
1429
  note.volumeEnvelopeNode.disconnect();
1441
- note.volumeNode.disconnect();
1442
- note.gainL.disconnect();
1443
- note.gainR.disconnect();
1444
1430
  if (note.modulationDepth) {
1445
1431
  note.volumeDepth.disconnect();
1446
1432
  note.modulationDepth.disconnect();
@@ -1583,9 +1569,10 @@ class MidyGM2 {
1583
1569
  const prev = channel.state.channelPressure;
1584
1570
  const next = value / 127;
1585
1571
  channel.state.channelPressure = next;
1586
- if (channel.channelPressureTable[0] !== 64) {
1587
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1588
- channel.detune += pressureDepth * (next - prev);
1572
+ const channelPressureRaw = channel.channelPressureTable[0];
1573
+ if (0 <= channelPressureRaw) {
1574
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1575
+ channel.detune += channelPressureDepth * (next - prev);
1589
1576
  }
1590
1577
  const table = channel.channelPressureTable;
1591
1578
  this.processActiveNotes(channel, scheduleTime, (note) => {
@@ -1645,10 +1632,12 @@ class MidyGM2 {
1645
1632
  .setValueAtTime(volumeDepth, scheduleTime);
1646
1633
  }
1647
1634
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1648
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1649
1635
  let value = note.voiceParams.reverbEffectsSend;
1650
- if (0 <= keyBasedValue) {
1651
- value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1636
+ if (channel.isDrum) {
1637
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1638
+ if (0 <= keyBasedValue) {
1639
+ value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1640
+ }
1652
1641
  }
1653
1642
  if (0 < prevValue) {
1654
1643
  if (0 < value) {
@@ -1673,10 +1662,12 @@ class MidyGM2 {
1673
1662
  }
1674
1663
  }
1675
1664
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1676
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1677
1665
  let value = note.voiceParams.chorusEffectsSend;
1678
- if (0 <= keyBasedValue) {
1679
- value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1666
+ if (channel.isDrum) {
1667
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1668
+ if (0 <= keyBasedValue) {
1669
+ value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1670
+ }
1680
1671
  }
1681
1672
  if (0 < prevValue) {
1682
1673
  if (0 < vaule) {
@@ -1694,7 +1685,7 @@ class MidyGM2 {
1694
1685
  note.chorusEffectsSend = new GainNode(this.audioContext, {
1695
1686
  gain: value,
1696
1687
  });
1697
- note.volumeNode.connect(note.chorusEffectsSend);
1688
+ note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1698
1689
  }
1699
1690
  note.chorusEffectsSend.connect(this.chorusEffect.input);
1700
1691
  }
@@ -1778,11 +1769,12 @@ class MidyGM2 {
1778
1769
  return state;
1779
1770
  }
1780
1771
  applyVoiceParams(channel, controllerType, scheduleTime) {
1781
- this.processScheduledNotes(channel, (note) => {
1772
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1782
1773
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1783
1774
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1784
- let appliedFilterEnvelope = false;
1785
- let appliedVolumeEnvelope = false;
1775
+ let applyVolumeEnvelope = false;
1776
+ let applyFilterEnvelope = false;
1777
+ let applyPitchEnvelope = false;
1786
1778
  for (const [key, value] of Object.entries(voiceParams)) {
1787
1779
  const prevValue = note.voiceParams[key];
1788
1780
  if (value === prevValue)
@@ -1791,37 +1783,23 @@ class MidyGM2 {
1791
1783
  if (key in this.voiceParamsHandlers) {
1792
1784
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1793
1785
  }
1794
- else if (filterEnvelopeKeySet.has(key)) {
1795
- if (appliedFilterEnvelope)
1796
- continue;
1797
- appliedFilterEnvelope = true;
1798
- const noteVoiceParams = note.voiceParams;
1799
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1800
- const key = filterEnvelopeKeys[i];
1801
- if (key in voiceParams)
1802
- noteVoiceParams[key] = voiceParams[key];
1803
- }
1804
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1805
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1806
- }
1807
- else {
1808
- this.setFilterEnvelope(channel, note, scheduleTime);
1809
- }
1810
- this.setPitchEnvelope(note, scheduleTime);
1811
- }
1812
- else if (volumeEnvelopeKeySet.has(key)) {
1813
- if (appliedVolumeEnvelope)
1814
- continue;
1815
- appliedVolumeEnvelope = true;
1816
- const noteVoiceParams = note.voiceParams;
1817
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1818
- const key = volumeEnvelopeKeys[i];
1819
- if (key in voiceParams)
1820
- noteVoiceParams[key] = voiceParams[key];
1821
- }
1822
- this.setVolumeEnvelope(channel, note, scheduleTime);
1786
+ else {
1787
+ if (volumeEnvelopeKeySet.has(key))
1788
+ applyVolumeEnvelope = true;
1789
+ if (filterEnvelopeKeySet.has(key))
1790
+ applyFilterEnvelope = true;
1791
+ if (pitchEnvelopeKeySet.has(key))
1792
+ applyPitchEnvelope = true;
1823
1793
  }
1824
1794
  }
1795
+ if (applyVolumeEnvelope) {
1796
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1797
+ }
1798
+ if (applyFilterEnvelope) {
1799
+ this.setFilterEnvelope(channel, note, scheduleTime);
1800
+ }
1801
+ if (applyPitchEnvelope)
1802
+ this.setPitchEnvelope(note, scheduleTime);
1825
1803
  });
1826
1804
  }
1827
1805
  createControlChangeHandlers() {
@@ -1858,7 +1836,7 @@ class MidyGM2 {
1858
1836
  handler.call(this, channelNumber, value, scheduleTime);
1859
1837
  const channel = this.channels[channelNumber];
1860
1838
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1861
- this.applyControlTable(channel, controllerType);
1839
+ this.applyControlTable(channel, controllerType, scheduleTime);
1862
1840
  }
1863
1841
  else {
1864
1842
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1869,7 +1847,7 @@ class MidyGM2 {
1869
1847
  }
1870
1848
  updateModulation(channel, scheduleTime) {
1871
1849
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1872
- this.processScheduledNotes(channel, (note) => {
1850
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1873
1851
  if (note.modulationDepth) {
1874
1852
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1875
1853
  }
@@ -1888,7 +1866,7 @@ class MidyGM2 {
1888
1866
  this.updateModulation(channel, scheduleTime);
1889
1867
  }
1890
1868
  updatePortamento(channel, scheduleTime) {
1891
- this.processScheduledNotes(channel, (note) => {
1869
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1892
1870
  if (0.5 <= channel.state.portamento) {
1893
1871
  if (0 <= note.portamentoNoteNumber) {
1894
1872
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
@@ -1915,22 +1893,12 @@ class MidyGM2 {
1915
1893
  return;
1916
1894
  this.updatePortamento(channel, scheduleTime);
1917
1895
  }
1918
- setKeyBasedVolume(channel, scheduleTime) {
1919
- this.processScheduledNotes(channel, (note) => {
1920
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1921
- if (0 <= keyBasedValue) {
1922
- note.volumeNode.gain
1923
- .cancelScheduledValues(scheduleTime)
1924
- .setValueAtTime(keyBasedValue / 127, scheduleTime);
1925
- }
1926
- });
1927
- }
1928
1896
  setVolume(channelNumber, volume, scheduleTime) {
1929
1897
  scheduleTime ??= this.audioContext.currentTime;
1930
1898
  const channel = this.channels[channelNumber];
1931
1899
  channel.state.volume = volume / 127;
1932
1900
  this.updateChannelVolume(channel, scheduleTime);
1933
- this.setKeyBasedVolume(channel, scheduleTime);
1901
+ this.updateKeyBasedVolume(channel, scheduleTime);
1934
1902
  }
1935
1903
  panToGain(pan) {
1936
1904
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1939,26 +1907,12 @@ class MidyGM2 {
1939
1907
  gainRight: Math.sin(theta),
1940
1908
  };
1941
1909
  }
1942
- setKeyBasedPan(channel, scheduleTime) {
1943
- this.processScheduledNotes(channel, (note) => {
1944
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1945
- if (0 <= keyBasedValue) {
1946
- const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
1947
- note.gainL.gain
1948
- .cancelScheduledValues(scheduleTime)
1949
- .setValueAtTime(gainLeft, scheduleTime);
1950
- note.gainR.gain
1951
- .cancelScheduledValues(scheduleTime)
1952
- .setValueAtTime(gainRight, scheduleTime);
1953
- }
1954
- });
1955
- }
1956
1910
  setPan(channelNumber, pan, scheduleTime) {
1957
1911
  scheduleTime ??= this.audioContext.currentTime;
1958
1912
  const channel = this.channels[channelNumber];
1959
1913
  channel.state.pan = pan / 127;
1960
1914
  this.updateChannelVolume(channel, scheduleTime);
1961
- this.setKeyBasedPan(channel, scheduleTime);
1915
+ this.updateKeyBasedVolume(channel, scheduleTime);
1962
1916
  }
1963
1917
  setExpression(channelNumber, expression, scheduleTime) {
1964
1918
  scheduleTime ??= this.audioContext.currentTime;
@@ -1984,6 +1938,34 @@ class MidyGM2 {
1984
1938
  .cancelScheduledValues(scheduleTime)
1985
1939
  .setValueAtTime(volume * gainRight, scheduleTime);
1986
1940
  }
1941
+ updateKeyBasedVolume(channel, scheduleTime) {
1942
+ if (!channel.isDrum)
1943
+ return;
1944
+ const state = channel.state;
1945
+ const defaultVolume = state.volume * state.expression;
1946
+ const defaultPan = state.pan;
1947
+ for (let i = 0; i < 128; i++) {
1948
+ const gainL = channel.keyBasedGainLs[i];
1949
+ const gainR = channel.keyBasedGainLs[i];
1950
+ if (!gainL)
1951
+ continue;
1952
+ if (!gainR)
1953
+ continue;
1954
+ const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
1955
+ const volume = (0 <= keyBasedVolume)
1956
+ ? defaultVolume * keyBasedVolume / 64
1957
+ : defaultVolume;
1958
+ const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
1959
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1960
+ const { gainLeft, gainRight } = this.panToGain(pan);
1961
+ gainL.gain
1962
+ .cancelScheduledValues(scheduleTime)
1963
+ .setValueAtTime(volume * gainLeft, scheduleTime);
1964
+ gainR.gain
1965
+ .cancelScheduledValues(scheduleTime)
1966
+ .setValueAtTime(volume * gainRight, scheduleTime);
1967
+ }
1968
+ }
1987
1969
  setSustainPedal(channelNumber, value, scheduleTime) {
1988
1970
  const channel = this.channels[channelNumber];
1989
1971
  if (channel.isDrum)
@@ -1991,7 +1973,7 @@ class MidyGM2 {
1991
1973
  scheduleTime ??= this.audioContext.currentTime;
1992
1974
  channel.state.sustainPedal = value / 127;
1993
1975
  if (64 <= value) {
1994
- this.processScheduledNotes(channel, (note) => {
1976
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1995
1977
  channel.sustainNotes.push(note);
1996
1978
  });
1997
1979
  }
@@ -2034,7 +2016,7 @@ class MidyGM2 {
2034
2016
  const state = channel.state;
2035
2017
  scheduleTime ??= this.audioContext.currentTime;
2036
2018
  state.softPedal = softPedal / 127;
2037
- this.processScheduledNotes(channel, (note) => {
2019
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2038
2020
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2039
2021
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2040
2022
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2058,7 +2040,7 @@ class MidyGM2 {
2058
2040
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2059
2041
  }
2060
2042
  else {
2061
- this.processScheduledNotes(channel, (note) => {
2043
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2062
2044
  if (note.voiceParams.reverbEffectsSend <= 0)
2063
2045
  return false;
2064
2046
  if (note.reverbEffectsSend)
@@ -2068,7 +2050,7 @@ class MidyGM2 {
2068
2050
  }
2069
2051
  else {
2070
2052
  if (0 < reverbSendLevel) {
2071
- this.processScheduledNotes(channel, (note) => {
2053
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2072
2054
  this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2073
2055
  });
2074
2056
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2091,7 +2073,7 @@ class MidyGM2 {
2091
2073
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2092
2074
  }
2093
2075
  else {
2094
- this.processScheduledNotes(channel, (note) => {
2076
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2095
2077
  if (note.voiceParams.chorusEffectsSend <= 0)
2096
2078
  return false;
2097
2079
  if (note.chorusEffectsSend)
@@ -2101,7 +2083,7 @@ class MidyGM2 {
2101
2083
  }
2102
2084
  else {
2103
2085
  if (0 < chorusSendLevel) {
2104
- this.processScheduledNotes(channel, (note) => {
2086
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2105
2087
  this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2106
2088
  });
2107
2089
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2485,8 +2467,7 @@ class MidyGM2 {
2485
2467
  setReverbType(type) {
2486
2468
  this.reverb.time = this.getReverbTimeFromType(type);
2487
2469
  this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
2488
- const { audioContext, options } = this;
2489
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2470
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2490
2471
  }
2491
2472
  getReverbTimeFromType(type) {
2492
2473
  switch (type) {
@@ -2508,8 +2489,7 @@ class MidyGM2 {
2508
2489
  }
2509
2490
  setReverbTime(value) {
2510
2491
  this.reverb.time = this.getReverbTime(value);
2511
- const { audioContext, options } = this;
2512
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2492
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2513
2493
  }
2514
2494
  getReverbTime(value) {
2515
2495
  return Math.exp((value - 40) * 0.025);
@@ -2680,50 +2660,60 @@ class MidyGM2 {
2680
2660
  }
2681
2661
  }
2682
2662
  getFilterCutoffControl(channel) {
2683
- const channelPressure = (channel.channelPressureTable[1] - 64) *
2684
- channel.state.channelPressure;
2663
+ const channelPressureRaw = channel.channelPressureTable[1];
2664
+ const channelPressure = (0 <= channelPressureRaw)
2665
+ ? (channelPressureRaw - 64) * channel.state.channelPressure
2666
+ : 0;
2685
2667
  return channelPressure * 15;
2686
2668
  }
2687
2669
  getAmplitudeControl(channel) {
2688
- const channelPressure = channel.channelPressureTable[2] *
2689
- channel.state.channelPressure;
2670
+ const channelPressureRaw = channel.channelPressureTable[2];
2671
+ const channelPressure = (0 <= channelPressureRaw)
2672
+ ? channelPressureRaw * channel.state.channelPressure
2673
+ : 0;
2690
2674
  return channelPressure / 64;
2691
2675
  }
2692
2676
  getLFOPitchDepth(channel) {
2693
- const channelPressure = channel.channelPressureTable[3] *
2694
- channel.state.channelPressure;
2677
+ const channelPressureRaw = channel.channelPressureTable[3];
2678
+ const channelPressure = (0 <= channelPressureRaw)
2679
+ ? channelPressureRaw * channel.state.channelPressure
2680
+ : 0;
2695
2681
  return channelPressure / 127 * 600;
2696
2682
  }
2697
2683
  getLFOFilterDepth(channel) {
2698
- const channelPressure = channel.channelPressureTable[4] *
2699
- channel.state.channelPressure;
2684
+ const channelPressureRaw = channel.channelPressureTable[4];
2685
+ const channelPressure = (0 <= channelPressureRaw)
2686
+ ? channelPressureRaw * channel.state.channelPressure
2687
+ : 0;
2700
2688
  return channelPressure / 127 * 2400;
2701
2689
  }
2702
2690
  getLFOAmplitudeDepth(channel) {
2703
- const channelPressure = channel.channelPressureTable[5] *
2704
- channel.state.channelPressure;
2691
+ const channelPressureRaw = channel.channelPressureTable[5];
2692
+ const channelPressure = (0 <= channelPressureRaw)
2693
+ ? channelPressureRaw * channel.state.channelPressure
2694
+ : 0;
2705
2695
  return channelPressure / 127;
2706
2696
  }
2707
2697
  setControllerParameters(channel, note, table) {
2708
- if (table[0] !== 64)
2698
+ if (0 <= table[0])
2709
2699
  this.updateDetune(channel, note);
2710
2700
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2711
- if (table[1] !== 64)
2701
+ if (0 <= table[1])
2712
2702
  this.setPortamentoFilterEnvelope(channel, note);
2713
- if (table[2] !== 64)
2703
+ if (0 <= table[2])
2714
2704
  this.setPortamentoVolumeEnvelope(channel, note);
2715
2705
  }
2716
2706
  else {
2717
- if (table[1] !== 64)
2707
+ if (0 <= table[1])
2718
2708
  this.setFilterEnvelope(channel, note);
2719
- if (table[2] !== 64)
2709
+ if (0 <= table[2])
2720
2710
  this.setVolumeEnvelope(channel, note);
2721
2711
  }
2722
- if (table[3] !== 0)
2712
+ if (0 <= table[3])
2723
2713
  this.setModLfoToPitch(channel, note);
2724
- if (table[4] !== 0)
2714
+ if (0 <= table[4])
2725
2715
  this.setModLfoToFilterFc(channel, note);
2726
- if (table[5] !== 0)
2716
+ if (0 <= table[5])
2727
2717
  this.setModLfoToVolume(channel, note);
2728
2718
  }
2729
2719
  handlePressureSysEx(data, tableName) {
@@ -2739,26 +2729,15 @@ class MidyGM2 {
2739
2729
  }
2740
2730
  }
2741
2731
  initControlTable() {
2742
- const channelCount = 128;
2732
+ const ccCount = 128;
2743
2733
  const slotSize = 6;
2744
- const table = new Uint8Array(channelCount * slotSize);
2745
- return this.resetControlTable(table);
2734
+ return new Int8Array(ccCount * slotSize).fill(-1);
2746
2735
  }
2747
- resetControlTable(table) {
2748
- const channelCount = 128;
2749
- const slotSize = 6;
2750
- const defaultValues = [64, 64, 64, 0, 0, 0];
2751
- for (let ch = 0; ch < channelCount; ch++) {
2752
- const offset = ch * slotSize;
2753
- table.set(defaultValues, offset);
2754
- }
2755
- return table;
2756
- }
2757
- applyControlTable(channel, controllerType) {
2736
+ applyControlTable(channel, controllerType, scheduleTime) {
2758
2737
  const slotSize = 6;
2759
2738
  const offset = controllerType * slotSize;
2760
2739
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2761
- this.processScheduledNotes(channel, (note) => {
2740
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2762
2741
  this.setControllerParameters(channel, note, table);
2763
2742
  });
2764
2743
  }
@@ -2783,7 +2762,7 @@ class MidyGM2 {
2783
2762
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2784
2763
  const channelNumber = data[4];
2785
2764
  const channel = this.channels[channelNumber];
2786
- if (channel.isDrum)
2765
+ if (!channel.isDrum)
2787
2766
  return;
2788
2767
  const keyNumber = data[5];
2789
2768
  const table = channel.keyBasedInstrumentControlTable;