@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.
package/esm/midy-GM2.js CHANGED
@@ -239,6 +239,12 @@ const volumeEnvelopeKeys = [
239
239
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
240
240
  export class MidyGM2 {
241
241
  constructor(audioContext, options = this.defaultOptions) {
242
+ Object.defineProperty(this, "mode", {
243
+ enumerable: true,
244
+ configurable: true,
245
+ writable: true,
246
+ value: "GM2"
247
+ });
242
248
  Object.defineProperty(this, "ticksPerBeat", {
243
249
  enumerable: true,
244
250
  configurable: true,
@@ -417,6 +423,11 @@ export class MidyGM2 {
417
423
  this.audioContext = audioContext;
418
424
  this.options = { ...this.defaultOptions, ...options };
419
425
  this.masterVolume = new GainNode(audioContext);
426
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
427
+ this.schedulerBuffer = new AudioBuffer({
428
+ length: 1,
429
+ sampleRate: audioContext.sampleRate,
430
+ });
420
431
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
421
432
  this.controlChangeHandlers = this.createControlChangeHandlers();
422
433
  this.channels = this.createChannels(audioContext);
@@ -425,6 +436,7 @@ export class MidyGM2 {
425
436
  this.chorusEffect.output.connect(this.masterVolume);
426
437
  this.reverbEffect.output.connect(this.masterVolume);
427
438
  this.masterVolume.connect(audioContext.destination);
439
+ this.scheduler.connect(audioContext.destination);
428
440
  this.GM2SystemOn();
429
441
  }
430
442
  initSoundFontTable() {
@@ -523,10 +535,24 @@ export class MidyGM2 {
523
535
  return audioBuffer;
524
536
  }
525
537
  }
526
- createNoteBufferNode(audioBuffer, voiceParams) {
538
+ calcLoopMode(channel, note, voiceParams) {
539
+ if (channel.isDrum) {
540
+ const noteNumber = note.noteNumber;
541
+ if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
542
+ return true;
543
+ }
544
+ else {
545
+ return false;
546
+ }
547
+ }
548
+ else {
549
+ return voiceParams.sampleModes % 2 !== 0;
550
+ }
551
+ }
552
+ createBufferSource(channel, note, voiceParams, audioBuffer) {
527
553
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
528
554
  bufferSource.buffer = audioBuffer;
529
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
555
+ bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
530
556
  if (bufferSource.loop) {
531
557
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
532
558
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -1003,7 +1029,9 @@ export class MidyGM2 {
1003
1029
  return 8.176 * this.centToRate(cent);
1004
1030
  }
1005
1031
  calcChannelDetune(channel) {
1006
- const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
1032
+ const masterTuning = channel.isDrum
1033
+ ? 0
1034
+ : this.masterCoarseTuning + this.masterFineTuning;
1007
1035
  const channelTuning = channel.coarseTuning + channel.fineTuning;
1008
1036
  const tuning = masterTuning + channelTuning;
1009
1037
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
@@ -1029,9 +1057,8 @@ export class MidyGM2 {
1029
1057
  .setValueAtTime(detune, scheduleTime);
1030
1058
  }
1031
1059
  getPortamentoTime(channel) {
1032
- const factor = 5 * Math.log(10) / 127;
1033
- const time = channel.state.portamentoTime;
1034
- return Math.log(time) / factor;
1060
+ const factor = 5 * Math.log(10) * 127;
1061
+ return channel.state.portamentoTime * factor;
1035
1062
  }
1036
1063
  setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
1037
1064
  const { voiceParams, startTime } = note;
@@ -1192,7 +1219,7 @@ export class MidyGM2 {
1192
1219
  const voiceParams = voice.getAllParams(controllerState);
1193
1220
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1194
1221
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1195
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
1222
+ note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
1196
1223
  note.volumeNode = new GainNode(this.audioContext);
1197
1224
  note.gainL = new GainNode(this.audioContext);
1198
1225
  note.gainR = new GainNode(this.audioContext);
@@ -1236,14 +1263,21 @@ export class MidyGM2 {
1236
1263
  note.bufferSource.start(startTime);
1237
1264
  return note;
1238
1265
  }
1239
- calcBank(channel, channelNumber) {
1240
- if (channel.bankMSB === 121) {
1241
- return 0;
1242
- }
1243
- if (channelNumber % 9 <= 1 && channel.bankMSB === 120) {
1244
- return 128;
1266
+ calcBank(channel) {
1267
+ switch (this.mode) {
1268
+ case "GM1":
1269
+ if (channel.isDrum)
1270
+ return 128;
1271
+ return 0;
1272
+ case "GM2":
1273
+ if (channel.bankMSB === 121)
1274
+ return 0;
1275
+ if (channel.isDrum)
1276
+ return 128;
1277
+ return channel.bank;
1278
+ default:
1279
+ return channel.bank;
1245
1280
  }
1246
- return channel.bank;
1247
1281
  }
1248
1282
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1249
1283
  const channel = this.channels[channelNumber];
@@ -1267,7 +1301,7 @@ export class MidyGM2 {
1267
1301
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1268
1302
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1269
1303
  const [prevNote, prevChannelNumber] = prevEntry;
1270
- if (!prevNote.ending) {
1304
+ if (prevNote && !prevNote.ending) {
1271
1305
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1272
1306
  startTime, true, // force
1273
1307
  undefined);
@@ -1416,9 +1450,21 @@ export class MidyGM2 {
1416
1450
  const channel = this.channels[channelNumber];
1417
1451
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1418
1452
  channel.program = program;
1453
+ if (this.mode === "GM2") {
1454
+ switch (channel.bankMSB) {
1455
+ case 120:
1456
+ channel.isDrum = true;
1457
+ break;
1458
+ case 121:
1459
+ channel.isDrum = false;
1460
+ break;
1461
+ }
1462
+ }
1419
1463
  }
1420
1464
  handleChannelPressure(channelNumber, value, scheduleTime) {
1421
1465
  const channel = this.channels[channelNumber];
1466
+ if (channel.isDrum)
1467
+ return;
1422
1468
  const prev = channel.state.channelPressure;
1423
1469
  const next = value / 127;
1424
1470
  channel.state.channelPressure = next;
@@ -1437,8 +1483,10 @@ export class MidyGM2 {
1437
1483
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
1438
1484
  }
1439
1485
  setPitchBend(channelNumber, value, scheduleTime) {
1440
- scheduleTime ??= this.audioContext.currentTime;
1441
1486
  const channel = this.channels[channelNumber];
1487
+ if (channel.isDrum)
1488
+ return;
1489
+ scheduleTime ??= this.audioContext.currentTime;
1442
1490
  const state = channel.state;
1443
1491
  const prev = state.pitchWheel * 2 - 1;
1444
1492
  const next = (value - 8192) / 8192;
@@ -1710,15 +1758,16 @@ export class MidyGM2 {
1710
1758
  });
1711
1759
  }
1712
1760
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1713
- scheduleTime ??= this.audioContext.currentTime;
1714
1761
  const channel = this.channels[channelNumber];
1762
+ if (channel.isDrum)
1763
+ return;
1764
+ scheduleTime ??= this.audioContext.currentTime;
1715
1765
  channel.state.modulationDepth = modulation / 127;
1716
1766
  this.updateModulation(channel, scheduleTime);
1717
1767
  }
1718
1768
  setPortamentoTime(channelNumber, portamentoTime) {
1719
1769
  const channel = this.channels[channelNumber];
1720
- const factor = 5 * Math.log(10) / 127;
1721
- channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1770
+ channel.state.portamentoTime = portamentoTime / 127;
1722
1771
  }
1723
1772
  setKeyBasedVolume(channel, scheduleTime) {
1724
1773
  this.processScheduledNotes(channel, (note) => {
@@ -1790,8 +1839,10 @@ export class MidyGM2 {
1790
1839
  .setValueAtTime(volume * gainRight, scheduleTime);
1791
1840
  }
1792
1841
  setSustainPedal(channelNumber, value, scheduleTime) {
1793
- scheduleTime ??= this.audioContext.currentTime;
1794
1842
  const channel = this.channels[channelNumber];
1843
+ if (channel.isDrum)
1844
+ return;
1845
+ scheduleTime ??= this.audioContext.currentTime;
1795
1846
  channel.state.sustainPedal = value / 127;
1796
1847
  if (64 <= value) {
1797
1848
  this.processScheduledNotes(channel, (note) => {
@@ -1803,11 +1854,16 @@ export class MidyGM2 {
1803
1854
  }
1804
1855
  }
1805
1856
  setPortamento(channelNumber, value) {
1806
- this.channels[channelNumber].state.portamento = value / 127;
1857
+ const channel = this.channels[channelNumber];
1858
+ if (channel.isDrum)
1859
+ return;
1860
+ channel.state.portamento = value / 127;
1807
1861
  }
1808
1862
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1809
- scheduleTime ??= this.audioContext.currentTime;
1810
1863
  const channel = this.channels[channelNumber];
1864
+ if (channel.isDrum)
1865
+ return;
1866
+ scheduleTime ??= this.audioContext.currentTime;
1811
1867
  channel.state.sostenutoPedal = value / 127;
1812
1868
  if (64 <= value) {
1813
1869
  channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
@@ -1816,9 +1872,22 @@ export class MidyGM2 {
1816
1872
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
1817
1873
  }
1818
1874
  }
1819
- setSoftPedal(channelNumber, softPedal, _scheduleTime) {
1875
+ setSoftPedal(channelNumber, softPedal, scheduleTime) {
1820
1876
  const channel = this.channels[channelNumber];
1877
+ if (channel.isDrum)
1878
+ return;
1879
+ scheduleTime ??= this.audioContext.currentTime;
1821
1880
  channel.state.softPedal = softPedal / 127;
1881
+ this.processScheduledNotes(channel, (note) => {
1882
+ if (note.portamento) {
1883
+ this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1884
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1885
+ }
1886
+ else {
1887
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1888
+ this.setFilterEnvelope(channel, note, scheduleTime);
1889
+ }
1890
+ });
1822
1891
  }
1823
1892
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
1824
1893
  scheduleTime ??= this.audioContext.currentTime;
@@ -1947,8 +2016,10 @@ export class MidyGM2 {
1947
2016
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1948
2017
  }
1949
2018
  setPitchBendRange(channelNumber, value, scheduleTime) {
1950
- scheduleTime ??= this.audioContext.currentTime;
1951
2019
  const channel = this.channels[channelNumber];
2020
+ if (channel.isDrum)
2021
+ return;
2022
+ scheduleTime ??= this.audioContext.currentTime;
1952
2023
  const state = channel.state;
1953
2024
  const prev = state.pitchWheelSensitivity;
1954
2025
  const next = value / 128;
@@ -1964,8 +2035,10 @@ export class MidyGM2 {
1964
2035
  this.setFineTuning(channelNumber, fineTuning, scheduleTime);
1965
2036
  }
1966
2037
  setFineTuning(channelNumber, value, scheduleTime) {
1967
- scheduleTime ??= this.audioContext.currentTime;
1968
2038
  const channel = this.channels[channelNumber];
2039
+ if (channel.isDrum)
2040
+ return;
2041
+ scheduleTime ??= this.audioContext.currentTime;
1969
2042
  const prev = channel.fineTuning;
1970
2043
  const next = (value - 8192) / 8.192; // cent
1971
2044
  channel.fineTuning = next;
@@ -1979,8 +2052,10 @@ export class MidyGM2 {
1979
2052
  this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
1980
2053
  }
1981
2054
  setCoarseTuning(channelNumber, value, scheduleTime) {
1982
- scheduleTime ??= this.audioContext.currentTime;
1983
2055
  const channel = this.channels[channelNumber];
2056
+ if (channel.isDrum)
2057
+ return;
2058
+ scheduleTime ??= this.audioContext.currentTime;
1984
2059
  const prev = channel.coarseTuning;
1985
2060
  const next = (value - 64) * 100; // cent
1986
2061
  channel.coarseTuning = next;
@@ -1994,8 +2069,10 @@ export class MidyGM2 {
1994
2069
  this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
1995
2070
  }
1996
2071
  setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
1997
- scheduleTime ??= this.audioContext.currentTime;
1998
2072
  const channel = this.channels[channelNumber];
2073
+ if (channel.isDrum)
2074
+ return;
2075
+ scheduleTime ??= this.audioContext.currentTime;
1999
2076
  channel.modulationDepthRange = modulationDepthRange;
2000
2077
  this.updateModulation(channel, scheduleTime);
2001
2078
  }
@@ -2063,12 +2140,12 @@ export class MidyGM2 {
2063
2140
  case 9:
2064
2141
  switch (data[3]) {
2065
2142
  case 1:
2066
- this.GM1SystemOn();
2143
+ this.GM1SystemOn(scheduleTime);
2067
2144
  break;
2068
2145
  case 2: // GM System Off
2069
2146
  break;
2070
2147
  case 3:
2071
- this.GM2SystemOn();
2148
+ this.GM2SystemOn(scheduleTime);
2072
2149
  break;
2073
2150
  default:
2074
2151
  console.warn(`Unsupported Exclusive Message: ${data}`);
@@ -2078,25 +2155,35 @@ export class MidyGM2 {
2078
2155
  console.warn(`Unsupported Exclusive Message: ${data}`);
2079
2156
  }
2080
2157
  }
2081
- GM1SystemOn() {
2158
+ GM1SystemOn(scheduleTime) {
2159
+ scheduleTime ??= this.audioContext.currentTime;
2160
+ this.mode = "GM1";
2082
2161
  for (let i = 0; i < this.channels.length; i++) {
2162
+ this.allSoundOff(i, 0, scheduleTime);
2083
2163
  const channel = this.channels[i];
2084
2164
  channel.bankMSB = 0;
2085
2165
  channel.bankLSB = 0;
2086
2166
  channel.bank = 0;
2167
+ channel.isDrum = false;
2087
2168
  }
2088
2169
  this.channels[9].bankMSB = 1;
2089
2170
  this.channels[9].bank = 128;
2171
+ this.channels[9].isDrum = true;
2090
2172
  }
2091
- GM2SystemOn() {
2173
+ GM2SystemOn(scheduleTime) {
2174
+ scheduleTime ??= this.audioContext.currentTime;
2175
+ this.mode = "GM2";
2092
2176
  for (let i = 0; i < this.channels.length; i++) {
2177
+ this.allSoundOff(i, 0, scheduleTime);
2093
2178
  const channel = this.channels[i];
2094
2179
  channel.bankMSB = 121;
2095
2180
  channel.bankLSB = 0;
2096
2181
  channel.bank = 121 * 128;
2182
+ channel.isDrum = false;
2097
2183
  }
2098
2184
  this.channels[9].bankMSB = 120;
2099
2185
  this.channels[9].bank = 120 * 128;
2186
+ this.channels[9].isDrum = true;
2100
2187
  }
2101
2188
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
2102
2189
  switch (data[2]) {
@@ -2159,8 +2246,14 @@ export class MidyGM2 {
2159
2246
  const prev = this.masterFineTuning;
2160
2247
  const next = (value - 8192) / 8.192; // cent
2161
2248
  this.masterFineTuning = next;
2162
- channel.detune += next - prev;
2163
- this.updateChannelDetune(channel, scheduleTime);
2249
+ const detuneChange = next - prev;
2250
+ for (let i = 0; i < this.channels.length; i++) {
2251
+ const channel = this.channels[i];
2252
+ if (channel.isDrum)
2253
+ continue;
2254
+ channel.detune += detuneChange;
2255
+ this.updateChannelDetune(channel, scheduleTime);
2256
+ }
2164
2257
  }
2165
2258
  handleMasterCoarseTuningSysEx(data, scheduleTime) {
2166
2259
  const coarseTuning = data[4];
@@ -2170,8 +2263,14 @@ export class MidyGM2 {
2170
2263
  const prev = this.masterCoarseTuning;
2171
2264
  const next = (value - 64) * 100; // cent
2172
2265
  this.masterCoarseTuning = next;
2173
- channel.detune += next - prev;
2174
- this.updateChannelDetune(channel, scheduleTime);
2266
+ const detuneChange = next - prev;
2267
+ for (let i = 0; i < this.channels.length; i++) {
2268
+ const channel = this.channels[i];
2269
+ if (channel.isDrum)
2270
+ continue;
2271
+ channel.detune += detuneChange;
2272
+ this.updateChannelDetune(channel, scheduleTime);
2273
+ }
2175
2274
  }
2176
2275
  handleGlobalParameterControlSysEx(data, scheduleTime) {
2177
2276
  if (data[7] === 1) {
@@ -2499,13 +2598,20 @@ export class MidyGM2 {
2499
2598
  }
2500
2599
  scheduleTask(callback, scheduleTime) {
2501
2600
  return new Promise((resolve) => {
2502
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
2601
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
2602
+ buffer: this.schedulerBuffer,
2603
+ });
2604
+ bufferSource.connect(this.scheduler);
2503
2605
  bufferSource.onended = () => {
2504
- callback();
2505
- resolve();
2606
+ try {
2607
+ callback();
2608
+ }
2609
+ finally {
2610
+ bufferSource.disconnect();
2611
+ resolve();
2612
+ }
2506
2613
  };
2507
2614
  bufferSource.start(scheduleTime);
2508
- bufferSource.stop(scheduleTime);
2509
2615
  });
2510
2616
  }
2511
2617
  }
@@ -2515,6 +2621,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2515
2621
  writable: true,
2516
2622
  value: {
2517
2623
  currentBufferSource: null,
2624
+ isDrum: false,
2518
2625
  detune: 0,
2519
2626
  program: 0,
2520
2627
  bank: 121 * 128,
@@ -2525,8 +2632,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2525
2632
  rpnMSB: 127,
2526
2633
  rpnLSB: 127,
2527
2634
  mono: false, // CC#124, CC#125
2635
+ modulationDepthRange: 50, // cent
2528
2636
  fineTuning: 0, // cb
2529
2637
  coarseTuning: 0, // cb
2530
- modulationDepthRange: 50, // cent
2531
2638
  }
2532
2639
  });
@@ -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"}
@@ -170,6 +170,12 @@ const volumeEnvelopeKeys = [
170
170
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
171
171
  export class MidyGMLite {
172
172
  constructor(audioContext) {
173
+ Object.defineProperty(this, "mode", {
174
+ enumerable: true,
175
+ configurable: true,
176
+ writable: true,
177
+ value: "GM1"
178
+ });
173
179
  Object.defineProperty(this, "ticksPerBeat", {
174
180
  enumerable: true,
175
181
  configurable: true,
@@ -292,10 +298,16 @@ export class MidyGMLite {
292
298
  });
293
299
  this.audioContext = audioContext;
294
300
  this.masterVolume = new GainNode(audioContext);
301
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
302
+ this.schedulerBuffer = new AudioBuffer({
303
+ length: 1,
304
+ sampleRate: audioContext.sampleRate,
305
+ });
295
306
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
296
307
  this.controlChangeHandlers = this.createControlChangeHandlers();
297
308
  this.channels = this.createChannels(audioContext);
298
309
  this.masterVolume.connect(audioContext.destination);
310
+ this.scheduler.connect(audioContext.destination);
299
311
  this.GM1SystemOn();
300
312
  }
301
313
  initSoundFontTable() {
@@ -389,10 +401,18 @@ export class MidyGMLite {
389
401
  return audioBuffer;
390
402
  }
391
403
  }
392
- createNoteBufferNode(audioBuffer, voiceParams) {
404
+ calcLoopMode(channel, voiceParams) {
405
+ if (channel.isDrum) {
406
+ return false;
407
+ }
408
+ else {
409
+ return voiceParams.sampleModes % 2 !== 0;
410
+ }
411
+ }
412
+ createBufferSource(channel, voiceParams, audioBuffer) {
393
413
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
394
414
  bufferSource.buffer = audioBuffer;
395
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
415
+ bufferSource.loop = this.calcLoopMode(channel, voiceParams);
396
416
  if (bufferSource.loop) {
397
417
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
398
418
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -803,7 +823,7 @@ export class MidyGMLite {
803
823
  const voiceParams = voice.getAllParams(controllerState);
804
824
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
805
825
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
806
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
826
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
807
827
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
808
828
  note.filterNode = new BiquadFilterNode(this.audioContext, {
809
829
  type: "lowpass",
@@ -842,7 +862,7 @@ export class MidyGMLite {
842
862
  if (this.exclusiveClassMap.has(exclusiveClass)) {
843
863
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
844
864
  const [prevNote, prevChannelNumber] = prevEntry;
845
- if (!prevNote.ending) {
865
+ if (prevNote && !prevNote.ending) {
846
866
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
847
867
  startTime, true);
848
868
  }
@@ -950,8 +970,10 @@ export class MidyGMLite {
950
970
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
951
971
  }
952
972
  setPitchBend(channelNumber, value, scheduleTime) {
953
- scheduleTime ??= this.audioContext.currentTime;
954
973
  const channel = this.channels[channelNumber];
974
+ if (channel.isDrum)
975
+ return;
976
+ scheduleTime ??= this.audioContext.currentTime;
955
977
  const state = channel.state;
956
978
  const prev = state.pitchWheel * 2 - 1;
957
979
  const next = (value - 8192) / 8192;
@@ -1100,7 +1122,6 @@ export class MidyGMLite {
1100
1122
  }
1101
1123
  }
1102
1124
  updateModulation(channel, scheduleTime) {
1103
- scheduleTime ??= this.audioContext.currentTime;
1104
1125
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1105
1126
  this.processScheduledNotes(channel, (note) => {
1106
1127
  if (note.modulationDepth) {
@@ -1113,8 +1134,10 @@ export class MidyGMLite {
1113
1134
  });
1114
1135
  }
1115
1136
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1116
- scheduleTime ??= this.audioContext.currentTime;
1117
1137
  const channel = this.channels[channelNumber];
1138
+ if (channel.isDrum)
1139
+ return;
1140
+ scheduleTime ??= this.audioContext.currentTime;
1118
1141
  channel.state.modulationDepth = modulation / 127;
1119
1142
  this.updateModulation(channel, scheduleTime);
1120
1143
  }
@@ -1159,8 +1182,10 @@ export class MidyGMLite {
1159
1182
  .setValueAtTime(volume * gainRight, scheduleTime);
1160
1183
  }
1161
1184
  setSustainPedal(channelNumber, value, scheduleTime) {
1162
- scheduleTime ??= this.audioContext.currentTime;
1163
1185
  const channel = this.channels[channelNumber];
1186
+ if (channel.isDrum)
1187
+ return;
1188
+ scheduleTime ??= this.audioContext.currentTime;
1164
1189
  channel.state.sustainPedal = value / 127;
1165
1190
  if (64 <= value) {
1166
1191
  this.processScheduledNotes(channel, (note) => {
@@ -1217,8 +1242,10 @@ export class MidyGMLite {
1217
1242
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1218
1243
  }
1219
1244
  setPitchBendRange(channelNumber, value, scheduleTime) {
1220
- scheduleTime ??= this.audioContext.currentTime;
1221
1245
  const channel = this.channels[channelNumber];
1246
+ if (channel.isDrum)
1247
+ return;
1248
+ scheduleTime ??= this.audioContext.currentTime;
1222
1249
  const state = channel.state;
1223
1250
  const prev = state.pitchWheelSensitivity;
1224
1251
  const next = value / 128;
@@ -1257,12 +1284,12 @@ export class MidyGMLite {
1257
1284
  scheduleTime ??= this.audioContext.currentTime;
1258
1285
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1259
1286
  }
1260
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1287
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1261
1288
  switch (data[2]) {
1262
1289
  case 9:
1263
1290
  switch (data[3]) {
1264
1291
  case 1:
1265
- this.GM1SystemOn();
1292
+ this.GM1SystemOn(scheduleTime);
1266
1293
  break;
1267
1294
  case 2: // GM System Off
1268
1295
  break;
@@ -1274,12 +1301,17 @@ export class MidyGMLite {
1274
1301
  console.warn(`Unsupported Exclusive Message: ${data}`);
1275
1302
  }
1276
1303
  }
1277
- GM1SystemOn() {
1304
+ GM1SystemOn(scheduleTime) {
1305
+ scheduleTime ??= this.audioContext.currentTime;
1306
+ this.mode = "GM1";
1278
1307
  for (let i = 0; i < this.channels.length; i++) {
1308
+ this.allSoundOff(i, 0, scheduleTime);
1279
1309
  const channel = this.channels[i];
1280
1310
  channel.bank = 0;
1311
+ channel.isDrum = false;
1281
1312
  }
1282
1313
  this.channels[9].bank = 128;
1314
+ this.channels[9].isDrum = true;
1283
1315
  }
1284
1316
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1285
1317
  switch (data[2]) {
@@ -1322,13 +1354,20 @@ export class MidyGMLite {
1322
1354
  }
1323
1355
  scheduleTask(callback, scheduleTime) {
1324
1356
  return new Promise((resolve) => {
1325
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1357
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1358
+ buffer: this.schedulerBuffer,
1359
+ });
1360
+ bufferSource.connect(this.scheduler);
1326
1361
  bufferSource.onended = () => {
1327
- callback();
1328
- resolve();
1362
+ try {
1363
+ callback();
1364
+ }
1365
+ finally {
1366
+ bufferSource.disconnect();
1367
+ resolve();
1368
+ }
1329
1369
  };
1330
1370
  bufferSource.start(scheduleTime);
1331
- bufferSource.stop(scheduleTime);
1332
1371
  });
1333
1372
  }
1334
1373
  }
@@ -1338,6 +1377,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1338
1377
  writable: true,
1339
1378
  value: {
1340
1379
  currentBufferSource: null,
1380
+ isDrum: false,
1341
1381
  detune: 0,
1342
1382
  program: 0,
1343
1383
  bank: 0,
@@ -1345,5 +1385,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1345
1385
  dataLSB: 0,
1346
1386
  rpnMSB: 127,
1347
1387
  rpnLSB: 127,
1388
+ modulationDepthRange: 50, // cent
1348
1389
  }
1349
1390
  });