@marmooo/midy 0.4.6 → 0.4.7

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.
@@ -3,6 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM2 = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
+ const ogg_vorbis_1 = require("@wasm-audio-decoders/ogg-vorbis");
7
+ let decoderPromise = null;
8
+ let decoderQueue = Promise.resolve();
9
+ function initDecoder() {
10
+ if (!decoderPromise) {
11
+ const instance = new ogg_vorbis_1.OggVorbisDecoderWebWorker();
12
+ decoderPromise = instance.ready.then(() => instance);
13
+ }
14
+ return decoderPromise;
15
+ }
6
16
  class Note {
7
17
  constructor(noteNumber, velocity, startTime) {
8
18
  Object.defineProperty(this, "voice", {
@@ -41,49 +51,49 @@ class Note {
41
51
  writable: true,
42
52
  value: void 0
43
53
  });
44
- Object.defineProperty(this, "filterNode", {
54
+ Object.defineProperty(this, "filterEnvelopeNode", {
45
55
  enumerable: true,
46
56
  configurable: true,
47
57
  writable: true,
48
58
  value: void 0
49
59
  });
50
- Object.defineProperty(this, "filterDepth", {
60
+ Object.defineProperty(this, "volumeEnvelopeNode", {
51
61
  enumerable: true,
52
62
  configurable: true,
53
63
  writable: true,
54
64
  value: void 0
55
65
  });
56
- Object.defineProperty(this, "volumeEnvelopeNode", {
66
+ Object.defineProperty(this, "modLfo", {
57
67
  enumerable: true,
58
68
  configurable: true,
59
69
  writable: true,
60
70
  value: void 0
61
- });
62
- Object.defineProperty(this, "volumeDepth", {
71
+ }); // CC#1 modulation LFO
72
+ Object.defineProperty(this, "modLfoToPitch", {
63
73
  enumerable: true,
64
74
  configurable: true,
65
75
  writable: true,
66
76
  value: void 0
67
77
  });
68
- Object.defineProperty(this, "modulationLFO", {
78
+ Object.defineProperty(this, "modLfoToFilterFc", {
69
79
  enumerable: true,
70
80
  configurable: true,
71
81
  writable: true,
72
82
  value: void 0
73
83
  });
74
- Object.defineProperty(this, "modulationDepth", {
84
+ Object.defineProperty(this, "modLfoToVolume", {
75
85
  enumerable: true,
76
86
  configurable: true,
77
87
  writable: true,
78
88
  value: void 0
79
89
  });
80
- Object.defineProperty(this, "vibratoLFO", {
90
+ Object.defineProperty(this, "vibLfo", {
81
91
  enumerable: true,
82
92
  configurable: true,
83
93
  writable: true,
84
94
  value: void 0
85
- });
86
- Object.defineProperty(this, "vibratoDepth", {
95
+ }); // vibrato LFO
96
+ Object.defineProperty(this, "vibLfoToPitch", {
87
97
  enumerable: true,
88
98
  configurable: true,
89
99
  writable: true,
@@ -234,7 +244,20 @@ const pitchEnvelopeKeys = [
234
244
  "playbackRate",
235
245
  ];
236
246
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
247
+ const effectParameters = [
248
+ 2400 / 64, // cent
249
+ 9600 / 64, // cent
250
+ 1 / 64,
251
+ 600 / 127, // cent
252
+ 2400 / 127, // cent
253
+ 1 / 127,
254
+ ];
255
+ const pressureBaselines = new Int8Array([64, 64, 0, 0, 0, 0]);
237
256
  const defaultPressureValues = new Int8Array([64, 64, 64, 0, 0, 0]);
257
+ const defaultControlValues = new Int8Array([
258
+ ...[-1, -1, -1, -1, -1, -1],
259
+ ...defaultPressureValues,
260
+ ]);
238
261
  function cbToRatio(cb) {
239
262
  return Math.pow(10, cb / 200);
240
263
  }
@@ -383,6 +406,12 @@ class MidyGM2 extends EventTarget {
383
406
  writable: true,
384
407
  value: new Map()
385
408
  });
409
+ Object.defineProperty(this, "decodeMethod", {
410
+ enumerable: true,
411
+ configurable: true,
412
+ writable: true,
413
+ value: "wasm-audio-decoders"
414
+ });
386
415
  Object.defineProperty(this, "isPlaying", {
387
416
  enumerable: true,
388
417
  configurable: true,
@@ -480,6 +509,7 @@ class MidyGM2 extends EventTarget {
480
509
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
481
510
  this.controlChangeHandlers = this.createControlChangeHandlers();
482
511
  this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
512
+ this.effectHandlers = this.createEffectHandlers();
483
513
  this.channels = this.createChannels(audioContext);
484
514
  this.reverbEffect = this.createReverbEffect(audioContext);
485
515
  this.chorusEffect = this.createChorusEffect(audioContext);
@@ -607,7 +637,7 @@ class MidyGM2 extends EventTarget {
607
637
  };
608
638
  }
609
639
  resetChannelTable(channel) {
610
- channel.controlTable.fill(-1);
640
+ channel.controlTable.set(defaultControlValues);
611
641
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
612
642
  channel.channelPressureTable.set(defaultPressureValues);
613
643
  channel.keyBasedTable.fill(-1);
@@ -623,7 +653,7 @@ class MidyGM2 extends EventTarget {
623
653
  scheduledNotes: [],
624
654
  sustainNotes: [],
625
655
  sostenutoNotes: [],
626
- controlTable: this.initControlTable(),
656
+ controlTable: new Int8Array(defaultControlValues),
627
657
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
628
658
  channelPressureTable: new Int8Array(defaultPressureValues),
629
659
  keyBasedTable: new Int8Array(128 * 128).fill(-1),
@@ -633,11 +663,57 @@ class MidyGM2 extends EventTarget {
633
663
  });
634
664
  return channels;
635
665
  }
666
+ decodeOggVorbis(sample) {
667
+ const task = decoderQueue.then(async () => {
668
+ const decoder = await initDecoder();
669
+ const slice = sample.data.slice();
670
+ const { channelData, sampleRate, errors } = await decoder.decodeFile(slice);
671
+ if (0 < errors.length) {
672
+ throw new Error(errors.join(", "));
673
+ }
674
+ const audioBuffer = new AudioBuffer({
675
+ numberOfChannels: channelData.length,
676
+ length: channelData[0].length,
677
+ sampleRate,
678
+ });
679
+ for (let ch = 0; ch < channelData.length; ch++) {
680
+ audioBuffer.getChannelData(ch).set(channelData[ch]);
681
+ }
682
+ return audioBuffer;
683
+ });
684
+ decoderQueue = task.catch(() => { });
685
+ return task;
686
+ }
636
687
  async createAudioBuffer(voiceParams) {
637
- const { sample, start, end } = voiceParams;
638
- const sampleEnd = sample.data.length + end;
639
- const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
640
- return audioBuffer;
688
+ const sample = voiceParams.sample;
689
+ if (sample.type === "compressed") {
690
+ switch (this.decodeMethod) {
691
+ case "decodeAudioData": {
692
+ // https://jakearchibald.com/2016/sounds-fun/
693
+ // https://github.com/WebAudio/web-audio-api/issues/1091
694
+ // decodeAudioData() has priming issues on Safari
695
+ const arrayBuffer = sample.data.slice().buffer;
696
+ return await this.audioContext.decodeAudioData(arrayBuffer);
697
+ }
698
+ case "wasm-audio-decoders":
699
+ return await this.decodeOggVorbis(sample);
700
+ default:
701
+ throw new Error(`Unknown decodeMethod: ${this.decodeMethod}`);
702
+ }
703
+ }
704
+ else {
705
+ const data = sample.data;
706
+ const end = data.length + voiceParams.end;
707
+ const subarray = data.subarray(voiceParams.start, end);
708
+ const pcm = sample.decodePCM(subarray);
709
+ const audioBuffer = new AudioBuffer({
710
+ numberOfChannels: 1,
711
+ length: pcm.length,
712
+ sampleRate: sample.sampleHeader.sampleRate,
713
+ });
714
+ audioBuffer.getChannelData(0).set(pcm);
715
+ return audioBuffer;
716
+ }
641
717
  }
642
718
  isLoopDrum(channel, noteNumber) {
643
719
  const programNumber = channel.programNumber;
@@ -711,7 +787,7 @@ class MidyGM2 extends EventTarget {
711
787
  this.voiceCache.clear();
712
788
  this.realtimeVoiceCache.clear();
713
789
  const channels = this.channels;
714
- for (let ch = 0; i < channels.length; ch++) {
790
+ for (let ch = 0; ch < channels.length; ch++) {
715
791
  channels[ch].scheduledNotes = [];
716
792
  this.resetChannelStates(ch);
717
793
  }
@@ -1201,16 +1277,8 @@ class MidyGM2 extends EventTarget {
1201
1277
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
1202
1278
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
1203
1279
  const pitch = pitchWheel * pitchWheelSensitivity;
1204
- const channelPressureRaw = channel.channelPressureTable[0];
1205
- if (0 <= channelPressureRaw) {
1206
- const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1207
- const channelPressure = channelPressureDepth *
1208
- channel.state.channelPressure;
1209
- return tuning + pitch + channelPressure;
1210
- }
1211
- else {
1212
- return tuning + pitch;
1213
- }
1280
+ const effect = this.getChannelPitchControl(channel);
1281
+ return tuning + pitch + effect;
1214
1282
  }
1215
1283
  updateChannelDetune(channel, scheduleTime) {
1216
1284
  this.processScheduledNotes(channel, (note) => {
@@ -1228,8 +1296,7 @@ class MidyGM2 extends EventTarget {
1228
1296
  calcNoteDetune(channel, note) {
1229
1297
  const noteDetune = note.voiceParams.detune +
1230
1298
  this.calcScaleOctaveTuning(channel, note);
1231
- const pitchControl = this.getPitchControl(channel, note);
1232
- return channel.detune + noteDetune + pitchControl;
1299
+ return channel.detune + noteDetune;
1233
1300
  }
1234
1301
  getPortamentoTime(channel, note) {
1235
1302
  const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
@@ -1386,7 +1453,7 @@ class MidyGM2 extends EventTarget {
1386
1453
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1387
1454
  const modDelay = startTime + voiceParams.modDelay;
1388
1455
  note.adjustedBaseFreq = adjustedSustainFreq;
1389
- note.filterNode.frequency
1456
+ note.filterEnvelopeNode.frequency
1390
1457
  .cancelScheduledValues(scheduleTime)
1391
1458
  .setValueAtTime(adjustedBaseFreq, startTime)
1392
1459
  .setValueAtTime(adjustedBaseFreq, modDelay)
@@ -1412,7 +1479,7 @@ class MidyGM2 extends EventTarget {
1412
1479
  const modHold = modAttack + voiceParams.modHold;
1413
1480
  const decayDuration = voiceParams.modDecay;
1414
1481
  note.adjustedBaseFreq = adjustedBaseFreq;
1415
- note.filterNode.frequency
1482
+ note.filterEnvelopeNode.frequency
1416
1483
  .cancelScheduledValues(scheduleTime)
1417
1484
  .setValueAtTime(adjustedBaseFreq, startTime)
1418
1485
  .setValueAtTime(adjustedBaseFreq, modDelay)
@@ -1423,37 +1490,37 @@ class MidyGM2 extends EventTarget {
1423
1490
  startModulation(channel, note, scheduleTime) {
1424
1491
  const audioContext = this.audioContext;
1425
1492
  const { voiceParams } = note;
1426
- note.modulationLFO = new OscillatorNode(audioContext, {
1493
+ note.modLfo = new OscillatorNode(audioContext, {
1427
1494
  frequency: this.centToHz(voiceParams.freqModLFO),
1428
1495
  });
1429
- note.filterDepth = new GainNode(audioContext, {
1496
+ note.modLfoToFilterFc = new GainNode(audioContext, {
1430
1497
  gain: voiceParams.modLfoToFilterFc,
1431
1498
  });
1432
- note.modulationDepth = new GainNode(audioContext);
1499
+ note.modLfoToPitch = new GainNode(audioContext);
1433
1500
  this.setModLfoToPitch(channel, note, scheduleTime);
1434
- note.volumeDepth = new GainNode(audioContext);
1501
+ note.modLfoToVolume = new GainNode(audioContext);
1435
1502
  this.setModLfoToVolume(note, scheduleTime);
1436
- note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
1437
- note.modulationLFO.connect(note.filterDepth);
1438
- note.filterDepth.connect(note.filterNode.frequency);
1439
- note.modulationLFO.connect(note.modulationDepth);
1440
- note.modulationDepth.connect(note.bufferSource.detune);
1441
- note.modulationLFO.connect(note.volumeDepth);
1442
- note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
1503
+ note.modLfo.start(note.startTime + voiceParams.delayModLFO);
1504
+ note.modLfo.connect(note.modLfoToFilterFc);
1505
+ note.modLfoToFilterFc.connect(note.filterEnvelopeNode.frequency);
1506
+ note.modLfo.connect(note.modLfoToPitch);
1507
+ note.modLfoToPitch.connect(note.bufferSource.detune);
1508
+ note.modLfo.connect(note.modLfoToVolume);
1509
+ note.modLfoToVolume.connect(note.volumeEnvelopeNode.gain);
1443
1510
  }
1444
1511
  startVibrato(channel, note, scheduleTime) {
1445
1512
  const { voiceParams } = note;
1446
1513
  const state = channel.state;
1447
1514
  const vibratoRate = state.vibratoRate * 2;
1448
1515
  const vibratoDelay = state.vibratoDelay * 2;
1449
- note.vibratoLFO = new OscillatorNode(this.audioContext, {
1516
+ note.vibLfo = new OscillatorNode(this.audioContext, {
1450
1517
  frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
1451
1518
  });
1452
- note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
1453
- note.vibratoDepth = new GainNode(this.audioContext);
1519
+ note.vibLfo.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
1520
+ note.vibLfoToPitch = new GainNode(this.audioContext);
1454
1521
  this.setVibLfoToPitch(channel, note, scheduleTime);
1455
- note.vibratoLFO.connect(note.vibratoDepth);
1456
- note.vibratoDepth.connect(note.bufferSource.detune);
1522
+ note.vibLfo.connect(note.vibLfoToPitch);
1523
+ note.vibLfoToPitch.connect(note.bufferSource.detune);
1457
1524
  }
1458
1525
  async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
1459
1526
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
@@ -1494,7 +1561,7 @@ class MidyGM2 extends EventTarget {
1494
1561
  const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
1495
1562
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1496
1563
  note.volumeEnvelopeNode = new GainNode(audioContext);
1497
- note.filterNode = new BiquadFilterNode(audioContext, {
1564
+ note.filterEnvelopeNode = new BiquadFilterNode(audioContext, {
1498
1565
  type: "lowpass",
1499
1566
  Q: voiceParams.initialFilterQ / 10, // dB
1500
1567
  });
@@ -1524,8 +1591,8 @@ class MidyGM2 extends EventTarget {
1524
1591
  channel.currentBufferSource.stop(startTime);
1525
1592
  channel.currentBufferSource = note.bufferSource;
1526
1593
  }
1527
- note.bufferSource.connect(note.filterNode);
1528
- note.filterNode.connect(note.volumeEnvelopeNode);
1594
+ note.bufferSource.connect(note.filterEnvelopeNode);
1595
+ note.filterEnvelopeNode.connect(note.volumeEnvelopeNode);
1529
1596
  this.setChorusSend(channel, note, now);
1530
1597
  this.setReverbSend(channel, note, now);
1531
1598
  if (voiceParams.sample.type === "compressed") {
@@ -1606,8 +1673,6 @@ class MidyGM2 extends EventTarget {
1606
1673
  scheduledNotes.push(note);
1607
1674
  const programNumber = channel.programNumber;
1608
1675
  const bankTable = this.soundFontTable[programNumber];
1609
- if (!bankTable)
1610
- return;
1611
1676
  let bank = channel.isDrum ? 128 : channel.bankLSB;
1612
1677
  if (bankTable[bank] === undefined) {
1613
1678
  if (channel.isDrum)
@@ -1627,16 +1692,16 @@ class MidyGM2 extends EventTarget {
1627
1692
  }
1628
1693
  disconnectNote(note) {
1629
1694
  note.bufferSource.disconnect();
1630
- note.filterNode.disconnect();
1695
+ note.filterEnvelopeNode.disconnect();
1631
1696
  note.volumeEnvelopeNode.disconnect();
1632
- if (note.modulationDepth) {
1633
- note.volumeDepth.disconnect();
1634
- note.modulationDepth.disconnect();
1635
- note.modulationLFO.stop();
1697
+ if (note.modLfoToPitch) {
1698
+ note.modLfoToVolume.disconnect();
1699
+ note.modLfoToPitch.disconnect();
1700
+ note.modLfo.stop();
1636
1701
  }
1637
- if (note.vibratoDepth) {
1638
- note.vibratoDepth.disconnect();
1639
- note.vibratoLFO.stop();
1702
+ if (note.vibLfoToPitch) {
1703
+ note.vibLfoToPitch.disconnect();
1704
+ note.vibLfo.stop();
1640
1705
  }
1641
1706
  if (note.reverbSend) {
1642
1707
  note.reverbSend.disconnect();
@@ -1649,7 +1714,7 @@ class MidyGM2 extends EventTarget {
1649
1714
  endTime ??= this.audioContext.currentTime;
1650
1715
  const volDuration = note.voiceParams.volRelease;
1651
1716
  const volRelease = endTime + volDuration;
1652
- note.filterNode.frequency
1717
+ note.filterEnvelopeNode.frequency
1653
1718
  .cancelScheduledValues(endTime)
1654
1719
  .setTargetAtTime(note.adjustedBaseFreq, endTime, note.voiceParams.modRelease * releaseCurve);
1655
1720
  note.volumeEnvelopeNode.gain
@@ -1790,17 +1855,12 @@ class MidyGM2 extends EventTarget {
1790
1855
  const channel = this.channels[channelNumber];
1791
1856
  if (channel.isDrum)
1792
1857
  return;
1793
- const prev = channel.state.channelPressure;
1794
- const next = value / 127;
1795
- channel.state.channelPressure = next;
1796
- const channelPressureRaw = channel.channelPressureTable[0];
1797
- if (0 <= channelPressureRaw) {
1798
- const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1799
- channel.detune += channelPressureDepth * (next - prev);
1800
- }
1801
- const table = channel.channelPressureTable;
1858
+ const prev = this.calcChannelPressureEffectValue(channel, 0);
1859
+ channel.state.channelPressure = value / 127;
1860
+ const next = this.calcChannelPressureEffectValue(channel, 0);
1861
+ channel.detune += next - prev;
1802
1862
  this.processActiveNotes(channel, scheduleTime, (note) => {
1803
- this.setEffects(channel, note, table, scheduleTime);
1863
+ this.setPressureEffects(channel, note, scheduleTime);
1804
1864
  });
1805
1865
  this.applyVoiceParams(channel, 13, scheduleTime);
1806
1866
  }
@@ -1823,13 +1883,13 @@ class MidyGM2 extends EventTarget {
1823
1883
  this.applyVoiceParams(channel, 14, scheduleTime);
1824
1884
  }
1825
1885
  setModLfoToPitch(channel, note, scheduleTime) {
1826
- if (note.modulationDepth) {
1886
+ if (note.modLfoToPitch) {
1827
1887
  const modLfoToPitch = note.voiceParams.modLfoToPitch +
1828
1888
  this.getLFOPitchDepth(channel, note);
1829
1889
  const baseDepth = Math.abs(modLfoToPitch) +
1830
1890
  channel.state.modulationDepthMSB;
1831
1891
  const depth = baseDepth * Math.sign(modLfoToPitch);
1832
- note.modulationDepth.gain
1892
+ note.modLfoToPitch.gain
1833
1893
  .cancelScheduledValues(scheduleTime)
1834
1894
  .setValueAtTime(depth, scheduleTime);
1835
1895
  }
@@ -1838,12 +1898,12 @@ class MidyGM2 extends EventTarget {
1838
1898
  }
1839
1899
  }
1840
1900
  setVibLfoToPitch(channel, note, scheduleTime) {
1841
- if (note.vibratoDepth) {
1901
+ if (note.vibLfoToPitch) {
1842
1902
  const vibratoDepth = channel.state.vibratoDepth * 2;
1843
1903
  const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1844
1904
  const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
1845
1905
  const depth = baseDepth * Math.sign(vibLfoToPitch);
1846
- note.vibratoDepth.gain
1906
+ note.vibLfoToPitch.gain
1847
1907
  .cancelScheduledValues(scheduleTime)
1848
1908
  .setValueAtTime(depth, scheduleTime);
1849
1909
  }
@@ -1854,18 +1914,18 @@ class MidyGM2 extends EventTarget {
1854
1914
  setModLfoToFilterFc(channel, note, scheduleTime) {
1855
1915
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
1856
1916
  this.getLFOFilterDepth(channel);
1857
- note.filterDepth.gain
1917
+ note.modLfoToFilterFc.gain
1858
1918
  .cancelScheduledValues(scheduleTime)
1859
1919
  .setValueAtTime(modLfoToFilterFc, scheduleTime);
1860
1920
  }
1861
1921
  setModLfoToVolume(channel, note, scheduleTime) {
1862
1922
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1863
1923
  const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
1864
- const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
1924
+ const depth = baseDepth * Math.sign(modLfoToVolume) *
1865
1925
  (1 + this.getLFOAmplitudeDepth(channel));
1866
- note.volumeDepth.gain
1926
+ note.modLfoToVolume.gain
1867
1927
  .cancelScheduledValues(scheduleTime)
1868
- .setValueAtTime(volumeDepth, scheduleTime);
1928
+ .setValueAtTime(depth, scheduleTime);
1869
1929
  }
1870
1930
  setReverbSend(channel, note, scheduleTime) {
1871
1931
  let value = note.voiceParams.reverbEffectsSend *
@@ -1930,13 +1990,13 @@ class MidyGM2 extends EventTarget {
1930
1990
  setDelayModLFO(note) {
1931
1991
  const startTime = note.startTime + note.voiceParams.delayModLFO;
1932
1992
  try {
1933
- note.modulationLFO.start(startTime);
1993
+ note.modLfo.start(startTime);
1934
1994
  }
1935
1995
  catch { /* empty */ }
1936
1996
  }
1937
1997
  setFreqModLFO(note, scheduleTime) {
1938
1998
  const freqModLFO = note.voiceParams.freqModLFO;
1939
- note.modulationLFO.frequency
1999
+ note.modLfo.frequency
1940
2000
  .cancelScheduledValues(scheduleTime)
1941
2001
  .setValueAtTime(freqModLFO, scheduleTime);
1942
2002
  }
@@ -1945,14 +2005,14 @@ class MidyGM2 extends EventTarget {
1945
2005
  const value = note.voiceParams.delayVibLFO;
1946
2006
  const startTime = note.startTime + value * vibratoDelay;
1947
2007
  try {
1948
- note.vibratoLFO.start(startTime);
2008
+ note.vibLfo.start(startTime);
1949
2009
  }
1950
2010
  catch { /* empty */ }
1951
2011
  }
1952
2012
  setFreqVibLFO(channel, note, scheduleTime) {
1953
2013
  const vibratoRate = channel.state.vibratoRate * 2;
1954
2014
  const freqVibLFO = note.voiceParams.freqVibLFO;
1955
- note.vibratoLFO.frequency
2015
+ note.vibLfo.frequency
1956
2016
  .cancelScheduledValues(scheduleTime)
1957
2017
  .setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
1958
2018
  }
@@ -1996,7 +2056,7 @@ class MidyGM2 extends EventTarget {
1996
2056
  },
1997
2057
  delayVibLFO: (channel, note, _scheduleTime) => {
1998
2058
  if (0 < channel.state.vibratoDepth) {
1999
- setDelayVibLFO(channel, note);
2059
+ this.setDelayVibLFO(channel, note);
2000
2060
  }
2001
2061
  },
2002
2062
  freqVibLFO: (channel, note, scheduleTime) => {
@@ -2085,12 +2145,16 @@ class MidyGM2 extends EventTarget {
2085
2145
  return handlers;
2086
2146
  }
2087
2147
  setControlChange(channelNumber, controllerType, value, scheduleTime) {
2148
+ if (!(0 <= scheduleTime))
2149
+ scheduleTime = this.audioContext.currentTime;
2088
2150
  const handler = this.controlChangeHandlers[controllerType];
2089
2151
  if (handler) {
2090
2152
  handler.call(this, channelNumber, value, scheduleTime);
2091
2153
  const channel = this.channels[channelNumber];
2092
2154
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
2093
- this.setControlChangeEffects(channel, controllerType, scheduleTime);
2155
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2156
+ this.setControlChangeEffects(channel, note, scheduleTime);
2157
+ });
2094
2158
  }
2095
2159
  else {
2096
2160
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -2103,8 +2167,8 @@ class MidyGM2 extends EventTarget {
2103
2167
  const depth = channel.state.modulationDepthMSB *
2104
2168
  channel.modulationDepthRange;
2105
2169
  this.processScheduledNotes(channel, (note) => {
2106
- if (note.modulationDepth) {
2107
- note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
2170
+ if (note.modLfoToPitch) {
2171
+ note.modLfoToPitch.gain.setValueAtTime(depth, scheduleTime);
2108
2172
  }
2109
2173
  else {
2110
2174
  this.startModulation(channel, note, scheduleTime);
@@ -2198,7 +2262,8 @@ class MidyGM2 extends EventTarget {
2198
2262
  }
2199
2263
  updateChannelVolume(channel, scheduleTime) {
2200
2264
  const state = channel.state;
2201
- const gain = state.volumeMSB * state.expressionMSB;
2265
+ const effect = this.getChannelAmplitudeControl(channel);
2266
+ const gain = state.volumeMSB * state.expressionMSB * (1 + effect);
2202
2267
  const { gainLeft, gainRight } = this.panToGain(state.panMSB);
2203
2268
  channel.gainL.gain
2204
2269
  .cancelScheduledValues(scheduleTime)
@@ -2647,8 +2712,8 @@ class MidyGM2 extends EventTarget {
2647
2712
  this.masterFineTuning = next;
2648
2713
  const detuneChange = next - prev;
2649
2714
  const channels = this.channels;
2650
- for (let ch = 0; i < channels.length; ch++) {
2651
- const channel = this.channels[ch];
2715
+ for (let ch = 0; ch < channels.length; ch++) {
2716
+ const channel = channels[ch];
2652
2717
  if (channel.isDrum)
2653
2718
  continue;
2654
2719
  channel.detune += detuneChange;
@@ -2666,7 +2731,7 @@ class MidyGM2 extends EventTarget {
2666
2731
  const detuneChange = next - prev;
2667
2732
  const channels = this.channels;
2668
2733
  for (let ch = 0; ch < channels.length; ch++) {
2669
- const channel = this.channels[ch];
2734
+ const channel = channels[ch];
2670
2735
  if (channel.isDrum)
2671
2736
  continue;
2672
2737
  channel.detune += detuneChange;
@@ -2891,70 +2956,106 @@ class MidyGM2 extends EventTarget {
2891
2956
  this.updateChannelDetune(channel, scheduleTime);
2892
2957
  }
2893
2958
  }
2959
+ calcEffectValue(channel, destination) {
2960
+ return this.calcChannelEffectValue(channel, destination);
2961
+ }
2962
+ calcChannelEffectValue(channel, destination) {
2963
+ return this.calcControlChangeEffectValue(channel, destination) +
2964
+ this.calcChannelPressureEffectValue(channel, destination);
2965
+ }
2966
+ calcControlChangeEffectValue(channel, destination) {
2967
+ const controlType = channel.controlTable[destination];
2968
+ if (controlType < 0)
2969
+ return 0;
2970
+ const pressure = channel.state.array[controlType];
2971
+ if (pressure <= 0)
2972
+ return 0;
2973
+ const baseline = pressureBaselines[destination];
2974
+ const tableValue = channel.controlTable[destination + 6];
2975
+ const value = (tableValue - baseline) * pressure;
2976
+ return value * effectParameters[destination];
2977
+ }
2978
+ calcChannelPressureEffectValue(channel, destination) {
2979
+ const pressure = channel.state.channelPressure;
2980
+ if (pressure <= 0)
2981
+ return 0;
2982
+ const baseline = pressureBaselines[destination];
2983
+ const tableValue = channel.channelPressureTable[destination];
2984
+ const value = (tableValue - baseline) * pressure;
2985
+ return value * effectParameters[destination];
2986
+ }
2987
+ getChannelPitchControl(channel) {
2988
+ return this.calcChannelEffectValue(channel, 0);
2989
+ }
2990
+ getPitchControl(channel, note) {
2991
+ return this.calcEffectValue(channel, note, 0);
2992
+ }
2894
2993
  getFilterCutoffControl(channel) {
2895
- const channelPressureRaw = channel.channelPressureTable[1];
2896
- const channelPressure = (0 <= channelPressureRaw)
2897
- ? (channelPressureRaw - 64) * channel.state.channelPressure
2898
- : 0;
2899
- return channelPressure * 15;
2900
- }
2901
- getAmplitudeControl(channel) {
2902
- const channelPressureRaw = channel.channelPressureTable[2];
2903
- const channelPressure = (0 <= channelPressureRaw)
2904
- ? channel.state.channelPressure * 127 / channelPressureRaw
2905
- : 0;
2906
- return channelPressure;
2994
+ return this.calcEffectValue(channel, 1);
2995
+ }
2996
+ getChannelAmplitudeControl(channel) {
2997
+ return this.calcChannelEffectValue(channel, 2);
2907
2998
  }
2908
2999
  getLFOPitchDepth(channel) {
2909
- const channelPressureRaw = channel.channelPressureTable[3];
2910
- const channelPressure = (0 <= channelPressureRaw)
2911
- ? channelPressureRaw * channel.state.channelPressure
2912
- : 0;
2913
- return channelPressure / 127 * 600;
3000
+ return this.calcEffectValue(channel, 3);
2914
3001
  }
2915
3002
  getLFOFilterDepth(channel) {
2916
- const channelPressureRaw = channel.channelPressureTable[4];
2917
- const channelPressure = (0 <= channelPressureRaw)
2918
- ? channelPressureRaw * channel.state.channelPressure
2919
- : 0;
2920
- return channelPressure / 127 * 2400;
3003
+ return this.calcEffectValue(channel, 4);
2921
3004
  }
2922
3005
  getLFOAmplitudeDepth(channel) {
2923
- const channelPressureRaw = channel.channelPressureTable[5];
2924
- const channelPressure = (0 <= channelPressureRaw)
2925
- ? channelPressureRaw * channel.state.channelPressure
2926
- : 0;
2927
- return channelPressure / 127;
2928
- }
2929
- setEffects(channel, note, table, scheduleTime) {
2930
- if (0 < table[0]) {
3006
+ return this.calcEffectValue(channel, 5);
3007
+ }
3008
+ createEffectHandlers() {
3009
+ const handlers = new Array(6);
3010
+ handlers[0] = (channel, note, scheduleTime) => {
2931
3011
  if (this.isPortamento(channel, note)) {
2932
3012
  this.setPortamentoDetune(channel, note, scheduleTime);
2933
3013
  }
2934
3014
  else {
2935
3015
  this.setDetune(channel, note, scheduleTime);
2936
3016
  }
2937
- }
2938
- if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2939
- if (0 < table[1]) {
3017
+ };
3018
+ handlers[1] = (channel, note, scheduleTime) => {
3019
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
2940
3020
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2941
3021
  }
2942
- if (0 < table[2]) {
3022
+ else {
3023
+ this.setFilterEnvelope(channel, note, scheduleTime);
3024
+ }
3025
+ };
3026
+ handlers[2] = (channel, note, scheduleTime) => {
3027
+ if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2943
3028
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2944
3029
  }
2945
- }
2946
- else {
2947
- if (0 < table[1])
2948
- this.setFilterEnvelope(channel, note, scheduleTime);
2949
- if (0 < table[2])
3030
+ else {
2950
3031
  this.setVolumeEnvelope(channel, note, scheduleTime);
3032
+ }
3033
+ };
3034
+ handlers[3] = (channel, note, scheduleTime) => this.setModLfoToPitch(channel, note, scheduleTime);
3035
+ handlers[4] = (channel, note, scheduleTime) => this.setModLfoToFilterFc(channel, note, scheduleTime);
3036
+ handlers[5] = (channel, note, scheduleTime) => this.setModLfoToVolume(channel, note, scheduleTime);
3037
+ return handlers;
3038
+ }
3039
+ setControlChangeEffects(channel, note, scheduleTime) {
3040
+ const handlers = this.effectHandlers;
3041
+ for (let i = 0; i < handlers.length; i++) {
3042
+ const baseline = pressureBaselines[i];
3043
+ const tableValue = channel.controlTable[i + 6];
3044
+ if (baseline === tableValue)
3045
+ continue;
3046
+ handlers[i](channel, note, scheduleTime);
3047
+ }
3048
+ }
3049
+ setPressureEffects(channel, note, tableName, scheduleTime) {
3050
+ const handlers = this.effectHandlers;
3051
+ const table = channel[tableName];
3052
+ for (let i = 0; i < handlers.length; i++) {
3053
+ const baseline = pressureBaselines[i];
3054
+ const tableValue = table[i];
3055
+ if (baseline === tableValue)
3056
+ continue;
3057
+ handlers[i](channel, note, scheduleTime);
2951
3058
  }
2952
- if (0 < table[3])
2953
- this.setModLfoToPitch(channel, note, scheduleTime);
2954
- if (0 < table[4])
2955
- this.setModLfoToFilterFc(channel, note, scheduleTime);
2956
- if (0 < table[5])
2957
- this.setModLfoToVolume(channel, note, scheduleTime);
2958
3059
  }
2959
3060
  handlePressureSysEx(data, tableName, scheduleTime) {
2960
3061
  const channelNumber = data[4];
@@ -2966,39 +3067,32 @@ class MidyGM2 extends EventTarget {
2966
3067
  const pp = data[i];
2967
3068
  const rr = data[i + 1];
2968
3069
  table[pp] = rr;
3070
+ const handler = this.effectHandlers[pp];
3071
+ this.processActiveNotes(channel, scheduleTime, (note) => {
3072
+ if (handler)
3073
+ handler(channel, note, scheduleTime);
3074
+ });
2969
3075
  }
2970
- this.processActiveNotes(channel, scheduleTime, (note) => {
2971
- this.setEffects(channel, note, table, scheduleTime);
2972
- });
2973
- }
2974
- initControlTable() {
2975
- const ccCount = 128;
2976
- const slotSize = 6;
2977
- return new Int8Array(ccCount * slotSize).fill(-1);
2978
- }
2979
- setControlChangeEffects(channel, controllerType, scheduleTime) {
2980
- const slotSize = 6;
2981
- const offset = controllerType * slotSize;
2982
- const table = channel.controlTable.subarray(offset, offset + slotSize);
2983
- this.processScheduledNotes(channel, (note) => {
2984
- this.setEffects(channel, note, table, scheduleTime);
2985
- });
2986
3076
  }
2987
3077
  handleControlChangeSysEx(data, scheduleTime) {
2988
3078
  const channelNumber = data[4];
2989
3079
  const channel = this.channels[channelNumber];
2990
3080
  if (channel.isDrum)
2991
3081
  return;
2992
- const slotSize = 6;
2993
- const controllerType = data[5];
2994
- const offset = controllerType * slotSize;
2995
3082
  const table = channel.controlTable;
3083
+ table.set(defaultControlValues);
3084
+ const controllerType = data[5];
2996
3085
  for (let i = 6; i < data.length; i += 2) {
2997
3086
  const pp = data[i];
2998
3087
  const rr = data[i + 1];
2999
- table[offset + pp] = rr;
3088
+ table[pp] = controllerType;
3089
+ table[pp + 6] = rr;
3090
+ const handler = this.effectHandlers[pp];
3091
+ this.processActiveNotes(channel, scheduleTime, (note) => {
3092
+ if (handler)
3093
+ handler(channel, note, scheduleTime);
3094
+ });
3000
3095
  }
3001
- this.setControlChangeEffects(channel, controllerType, scheduleTime);
3002
3096
  }
3003
3097
  getKeyBasedValue(channel, keyNumber, controllerType) {
3004
3098
  const index = keyNumber * 128 + controllerType;