@marmooo/midy 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -242,6 +242,12 @@ const volumeEnvelopeKeys = [
242
242
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
243
243
  class MidyGM2 {
244
244
  constructor(audioContext, options = this.defaultOptions) {
245
+ Object.defineProperty(this, "mode", {
246
+ enumerable: true,
247
+ configurable: true,
248
+ writable: true,
249
+ value: "GM2"
250
+ });
245
251
  Object.defineProperty(this, "ticksPerBeat", {
246
252
  enumerable: true,
247
253
  configurable: true,
@@ -420,6 +426,11 @@ class MidyGM2 {
420
426
  this.audioContext = audioContext;
421
427
  this.options = { ...this.defaultOptions, ...options };
422
428
  this.masterVolume = new GainNode(audioContext);
429
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
430
+ this.schedulerBuffer = new AudioBuffer({
431
+ length: 1,
432
+ sampleRate: audioContext.sampleRate,
433
+ });
423
434
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
424
435
  this.controlChangeHandlers = this.createControlChangeHandlers();
425
436
  this.channels = this.createChannels(audioContext);
@@ -428,6 +439,7 @@ class MidyGM2 {
428
439
  this.chorusEffect.output.connect(this.masterVolume);
429
440
  this.reverbEffect.output.connect(this.masterVolume);
430
441
  this.masterVolume.connect(audioContext.destination);
442
+ this.scheduler.connect(audioContext.destination);
431
443
  this.GM2SystemOn();
432
444
  }
433
445
  initSoundFontTable() {
@@ -526,10 +538,24 @@ class MidyGM2 {
526
538
  return audioBuffer;
527
539
  }
528
540
  }
529
- createNoteBufferNode(audioBuffer, voiceParams) {
541
+ calcLoopMode(channel, note, voiceParams) {
542
+ if (channel.isDrum) {
543
+ const noteNumber = note.noteNumber;
544
+ if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
545
+ return true;
546
+ }
547
+ else {
548
+ return false;
549
+ }
550
+ }
551
+ else {
552
+ return voiceParams.sampleModes % 2 !== 0;
553
+ }
554
+ }
555
+ createBufferSource(channel, note, voiceParams, audioBuffer) {
530
556
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
531
557
  bufferSource.buffer = audioBuffer;
532
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
558
+ bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
533
559
  if (bufferSource.loop) {
534
560
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
535
561
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -1006,7 +1032,9 @@ class MidyGM2 {
1006
1032
  return 8.176 * this.centToRate(cent);
1007
1033
  }
1008
1034
  calcChannelDetune(channel) {
1009
- const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
1035
+ const masterTuning = channel.isDrum
1036
+ ? 0
1037
+ : this.masterCoarseTuning + this.masterFineTuning;
1010
1038
  const channelTuning = channel.coarseTuning + channel.fineTuning;
1011
1039
  const tuning = masterTuning + channelTuning;
1012
1040
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
@@ -1032,9 +1060,8 @@ class MidyGM2 {
1032
1060
  .setValueAtTime(detune, scheduleTime);
1033
1061
  }
1034
1062
  getPortamentoTime(channel) {
1035
- const factor = 5 * Math.log(10) / 127;
1036
- const time = channel.state.portamentoTime;
1037
- return Math.log(time) / factor;
1063
+ const factor = 5 * Math.log(10) * 127;
1064
+ return channel.state.portamentoTime * factor;
1038
1065
  }
1039
1066
  setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
1040
1067
  const { voiceParams, startTime } = note;
@@ -1195,7 +1222,7 @@ class MidyGM2 {
1195
1222
  const voiceParams = voice.getAllParams(controllerState);
1196
1223
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1197
1224
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1198
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
1225
+ note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
1199
1226
  note.volumeNode = new GainNode(this.audioContext);
1200
1227
  note.gainL = new GainNode(this.audioContext);
1201
1228
  note.gainR = new GainNode(this.audioContext);
@@ -1239,14 +1266,21 @@ class MidyGM2 {
1239
1266
  note.bufferSource.start(startTime);
1240
1267
  return note;
1241
1268
  }
1242
- calcBank(channel, channelNumber) {
1243
- if (channel.bankMSB === 121) {
1244
- return 0;
1245
- }
1246
- if (channelNumber % 9 <= 1 && channel.bankMSB === 120) {
1247
- return 128;
1269
+ calcBank(channel) {
1270
+ switch (this.mode) {
1271
+ case "GM1":
1272
+ if (channel.isDrum)
1273
+ return 128;
1274
+ return 0;
1275
+ case "GM2":
1276
+ if (channel.bankMSB === 121)
1277
+ return 0;
1278
+ if (channel.isDrum)
1279
+ return 128;
1280
+ return channel.bank;
1281
+ default:
1282
+ return channel.bank;
1248
1283
  }
1249
- return channel.bank;
1250
1284
  }
1251
1285
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1252
1286
  const channel = this.channels[channelNumber];
@@ -1270,7 +1304,7 @@ class MidyGM2 {
1270
1304
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1271
1305
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1272
1306
  const [prevNote, prevChannelNumber] = prevEntry;
1273
- if (!prevNote.ending) {
1307
+ if (prevNote && !prevNote.ending) {
1274
1308
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1275
1309
  startTime, true, // force
1276
1310
  undefined);
@@ -1419,9 +1453,21 @@ class MidyGM2 {
1419
1453
  const channel = this.channels[channelNumber];
1420
1454
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1421
1455
  channel.program = program;
1456
+ if (this.mode === "GM2") {
1457
+ switch (channel.bankMSB) {
1458
+ case 120:
1459
+ channel.isDrum = true;
1460
+ break;
1461
+ case 121:
1462
+ channel.isDrum = false;
1463
+ break;
1464
+ }
1465
+ }
1422
1466
  }
1423
1467
  handleChannelPressure(channelNumber, value, scheduleTime) {
1424
1468
  const channel = this.channels[channelNumber];
1469
+ if (channel.isDrum)
1470
+ return;
1425
1471
  const prev = channel.state.channelPressure;
1426
1472
  const next = value / 127;
1427
1473
  channel.state.channelPressure = next;
@@ -1440,8 +1486,10 @@ class MidyGM2 {
1440
1486
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
1441
1487
  }
1442
1488
  setPitchBend(channelNumber, value, scheduleTime) {
1443
- scheduleTime ??= this.audioContext.currentTime;
1444
1489
  const channel = this.channels[channelNumber];
1490
+ if (channel.isDrum)
1491
+ return;
1492
+ scheduleTime ??= this.audioContext.currentTime;
1445
1493
  const state = channel.state;
1446
1494
  const prev = state.pitchWheel * 2 - 1;
1447
1495
  const next = (value - 8192) / 8192;
@@ -1713,15 +1761,16 @@ class MidyGM2 {
1713
1761
  });
1714
1762
  }
1715
1763
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1716
- scheduleTime ??= this.audioContext.currentTime;
1717
1764
  const channel = this.channels[channelNumber];
1765
+ if (channel.isDrum)
1766
+ return;
1767
+ scheduleTime ??= this.audioContext.currentTime;
1718
1768
  channel.state.modulationDepth = modulation / 127;
1719
1769
  this.updateModulation(channel, scheduleTime);
1720
1770
  }
1721
1771
  setPortamentoTime(channelNumber, portamentoTime) {
1722
1772
  const channel = this.channels[channelNumber];
1723
- const factor = 5 * Math.log(10) / 127;
1724
- channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1773
+ channel.state.portamentoTime = portamentoTime / 127;
1725
1774
  }
1726
1775
  setKeyBasedVolume(channel, scheduleTime) {
1727
1776
  this.processScheduledNotes(channel, (note) => {
@@ -1793,8 +1842,10 @@ class MidyGM2 {
1793
1842
  .setValueAtTime(volume * gainRight, scheduleTime);
1794
1843
  }
1795
1844
  setSustainPedal(channelNumber, value, scheduleTime) {
1796
- scheduleTime ??= this.audioContext.currentTime;
1797
1845
  const channel = this.channels[channelNumber];
1846
+ if (channel.isDrum)
1847
+ return;
1848
+ scheduleTime ??= this.audioContext.currentTime;
1798
1849
  channel.state.sustainPedal = value / 127;
1799
1850
  if (64 <= value) {
1800
1851
  this.processScheduledNotes(channel, (note) => {
@@ -1806,11 +1857,16 @@ class MidyGM2 {
1806
1857
  }
1807
1858
  }
1808
1859
  setPortamento(channelNumber, value) {
1809
- this.channels[channelNumber].state.portamento = value / 127;
1860
+ const channel = this.channels[channelNumber];
1861
+ if (channel.isDrum)
1862
+ return;
1863
+ channel.state.portamento = value / 127;
1810
1864
  }
1811
1865
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1812
- scheduleTime ??= this.audioContext.currentTime;
1813
1866
  const channel = this.channels[channelNumber];
1867
+ if (channel.isDrum)
1868
+ return;
1869
+ scheduleTime ??= this.audioContext.currentTime;
1814
1870
  channel.state.sostenutoPedal = value / 127;
1815
1871
  if (64 <= value) {
1816
1872
  channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
@@ -1819,9 +1875,22 @@ class MidyGM2 {
1819
1875
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
1820
1876
  }
1821
1877
  }
1822
- setSoftPedal(channelNumber, softPedal, _scheduleTime) {
1878
+ setSoftPedal(channelNumber, softPedal, scheduleTime) {
1823
1879
  const channel = this.channels[channelNumber];
1880
+ if (channel.isDrum)
1881
+ return;
1882
+ scheduleTime ??= this.audioContext.currentTime;
1824
1883
  channel.state.softPedal = softPedal / 127;
1884
+ this.processScheduledNotes(channel, (note) => {
1885
+ if (note.portamento) {
1886
+ this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1887
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1888
+ }
1889
+ else {
1890
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1891
+ this.setFilterEnvelope(channel, note, scheduleTime);
1892
+ }
1893
+ });
1825
1894
  }
1826
1895
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
1827
1896
  scheduleTime ??= this.audioContext.currentTime;
@@ -1950,8 +2019,10 @@ class MidyGM2 {
1950
2019
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1951
2020
  }
1952
2021
  setPitchBendRange(channelNumber, value, scheduleTime) {
1953
- scheduleTime ??= this.audioContext.currentTime;
1954
2022
  const channel = this.channels[channelNumber];
2023
+ if (channel.isDrum)
2024
+ return;
2025
+ scheduleTime ??= this.audioContext.currentTime;
1955
2026
  const state = channel.state;
1956
2027
  const prev = state.pitchWheelSensitivity;
1957
2028
  const next = value / 128;
@@ -1967,8 +2038,10 @@ class MidyGM2 {
1967
2038
  this.setFineTuning(channelNumber, fineTuning, scheduleTime);
1968
2039
  }
1969
2040
  setFineTuning(channelNumber, value, scheduleTime) {
1970
- scheduleTime ??= this.audioContext.currentTime;
1971
2041
  const channel = this.channels[channelNumber];
2042
+ if (channel.isDrum)
2043
+ return;
2044
+ scheduleTime ??= this.audioContext.currentTime;
1972
2045
  const prev = channel.fineTuning;
1973
2046
  const next = (value - 8192) / 8.192; // cent
1974
2047
  channel.fineTuning = next;
@@ -1982,8 +2055,10 @@ class MidyGM2 {
1982
2055
  this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
1983
2056
  }
1984
2057
  setCoarseTuning(channelNumber, value, scheduleTime) {
1985
- scheduleTime ??= this.audioContext.currentTime;
1986
2058
  const channel = this.channels[channelNumber];
2059
+ if (channel.isDrum)
2060
+ return;
2061
+ scheduleTime ??= this.audioContext.currentTime;
1987
2062
  const prev = channel.coarseTuning;
1988
2063
  const next = (value - 64) * 100; // cent
1989
2064
  channel.coarseTuning = next;
@@ -1997,8 +2072,10 @@ class MidyGM2 {
1997
2072
  this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
1998
2073
  }
1999
2074
  setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2000
- scheduleTime ??= this.audioContext.currentTime;
2001
2075
  const channel = this.channels[channelNumber];
2076
+ if (channel.isDrum)
2077
+ return;
2078
+ scheduleTime ??= this.audioContext.currentTime;
2002
2079
  channel.modulationDepthRange = modulationDepthRange;
2003
2080
  this.updateModulation(channel, scheduleTime);
2004
2081
  }
@@ -2066,12 +2143,12 @@ class MidyGM2 {
2066
2143
  case 9:
2067
2144
  switch (data[3]) {
2068
2145
  case 1:
2069
- this.GM1SystemOn();
2146
+ this.GM1SystemOn(scheduleTime);
2070
2147
  break;
2071
2148
  case 2: // GM System Off
2072
2149
  break;
2073
2150
  case 3:
2074
- this.GM2SystemOn();
2151
+ this.GM2SystemOn(scheduleTime);
2075
2152
  break;
2076
2153
  default:
2077
2154
  console.warn(`Unsupported Exclusive Message: ${data}`);
@@ -2081,25 +2158,35 @@ class MidyGM2 {
2081
2158
  console.warn(`Unsupported Exclusive Message: ${data}`);
2082
2159
  }
2083
2160
  }
2084
- GM1SystemOn() {
2161
+ GM1SystemOn(scheduleTime) {
2162
+ scheduleTime ??= this.audioContext.currentTime;
2163
+ this.mode = "GM1";
2085
2164
  for (let i = 0; i < this.channels.length; i++) {
2165
+ this.allSoundOff(i, 0, scheduleTime);
2086
2166
  const channel = this.channels[i];
2087
2167
  channel.bankMSB = 0;
2088
2168
  channel.bankLSB = 0;
2089
2169
  channel.bank = 0;
2170
+ channel.isDrum = false;
2090
2171
  }
2091
2172
  this.channels[9].bankMSB = 1;
2092
2173
  this.channels[9].bank = 128;
2174
+ this.channels[9].isDrum = true;
2093
2175
  }
2094
- GM2SystemOn() {
2176
+ GM2SystemOn(scheduleTime) {
2177
+ scheduleTime ??= this.audioContext.currentTime;
2178
+ this.mode = "GM2";
2095
2179
  for (let i = 0; i < this.channels.length; i++) {
2180
+ this.allSoundOff(i, 0, scheduleTime);
2096
2181
  const channel = this.channels[i];
2097
2182
  channel.bankMSB = 121;
2098
2183
  channel.bankLSB = 0;
2099
2184
  channel.bank = 121 * 128;
2185
+ channel.isDrum = false;
2100
2186
  }
2101
2187
  this.channels[9].bankMSB = 120;
2102
2188
  this.channels[9].bank = 120 * 128;
2189
+ this.channels[9].isDrum = true;
2103
2190
  }
2104
2191
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
2105
2192
  switch (data[2]) {
@@ -2162,8 +2249,14 @@ class MidyGM2 {
2162
2249
  const prev = this.masterFineTuning;
2163
2250
  const next = (value - 8192) / 8.192; // cent
2164
2251
  this.masterFineTuning = next;
2165
- channel.detune += next - prev;
2166
- this.updateChannelDetune(channel, scheduleTime);
2252
+ const detuneChange = next - prev;
2253
+ for (let i = 0; i < this.channels.length; i++) {
2254
+ const channel = this.channels[i];
2255
+ if (channel.isDrum)
2256
+ continue;
2257
+ channel.detune += detuneChange;
2258
+ this.updateChannelDetune(channel, scheduleTime);
2259
+ }
2167
2260
  }
2168
2261
  handleMasterCoarseTuningSysEx(data, scheduleTime) {
2169
2262
  const coarseTuning = data[4];
@@ -2173,8 +2266,14 @@ class MidyGM2 {
2173
2266
  const prev = this.masterCoarseTuning;
2174
2267
  const next = (value - 64) * 100; // cent
2175
2268
  this.masterCoarseTuning = next;
2176
- channel.detune += next - prev;
2177
- this.updateChannelDetune(channel, scheduleTime);
2269
+ const detuneChange = next - prev;
2270
+ for (let i = 0; i < this.channels.length; i++) {
2271
+ const channel = this.channels[i];
2272
+ if (channel.isDrum)
2273
+ continue;
2274
+ channel.detune += detuneChange;
2275
+ this.updateChannelDetune(channel, scheduleTime);
2276
+ }
2178
2277
  }
2179
2278
  handleGlobalParameterControlSysEx(data, scheduleTime) {
2180
2279
  if (data[7] === 1) {
@@ -2502,13 +2601,20 @@ class MidyGM2 {
2502
2601
  }
2503
2602
  scheduleTask(callback, scheduleTime) {
2504
2603
  return new Promise((resolve) => {
2505
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
2604
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
2605
+ buffer: this.schedulerBuffer,
2606
+ });
2607
+ bufferSource.connect(this.scheduler);
2506
2608
  bufferSource.onended = () => {
2507
- callback();
2508
- resolve();
2609
+ try {
2610
+ callback();
2611
+ }
2612
+ finally {
2613
+ bufferSource.disconnect();
2614
+ resolve();
2615
+ }
2509
2616
  };
2510
2617
  bufferSource.start(scheduleTime);
2511
- bufferSource.stop(scheduleTime);
2512
2618
  });
2513
2619
  }
2514
2620
  }
@@ -2519,6 +2625,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2519
2625
  writable: true,
2520
2626
  value: {
2521
2627
  currentBufferSource: null,
2628
+ isDrum: false,
2522
2629
  detune: 0,
2523
2630
  program: 0,
2524
2631
  bank: 121 * 128,
@@ -2529,8 +2636,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2529
2636
  rpnMSB: 127,
2530
2637
  rpnLSB: 127,
2531
2638
  mono: false, // CC#124, CC#125
2639
+ modulationDepthRange: 50, // cent
2532
2640
  fineTuning: 0, // cb
2533
2641
  coarseTuning: 0, // cb
2534
- modulationDepthRange: 50, // cent
2535
2642
  }
2536
2643
  });
@@ -1,6 +1,7 @@
1
1
  export class MidyGMLite {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ isDrum: boolean;
4
5
  detune: number;
5
6
  program: number;
6
7
  bank: number;
@@ -8,8 +9,10 @@ export class MidyGMLite {
8
9
  dataLSB: number;
9
10
  rpnMSB: number;
10
11
  rpnLSB: number;
12
+ modulationDepthRange: number;
11
13
  };
12
14
  constructor(audioContext: any);
15
+ mode: string;
13
16
  ticksPerBeat: number;
14
17
  totalTime: number;
15
18
  noteCheckInterval: number;
@@ -32,6 +35,8 @@ export class MidyGMLite {
32
35
  exclusiveClassMap: SparseMap;
33
36
  audioContext: any;
34
37
  masterVolume: any;
38
+ scheduler: any;
39
+ schedulerBuffer: any;
35
40
  voiceParamsHandlers: {
36
41
  modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
37
42
  vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
@@ -70,7 +75,8 @@ export class MidyGMLite {
70
75
  };
71
76
  createChannels(audioContext: any): any[];
72
77
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
73
- createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
78
+ calcLoopMode(channel: any, voiceParams: any): boolean;
79
+ createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
74
80
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
75
81
  getQueueIndex(second: any): number;
76
82
  playNotes(): Promise<any>;
@@ -173,8 +179,8 @@ export class MidyGMLite {
173
179
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
174
180
  resetAllControllers(channelNumber: any): void;
175
181
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
176
- handleUniversalNonRealTimeExclusiveMessage(data: any, _scheduleTime: any): void;
177
- GM1SystemOn(): void;
182
+ handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
183
+ GM1SystemOn(scheduleTime: any): void;
178
184
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
179
185
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
180
186
  setMasterVolume(volume: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAsBE;;;;;;;;;MASE;IAEF,+BAQC;IAxCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAcrC,kBAAgC;IAChC,kBAA8C;IAC9C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAWC;IAED,6DA2BC;IAED,8DASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHAwCC;IAED,kGAkDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAWC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,gFAgBC;IAED,oBAMC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAUC;CACF;AAj3CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAuBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAjDD,aAAa;IACb,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAgBrC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAWC;IAED,6DA2BC;IAED,sDAMC;IAED,0EASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHA4CC;IAED,kGAkDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAWC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAMC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAYC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAWC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAgBC;CACF;AAp5CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -173,6 +173,12 @@ const volumeEnvelopeKeys = [
173
173
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
174
174
  class MidyGMLite {
175
175
  constructor(audioContext) {
176
+ Object.defineProperty(this, "mode", {
177
+ enumerable: true,
178
+ configurable: true,
179
+ writable: true,
180
+ value: "GM1"
181
+ });
176
182
  Object.defineProperty(this, "ticksPerBeat", {
177
183
  enumerable: true,
178
184
  configurable: true,
@@ -295,10 +301,16 @@ class MidyGMLite {
295
301
  });
296
302
  this.audioContext = audioContext;
297
303
  this.masterVolume = new GainNode(audioContext);
304
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
305
+ this.schedulerBuffer = new AudioBuffer({
306
+ length: 1,
307
+ sampleRate: audioContext.sampleRate,
308
+ });
298
309
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
299
310
  this.controlChangeHandlers = this.createControlChangeHandlers();
300
311
  this.channels = this.createChannels(audioContext);
301
312
  this.masterVolume.connect(audioContext.destination);
313
+ this.scheduler.connect(audioContext.destination);
302
314
  this.GM1SystemOn();
303
315
  }
304
316
  initSoundFontTable() {
@@ -392,10 +404,18 @@ class MidyGMLite {
392
404
  return audioBuffer;
393
405
  }
394
406
  }
395
- createNoteBufferNode(audioBuffer, voiceParams) {
407
+ calcLoopMode(channel, voiceParams) {
408
+ if (channel.isDrum) {
409
+ return false;
410
+ }
411
+ else {
412
+ return voiceParams.sampleModes % 2 !== 0;
413
+ }
414
+ }
415
+ createBufferSource(channel, voiceParams, audioBuffer) {
396
416
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
397
417
  bufferSource.buffer = audioBuffer;
398
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
418
+ bufferSource.loop = this.calcLoopMode(channel, voiceParams);
399
419
  if (bufferSource.loop) {
400
420
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
401
421
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -806,7 +826,7 @@ class MidyGMLite {
806
826
  const voiceParams = voice.getAllParams(controllerState);
807
827
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
808
828
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
809
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
829
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
810
830
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
811
831
  note.filterNode = new BiquadFilterNode(this.audioContext, {
812
832
  type: "lowpass",
@@ -845,7 +865,7 @@ class MidyGMLite {
845
865
  if (this.exclusiveClassMap.has(exclusiveClass)) {
846
866
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
847
867
  const [prevNote, prevChannelNumber] = prevEntry;
848
- if (!prevNote.ending) {
868
+ if (prevNote && !prevNote.ending) {
849
869
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
850
870
  startTime, true);
851
871
  }
@@ -953,8 +973,10 @@ class MidyGMLite {
953
973
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
954
974
  }
955
975
  setPitchBend(channelNumber, value, scheduleTime) {
956
- scheduleTime ??= this.audioContext.currentTime;
957
976
  const channel = this.channels[channelNumber];
977
+ if (channel.isDrum)
978
+ return;
979
+ scheduleTime ??= this.audioContext.currentTime;
958
980
  const state = channel.state;
959
981
  const prev = state.pitchWheel * 2 - 1;
960
982
  const next = (value - 8192) / 8192;
@@ -1103,7 +1125,6 @@ class MidyGMLite {
1103
1125
  }
1104
1126
  }
1105
1127
  updateModulation(channel, scheduleTime) {
1106
- scheduleTime ??= this.audioContext.currentTime;
1107
1128
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1108
1129
  this.processScheduledNotes(channel, (note) => {
1109
1130
  if (note.modulationDepth) {
@@ -1116,8 +1137,10 @@ class MidyGMLite {
1116
1137
  });
1117
1138
  }
1118
1139
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1119
- scheduleTime ??= this.audioContext.currentTime;
1120
1140
  const channel = this.channels[channelNumber];
1141
+ if (channel.isDrum)
1142
+ return;
1143
+ scheduleTime ??= this.audioContext.currentTime;
1121
1144
  channel.state.modulationDepth = modulation / 127;
1122
1145
  this.updateModulation(channel, scheduleTime);
1123
1146
  }
@@ -1162,8 +1185,10 @@ class MidyGMLite {
1162
1185
  .setValueAtTime(volume * gainRight, scheduleTime);
1163
1186
  }
1164
1187
  setSustainPedal(channelNumber, value, scheduleTime) {
1165
- scheduleTime ??= this.audioContext.currentTime;
1166
1188
  const channel = this.channels[channelNumber];
1189
+ if (channel.isDrum)
1190
+ return;
1191
+ scheduleTime ??= this.audioContext.currentTime;
1167
1192
  channel.state.sustainPedal = value / 127;
1168
1193
  if (64 <= value) {
1169
1194
  this.processScheduledNotes(channel, (note) => {
@@ -1220,8 +1245,10 @@ class MidyGMLite {
1220
1245
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1221
1246
  }
1222
1247
  setPitchBendRange(channelNumber, value, scheduleTime) {
1223
- scheduleTime ??= this.audioContext.currentTime;
1224
1248
  const channel = this.channels[channelNumber];
1249
+ if (channel.isDrum)
1250
+ return;
1251
+ scheduleTime ??= this.audioContext.currentTime;
1225
1252
  const state = channel.state;
1226
1253
  const prev = state.pitchWheelSensitivity;
1227
1254
  const next = value / 128;
@@ -1260,12 +1287,12 @@ class MidyGMLite {
1260
1287
  scheduleTime ??= this.audioContext.currentTime;
1261
1288
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1262
1289
  }
1263
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1290
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1264
1291
  switch (data[2]) {
1265
1292
  case 9:
1266
1293
  switch (data[3]) {
1267
1294
  case 1:
1268
- this.GM1SystemOn();
1295
+ this.GM1SystemOn(scheduleTime);
1269
1296
  break;
1270
1297
  case 2: // GM System Off
1271
1298
  break;
@@ -1277,12 +1304,17 @@ class MidyGMLite {
1277
1304
  console.warn(`Unsupported Exclusive Message: ${data}`);
1278
1305
  }
1279
1306
  }
1280
- GM1SystemOn() {
1307
+ GM1SystemOn(scheduleTime) {
1308
+ scheduleTime ??= this.audioContext.currentTime;
1309
+ this.mode = "GM1";
1281
1310
  for (let i = 0; i < this.channels.length; i++) {
1311
+ this.allSoundOff(i, 0, scheduleTime);
1282
1312
  const channel = this.channels[i];
1283
1313
  channel.bank = 0;
1314
+ channel.isDrum = false;
1284
1315
  }
1285
1316
  this.channels[9].bank = 128;
1317
+ this.channels[9].isDrum = true;
1286
1318
  }
1287
1319
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1288
1320
  switch (data[2]) {
@@ -1325,13 +1357,20 @@ class MidyGMLite {
1325
1357
  }
1326
1358
  scheduleTask(callback, scheduleTime) {
1327
1359
  return new Promise((resolve) => {
1328
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1360
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1361
+ buffer: this.schedulerBuffer,
1362
+ });
1363
+ bufferSource.connect(this.scheduler);
1329
1364
  bufferSource.onended = () => {
1330
- callback();
1331
- resolve();
1365
+ try {
1366
+ callback();
1367
+ }
1368
+ finally {
1369
+ bufferSource.disconnect();
1370
+ resolve();
1371
+ }
1332
1372
  };
1333
1373
  bufferSource.start(scheduleTime);
1334
- bufferSource.stop(scheduleTime);
1335
1374
  });
1336
1375
  }
1337
1376
  }
@@ -1342,6 +1381,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1342
1381
  writable: true,
1343
1382
  value: {
1344
1383
  currentBufferSource: null,
1384
+ isDrum: false,
1345
1385
  detune: 0,
1346
1386
  program: 0,
1347
1387
  bank: 0,
@@ -1349,5 +1389,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1349
1389
  dataLSB: 0,
1350
1390
  rpnMSB: 127,
1351
1391
  rpnLSB: 127,
1392
+ modulationDepthRange: 50, // cent
1352
1393
  }
1353
1394
  });