@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-GM1.d.ts +20 -3
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +104 -10
- package/esm/midy-GM2.d.ts +22 -5
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +116 -25
- package/esm/midy-GMLite.d.ts +20 -3
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +104 -10
- package/esm/midy.d.ts +23 -5
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +145 -27
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +20 -3
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +104 -10
- package/script/midy-GM2.d.ts +22 -5
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +116 -25
- package/script/midy-GMLite.d.ts +20 -3
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +104 -10
- package/script/midy.d.ts +23 -5
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +145 -27
package/esm/midy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"
|
|
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
|
|
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
|
|
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
|
|
437
|
-
sostenutoNotes: new
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
1375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2236
|
-
|
|
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 <
|
|
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
|
|
2517
|
-
|
|
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
|
|
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
package/script/midy-GM1.d.ts
CHANGED
|
@@ -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:
|
|
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(
|
|
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):
|
|
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;
|
package/script/midy-GM1.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"
|
|
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"}
|
package/script/midy-GM1.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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);
|