@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.
@@ -317,13 +317,13 @@ class MidyGM2 {
317
317
  writable: true,
318
318
  value: this.initSoundFontTable()
319
319
  });
320
- Object.defineProperty(this, "audioBufferCounter", {
320
+ Object.defineProperty(this, "voiceCounter", {
321
321
  enumerable: true,
322
322
  configurable: true,
323
323
  writable: true,
324
324
  value: new Map()
325
325
  });
326
- Object.defineProperty(this, "audioBufferCache", {
326
+ Object.defineProperty(this, "voiceCache", {
327
327
  enumerable: true,
328
328
  configurable: true,
329
329
  writable: true,
@@ -420,13 +420,11 @@ class MidyGM2 {
420
420
  const presetHeaders = soundFont.parsed.presetHeaders;
421
421
  for (let i = 0; i < presetHeaders.length; i++) {
422
422
  const presetHeader = presetHeaders[i];
423
- if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
424
- const banks = this.soundFontTable[presetHeader.preset];
425
- banks.set(presetHeader.bank, index);
426
- }
423
+ const banks = this.soundFontTable[presetHeader.preset];
424
+ banks.set(presetHeader.bank, index);
427
425
  }
428
426
  }
429
- async loadSoundFont(input) {
427
+ async toUint8Array(input) {
430
428
  let uint8Array;
431
429
  if (typeof input === "string") {
432
430
  const response = await fetch(input);
@@ -439,23 +437,32 @@ class MidyGM2 {
439
437
  else {
440
438
  throw new TypeError("input must be a URL string or Uint8Array");
441
439
  }
442
- const parsed = (0, soundfont_parser_1.parse)(uint8Array);
443
- const soundFont = new soundfont_parser_1.SoundFont(parsed);
444
- this.addSoundFont(soundFont);
440
+ return uint8Array;
445
441
  }
446
- async loadMIDI(input) {
447
- let uint8Array;
448
- if (typeof input === "string") {
449
- const response = await fetch(input);
450
- const arrayBuffer = await response.arrayBuffer();
451
- uint8Array = new Uint8Array(arrayBuffer);
452
- }
453
- else if (input instanceof Uint8Array) {
454
- uint8Array = input;
442
+ async loadSoundFont(input) {
443
+ this.voiceCounter.clear();
444
+ if (Array.isArray(input)) {
445
+ const promises = new Array(input.length);
446
+ for (let i = 0; i < input.length; i++) {
447
+ promises[i] = this.toUint8Array(input[i]);
448
+ }
449
+ const uint8Arrays = await Promise.all(promises);
450
+ for (let i = 0; i < uint8Arrays.length; i++) {
451
+ const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
452
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
453
+ this.addSoundFont(soundFont);
454
+ }
455
455
  }
456
456
  else {
457
- throw new TypeError("input must be a URL string or Uint8Array");
457
+ const uint8Array = await this.toUint8Array(input);
458
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
459
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
460
+ this.addSoundFont(soundFont);
458
461
  }
462
+ }
463
+ async loadMIDI(input) {
464
+ this.voiceCounter.clear();
465
+ const uint8Array = await this.toUint8Array(input);
459
466
  const midi = (0, midi_file_1.parseMidi)(uint8Array);
460
467
  this.ticksPerBeat = midi.header.ticksPerBeat;
461
468
  const midiData = this.extractMidiData(midi);
@@ -463,6 +470,45 @@ class MidyGM2 {
463
470
  this.timeline = midiData.timeline;
464
471
  this.totalTime = this.calcTotalTime();
465
472
  }
473
+ cacheVoiceIds() {
474
+ const timeline = this.timeline;
475
+ for (let i = 0; i < timeline.length; i++) {
476
+ const event = timeline[i];
477
+ switch (event.type) {
478
+ case "noteOn": {
479
+ const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
480
+ this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
481
+ break;
482
+ }
483
+ case "controller":
484
+ if (event.controllerType === 0) {
485
+ this.setBankMSB(event.channel, event.value);
486
+ }
487
+ else if (event.controllerType === 32) {
488
+ this.setBankLSB(event.channel, event.value);
489
+ }
490
+ break;
491
+ case "programChange":
492
+ this.setProgramChange(event.channel, event.programNumber, event.startTime);
493
+ }
494
+ }
495
+ for (const [audioBufferId, count] of this.voiceCounter) {
496
+ if (count === 1)
497
+ this.voiceCounter.delete(audioBufferId);
498
+ }
499
+ this.GM2SystemOn();
500
+ }
501
+ getVoiceId(channel, noteNumber, velocity) {
502
+ const bankNumber = this.calcBank(channel);
503
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
504
+ .get(bankNumber);
505
+ if (soundFontIndex === undefined)
506
+ return;
507
+ const soundFont = this.soundFonts[soundFontIndex];
508
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
509
+ const { instrument, sampleID } = voice.generators;
510
+ return `${soundFontIndex}:${instrument}:${sampleID}`;
511
+ }
466
512
  createChannelAudioNodes(audioContext) {
467
513
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
468
514
  const gainL = new GainNode(audioContext, { gain: gainLeft });
@@ -504,34 +550,12 @@ class MidyGM2 {
504
550
  });
505
551
  return channels;
506
552
  }
507
- async createNoteBuffer(voiceParams, isSF3) {
553
+ async createAudioBuffer(voiceParams) {
554
+ const sample = voiceParams.sample;
508
555
  const sampleStart = voiceParams.start;
509
- const sampleEnd = voiceParams.sample.length + voiceParams.end;
510
- if (isSF3) {
511
- const sample = voiceParams.sample;
512
- const start = sample.byteOffset + sampleStart;
513
- const end = sample.byteOffset + sampleEnd;
514
- const buffer = sample.buffer.slice(start, end);
515
- const audioBuffer = await this.audioContext.decodeAudioData(buffer);
516
- return audioBuffer;
517
- }
518
- else {
519
- const sample = voiceParams.sample;
520
- const start = sample.byteOffset + sampleStart;
521
- const end = sample.byteOffset + sampleEnd;
522
- const buffer = sample.buffer.slice(start, end);
523
- const audioBuffer = new AudioBuffer({
524
- numberOfChannels: 1,
525
- length: sample.length,
526
- sampleRate: voiceParams.sampleRate,
527
- });
528
- const channelData = audioBuffer.getChannelData(0);
529
- const int16Array = new Int16Array(buffer);
530
- for (let i = 0; i < int16Array.length; i++) {
531
- channelData[i] = int16Array[i] / 32768;
532
- }
533
- return audioBuffer;
534
- }
556
+ const sampleEnd = sample.data.length + voiceParams.end;
557
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
558
+ return audioBuffer;
535
559
  }
536
560
  isLoopDrum(channel, noteNumber) {
537
561
  const programNumber = channel.programNumber;
@@ -568,13 +592,13 @@ class MidyGM2 {
568
592
  break;
569
593
  }
570
594
  case "controller":
571
- this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
595
+ this.setControlChange(event.channel, event.controllerType, event.value, startTime);
572
596
  break;
573
597
  case "programChange":
574
- this.handleProgramChange(event.channel, event.programNumber, startTime);
598
+ this.setProgramChange(event.channel, event.programNumber, startTime);
575
599
  break;
576
600
  case "channelAftertouch":
577
- this.handleChannelPressure(event.channel, event.amount, startTime);
601
+ this.setChannelPressure(event.channel, event.amount, startTime);
578
602
  break;
579
603
  case "pitchBend":
580
604
  this.setPitchBend(event.channel, event.value + 8192, startTime);
@@ -608,8 +632,9 @@ class MidyGM2 {
608
632
  this.notePromises = [];
609
633
  this.exclusiveClassNotes.fill(undefined);
610
634
  this.drumExclusiveClassNotes.fill(undefined);
611
- this.audioBufferCache.clear();
635
+ this.voiceCache.clear();
612
636
  for (let i = 0; i < this.channels.length; i++) {
637
+ this.channels[i].scheduledNotes = [];
613
638
  this.resetAllStates(i);
614
639
  }
615
640
  resolve();
@@ -631,8 +656,9 @@ class MidyGM2 {
631
656
  this.notePromises = [];
632
657
  this.exclusiveClassNotes.fill(undefined);
633
658
  this.drumExclusiveClassNotes.fill(undefined);
634
- this.audioBufferCache.clear();
659
+ this.voiceCache.clear();
635
660
  for (let i = 0; i < this.channels.length; i++) {
661
+ this.channels[i].scheduledNotes = [];
636
662
  this.resetAllStates(i);
637
663
  }
638
664
  this.isStopping = false;
@@ -665,11 +691,7 @@ class MidyGM2 {
665
691
  secondToTicks(second, secondsPerBeat) {
666
692
  return second * this.ticksPerBeat / secondsPerBeat;
667
693
  }
668
- getAudioBufferId(programNumber, noteNumber, velocity) {
669
- return `${programNumber}:${noteNumber}:${velocity}`;
670
- }
671
694
  extractMidiData(midi) {
672
- this.audioBufferCounter.clear();
673
695
  const instruments = new Set();
674
696
  const timeline = [];
675
697
  const tmpChannels = new Array(this.channels.length);
@@ -690,8 +712,6 @@ class MidyGM2 {
690
712
  switch (event.type) {
691
713
  case "noteOn": {
692
714
  const channel = tmpChannels[event.channel];
693
- const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
694
- this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
695
715
  if (channel.programNumber < 0) {
696
716
  channel.programNumber = event.programNumber;
697
717
  switch (channel.bankMSB) {
@@ -741,10 +761,6 @@ class MidyGM2 {
741
761
  timeline.push(event);
742
762
  }
743
763
  }
744
- for (const [audioBufferId, count] of this.audioBufferCounter) {
745
- if (count === 1)
746
- this.audioBufferCounter.delete(audioBufferId);
747
- }
748
764
  const priority = {
749
765
  controller: 0,
750
766
  sysEx: 1,
@@ -784,12 +800,11 @@ class MidyGM2 {
784
800
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
785
801
  const channel = this.channels[channelNumber];
786
802
  const promises = [];
787
- this.processScheduledNotes(channel, scheduleTime, (note) => {
803
+ this.processScheduledNotes(channel, (note) => {
788
804
  const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
789
805
  this.notePromises.push(promise);
790
806
  promises.push(promise);
791
807
  });
792
- channel.scheduledNotes = [];
793
808
  return Promise.all(promises);
794
809
  }
795
810
  stopNotes(velocity, force, scheduleTime) {
@@ -803,6 +818,8 @@ class MidyGM2 {
803
818
  if (this.isPlaying || this.isPaused)
804
819
  return;
805
820
  this.resumeTime = 0;
821
+ if (this.voiceCounter.size === 0)
822
+ this.cacheVoiceIds();
806
823
  await this.playNotes();
807
824
  this.isPlaying = false;
808
825
  }
@@ -843,22 +860,20 @@ class MidyGM2 {
843
860
  const now = this.audioContext.currentTime;
844
861
  return this.resumeTime + now - this.startTime - this.startDelay;
845
862
  }
846
- processScheduledNotes(channel, scheduleTime, callback) {
863
+ processScheduledNotes(channel, callback) {
847
864
  const scheduledNotes = channel.scheduledNotes;
848
- for (let i = 0; i < scheduledNotes.length; i++) {
865
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
849
866
  const note = scheduledNotes[i];
850
867
  if (!note)
851
868
  continue;
852
869
  if (note.ending)
853
870
  continue;
854
- if (note.startTime < scheduleTime)
855
- continue;
856
871
  callback(note);
857
872
  }
858
873
  }
859
874
  processActiveNotes(channel, scheduleTime, callback) {
860
875
  const scheduledNotes = channel.scheduledNotes;
861
- for (let i = 0; i < scheduledNotes.length; i++) {
876
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
862
877
  const note = scheduledNotes[i];
863
878
  if (!note)
864
879
  continue;
@@ -1049,7 +1064,7 @@ class MidyGM2 {
1049
1064
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1050
1065
  }
1051
1066
  updateChannelDetune(channel, scheduleTime) {
1052
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1067
+ this.processScheduledNotes(channel, (note) => {
1053
1068
  this.updateDetune(channel, note, scheduleTime);
1054
1069
  });
1055
1070
  }
@@ -1270,31 +1285,31 @@ class MidyGM2 {
1270
1285
  note.vibratoLFO.connect(note.vibratoDepth);
1271
1286
  note.vibratoDepth.connect(note.bufferSource.detune);
1272
1287
  }
1273
- async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1274
- const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1275
- const cache = this.audioBufferCache.get(audioBufferId);
1288
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
1289
+ const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
1290
+ const cache = this.voiceCache.get(audioBufferId);
1276
1291
  if (cache) {
1277
1292
  cache.counter += 1;
1278
1293
  if (cache.maxCount <= cache.counter) {
1279
- this.audioBufferCache.delete(audioBufferId);
1294
+ this.voiceCache.delete(audioBufferId);
1280
1295
  }
1281
1296
  return cache.audioBuffer;
1282
1297
  }
1283
1298
  else {
1284
- const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1285
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1299
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1300
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
1286
1301
  const cache = { audioBuffer, maxCount, counter: 1 };
1287
- this.audioBufferCache.set(audioBufferId, cache);
1302
+ this.voiceCache.set(audioBufferId, cache);
1288
1303
  return audioBuffer;
1289
1304
  }
1290
1305
  }
1291
- async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1306
+ async createNote(channel, voice, noteNumber, velocity, startTime) {
1292
1307
  const now = this.audioContext.currentTime;
1293
1308
  const state = channel.state;
1294
1309
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1295
1310
  const voiceParams = voice.getAllParams(controllerState);
1296
1311
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1297
- const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1312
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
1298
1313
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1299
1314
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1300
1315
  note.filterNode = new BiquadFilterNode(this.audioContext, {
@@ -1389,15 +1404,15 @@ class MidyGM2 {
1389
1404
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1390
1405
  const channel = this.channels[channelNumber];
1391
1406
  const bankNumber = this.calcBank(channel, channelNumber);
1392
- const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1407
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
1408
+ .get(bankNumber);
1393
1409
  if (soundFontIndex === undefined)
1394
1410
  return;
1395
1411
  const soundFont = this.soundFonts[soundFontIndex];
1396
1412
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1397
1413
  if (!voice)
1398
1414
  return;
1399
- const isSF3 = soundFont.parsed.info.version.major === 3;
1400
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1415
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1401
1416
  if (channel.isDrum) {
1402
1417
  const audioContext = this.audioContext;
1403
1418
  const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
@@ -1479,15 +1494,29 @@ class MidyGM2 {
1479
1494
  return;
1480
1495
  }
1481
1496
  }
1482
- const note = this.findNoteOffTarget(channel, noteNumber);
1483
- if (!note)
1497
+ const index = this.findNoteOffIndex(channel, noteNumber);
1498
+ if (index < 0)
1484
1499
  return;
1500
+ const note = channel.scheduledNotes[index];
1485
1501
  note.ending = true;
1502
+ this.setNoteIndex(channel, index);
1486
1503
  this.releaseNote(channel, note, endTime);
1487
1504
  }
1488
- findNoteOffTarget(channel, noteNumber) {
1505
+ setNoteIndex(channel, index) {
1506
+ let allEnds = true;
1507
+ for (let i = channel.scheduleIndex; i < index; i++) {
1508
+ const note = channel.scheduledNotes[i];
1509
+ if (note && !note.ending) {
1510
+ allEnds = false;
1511
+ break;
1512
+ }
1513
+ }
1514
+ if (allEnds)
1515
+ channel.scheduleIndex = index + 1;
1516
+ }
1517
+ findNoteOffIndex(channel, noteNumber) {
1489
1518
  const scheduledNotes = channel.scheduledNotes;
1490
- for (let i = 0; i < scheduledNotes.length; i++) {
1519
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
1491
1520
  const note = scheduledNotes[i];
1492
1521
  if (!note)
1493
1522
  continue;
@@ -1495,8 +1524,9 @@ class MidyGM2 {
1495
1524
  continue;
1496
1525
  if (note.noteNumber !== noteNumber)
1497
1526
  continue;
1498
- return note;
1527
+ return i;
1499
1528
  }
1529
+ return -1;
1500
1530
  }
1501
1531
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1502
1532
  scheduleTime ??= this.audioContext.currentTime;
@@ -1536,18 +1566,18 @@ class MidyGM2 {
1536
1566
  case 0x90:
1537
1567
  return this.noteOn(channelNumber, data1, data2, scheduleTime);
1538
1568
  case 0xB0:
1539
- return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
1569
+ return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1540
1570
  case 0xC0:
1541
- return this.handleProgramChange(channelNumber, data1, scheduleTime);
1571
+ return this.setProgramChange(channelNumber, data1, scheduleTime);
1542
1572
  case 0xD0:
1543
- return this.handleChannelPressure(channelNumber, data1, scheduleTime);
1573
+ return this.setChannelPressure(channelNumber, data1, scheduleTime);
1544
1574
  case 0xE0:
1545
1575
  return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1546
1576
  default:
1547
1577
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1548
1578
  }
1549
1579
  }
1550
- handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1580
+ setProgramChange(channelNumber, programNumber, _scheduleTime) {
1551
1581
  const channel = this.channels[channelNumber];
1552
1582
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1553
1583
  channel.programNumber = programNumber;
@@ -1562,7 +1592,7 @@ class MidyGM2 {
1562
1592
  }
1563
1593
  }
1564
1594
  }
1565
- handleChannelPressure(channelNumber, value, scheduleTime) {
1595
+ setChannelPressure(channelNumber, value, scheduleTime) {
1566
1596
  const channel = this.channels[channelNumber];
1567
1597
  if (channel.isDrum)
1568
1598
  return;
@@ -1634,7 +1664,7 @@ class MidyGM2 {
1634
1664
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1635
1665
  let value = note.voiceParams.reverbEffectsSend;
1636
1666
  if (channel.isDrum) {
1637
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1667
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1638
1668
  if (0 <= keyBasedValue) {
1639
1669
  value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1640
1670
  }
@@ -1664,13 +1694,13 @@ class MidyGM2 {
1664
1694
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1665
1695
  let value = note.voiceParams.chorusEffectsSend;
1666
1696
  if (channel.isDrum) {
1667
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1697
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1668
1698
  if (0 <= keyBasedValue) {
1669
1699
  value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1670
1700
  }
1671
1701
  }
1672
1702
  if (0 < prevValue) {
1673
- if (0 < vaule) {
1703
+ if (0 < value) {
1674
1704
  note.chorusEffectsSend.gain
1675
1705
  .cancelScheduledValues(scheduleTime)
1676
1706
  .setValueAtTime(value, scheduleTime);
@@ -1769,7 +1799,7 @@ class MidyGM2 {
1769
1799
  return state;
1770
1800
  }
1771
1801
  applyVoiceParams(channel, controllerType, scheduleTime) {
1772
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1802
+ this.processScheduledNotes(channel, (note) => {
1773
1803
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1774
1804
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1775
1805
  let applyVolumeEnvelope = false;
@@ -1830,7 +1860,7 @@ class MidyGM2 {
1830
1860
  handlers[127] = this.polyOn;
1831
1861
  return handlers;
1832
1862
  }
1833
- handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1863
+ setControlChange(channelNumber, controllerType, value, scheduleTime) {
1834
1864
  const handler = this.controlChangeHandlers[controllerType];
1835
1865
  if (handler) {
1836
1866
  handler.call(this, channelNumber, value, scheduleTime);
@@ -1847,7 +1877,7 @@ class MidyGM2 {
1847
1877
  }
1848
1878
  updateModulation(channel, scheduleTime) {
1849
1879
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1850
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1880
+ this.processScheduledNotes(channel, (note) => {
1851
1881
  if (note.modulationDepth) {
1852
1882
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1853
1883
  }
@@ -1866,7 +1896,7 @@ class MidyGM2 {
1866
1896
  this.updateModulation(channel, scheduleTime);
1867
1897
  }
1868
1898
  updatePortamento(channel, scheduleTime) {
1869
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1899
+ this.processScheduledNotes(channel, (note) => {
1870
1900
  if (0.5 <= channel.state.portamento) {
1871
1901
  if (0 <= note.portamentoNoteNumber) {
1872
1902
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
@@ -1951,11 +1981,11 @@ class MidyGM2 {
1951
1981
  continue;
1952
1982
  if (!gainR)
1953
1983
  continue;
1954
- const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
1984
+ const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
1955
1985
  const volume = (0 <= keyBasedVolume)
1956
1986
  ? defaultVolume * keyBasedVolume / 64
1957
1987
  : defaultVolume;
1958
- const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
1988
+ const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
1959
1989
  const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1960
1990
  const { gainLeft, gainRight } = this.panToGain(pan);
1961
1991
  gainL.gain
@@ -1973,7 +2003,7 @@ class MidyGM2 {
1973
2003
  scheduleTime ??= this.audioContext.currentTime;
1974
2004
  channel.state.sustainPedal = value / 127;
1975
2005
  if (64 <= value) {
1976
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2006
+ this.processScheduledNotes(channel, (note) => {
1977
2007
  channel.sustainNotes.push(note);
1978
2008
  });
1979
2009
  }
@@ -2016,7 +2046,7 @@ class MidyGM2 {
2016
2046
  const state = channel.state;
2017
2047
  scheduleTime ??= this.audioContext.currentTime;
2018
2048
  state.softPedal = softPedal / 127;
2019
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2049
+ this.processScheduledNotes(channel, (note) => {
2020
2050
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2021
2051
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2022
2052
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2040,7 +2070,7 @@ class MidyGM2 {
2040
2070
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2041
2071
  }
2042
2072
  else {
2043
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2073
+ this.processScheduledNotes(channel, (note) => {
2044
2074
  if (note.voiceParams.reverbEffectsSend <= 0)
2045
2075
  return false;
2046
2076
  if (note.reverbEffectsSend)
@@ -2050,7 +2080,7 @@ class MidyGM2 {
2050
2080
  }
2051
2081
  else {
2052
2082
  if (0 < reverbSendLevel) {
2053
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2083
+ this.processScheduledNotes(channel, (note) => {
2054
2084
  this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2055
2085
  });
2056
2086
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2073,7 +2103,7 @@ class MidyGM2 {
2073
2103
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2074
2104
  }
2075
2105
  else {
2076
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2106
+ this.processScheduledNotes(channel, (note) => {
2077
2107
  if (note.voiceParams.chorusEffectsSend <= 0)
2078
2108
  return false;
2079
2109
  if (note.chorusEffectsSend)
@@ -2083,7 +2113,7 @@ class MidyGM2 {
2083
2113
  }
2084
2114
  else {
2085
2115
  if (0 < chorusSendLevel) {
2086
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2116
+ this.processScheduledNotes(channel, (note) => {
2087
2117
  this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2088
2118
  });
2089
2119
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2227,7 +2257,7 @@ class MidyGM2 {
2227
2257
  const entries = Object.entries(defaultControllerState);
2228
2258
  for (const [key, { type, defaultValue }] of entries) {
2229
2259
  if (128 <= type) {
2230
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2260
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2231
2261
  }
2232
2262
  else {
2233
2263
  state[key] = defaultValue;
@@ -2259,7 +2289,7 @@ class MidyGM2 {
2259
2289
  const key = keys[i];
2260
2290
  const { type, defaultValue } = defaultControllerState[key];
2261
2291
  if (128 <= type) {
2262
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2292
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2263
2293
  }
2264
2294
  else {
2265
2295
  state[key] = defaultValue;
@@ -2694,27 +2724,29 @@ class MidyGM2 {
2694
2724
  : 0;
2695
2725
  return channelPressure / 127;
2696
2726
  }
2697
- setControllerParameters(channel, note, table) {
2727
+ setControllerParameters(channel, note, table, scheduleTime) {
2698
2728
  if (0 <= table[0])
2699
- this.updateDetune(channel, note);
2729
+ this.updateDetune(channel, note, scueduleTime);
2700
2730
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2701
- if (0 <= table[1])
2702
- this.setPortamentoFilterEnvelope(channel, note);
2703
- if (0 <= table[2])
2704
- this.setPortamentoVolumeEnvelope(channel, note);
2731
+ if (0 <= table[1]) {
2732
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2733
+ }
2734
+ if (0 <= table[2]) {
2735
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2736
+ }
2705
2737
  }
2706
2738
  else {
2707
2739
  if (0 <= table[1])
2708
- this.setFilterEnvelope(channel, note);
2740
+ this.setFilterEnvelope(channel, note, scheduleTime);
2709
2741
  if (0 <= table[2])
2710
- this.setVolumeEnvelope(channel, note);
2742
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2711
2743
  }
2712
2744
  if (0 <= table[3])
2713
- this.setModLfoToPitch(channel, note);
2745
+ this.setModLfoToPitch(channel, note, scheduleTime);
2714
2746
  if (0 <= table[4])
2715
- this.setModLfoToFilterFc(channel, note);
2747
+ this.setModLfoToFilterFc(channel, note, scheduleTime);
2716
2748
  if (0 <= table[5])
2717
- this.setModLfoToVolume(channel, note);
2749
+ this.setModLfoToVolume(channel, note, scheduleTime);
2718
2750
  }
2719
2751
  handlePressureSysEx(data, tableName) {
2720
2752
  const channelNumber = data[4];
@@ -2737,8 +2769,8 @@ class MidyGM2 {
2737
2769
  const slotSize = 6;
2738
2770
  const offset = controllerType * slotSize;
2739
2771
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2740
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2741
- this.setControllerParameters(channel, note, table);
2772
+ this.processScheduledNotes(channel, (note) => {
2773
+ this.setControllerParameters(channel, note, table, scheduleTime);
2742
2774
  });
2743
2775
  }
2744
2776
  handleControlChangeSysEx(data) {
@@ -2754,7 +2786,7 @@ class MidyGM2 {
2754
2786
  table[pp] = rr;
2755
2787
  }
2756
2788
  }
2757
- getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2789
+ getKeyBasedValue(channel, keyNumber, controllerType) {
2758
2790
  const index = keyNumber * 128 + controllerType;
2759
2791
  const controlValue = channel.keyBasedInstrumentControlTable[index];
2760
2792
  return controlValue;
@@ -2772,7 +2804,7 @@ class MidyGM2 {
2772
2804
  const index = keyNumber * 128 + controllerType;
2773
2805
  table[index] = value;
2774
2806
  }
2775
- this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2807
+ this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2776
2808
  }
2777
2809
  handleSysEx(data, scheduleTime) {
2778
2810
  switch (data[0]) {
@@ -2810,6 +2842,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2810
2842
  configurable: true,
2811
2843
  writable: true,
2812
2844
  value: {
2845
+ scheduleIndex: 0,
2813
2846
  detune: 0,
2814
2847
  programNumber: 0,
2815
2848
  bank: 121 * 128,