@marmooo/midy 0.3.7 → 0.4.0
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/README.md +31 -11
- package/esm/midy-GM1.d.ts +15 -30
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +161 -104
- package/esm/midy-GM2.d.ts +19 -36
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +245 -205
- package/esm/midy-GMLite.d.ts +14 -30
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +163 -107
- package/esm/midy.d.ts +19 -37
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +306 -277
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +15 -30
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +161 -104
- package/script/midy-GM2.d.ts +19 -36
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +245 -205
- package/script/midy-GMLite.d.ts +14 -30
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +163 -107
- package/script/midy.d.ts +19 -37
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +306 -277
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export class MidyGMLite {
|
|
|
3
3
|
scheduleIndex: number;
|
|
4
4
|
detune: number;
|
|
5
5
|
programNumber: number;
|
|
6
|
-
bank: number;
|
|
7
6
|
dataMSB: number;
|
|
8
7
|
dataLSB: number;
|
|
9
8
|
rpnMSB: number;
|
|
@@ -21,9 +20,10 @@ export class MidyGMLite {
|
|
|
21
20
|
startTime: number;
|
|
22
21
|
resumeTime: number;
|
|
23
22
|
soundFonts: any[];
|
|
24
|
-
soundFontTable:
|
|
23
|
+
soundFontTable: never[][];
|
|
25
24
|
voiceCounter: Map<any, any>;
|
|
26
25
|
voiceCache: Map<any, any>;
|
|
26
|
+
realtimeVoiceCache: Map<any, any>;
|
|
27
27
|
isPlaying: boolean;
|
|
28
28
|
isPausing: boolean;
|
|
29
29
|
isPaused: boolean;
|
|
@@ -39,6 +39,7 @@ export class MidyGMLite {
|
|
|
39
39
|
masterVolume: any;
|
|
40
40
|
scheduler: any;
|
|
41
41
|
schedulerBuffer: any;
|
|
42
|
+
messageHandlers: any[];
|
|
42
43
|
voiceParamsHandlers: {
|
|
43
44
|
modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
|
|
44
45
|
vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
@@ -53,7 +54,6 @@ export class MidyGMLite {
|
|
|
53
54
|
};
|
|
54
55
|
controlChangeHandlers: any[];
|
|
55
56
|
channels: any[];
|
|
56
|
-
initSoundFontTable(): any[];
|
|
57
57
|
addSoundFont(soundFont: any): void;
|
|
58
58
|
toUint8Array(input: any): Promise<Uint8Array<ArrayBuffer>>;
|
|
59
59
|
loadSoundFont(input: any): Promise<void>;
|
|
@@ -68,13 +68,14 @@ export class MidyGMLite {
|
|
|
68
68
|
createChannels(audioContext: any): any[];
|
|
69
69
|
createAudioBuffer(voiceParams: any): Promise<any>;
|
|
70
70
|
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
71
|
-
scheduleTimelineEvents(
|
|
71
|
+
scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
|
|
72
72
|
getQueueIndex(second: any): number;
|
|
73
73
|
resetAllStates(): void;
|
|
74
74
|
updateStates(queueIndex: any, nextQueueIndex: any): void;
|
|
75
75
|
playNotes(): Promise<void>;
|
|
76
76
|
ticksToSecond(ticks: any, secondsPerBeat: any): number;
|
|
77
77
|
secondToTicks(second: any, secondsPerBeat: any): number;
|
|
78
|
+
getSoundFontId(channel: any): string;
|
|
78
79
|
extractMidiData(midi: any): {
|
|
79
80
|
instruments: Set<any>;
|
|
80
81
|
timeline: any[];
|
|
@@ -103,20 +104,21 @@ export class MidyGMLite {
|
|
|
103
104
|
clampCutoffFrequency(frequency: any): number;
|
|
104
105
|
setFilterEnvelope(note: any, scheduleTime: any): void;
|
|
105
106
|
startModulation(channel: any, note: any, scheduleTime: any): void;
|
|
106
|
-
getAudioBuffer(channel: any, noteNumber: any, velocity: any, voiceParams: any): Promise<any>;
|
|
107
|
-
|
|
107
|
+
getAudioBuffer(channel: any, noteNumber: any, velocity: any, voiceParams: any, realtime: any): Promise<any>;
|
|
108
|
+
setNoteAudioNode(channel: any, note: any, realtime: any): Promise<any>;
|
|
108
109
|
handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
109
110
|
handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
110
|
-
|
|
111
|
-
noteOn(channelNumber: any, noteNumber: any, velocity: any,
|
|
111
|
+
setNoteRouting(channelNumber: any, note: any, startTime: any): void;
|
|
112
|
+
noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
112
113
|
disconnectNote(note: any): void;
|
|
113
114
|
releaseNote(channel: any, note: any, endTime: any): Promise<any>;
|
|
114
|
-
|
|
115
|
+
noteOff(channelNumber: any, noteNumber: any, velocity: any, endTime: any, force: any): void;
|
|
115
116
|
setNoteIndex(channel: any, index: any): void;
|
|
116
117
|
findNoteOffIndex(channel: any, noteNumber: any): any;
|
|
117
|
-
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
|
|
118
118
|
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
|
|
119
|
-
|
|
119
|
+
createMessageHandlers(): any[];
|
|
120
|
+
handleMessage(data: any, scheduleTime: any): void;
|
|
121
|
+
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
|
|
120
122
|
setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
121
123
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
122
124
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
|
@@ -168,26 +170,8 @@ export class MidyGMLite {
|
|
|
168
170
|
GM1SystemOn(scheduleTime: any): void;
|
|
169
171
|
handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
170
172
|
handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
|
|
171
|
-
setMasterVolume(
|
|
173
|
+
setMasterVolume(value: any, scheduleTime: any): void;
|
|
172
174
|
handleSysEx(data: any, scheduleTime: any): void;
|
|
173
175
|
scheduleTask(callback: any, scheduleTime: any): Promise<any>;
|
|
174
176
|
}
|
|
175
|
-
declare class Note {
|
|
176
|
-
constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
|
|
177
|
-
index: number;
|
|
178
|
-
ending: boolean;
|
|
179
|
-
bufferSource: any;
|
|
180
|
-
filterNode: any;
|
|
181
|
-
filterDepth: any;
|
|
182
|
-
volumeEnvelopeNode: any;
|
|
183
|
-
volumeDepth: any;
|
|
184
|
-
modulationLFO: any;
|
|
185
|
-
modulationDepth: any;
|
|
186
|
-
noteNumber: any;
|
|
187
|
-
velocity: any;
|
|
188
|
-
startTime: any;
|
|
189
|
-
voice: any;
|
|
190
|
-
voiceParams: any;
|
|
191
|
-
}
|
|
192
|
-
export {};
|
|
193
177
|
//# sourceMappingURL=midy-GMLite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA2GA;IA6BE;;;;;;;;;MASE;IAEF,+BAeC;IAtDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAWC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,yEAqDC;IAED,mCAOC;IAED,uBASC;IAED,yDA2BC;IAED,2BAwCC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAKC;IAED,uBAQC;IAED,wBAKC;IAED,0BAKC;IAED,wBAOC;IAED,sBAIC;IAED,yDAQC;IAED,yEASC;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,4GAkCC;IAED,uEAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,oEAUC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAsBC;IAED,6CAUC;IAED,qDAUC;IAED,sFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,uGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDASC;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,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCASC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -4,7 +4,19 @@ exports.MidyGMLite = void 0;
|
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime
|
|
7
|
+
constructor(noteNumber, velocity, startTime) {
|
|
8
|
+
Object.defineProperty(this, "voice", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "voiceParams", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
8
20
|
Object.defineProperty(this, "index", {
|
|
9
21
|
enumerable: true,
|
|
10
22
|
configurable: true,
|
|
@@ -17,6 +29,12 @@ class Note {
|
|
|
17
29
|
writable: true,
|
|
18
30
|
value: false
|
|
19
31
|
});
|
|
32
|
+
Object.defineProperty(this, "pending", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: true
|
|
37
|
+
});
|
|
20
38
|
Object.defineProperty(this, "bufferSource", {
|
|
21
39
|
enumerable: true,
|
|
22
40
|
configurable: true,
|
|
@@ -62,8 +80,6 @@ class Note {
|
|
|
62
80
|
this.noteNumber = noteNumber;
|
|
63
81
|
this.velocity = velocity;
|
|
64
82
|
this.startTime = startTime;
|
|
65
|
-
this.voice = voice;
|
|
66
|
-
this.voiceParams = voiceParams;
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
const drumExclusiveClasses = new Uint8Array(128);
|
|
@@ -216,7 +232,7 @@ class MidyGMLite {
|
|
|
216
232
|
enumerable: true,
|
|
217
233
|
configurable: true,
|
|
218
234
|
writable: true,
|
|
219
|
-
value:
|
|
235
|
+
value: Array.from({ length: 128 }, () => [])
|
|
220
236
|
});
|
|
221
237
|
Object.defineProperty(this, "voiceCounter", {
|
|
222
238
|
enumerable: true,
|
|
@@ -230,6 +246,12 @@ class MidyGMLite {
|
|
|
230
246
|
writable: true,
|
|
231
247
|
value: new Map()
|
|
232
248
|
});
|
|
249
|
+
Object.defineProperty(this, "realtimeVoiceCache", {
|
|
250
|
+
enumerable: true,
|
|
251
|
+
configurable: true,
|
|
252
|
+
writable: true,
|
|
253
|
+
value: new Map()
|
|
254
|
+
});
|
|
233
255
|
Object.defineProperty(this, "isPlaying", {
|
|
234
256
|
enumerable: true,
|
|
235
257
|
configurable: true,
|
|
@@ -303,6 +325,7 @@ class MidyGMLite {
|
|
|
303
325
|
length: 1,
|
|
304
326
|
sampleRate: audioContext.sampleRate,
|
|
305
327
|
});
|
|
328
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
306
329
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
307
330
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
308
331
|
this.channels = this.createChannels(audioContext);
|
|
@@ -310,21 +333,14 @@ class MidyGMLite {
|
|
|
310
333
|
this.scheduler.connect(audioContext.destination);
|
|
311
334
|
this.GM1SystemOn();
|
|
312
335
|
}
|
|
313
|
-
initSoundFontTable() {
|
|
314
|
-
const table = new Array(128);
|
|
315
|
-
for (let i = 0; i < 128; i++) {
|
|
316
|
-
table[i] = new Map();
|
|
317
|
-
}
|
|
318
|
-
return table;
|
|
319
|
-
}
|
|
320
336
|
addSoundFont(soundFont) {
|
|
321
337
|
const index = this.soundFonts.length;
|
|
322
338
|
this.soundFonts.push(soundFont);
|
|
323
339
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
340
|
+
const soundFontTable = this.soundFontTable;
|
|
324
341
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
banks.set(presetHeader.bank, index);
|
|
342
|
+
const { preset, bank } = presetHeaders[i];
|
|
343
|
+
soundFontTable[preset][bank] = index;
|
|
328
344
|
}
|
|
329
345
|
}
|
|
330
346
|
async toUint8Array(input) {
|
|
@@ -402,13 +418,16 @@ class MidyGMLite {
|
|
|
402
418
|
this.GM1SystemOn();
|
|
403
419
|
}
|
|
404
420
|
getVoiceId(channel, noteNumber, velocity) {
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
421
|
+
const programNumber = channel.programNumber;
|
|
422
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
423
|
+
if (!bankTable)
|
|
424
|
+
return;
|
|
425
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
426
|
+
const soundFontIndex = bankTable[bank];
|
|
408
427
|
if (soundFontIndex === undefined)
|
|
409
428
|
return;
|
|
410
429
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
411
|
-
const voice = soundFont.getVoice(
|
|
430
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
412
431
|
const { instrument, sampleID } = voice.generators;
|
|
413
432
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
414
433
|
}
|
|
@@ -459,19 +478,22 @@ class MidyGMLite {
|
|
|
459
478
|
}
|
|
460
479
|
return bufferSource;
|
|
461
480
|
}
|
|
462
|
-
async scheduleTimelineEvents(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
481
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
482
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
483
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
484
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
485
|
+
const timeline = this.timeline;
|
|
486
|
+
while (queueIndex < timeline.length) {
|
|
487
|
+
const event = timeline[queueIndex];
|
|
488
|
+
if (lookAheadCheckTime < event.startTime)
|
|
466
489
|
break;
|
|
467
|
-
const
|
|
468
|
-
const startTime = event.startTime + delay;
|
|
490
|
+
const startTime = event.startTime + schedulingOffset;
|
|
469
491
|
switch (event.type) {
|
|
470
492
|
case "noteOn":
|
|
471
|
-
await this.
|
|
493
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
472
494
|
break;
|
|
473
495
|
case "noteOff": {
|
|
474
|
-
const notePromise = this.
|
|
496
|
+
const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
475
497
|
if (notePromise)
|
|
476
498
|
this.notePromises.push(notePromise);
|
|
477
499
|
break;
|
|
@@ -504,6 +526,7 @@ class MidyGMLite {
|
|
|
504
526
|
this.exclusiveClassNotes.fill(undefined);
|
|
505
527
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
506
528
|
this.voiceCache.clear();
|
|
529
|
+
this.realtimeVoiceCache.clear();
|
|
507
530
|
for (let i = 0; i < this.channels.length; i++) {
|
|
508
531
|
this.channels[i].scheduledNotes = [];
|
|
509
532
|
this.resetChannelStates(i);
|
|
@@ -537,13 +560,10 @@ class MidyGMLite {
|
|
|
537
560
|
this.isPaused = false;
|
|
538
561
|
this.startTime = this.audioContext.currentTime;
|
|
539
562
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
540
|
-
let resumeTime = this.resumeTime - this.startTime;
|
|
541
563
|
let finished = false;
|
|
542
564
|
this.notePromises = [];
|
|
543
565
|
while (queueIndex < this.timeline.length) {
|
|
544
566
|
const now = this.audioContext.currentTime;
|
|
545
|
-
const t = now + resumeTime;
|
|
546
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
547
567
|
if (this.isPausing) {
|
|
548
568
|
await this.stopNotes(0, true, now);
|
|
549
569
|
await this.audioContext.suspend();
|
|
@@ -562,10 +582,10 @@ class MidyGMLite {
|
|
|
562
582
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
563
583
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
564
584
|
queueIndex = nextQueueIndex;
|
|
565
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
566
585
|
this.isSeeking = false;
|
|
567
586
|
continue;
|
|
568
587
|
}
|
|
588
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
569
589
|
const waitTime = now + this.noteCheckInterval;
|
|
570
590
|
await this.scheduleTask(() => { }, waitTime);
|
|
571
591
|
}
|
|
@@ -581,16 +601,16 @@ class MidyGMLite {
|
|
|
581
601
|
secondToTicks(second, secondsPerBeat) {
|
|
582
602
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
583
603
|
}
|
|
604
|
+
getSoundFontId(channel) {
|
|
605
|
+
const programNumber = channel.programNumber;
|
|
606
|
+
const bank = channel.isDrum ? "128" : "000";
|
|
607
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
608
|
+
return `${bank}:${program}`;
|
|
609
|
+
}
|
|
584
610
|
extractMidiData(midi) {
|
|
585
611
|
const instruments = new Set();
|
|
586
612
|
const timeline = [];
|
|
587
|
-
const
|
|
588
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
589
|
-
tmpChannels[i] = {
|
|
590
|
-
programNumber: -1,
|
|
591
|
-
bank: this.channels[i].bank,
|
|
592
|
-
};
|
|
593
|
-
}
|
|
613
|
+
const channels = this.channels;
|
|
594
614
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
595
615
|
const track = midi.tracks[i];
|
|
596
616
|
let currentTicks = 0;
|
|
@@ -600,17 +620,15 @@ class MidyGMLite {
|
|
|
600
620
|
event.ticks = currentTicks;
|
|
601
621
|
switch (event.type) {
|
|
602
622
|
case "noteOn": {
|
|
603
|
-
const channel =
|
|
604
|
-
|
|
605
|
-
instruments.add(`${channel.bank}:0`);
|
|
606
|
-
channel.programNumber = 0;
|
|
607
|
-
}
|
|
623
|
+
const channel = channels[event.channel];
|
|
624
|
+
instruments.add(this.getSoundFontId(channel));
|
|
608
625
|
break;
|
|
609
626
|
}
|
|
610
627
|
case "programChange": {
|
|
611
|
-
const channel =
|
|
612
|
-
channel
|
|
613
|
-
instruments.add(
|
|
628
|
+
const channel = channels[event.channel];
|
|
629
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
630
|
+
instruments.add(this.getSoundFontId(channel));
|
|
631
|
+
break;
|
|
614
632
|
}
|
|
615
633
|
}
|
|
616
634
|
delete event.deltaTime;
|
|
@@ -645,7 +663,7 @@ class MidyGMLite {
|
|
|
645
663
|
const channel = this.channels[channelNumber];
|
|
646
664
|
const promises = [];
|
|
647
665
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
648
|
-
const promise = this.
|
|
666
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
649
667
|
this.notePromises.push(promise);
|
|
650
668
|
promises.push(promise);
|
|
651
669
|
});
|
|
@@ -655,7 +673,7 @@ class MidyGMLite {
|
|
|
655
673
|
const channel = this.channels[channelNumber];
|
|
656
674
|
const promises = [];
|
|
657
675
|
this.processScheduledNotes(channel, (note) => {
|
|
658
|
-
const promise = this.
|
|
676
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
659
677
|
this.notePromises.push(promise);
|
|
660
678
|
promises.push(promise);
|
|
661
679
|
});
|
|
@@ -688,7 +706,7 @@ class MidyGMLite {
|
|
|
688
706
|
if (!this.isPlaying || this.isPaused)
|
|
689
707
|
return;
|
|
690
708
|
const now = this.audioContext.currentTime;
|
|
691
|
-
this.resumeTime
|
|
709
|
+
this.resumeTime = now - this.startTime - this.startDelay;
|
|
692
710
|
this.isPausing = true;
|
|
693
711
|
await this.playPromise;
|
|
694
712
|
this.isPausing = false;
|
|
@@ -714,11 +732,13 @@ class MidyGMLite {
|
|
|
714
732
|
if (totalTime < event.startTime)
|
|
715
733
|
totalTime = event.startTime;
|
|
716
734
|
}
|
|
717
|
-
return totalTime;
|
|
735
|
+
return totalTime + this.startDelay;
|
|
718
736
|
}
|
|
719
737
|
currentTime() {
|
|
738
|
+
if (!this.isPlaying)
|
|
739
|
+
return this.resumeTime;
|
|
720
740
|
const now = this.audioContext.currentTime;
|
|
721
|
-
return
|
|
741
|
+
return now + this.resumeTime - this.startTime;
|
|
722
742
|
}
|
|
723
743
|
processScheduledNotes(channel, callback) {
|
|
724
744
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -855,32 +875,43 @@ class MidyGMLite {
|
|
|
855
875
|
note.modulationLFO.connect(note.volumeDepth);
|
|
856
876
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
857
877
|
}
|
|
858
|
-
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
878
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
859
879
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
this.voiceCache.delete(audioBufferId);
|
|
865
|
-
}
|
|
866
|
-
return cache.audioBuffer;
|
|
867
|
-
}
|
|
868
|
-
else {
|
|
869
|
-
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
880
|
+
if (realtime) {
|
|
881
|
+
const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
|
|
882
|
+
if (cachedAudioBuffer)
|
|
883
|
+
return cachedAudioBuffer;
|
|
870
884
|
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
871
|
-
|
|
872
|
-
this.voiceCache.set(audioBufferId, cache);
|
|
885
|
+
this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
|
|
873
886
|
return audioBuffer;
|
|
874
887
|
}
|
|
888
|
+
else {
|
|
889
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
890
|
+
if (cache) {
|
|
891
|
+
cache.counter += 1;
|
|
892
|
+
if (cache.maxCount <= cache.counter) {
|
|
893
|
+
this.voiceCache.delete(audioBufferId);
|
|
894
|
+
}
|
|
895
|
+
return cache.audioBuffer;
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
899
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
900
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
901
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
902
|
+
return audioBuffer;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
875
905
|
}
|
|
876
|
-
async
|
|
906
|
+
async setNoteAudioNode(channel, note, realtime) {
|
|
877
907
|
const now = this.audioContext.currentTime;
|
|
908
|
+
const { noteNumber, velocity, startTime } = note;
|
|
878
909
|
const state = channel.state;
|
|
879
910
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
880
|
-
const voiceParams = voice.getAllParams(controllerState);
|
|
881
|
-
|
|
882
|
-
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
883
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
911
|
+
const voiceParams = note.voice.getAllParams(controllerState);
|
|
912
|
+
note.voiceParams = voiceParams;
|
|
913
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
914
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
884
915
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
885
916
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
886
917
|
type: "lowpass",
|
|
@@ -906,7 +937,7 @@ class MidyGMLite {
|
|
|
906
937
|
if (prev) {
|
|
907
938
|
const [prevNote, prevChannelNumber] = prev;
|
|
908
939
|
if (prevNote && !prevNote.ending) {
|
|
909
|
-
this.
|
|
940
|
+
this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
910
941
|
startTime, true);
|
|
911
942
|
}
|
|
912
943
|
}
|
|
@@ -916,43 +947,56 @@ class MidyGMLite {
|
|
|
916
947
|
const channel = this.channels[channelNumber];
|
|
917
948
|
if (!channel.isDrum)
|
|
918
949
|
return;
|
|
919
|
-
const drumExclusiveClass = drumExclusiveClasses[noteNumber];
|
|
950
|
+
const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
|
|
920
951
|
if (drumExclusiveClass === 0)
|
|
921
952
|
return;
|
|
922
953
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
923
954
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
924
955
|
if (prevNote && !prevNote.ending) {
|
|
925
|
-
this.
|
|
956
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
926
957
|
startTime, true);
|
|
927
958
|
}
|
|
928
959
|
this.drumExclusiveClassNotes[index] = note;
|
|
929
960
|
}
|
|
930
|
-
|
|
961
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
931
962
|
const channel = this.channels[channelNumber];
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (soundFontIndex === undefined)
|
|
936
|
-
return;
|
|
937
|
-
const soundFont = this.soundFonts[soundFontIndex];
|
|
938
|
-
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
939
|
-
if (!voice)
|
|
940
|
-
return;
|
|
941
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
942
|
-
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
943
|
-
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
963
|
+
const volumeEnvelopeNode = note.volumeEnvelopeNode;
|
|
964
|
+
volumeEnvelopeNode.connect(channel.gainL);
|
|
965
|
+
volumeEnvelopeNode.connect(channel.gainR);
|
|
944
966
|
if (0.5 <= channel.state.sustainPedal) {
|
|
945
967
|
channel.sustainNotes.push(note);
|
|
946
968
|
}
|
|
947
969
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
948
970
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
971
|
+
}
|
|
972
|
+
async noteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
973
|
+
const channel = this.channels[channelNumber];
|
|
974
|
+
const realtime = startTime === undefined;
|
|
975
|
+
if (realtime)
|
|
976
|
+
startTime = this.audioContext.currentTime;
|
|
977
|
+
const note = new Note(noteNumber, velocity, startTime);
|
|
949
978
|
const scheduledNotes = channel.scheduledNotes;
|
|
950
979
|
note.index = scheduledNotes.length;
|
|
951
980
|
scheduledNotes.push(note);
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
981
|
+
const programNumber = channel.programNumber;
|
|
982
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
983
|
+
if (!bankTable)
|
|
984
|
+
return;
|
|
985
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
986
|
+
const soundFontIndex = bankTable[bank];
|
|
987
|
+
if (soundFontIndex === undefined)
|
|
988
|
+
return;
|
|
989
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
990
|
+
note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
991
|
+
if (!note.voice)
|
|
992
|
+
return;
|
|
993
|
+
await this.setNoteAudioNode(channel, note, realtime);
|
|
994
|
+
this.setNoteRouting(channelNumber, note, startTime);
|
|
995
|
+
note.pending = false;
|
|
996
|
+
const off = note.offEvent;
|
|
997
|
+
if (off) {
|
|
998
|
+
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
999
|
+
}
|
|
956
1000
|
}
|
|
957
1001
|
disconnectNote(note) {
|
|
958
1002
|
note.bufferSource.disconnect();
|
|
@@ -965,6 +1009,7 @@ class MidyGMLite {
|
|
|
965
1009
|
}
|
|
966
1010
|
}
|
|
967
1011
|
releaseNote(channel, note, endTime) {
|
|
1012
|
+
endTime ??= this.audioContext.currentTime;
|
|
968
1013
|
const volRelease = endTime + note.voiceParams.volRelease;
|
|
969
1014
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
970
1015
|
const stopTime = Math.min(volRelease, modRelease);
|
|
@@ -985,7 +1030,7 @@ class MidyGMLite {
|
|
|
985
1030
|
}, stopTime);
|
|
986
1031
|
});
|
|
987
1032
|
}
|
|
988
|
-
|
|
1033
|
+
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
989
1034
|
const channel = this.channels[channelNumber];
|
|
990
1035
|
if (!force) {
|
|
991
1036
|
if (channel.isDrum)
|
|
@@ -997,6 +1042,10 @@ class MidyGMLite {
|
|
|
997
1042
|
if (index < 0)
|
|
998
1043
|
return;
|
|
999
1044
|
const note = channel.scheduledNotes[index];
|
|
1045
|
+
if (note.pending) {
|
|
1046
|
+
note.offEvent = { velocity, startTime: endTime };
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1000
1049
|
note.ending = true;
|
|
1001
1050
|
this.setNoteIndex(channel, index);
|
|
1002
1051
|
this.releaseNote(channel, note, endTime);
|
|
@@ -1027,22 +1076,37 @@ class MidyGMLite {
|
|
|
1027
1076
|
}
|
|
1028
1077
|
return -1;
|
|
1029
1078
|
}
|
|
1030
|
-
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1031
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1032
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1033
|
-
}
|
|
1034
1079
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1035
1080
|
const velocity = halfVelocity * 2;
|
|
1036
1081
|
const channel = this.channels[channelNumber];
|
|
1037
1082
|
const promises = [];
|
|
1038
1083
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1039
|
-
const promise = this.
|
|
1084
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1040
1085
|
promises.push(promise);
|
|
1041
1086
|
}
|
|
1042
1087
|
channel.sustainNotes = [];
|
|
1043
1088
|
return promises;
|
|
1044
1089
|
}
|
|
1045
|
-
|
|
1090
|
+
createMessageHandlers() {
|
|
1091
|
+
const handlers = new Array(256);
|
|
1092
|
+
// Channel Message
|
|
1093
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1094
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1095
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1096
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1097
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1098
|
+
return handlers;
|
|
1099
|
+
}
|
|
1100
|
+
handleMessage(data, scheduleTime) {
|
|
1101
|
+
const status = data[0];
|
|
1102
|
+
if (status === 0xF0) {
|
|
1103
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1104
|
+
}
|
|
1105
|
+
const handler = this.messageHandlers[status];
|
|
1106
|
+
if (handler)
|
|
1107
|
+
handler(data, scheduleTime);
|
|
1108
|
+
}
|
|
1109
|
+
handleChannelMessage(statusByte, data1, data2, scheduleTime) {
|
|
1046
1110
|
const channelNumber = statusByte & 0x0F;
|
|
1047
1111
|
const messageType = statusByte & 0xF0;
|
|
1048
1112
|
switch (messageType) {
|
|
@@ -1425,10 +1489,8 @@ class MidyGMLite {
|
|
|
1425
1489
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1426
1490
|
this.allSoundOff(i, 0, scheduleTime);
|
|
1427
1491
|
const channel = this.channels[i];
|
|
1428
|
-
channel.bank = 0;
|
|
1429
1492
|
channel.isDrum = false;
|
|
1430
1493
|
}
|
|
1431
|
-
this.channels[9].bank = 128;
|
|
1432
1494
|
this.channels[9].isDrum = true;
|
|
1433
1495
|
}
|
|
1434
1496
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -1449,16 +1511,11 @@ class MidyGMLite {
|
|
|
1449
1511
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1450
1512
|
this.setMasterVolume(volume, scheduleTime);
|
|
1451
1513
|
}
|
|
1452
|
-
setMasterVolume(
|
|
1514
|
+
setMasterVolume(value, scheduleTime) {
|
|
1453
1515
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
else {
|
|
1458
|
-
this.masterVolume.gain
|
|
1459
|
-
.cancelScheduledValues(scheduleTime)
|
|
1460
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
1461
|
-
}
|
|
1516
|
+
this.masterVolume.gain
|
|
1517
|
+
.cancelScheduledValues(scheduleTime)
|
|
1518
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
1462
1519
|
}
|
|
1463
1520
|
handleSysEx(data, scheduleTime) {
|
|
1464
1521
|
switch (data[0]) {
|
|
@@ -1499,7 +1556,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1499
1556
|
scheduleIndex: 0,
|
|
1500
1557
|
detune: 0,
|
|
1501
1558
|
programNumber: 0,
|
|
1502
|
-
bank: 0,
|
|
1503
1559
|
dataMSB: 0,
|
|
1504
1560
|
dataLSB: 0,
|
|
1505
1561
|
rpnMSB: 127,
|