@marmooo/midy 0.3.4 → 0.3.5

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-GM2.js CHANGED
@@ -314,13 +314,13 @@ export class MidyGM2 {
314
314
  writable: true,
315
315
  value: this.initSoundFontTable()
316
316
  });
317
- Object.defineProperty(this, "audioBufferCounter", {
317
+ Object.defineProperty(this, "voiceCounter", {
318
318
  enumerable: true,
319
319
  configurable: true,
320
320
  writable: true,
321
321
  value: new Map()
322
322
  });
323
- Object.defineProperty(this, "audioBufferCache", {
323
+ Object.defineProperty(this, "voiceCache", {
324
324
  enumerable: true,
325
325
  configurable: true,
326
326
  writable: true,
@@ -417,13 +417,11 @@ export class MidyGM2 {
417
417
  const presetHeaders = soundFont.parsed.presetHeaders;
418
418
  for (let i = 0; i < presetHeaders.length; i++) {
419
419
  const presetHeader = presetHeaders[i];
420
- if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
421
- const banks = this.soundFontTable[presetHeader.preset];
422
- banks.set(presetHeader.bank, index);
423
- }
420
+ const banks = this.soundFontTable[presetHeader.preset];
421
+ banks.set(presetHeader.bank, index);
424
422
  }
425
423
  }
426
- async loadSoundFont(input) {
424
+ async toUint8Array(input) {
427
425
  let uint8Array;
428
426
  if (typeof input === "string") {
429
427
  const response = await fetch(input);
@@ -436,23 +434,32 @@ export class MidyGM2 {
436
434
  else {
437
435
  throw new TypeError("input must be a URL string or Uint8Array");
438
436
  }
439
- const parsed = parse(uint8Array);
440
- const soundFont = new SoundFont(parsed);
441
- this.addSoundFont(soundFont);
437
+ return uint8Array;
442
438
  }
443
- async loadMIDI(input) {
444
- let uint8Array;
445
- if (typeof input === "string") {
446
- const response = await fetch(input);
447
- const arrayBuffer = await response.arrayBuffer();
448
- uint8Array = new Uint8Array(arrayBuffer);
449
- }
450
- else if (input instanceof Uint8Array) {
451
- uint8Array = input;
439
+ async loadSoundFont(input) {
440
+ this.voiceCounter.clear();
441
+ if (Array.isArray(input)) {
442
+ const promises = new Array(input.length);
443
+ for (let i = 0; i < input.length; i++) {
444
+ promises[i] = this.toUint8Array(input[i]);
445
+ }
446
+ const uint8Arrays = await Promise.all(promises);
447
+ for (let i = 0; i < uint8Arrays.length; i++) {
448
+ const parsed = parse(uint8Arrays[i]);
449
+ const soundFont = new SoundFont(parsed);
450
+ this.addSoundFont(soundFont);
451
+ }
452
452
  }
453
453
  else {
454
- throw new TypeError("input must be a URL string or Uint8Array");
454
+ const uint8Array = await this.toUint8Array(input);
455
+ const parsed = parse(uint8Array);
456
+ const soundFont = new SoundFont(parsed);
457
+ this.addSoundFont(soundFont);
455
458
  }
459
+ }
460
+ async loadMIDI(input) {
461
+ this.voiceCounter.clear();
462
+ const uint8Array = await this.toUint8Array(input);
456
463
  const midi = parseMidi(uint8Array);
457
464
  this.ticksPerBeat = midi.header.ticksPerBeat;
458
465
  const midiData = this.extractMidiData(midi);
@@ -460,6 +467,45 @@ export class MidyGM2 {
460
467
  this.timeline = midiData.timeline;
461
468
  this.totalTime = this.calcTotalTime();
462
469
  }
470
+ cacheVoiceIds() {
471
+ const timeline = this.timeline;
472
+ for (let i = 0; i < timeline.length; i++) {
473
+ const event = timeline[i];
474
+ switch (event.type) {
475
+ case "noteOn": {
476
+ const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
477
+ this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
478
+ break;
479
+ }
480
+ case "controller":
481
+ if (event.controllerType === 0) {
482
+ this.setBankMSB(event.channel, event.value);
483
+ }
484
+ else if (event.controllerType === 32) {
485
+ this.setBankLSB(event.channel, event.value);
486
+ }
487
+ break;
488
+ case "programChange":
489
+ this.setProgramChange(event.channel, event.programNumber, event.startTime);
490
+ }
491
+ }
492
+ for (const [audioBufferId, count] of this.voiceCounter) {
493
+ if (count === 1)
494
+ this.voiceCounter.delete(audioBufferId);
495
+ }
496
+ this.GM2SystemOn();
497
+ }
498
+ getVoiceId(channel, noteNumber, velocity) {
499
+ const bankNumber = this.calcBank(channel);
500
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
501
+ .get(bankNumber);
502
+ if (soundFontIndex === undefined)
503
+ return;
504
+ const soundFont = this.soundFonts[soundFontIndex];
505
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
506
+ const { instrument, sampleID } = voice.generators;
507
+ return `${soundFontIndex}:${instrument}:${sampleID}`;
508
+ }
463
509
  createChannelAudioNodes(audioContext) {
464
510
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
465
511
  const gainL = new GainNode(audioContext, { gain: gainLeft });
@@ -501,34 +547,12 @@ export class MidyGM2 {
501
547
  });
502
548
  return channels;
503
549
  }
504
- async createNoteBuffer(voiceParams, isSF3) {
550
+ async createAudioBuffer(voiceParams) {
551
+ const sample = voiceParams.sample;
505
552
  const sampleStart = voiceParams.start;
506
- const sampleEnd = voiceParams.sample.length + voiceParams.end;
507
- if (isSF3) {
508
- const sample = voiceParams.sample;
509
- const start = sample.byteOffset + sampleStart;
510
- const end = sample.byteOffset + sampleEnd;
511
- const buffer = sample.buffer.slice(start, end);
512
- const audioBuffer = await this.audioContext.decodeAudioData(buffer);
513
- return audioBuffer;
514
- }
515
- else {
516
- const sample = voiceParams.sample;
517
- const start = sample.byteOffset + sampleStart;
518
- const end = sample.byteOffset + sampleEnd;
519
- const buffer = sample.buffer.slice(start, end);
520
- const audioBuffer = new AudioBuffer({
521
- numberOfChannels: 1,
522
- length: sample.length,
523
- sampleRate: voiceParams.sampleRate,
524
- });
525
- const channelData = audioBuffer.getChannelData(0);
526
- const int16Array = new Int16Array(buffer);
527
- for (let i = 0; i < int16Array.length; i++) {
528
- channelData[i] = int16Array[i] / 32768;
529
- }
530
- return audioBuffer;
531
- }
553
+ const sampleEnd = sample.data.length + voiceParams.end;
554
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
555
+ return audioBuffer;
532
556
  }
533
557
  isLoopDrum(channel, noteNumber) {
534
558
  const programNumber = channel.programNumber;
@@ -565,13 +589,13 @@ export class MidyGM2 {
565
589
  break;
566
590
  }
567
591
  case "controller":
568
- this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
592
+ this.setControlChange(event.channel, event.controllerType, event.value, startTime);
569
593
  break;
570
594
  case "programChange":
571
- this.handleProgramChange(event.channel, event.programNumber, startTime);
595
+ this.setProgramChange(event.channel, event.programNumber, startTime);
572
596
  break;
573
597
  case "channelAftertouch":
574
- this.handleChannelPressure(event.channel, event.amount, startTime);
598
+ this.setChannelPressure(event.channel, event.amount, startTime);
575
599
  break;
576
600
  case "pitchBend":
577
601
  this.setPitchBend(event.channel, event.value + 8192, startTime);
@@ -605,8 +629,9 @@ export class MidyGM2 {
605
629
  this.notePromises = [];
606
630
  this.exclusiveClassNotes.fill(undefined);
607
631
  this.drumExclusiveClassNotes.fill(undefined);
608
- this.audioBufferCache.clear();
632
+ this.voiceCache.clear();
609
633
  for (let i = 0; i < this.channels.length; i++) {
634
+ this.channels[i].scheduledNotes = [];
610
635
  this.resetAllStates(i);
611
636
  }
612
637
  resolve();
@@ -628,8 +653,9 @@ export class MidyGM2 {
628
653
  this.notePromises = [];
629
654
  this.exclusiveClassNotes.fill(undefined);
630
655
  this.drumExclusiveClassNotes.fill(undefined);
631
- this.audioBufferCache.clear();
656
+ this.voiceCache.clear();
632
657
  for (let i = 0; i < this.channels.length; i++) {
658
+ this.channels[i].scheduledNotes = [];
633
659
  this.resetAllStates(i);
634
660
  }
635
661
  this.isStopping = false;
@@ -662,11 +688,7 @@ export class MidyGM2 {
662
688
  secondToTicks(second, secondsPerBeat) {
663
689
  return second * this.ticksPerBeat / secondsPerBeat;
664
690
  }
665
- getAudioBufferId(programNumber, noteNumber, velocity) {
666
- return `${programNumber}:${noteNumber}:${velocity}`;
667
- }
668
691
  extractMidiData(midi) {
669
- this.audioBufferCounter.clear();
670
692
  const instruments = new Set();
671
693
  const timeline = [];
672
694
  const tmpChannels = new Array(this.channels.length);
@@ -687,8 +709,6 @@ export class MidyGM2 {
687
709
  switch (event.type) {
688
710
  case "noteOn": {
689
711
  const channel = tmpChannels[event.channel];
690
- const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
691
- this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
692
712
  if (channel.programNumber < 0) {
693
713
  channel.programNumber = event.programNumber;
694
714
  switch (channel.bankMSB) {
@@ -738,10 +758,6 @@ export class MidyGM2 {
738
758
  timeline.push(event);
739
759
  }
740
760
  }
741
- for (const [audioBufferId, count] of this.audioBufferCounter) {
742
- if (count === 1)
743
- this.audioBufferCounter.delete(audioBufferId);
744
- }
745
761
  const priority = {
746
762
  controller: 0,
747
763
  sysEx: 1,
@@ -781,12 +797,11 @@ export class MidyGM2 {
781
797
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
782
798
  const channel = this.channels[channelNumber];
783
799
  const promises = [];
784
- this.processScheduledNotes(channel, scheduleTime, (note) => {
800
+ this.processScheduledNotes(channel, (note) => {
785
801
  const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
786
802
  this.notePromises.push(promise);
787
803
  promises.push(promise);
788
804
  });
789
- channel.scheduledNotes = [];
790
805
  return Promise.all(promises);
791
806
  }
792
807
  stopNotes(velocity, force, scheduleTime) {
@@ -800,6 +815,8 @@ export class MidyGM2 {
800
815
  if (this.isPlaying || this.isPaused)
801
816
  return;
802
817
  this.resumeTime = 0;
818
+ if (this.voiceCounter.size === 0)
819
+ this.cacheVoiceIds();
803
820
  await this.playNotes();
804
821
  this.isPlaying = false;
805
822
  }
@@ -840,22 +857,20 @@ export class MidyGM2 {
840
857
  const now = this.audioContext.currentTime;
841
858
  return this.resumeTime + now - this.startTime - this.startDelay;
842
859
  }
843
- processScheduledNotes(channel, scheduleTime, callback) {
860
+ processScheduledNotes(channel, callback) {
844
861
  const scheduledNotes = channel.scheduledNotes;
845
- for (let i = 0; i < scheduledNotes.length; i++) {
862
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
846
863
  const note = scheduledNotes[i];
847
864
  if (!note)
848
865
  continue;
849
866
  if (note.ending)
850
867
  continue;
851
- if (note.startTime < scheduleTime)
852
- continue;
853
868
  callback(note);
854
869
  }
855
870
  }
856
871
  processActiveNotes(channel, scheduleTime, callback) {
857
872
  const scheduledNotes = channel.scheduledNotes;
858
- for (let i = 0; i < scheduledNotes.length; i++) {
873
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
859
874
  const note = scheduledNotes[i];
860
875
  if (!note)
861
876
  continue;
@@ -1046,7 +1061,7 @@ export class MidyGM2 {
1046
1061
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1047
1062
  }
1048
1063
  updateChannelDetune(channel, scheduleTime) {
1049
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1064
+ this.processScheduledNotes(channel, (note) => {
1050
1065
  this.updateDetune(channel, note, scheduleTime);
1051
1066
  });
1052
1067
  }
@@ -1267,31 +1282,31 @@ export class MidyGM2 {
1267
1282
  note.vibratoLFO.connect(note.vibratoDepth);
1268
1283
  note.vibratoDepth.connect(note.bufferSource.detune);
1269
1284
  }
1270
- async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1271
- const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1272
- const cache = this.audioBufferCache.get(audioBufferId);
1285
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
1286
+ const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
1287
+ const cache = this.voiceCache.get(audioBufferId);
1273
1288
  if (cache) {
1274
1289
  cache.counter += 1;
1275
1290
  if (cache.maxCount <= cache.counter) {
1276
- this.audioBufferCache.delete(audioBufferId);
1291
+ this.voiceCache.delete(audioBufferId);
1277
1292
  }
1278
1293
  return cache.audioBuffer;
1279
1294
  }
1280
1295
  else {
1281
- const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1282
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1296
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1297
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
1283
1298
  const cache = { audioBuffer, maxCount, counter: 1 };
1284
- this.audioBufferCache.set(audioBufferId, cache);
1299
+ this.voiceCache.set(audioBufferId, cache);
1285
1300
  return audioBuffer;
1286
1301
  }
1287
1302
  }
1288
- async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1303
+ async createNote(channel, voice, noteNumber, velocity, startTime) {
1289
1304
  const now = this.audioContext.currentTime;
1290
1305
  const state = channel.state;
1291
1306
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1292
1307
  const voiceParams = voice.getAllParams(controllerState);
1293
1308
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1294
- const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1309
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
1295
1310
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1296
1311
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1297
1312
  note.filterNode = new BiquadFilterNode(this.audioContext, {
@@ -1386,15 +1401,15 @@ export class MidyGM2 {
1386
1401
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1387
1402
  const channel = this.channels[channelNumber];
1388
1403
  const bankNumber = this.calcBank(channel, channelNumber);
1389
- const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1404
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
1405
+ .get(bankNumber);
1390
1406
  if (soundFontIndex === undefined)
1391
1407
  return;
1392
1408
  const soundFont = this.soundFonts[soundFontIndex];
1393
1409
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1394
1410
  if (!voice)
1395
1411
  return;
1396
- const isSF3 = soundFont.parsed.info.version.major === 3;
1397
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1412
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1398
1413
  if (channel.isDrum) {
1399
1414
  const audioContext = this.audioContext;
1400
1415
  const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
@@ -1476,15 +1491,29 @@ export class MidyGM2 {
1476
1491
  return;
1477
1492
  }
1478
1493
  }
1479
- const note = this.findNoteOffTarget(channel, noteNumber);
1480
- if (!note)
1494
+ const index = this.findNoteOffIndex(channel, noteNumber);
1495
+ if (index < 0)
1481
1496
  return;
1497
+ const note = channel.scheduledNotes[index];
1482
1498
  note.ending = true;
1499
+ this.setNoteIndex(channel, index);
1483
1500
  this.releaseNote(channel, note, endTime);
1484
1501
  }
1485
- findNoteOffTarget(channel, noteNumber) {
1502
+ setNoteIndex(channel, index) {
1503
+ let allEnds = true;
1504
+ for (let i = channel.scheduleIndex; i < index; i++) {
1505
+ const note = channel.scheduledNotes[i];
1506
+ if (note && !note.ending) {
1507
+ allEnds = false;
1508
+ break;
1509
+ }
1510
+ }
1511
+ if (allEnds)
1512
+ channel.scheduleIndex = index + 1;
1513
+ }
1514
+ findNoteOffIndex(channel, noteNumber) {
1486
1515
  const scheduledNotes = channel.scheduledNotes;
1487
- for (let i = 0; i < scheduledNotes.length; i++) {
1516
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
1488
1517
  const note = scheduledNotes[i];
1489
1518
  if (!note)
1490
1519
  continue;
@@ -1492,8 +1521,9 @@ export class MidyGM2 {
1492
1521
  continue;
1493
1522
  if (note.noteNumber !== noteNumber)
1494
1523
  continue;
1495
- return note;
1524
+ return i;
1496
1525
  }
1526
+ return -1;
1497
1527
  }
1498
1528
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1499
1529
  scheduleTime ??= this.audioContext.currentTime;
@@ -1533,18 +1563,18 @@ export class MidyGM2 {
1533
1563
  case 0x90:
1534
1564
  return this.noteOn(channelNumber, data1, data2, scheduleTime);
1535
1565
  case 0xB0:
1536
- return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
1566
+ return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1537
1567
  case 0xC0:
1538
- return this.handleProgramChange(channelNumber, data1, scheduleTime);
1568
+ return this.setProgramChange(channelNumber, data1, scheduleTime);
1539
1569
  case 0xD0:
1540
- return this.handleChannelPressure(channelNumber, data1, scheduleTime);
1570
+ return this.setChannelPressure(channelNumber, data1, scheduleTime);
1541
1571
  case 0xE0:
1542
1572
  return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1543
1573
  default:
1544
1574
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1545
1575
  }
1546
1576
  }
1547
- handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1577
+ setProgramChange(channelNumber, programNumber, _scheduleTime) {
1548
1578
  const channel = this.channels[channelNumber];
1549
1579
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1550
1580
  channel.programNumber = programNumber;
@@ -1559,7 +1589,7 @@ export class MidyGM2 {
1559
1589
  }
1560
1590
  }
1561
1591
  }
1562
- handleChannelPressure(channelNumber, value, scheduleTime) {
1592
+ setChannelPressure(channelNumber, value, scheduleTime) {
1563
1593
  const channel = this.channels[channelNumber];
1564
1594
  if (channel.isDrum)
1565
1595
  return;
@@ -1631,7 +1661,7 @@ export class MidyGM2 {
1631
1661
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1632
1662
  let value = note.voiceParams.reverbEffectsSend;
1633
1663
  if (channel.isDrum) {
1634
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1664
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1635
1665
  if (0 <= keyBasedValue) {
1636
1666
  value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1637
1667
  }
@@ -1661,13 +1691,13 @@ export class MidyGM2 {
1661
1691
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1662
1692
  let value = note.voiceParams.chorusEffectsSend;
1663
1693
  if (channel.isDrum) {
1664
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1694
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1665
1695
  if (0 <= keyBasedValue) {
1666
1696
  value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1667
1697
  }
1668
1698
  }
1669
1699
  if (0 < prevValue) {
1670
- if (0 < vaule) {
1700
+ if (0 < value) {
1671
1701
  note.chorusEffectsSend.gain
1672
1702
  .cancelScheduledValues(scheduleTime)
1673
1703
  .setValueAtTime(value, scheduleTime);
@@ -1766,7 +1796,7 @@ export class MidyGM2 {
1766
1796
  return state;
1767
1797
  }
1768
1798
  applyVoiceParams(channel, controllerType, scheduleTime) {
1769
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1799
+ this.processScheduledNotes(channel, (note) => {
1770
1800
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1771
1801
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1772
1802
  let applyVolumeEnvelope = false;
@@ -1827,7 +1857,7 @@ export class MidyGM2 {
1827
1857
  handlers[127] = this.polyOn;
1828
1858
  return handlers;
1829
1859
  }
1830
- handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1860
+ setControlChange(channelNumber, controllerType, value, scheduleTime) {
1831
1861
  const handler = this.controlChangeHandlers[controllerType];
1832
1862
  if (handler) {
1833
1863
  handler.call(this, channelNumber, value, scheduleTime);
@@ -1844,7 +1874,7 @@ export class MidyGM2 {
1844
1874
  }
1845
1875
  updateModulation(channel, scheduleTime) {
1846
1876
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1847
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1877
+ this.processScheduledNotes(channel, (note) => {
1848
1878
  if (note.modulationDepth) {
1849
1879
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1850
1880
  }
@@ -1863,7 +1893,7 @@ export class MidyGM2 {
1863
1893
  this.updateModulation(channel, scheduleTime);
1864
1894
  }
1865
1895
  updatePortamento(channel, scheduleTime) {
1866
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1896
+ this.processScheduledNotes(channel, (note) => {
1867
1897
  if (0.5 <= channel.state.portamento) {
1868
1898
  if (0 <= note.portamentoNoteNumber) {
1869
1899
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
@@ -1948,11 +1978,11 @@ export class MidyGM2 {
1948
1978
  continue;
1949
1979
  if (!gainR)
1950
1980
  continue;
1951
- const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
1981
+ const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
1952
1982
  const volume = (0 <= keyBasedVolume)
1953
1983
  ? defaultVolume * keyBasedVolume / 64
1954
1984
  : defaultVolume;
1955
- const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
1985
+ const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
1956
1986
  const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1957
1987
  const { gainLeft, gainRight } = this.panToGain(pan);
1958
1988
  gainL.gain
@@ -1970,7 +2000,7 @@ export class MidyGM2 {
1970
2000
  scheduleTime ??= this.audioContext.currentTime;
1971
2001
  channel.state.sustainPedal = value / 127;
1972
2002
  if (64 <= value) {
1973
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2003
+ this.processScheduledNotes(channel, (note) => {
1974
2004
  channel.sustainNotes.push(note);
1975
2005
  });
1976
2006
  }
@@ -2013,7 +2043,7 @@ export class MidyGM2 {
2013
2043
  const state = channel.state;
2014
2044
  scheduleTime ??= this.audioContext.currentTime;
2015
2045
  state.softPedal = softPedal / 127;
2016
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2046
+ this.processScheduledNotes(channel, (note) => {
2017
2047
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2018
2048
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2019
2049
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2037,7 +2067,7 @@ export class MidyGM2 {
2037
2067
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2038
2068
  }
2039
2069
  else {
2040
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2070
+ this.processScheduledNotes(channel, (note) => {
2041
2071
  if (note.voiceParams.reverbEffectsSend <= 0)
2042
2072
  return false;
2043
2073
  if (note.reverbEffectsSend)
@@ -2047,7 +2077,7 @@ export class MidyGM2 {
2047
2077
  }
2048
2078
  else {
2049
2079
  if (0 < reverbSendLevel) {
2050
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2080
+ this.processScheduledNotes(channel, (note) => {
2051
2081
  this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2052
2082
  });
2053
2083
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2070,7 +2100,7 @@ export class MidyGM2 {
2070
2100
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2071
2101
  }
2072
2102
  else {
2073
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2103
+ this.processScheduledNotes(channel, (note) => {
2074
2104
  if (note.voiceParams.chorusEffectsSend <= 0)
2075
2105
  return false;
2076
2106
  if (note.chorusEffectsSend)
@@ -2080,7 +2110,7 @@ export class MidyGM2 {
2080
2110
  }
2081
2111
  else {
2082
2112
  if (0 < chorusSendLevel) {
2083
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2113
+ this.processScheduledNotes(channel, (note) => {
2084
2114
  this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2085
2115
  });
2086
2116
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2224,7 +2254,7 @@ export class MidyGM2 {
2224
2254
  const entries = Object.entries(defaultControllerState);
2225
2255
  for (const [key, { type, defaultValue }] of entries) {
2226
2256
  if (128 <= type) {
2227
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2257
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2228
2258
  }
2229
2259
  else {
2230
2260
  state[key] = defaultValue;
@@ -2256,7 +2286,7 @@ export class MidyGM2 {
2256
2286
  const key = keys[i];
2257
2287
  const { type, defaultValue } = defaultControllerState[key];
2258
2288
  if (128 <= type) {
2259
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2289
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2260
2290
  }
2261
2291
  else {
2262
2292
  state[key] = defaultValue;
@@ -2691,27 +2721,29 @@ export class MidyGM2 {
2691
2721
  : 0;
2692
2722
  return channelPressure / 127;
2693
2723
  }
2694
- setControllerParameters(channel, note, table) {
2724
+ setControllerParameters(channel, note, table, scheduleTime) {
2695
2725
  if (0 <= table[0])
2696
- this.updateDetune(channel, note);
2726
+ this.updateDetune(channel, note, scueduleTime);
2697
2727
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2698
- if (0 <= table[1])
2699
- this.setPortamentoFilterEnvelope(channel, note);
2700
- if (0 <= table[2])
2701
- this.setPortamentoVolumeEnvelope(channel, note);
2728
+ if (0 <= table[1]) {
2729
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2730
+ }
2731
+ if (0 <= table[2]) {
2732
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2733
+ }
2702
2734
  }
2703
2735
  else {
2704
2736
  if (0 <= table[1])
2705
- this.setFilterEnvelope(channel, note);
2737
+ this.setFilterEnvelope(channel, note, scheduleTime);
2706
2738
  if (0 <= table[2])
2707
- this.setVolumeEnvelope(channel, note);
2739
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2708
2740
  }
2709
2741
  if (0 <= table[3])
2710
- this.setModLfoToPitch(channel, note);
2742
+ this.setModLfoToPitch(channel, note, scheduleTime);
2711
2743
  if (0 <= table[4])
2712
- this.setModLfoToFilterFc(channel, note);
2744
+ this.setModLfoToFilterFc(channel, note, scheduleTime);
2713
2745
  if (0 <= table[5])
2714
- this.setModLfoToVolume(channel, note);
2746
+ this.setModLfoToVolume(channel, note, scheduleTime);
2715
2747
  }
2716
2748
  handlePressureSysEx(data, tableName) {
2717
2749
  const channelNumber = data[4];
@@ -2734,8 +2766,8 @@ export class MidyGM2 {
2734
2766
  const slotSize = 6;
2735
2767
  const offset = controllerType * slotSize;
2736
2768
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2737
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2738
- this.setControllerParameters(channel, note, table);
2769
+ this.processScheduledNotes(channel, (note) => {
2770
+ this.setControllerParameters(channel, note, table, scheduleTime);
2739
2771
  });
2740
2772
  }
2741
2773
  handleControlChangeSysEx(data) {
@@ -2751,7 +2783,7 @@ export class MidyGM2 {
2751
2783
  table[pp] = rr;
2752
2784
  }
2753
2785
  }
2754
- getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2786
+ getKeyBasedValue(channel, keyNumber, controllerType) {
2755
2787
  const index = keyNumber * 128 + controllerType;
2756
2788
  const controlValue = channel.keyBasedInstrumentControlTable[index];
2757
2789
  return controlValue;
@@ -2769,7 +2801,7 @@ export class MidyGM2 {
2769
2801
  const index = keyNumber * 128 + controllerType;
2770
2802
  table[index] = value;
2771
2803
  }
2772
- this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2804
+ this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2773
2805
  }
2774
2806
  handleSysEx(data, scheduleTime) {
2775
2807
  switch (data[0]) {
@@ -2806,6 +2838,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2806
2838
  configurable: true,
2807
2839
  writable: true,
2808
2840
  value: {
2841
+ scheduleIndex: 0,
2809
2842
  detune: 0,
2810
2843
  programNumber: 0,
2811
2844
  bank: 121 * 128,