@marmooo/midy 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/midy.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
package/esm/midy.js CHANGED
@@ -1,5 +1,57 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
+ // 2-3 times faster than Map
4
+ class SparseMap {
5
+ constructor(size) {
6
+ this.data = new Array(size);
7
+ this.activeIndices = [];
8
+ }
9
+ set(key, value) {
10
+ if (this.data[key] === undefined) {
11
+ this.activeIndices.push(key);
12
+ }
13
+ this.data[key] = value;
14
+ }
15
+ get(key) {
16
+ return this.data[key];
17
+ }
18
+ delete(key) {
19
+ if (this.data[key] !== undefined) {
20
+ this.data[key] = undefined;
21
+ const index = this.activeIndices.indexOf(key);
22
+ if (index !== -1) {
23
+ this.activeIndices.splice(index, 1);
24
+ }
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ has(key) {
30
+ return this.data[key] !== undefined;
31
+ }
32
+ get size() {
33
+ return this.activeIndices.length;
34
+ }
35
+ clear() {
36
+ for (let i = 0; i < this.activeIndices.length; i++) {
37
+ const key = this.activeIndices[i];
38
+ this.data[key] = undefined;
39
+ }
40
+ this.activeIndices = [];
41
+ }
42
+ *[Symbol.iterator]() {
43
+ for (let i = 0; i < this.activeIndices.length; i++) {
44
+ const key = this.activeIndices[i];
45
+ yield [key, this.data[key]];
46
+ }
47
+ }
48
+ forEach(callback) {
49
+ for (let i = 0; i < this.activeIndices.length; i++) {
50
+ const key = this.activeIndices[i];
51
+ callback(this.data[key], key, this);
52
+ }
53
+ }
54
+ }
3
55
  class Note {
4
56
  constructor(noteNumber, velocity, startTime, voice, voiceParams) {
5
57
  Object.defineProperty(this, "bufferSource", {
@@ -287,6 +339,18 @@ export class Midy {
287
339
  writable: true,
288
340
  value: this.initSoundFontTable()
289
341
  });
342
+ Object.defineProperty(this, "audioBufferCounter", {
343
+ enumerable: true,
344
+ configurable: true,
345
+ writable: true,
346
+ value: new Map()
347
+ });
348
+ Object.defineProperty(this, "audioBufferCache", {
349
+ enumerable: true,
350
+ configurable: true,
351
+ writable: true,
352
+ value: new Map()
353
+ });
290
354
  Object.defineProperty(this, "isPlaying", {
291
355
  enumerable: true,
292
356
  configurable: true,
@@ -339,7 +403,7 @@ export class Midy {
339
403
  enumerable: true,
340
404
  configurable: true,
341
405
  writable: true,
342
- value: new Map()
406
+ value: new SparseMap(128)
343
407
  });
344
408
  Object.defineProperty(this, "defaultOptions", {
345
409
  enumerable: true,
@@ -379,7 +443,7 @@ export class Midy {
379
443
  initSoundFontTable() {
380
444
  const table = new Array(128);
381
445
  for (let i = 0; i < 128; i++) {
382
- table[i] = new Map();
446
+ table[i] = new SparseMap(128);
383
447
  }
384
448
  return table;
385
449
  }
@@ -433,8 +497,8 @@ export class Midy {
433
497
  state: new ControllerState(),
434
498
  controlTable: this.initControlTable(),
435
499
  ...this.setChannelAudioNodes(audioContext),
436
- scheduledNotes: new Map(),
437
- sostenutoNotes: new Map(),
500
+ scheduledNotes: new SparseMap(128),
501
+ sostenutoNotes: new SparseMap(128),
438
502
  };
439
503
  });
440
504
  return channels;
@@ -468,9 +532,8 @@ export class Midy {
468
532
  return audioBuffer;
469
533
  }
470
534
  }
471
- async createNoteBufferNode(voiceParams, isSF3) {
535
+ createNoteBufferNode(audioBuffer, voiceParams) {
472
536
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
473
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
474
537
  bufferSource.buffer = audioBuffer;
475
538
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
476
539
  if (bufferSource.loop) {
@@ -562,6 +625,7 @@ export class Midy {
562
625
  await Promise.all(this.notePromises);
563
626
  this.notePromises = [];
564
627
  this.exclusiveClassMap.clear();
628
+ this.audioBufferCache.clear();
565
629
  resolve();
566
630
  return;
567
631
  }
@@ -577,8 +641,9 @@ export class Midy {
577
641
  }
578
642
  else if (this.isStopping) {
579
643
  await this.stopNotes(0, true);
580
- this.exclusiveClassMap.clear();
581
644
  this.notePromises = [];
645
+ this.exclusiveClassMap.clear();
646
+ this.audioBufferCache.clear();
582
647
  resolve();
583
648
  this.isStopping = false;
584
649
  this.isPaused = false;
@@ -609,6 +674,9 @@ export class Midy {
609
674
  secondToTicks(second, secondsPerBeat) {
610
675
  return second * this.ticksPerBeat / secondsPerBeat;
611
676
  }
677
+ getAudioBufferId(programNumber, noteNumber, velocity) {
678
+ return `${programNumber}:${noteNumber}:${velocity}`;
679
+ }
612
680
  extractMidiData(midi) {
613
681
  const instruments = new Set();
614
682
  const timeline = [];
@@ -630,6 +698,8 @@ export class Midy {
630
698
  switch (event.type) {
631
699
  case "noteOn": {
632
700
  const channel = tmpChannels[event.channel];
701
+ const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
702
+ this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
633
703
  if (channel.programNumber < 0) {
634
704
  channel.programNumber = event.programNumber;
635
705
  switch (channel.bankMSB) {
@@ -679,6 +749,10 @@ export class Midy {
679
749
  timeline.push(event);
680
750
  }
681
751
  }
752
+ for (const [audioBufferId, count] of this.audioBufferCounter) {
753
+ if (count === 1)
754
+ this.audioBufferCounter.delete(audioBufferId);
755
+ }
682
756
  const priority = {
683
757
  controller: 0,
684
758
  sysEx: 1,
@@ -772,7 +846,7 @@ export class Midy {
772
846
  return this.resumeTime + now - this.startTime - this.startDelay;
773
847
  }
774
848
  getActiveNotes(channel, time) {
775
- const activeNotes = new Map();
849
+ const activeNotes = new SparseMap(128);
776
850
  channel.scheduledNotes.forEach((noteList) => {
777
851
  const activeNote = this.getActiveNote(noteList, time);
778
852
  if (activeNote) {
@@ -1110,12 +1184,31 @@ export class Midy {
1110
1184
  note.vibratoLFO.connect(note.vibratoDepth);
1111
1185
  note.vibratoDepth.connect(note.bufferSource.detune);
1112
1186
  }
1187
+ async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
1188
+ const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
1189
+ const cache = this.audioBufferCache.get(audioBufferId);
1190
+ if (cache) {
1191
+ cache.counter += 1;
1192
+ if (cache.maxCount <= cache.counter) {
1193
+ this.audioBufferCache.delete(audioBufferId);
1194
+ }
1195
+ return cache.audioBuffer;
1196
+ }
1197
+ else {
1198
+ const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1199
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1200
+ const cache = { audioBuffer, maxCount, counter: 1 };
1201
+ this.audioBufferCache.set(audioBufferId, cache);
1202
+ return audioBuffer;
1203
+ }
1204
+ }
1113
1205
  async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1114
1206
  const state = channel.state;
1115
1207
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1116
1208
  const voiceParams = voice.getAllParams(controllerState);
1117
1209
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1118
- note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
1210
+ const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1211
+ note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
1119
1212
  note.volumeNode = new GainNode(this.audioContext);
1120
1213
  note.gainL = new GainNode(this.audioContext);
1121
1214
  note.gainR = new GainNode(this.audioContext);
@@ -1175,10 +1268,10 @@ export class Midy {
1175
1268
  if (soundFontIndex === undefined)
1176
1269
  return;
1177
1270
  const soundFont = this.soundFonts[soundFontIndex];
1178
- const isSF3 = soundFont.parsed.info.version.major === 3;
1179
1271
  const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1180
1272
  if (!voice)
1181
1273
  return;
1274
+ const isSF3 = soundFont.parsed.info.version.major === 3;
1182
1275
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1183
1276
  note.gainL.connect(channel.gainL);
1184
1277
  note.gainR.connect(channel.gainR);
@@ -1362,6 +1455,7 @@ export class Midy {
1362
1455
  channel.program = program;
1363
1456
  }
1364
1457
  handleChannelPressure(channelNumber, value) {
1458
+ const now = this.audioContext.currentTime;
1365
1459
  const channel = this.channels[channelNumber];
1366
1460
  const prev = channel.state.channelPressure;
1367
1461
  const next = value / 127;
@@ -1371,13 +1465,8 @@ export class Midy {
1371
1465
  channel.detune += pressureDepth * (next - prev);
1372
1466
  }
1373
1467
  const table = channel.channelPressureTable;
1374
- channel.scheduledNotes.forEach((noteList) => {
1375
- for (let i = 0; i < noteList.length; i++) {
1376
- const note = noteList[i];
1377
- if (!note)
1378
- continue;
1379
- this.applyDestinationSettings(channel, note, table);
1380
- }
1468
+ this.getActiveNotes(channel, now).forEach((note) => {
1469
+ this.applyDestinationSettings(channel, note, table);
1381
1470
  });
1382
1471
  // this.applyVoiceParams(channel, 13);
1383
1472
  }
@@ -1788,8 +1877,7 @@ export class Midy {
1788
1877
  channel.state.sostenutoPedal = value / 127;
1789
1878
  if (64 <= value) {
1790
1879
  const now = this.audioContext.currentTime;
1791
- const activeNotes = this.getActiveNotes(channel, now);
1792
- channel.sostenutoNotes = new Map(activeNotes);
1880
+ channel.sostenutoNotes = this.getActiveNotes(channel, now);
1793
1881
  }
1794
1882
  else {
1795
1883
  this.releaseSostenutoPedal(channelNumber, value);
@@ -2170,7 +2258,10 @@ export class Midy {
2170
2258
  switch (data[3]) {
2171
2259
  case 8:
2172
2260
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2173
- return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
2261
+ return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
2262
+ case 9:
2263
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2264
+ return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
2174
2265
  default:
2175
2266
  console.warn(`Unsupported Exclusive Message: ${data}`);
2176
2267
  }
@@ -2232,8 +2323,10 @@ export class Midy {
2232
2323
  case 8:
2233
2324
  switch (data[3]) {
2234
2325
  case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2235
- // TODO: realtime
2236
- return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
2326
+ return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
2327
+ case 9:
2328
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2329
+ return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
2237
2330
  default:
2238
2331
  console.warn(`Unsupported Exclusive Message: ${data}`);
2239
2332
  }
@@ -2503,8 +2596,26 @@ export class Midy {
2503
2596
  }
2504
2597
  return bitmap;
2505
2598
  }
2506
- handleScaleOctaveTuning1ByteFormatSysEx(data) {
2507
- if (data.length < 18) {
2599
+ handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
2600
+ if (data.length < 19) {
2601
+ console.error("Data length is too short");
2602
+ return;
2603
+ }
2604
+ const channelBitmap = this.getChannelBitmap(data);
2605
+ for (let i = 0; i < channelBitmap.length; i++) {
2606
+ if (!channelBitmap[i])
2607
+ continue;
2608
+ const channel = this.channels[i];
2609
+ for (let j = 0; j < 12; j++) {
2610
+ const centValue = data[j + 7] - 64;
2611
+ channel.scaleOctaveTuningTable[j] = centValue;
2612
+ }
2613
+ if (realtime)
2614
+ this.updateChannelDetune(channel);
2615
+ }
2616
+ }
2617
+ handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
2618
+ if (data.length < 31) {
2508
2619
  console.error("Data length is too short");
2509
2620
  return;
2510
2621
  }
@@ -2512,10 +2623,17 @@ export class Midy {
2512
2623
  for (let i = 0; i < channelBitmap.length; i++) {
2513
2624
  if (!channelBitmap[i])
2514
2625
  continue;
2626
+ const channel = this.channels[i];
2515
2627
  for (let j = 0; j < 12; j++) {
2516
- const value = data[j + 7] - 64; // cent
2517
- this.channels[i].scaleOctaveTuningTable[j] = value;
2628
+ const index = 7 + j * 2;
2629
+ const msb = data[index] & 0x7F;
2630
+ const lsb = data[index + 1] & 0x7F;
2631
+ const value14bit = msb * 128 + lsb;
2632
+ const centValue = (value14bit - 8192) / 8.192;
2633
+ channel.scaleOctaveTuningTable[j] = centValue;
2518
2634
  }
2635
+ if (realtime)
2636
+ this.updateChannelDetune(channel);
2519
2637
  }
2520
2638
  }
2521
2639
  applyDestinationSettings(channel, note, table) {
@@ -2666,7 +2784,7 @@ Object.defineProperty(Midy, "channelSettings", {
2666
2784
  value: {
2667
2785
  currentBufferSource: null,
2668
2786
  detune: 0,
2669
- scaleOctaveTuningTable: new Array(12).fill(0), // cent
2787
+ scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
2670
2788
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2671
2789
  polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2672
2790
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,6 +22,8 @@ export class MidyGM1 {
22
22
  resumeTime: number;
23
23
  soundFonts: any[];
24
24
  soundFontTable: any[];
25
+ audioBufferCounter: Map<any, any>;
26
+ audioBufferCache: Map<any, any>;
25
27
  isPlaying: boolean;
26
28
  isPausing: boolean;
27
29
  isPaused: boolean;
@@ -30,7 +32,7 @@ export class MidyGM1 {
30
32
  timeline: any[];
31
33
  instruments: any[];
32
34
  notePromises: any[];
33
- exclusiveClassMap: Map<any, any>;
35
+ exclusiveClassMap: SparseMap;
34
36
  audioContext: any;
35
37
  masterVolume: any;
36
38
  voiceParamsHandlers: {
@@ -71,12 +73,13 @@ export class MidyGM1 {
71
73
  };
72
74
  createChannels(audioContext: any): any[];
73
75
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
74
- createNoteBufferNode(voiceParams: any, isSF3: any): Promise<any>;
76
+ createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
75
77
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
76
78
  getQueueIndex(second: any): number;
77
79
  playNotes(): Promise<any>;
78
80
  ticksToSecond(ticks: any, secondsPerBeat: any): number;
79
81
  secondToTicks(second: any, secondsPerBeat: any): number;
82
+ getAudioBufferId(programNumber: any, noteNumber: any, velocity: any): string;
80
83
  extractMidiData(midi: any): {
81
84
  instruments: Set<any>;
82
85
  timeline: any[];
@@ -90,7 +93,7 @@ export class MidyGM1 {
90
93
  seekTo(second: any): void;
91
94
  calcTotalTime(): number;
92
95
  currentTime(): number;
93
- getActiveNotes(channel: any, time: any): Map<any, any>;
96
+ getActiveNotes(channel: any, time: any): SparseMap;
94
97
  getActiveNote(noteList: any, time: any): any;
95
98
  cbToRatio(cb: any): number;
96
99
  rateToCent(rate: any): number;
@@ -104,6 +107,7 @@ export class MidyGM1 {
104
107
  clampCutoffFrequency(frequency: any): number;
105
108
  setFilterEnvelope(note: any): void;
106
109
  startModulation(channel: any, note: any, startTime: any): void;
110
+ getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
107
111
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
108
112
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
109
113
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
@@ -186,6 +190,19 @@ export class MidyGM1 {
186
190
  handleSysEx(data: any): void;
187
191
  scheduleTask(callback: any, startTime: any): Promise<any>;
188
192
  }
193
+ declare class SparseMap {
194
+ constructor(size: any);
195
+ data: any[];
196
+ activeIndices: any[];
197
+ set(key: any, value: any): void;
198
+ get(key: any): any;
199
+ delete(key: any): boolean;
200
+ has(key: any): boolean;
201
+ get size(): number;
202
+ clear(): void;
203
+ forEach(callback: any): void;
204
+ [Symbol.iterator](): Generator<any[], void, unknown>;
205
+ }
189
206
  declare class Note {
190
207
  constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
191
208
  bufferSource: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqFA;IAoBE;;;;;;;;;;;;MAYE;IAEF,+BAQC;IAzCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,iCAA8B;IAiB5B,kBAAgC;IAChC,kBAA8C;IAC9C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,iEAUC;IAED,2EA+CC;IAED,mCAOC;IAED,0BAkDC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,wCAQC;IAED,4CAKC;IAED,mCAgBC;IAED,kCAqBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,gHA2BC;IAED,kGAgDC;IAED,0EAGC;IAED,qFA4BC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDASC;IAED,gDASC;IAED,gDASC;IAED,qCAMC;IAED,mCAQC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MA4CC;IAED,oFAMC;IAED,0DA6CC;IAED;;;;;;;;;;;;;MAeC;IAED,+EAWC;IAED,qCAeC;IAED,8DAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,wCAWC;IAED,sDAKC;IAED,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,kDAKC;IAED,wDASC;IAED,8CAKC;IAED,oDAOC;IAED,gDAKC;IAED,sDAOC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AArzCD;IAUE,0FAMC;IAfD,kBAAa;IACb,gBAAW;IACX,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IAGX,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAkJA;IAsBE;;;;;;;;;;;;MAYE;IAEF,+BAQC;IA3CD,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;IAiBrC,kBAAgC;IAChC,kBAA8C;IAC9C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,8DASC;IAED,2EA+CC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,mDASC;IAED,6CAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,wCAQC;IAED,4CAKC;IAED,mCAgBC;IAED,kCAqBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,yGAgBC;IAED,gHAuCC;IAED,kGAgDC;IAED,0EAGC;IAED,qFA4BC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDASC;IAED,gDASC;IAED,gDASC;IAED,qCAMC;IAED,mCAQC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MA4CC;IAED,oFAMC;IAED,0DA6CC;IAED;;;;;;;;;;;;;MAeC;IAED,+EAWC;IAED,qCAeC;IAED,8DAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,wCAWC;IAED,sDAKC;IAED,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,kDAKC;IAED,wDASC;IAED,8CAKC;IAED,oDAOC;IAED,gDAKC;IAED,sDAOC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAl6CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IAUE,0FAMC;IAfD,kBAAa;IACb,gBAAW;IACX,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IAGX,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -3,6 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM1 = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
+ // 2-3 times faster than Map
7
+ class SparseMap {
8
+ constructor(size) {
9
+ this.data = new Array(size);
10
+ this.activeIndices = [];
11
+ }
12
+ set(key, value) {
13
+ if (this.data[key] === undefined) {
14
+ this.activeIndices.push(key);
15
+ }
16
+ this.data[key] = value;
17
+ }
18
+ get(key) {
19
+ return this.data[key];
20
+ }
21
+ delete(key) {
22
+ if (this.data[key] !== undefined) {
23
+ this.data[key] = undefined;
24
+ const index = this.activeIndices.indexOf(key);
25
+ if (index !== -1) {
26
+ this.activeIndices.splice(index, 1);
27
+ }
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ has(key) {
33
+ return this.data[key] !== undefined;
34
+ }
35
+ get size() {
36
+ return this.activeIndices.length;
37
+ }
38
+ clear() {
39
+ for (let i = 0; i < this.activeIndices.length; i++) {
40
+ const key = this.activeIndices[i];
41
+ this.data[key] = undefined;
42
+ }
43
+ this.activeIndices = [];
44
+ }
45
+ *[Symbol.iterator]() {
46
+ for (let i = 0; i < this.activeIndices.length; i++) {
47
+ const key = this.activeIndices[i];
48
+ yield [key, this.data[key]];
49
+ }
50
+ }
51
+ forEach(callback) {
52
+ for (let i = 0; i < this.activeIndices.length; i++) {
53
+ const key = this.activeIndices[i];
54
+ callback(this.data[key], key, this);
55
+ }
56
+ }
57
+ }
6
58
  class Note {
7
59
  constructor(noteNumber, velocity, startTime, voice, voiceParams) {
8
60
  Object.defineProperty(this, "bufferSource", {
@@ -181,6 +233,18 @@ class MidyGM1 {
181
233
  writable: true,
182
234
  value: this.initSoundFontTable()
183
235
  });
236
+ Object.defineProperty(this, "audioBufferCounter", {
237
+ enumerable: true,
238
+ configurable: true,
239
+ writable: true,
240
+ value: new Map()
241
+ });
242
+ Object.defineProperty(this, "audioBufferCache", {
243
+ enumerable: true,
244
+ configurable: true,
245
+ writable: true,
246
+ value: new Map()
247
+ });
184
248
  Object.defineProperty(this, "isPlaying", {
185
249
  enumerable: true,
186
250
  configurable: true,
@@ -233,7 +297,7 @@ class MidyGM1 {
233
297
  enumerable: true,
234
298
  configurable: true,
235
299
  writable: true,
236
- value: new Map()
300
+ value: new SparseMap(128)
237
301
  });
238
302
  this.audioContext = audioContext;
239
303
  this.masterVolume = new GainNode(audioContext);
@@ -246,7 +310,7 @@ class MidyGM1 {
246
310
  initSoundFontTable() {
247
311
  const table = new Array(128);
248
312
  for (let i = 0; i < 128; i++) {
249
- table[i] = new Map();
313
+ table[i] = new SparseMap(128);
250
314
  }
251
315
  return table;
252
316
  }
@@ -299,7 +363,7 @@ class MidyGM1 {
299
363
  ...this.constructor.channelSettings,
300
364
  state: new ControllerState(),
301
365
  ...this.setChannelAudioNodes(audioContext),
302
- scheduledNotes: new Map(),
366
+ scheduledNotes: new SparseMap(128),
303
367
  };
304
368
  });
305
369
  return channels;
@@ -333,9 +397,8 @@ class MidyGM1 {
333
397
  return audioBuffer;
334
398
  }
335
399
  }
336
- async createNoteBufferNode(voiceParams, isSF3) {
400
+ createNoteBufferNode(audioBuffer, voiceParams) {
337
401
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
338
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
339
402
  bufferSource.buffer = audioBuffer;
340
403
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
341
404
  if (bufferSource.loop) {
@@ -400,6 +463,7 @@ class MidyGM1 {
400
463
  await Promise.all(this.notePromises);
401
464
  this.notePromises = [];
402
465
  this.exclusiveClassMap.clear();
466
+ this.audioBufferCache.clear();
403
467
  resolve();
404
468
  return;
405
469
  }
@@ -415,8 +479,9 @@ class MidyGM1 {
415
479
  }
416
480
  else if (this.isStopping) {
417
481
  await this.stopNotes(0, true);
418
- this.exclusiveClassMap.clear();
419
482
  this.notePromises = [];
483
+ this.exclusiveClassMap.clear();
484
+ this.audioBufferCache.clear();
420
485
  resolve();
421
486
  this.isStopping = false;
422
487
  this.isPaused = false;
@@ -447,6 +512,9 @@ class MidyGM1 {
447
512
  secondToTicks(second, secondsPerBeat) {
448
513
  return second * this.ticksPerBeat / secondsPerBeat;
449
514
  }
515
+ getAudioBufferId(programNumber, noteNumber, velocity) {
516
+ return `${programNumber}:${noteNumber}:${velocity}`;
517
+ }
450
518
  extractMidiData(midi) {
451
519
  const instruments = new Set();
452
520
  const timeline = [];
@@ -467,6 +535,8 @@ class MidyGM1 {
467
535
  switch (event.type) {
468
536
  case "noteOn": {
469
537
  const channel = tmpChannels[event.channel];
538
+ const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
539
+ this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
470
540
  if (channel.programNumber < 0) {
471
541
  instruments.add(`${channel.bank}:0`);
472
542
  channel.programNumber = 0;
@@ -483,6 +553,10 @@ class MidyGM1 {
483
553
  timeline.push(event);
484
554
  }
485
555
  }
556
+ for (const [audioBufferId, count] of this.audioBufferCounter) {
557
+ if (count === 1)
558
+ this.audioBufferCounter.delete(audioBufferId);
559
+ }
486
560
  const priority = {
487
561
  controller: 0,
488
562
  sysEx: 1,
@@ -573,7 +647,7 @@ class MidyGM1 {
573
647
  return this.resumeTime + now - this.startTime - this.startDelay;
574
648
  }
575
649
  getActiveNotes(channel, time) {
576
- const activeNotes = new Map();
650
+ const activeNotes = new SparseMap(128);
577
651
  channel.scheduledNotes.forEach((noteList) => {
578
652
  const activeNote = this.getActiveNote(noteList, time);
579
653
  if (activeNote) {
@@ -715,11 +789,31 @@ class MidyGM1 {
715
789
  note.modulationLFO.connect(note.volumeDepth);
716
790
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
717
791
  }
792
+ async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
793
+ const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
794
+ const cache = this.audioBufferCache.get(audioBufferId);
795
+ if (cache) {
796
+ cache.counter += 1;
797
+ if (cache.maxCount <= cache.counter) {
798
+ this.audioBufferCache.delete(audioBufferId);
799
+ }
800
+ return cache.audioBuffer;
801
+ }
802
+ else {
803
+ const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
804
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
805
+ const cache = { audioBuffer, maxCount, counter: 1 };
806
+ this.audioBufferCache.set(audioBufferId, cache);
807
+ return audioBuffer;
808
+ }
809
+ }
718
810
  async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
719
811
  const state = channel.state;
720
- const voiceParams = voice.getAllParams(state.array);
812
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
813
+ const voiceParams = voice.getAllParams(controllerState);
721
814
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
722
- note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
815
+ const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
816
+ note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
723
817
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
724
818
  note.filterNode = new BiquadFilterNode(this.audioContext, {
725
819
  type: "lowpass",
@@ -743,10 +837,10 @@ class MidyGM1 {
743
837
  if (soundFontIndex === undefined)
744
838
  return;
745
839
  const soundFont = this.soundFonts[soundFontIndex];
746
- const isSF3 = soundFont.parsed.info.version.major === 3;
747
840
  const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
748
841
  if (!voice)
749
842
  return;
843
+ const isSF3 = soundFont.parsed.info.version.major === 3;
750
844
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
751
845
  note.volumeEnvelopeNode.connect(channel.gainL);
752
846
  note.volumeEnvelopeNode.connect(channel.gainR);