@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/esm/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
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -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/esm/midy-GMLite.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
3
|
class Note {
|
|
4
|
-
constructor(noteNumber, velocity, startTime
|
|
4
|
+
constructor(noteNumber, velocity, startTime) {
|
|
5
|
+
Object.defineProperty(this, "voice", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "voiceParams", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
5
17
|
Object.defineProperty(this, "index", {
|
|
6
18
|
enumerable: true,
|
|
7
19
|
configurable: true,
|
|
@@ -14,6 +26,12 @@ class Note {
|
|
|
14
26
|
writable: true,
|
|
15
27
|
value: false
|
|
16
28
|
});
|
|
29
|
+
Object.defineProperty(this, "pending", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: true
|
|
34
|
+
});
|
|
17
35
|
Object.defineProperty(this, "bufferSource", {
|
|
18
36
|
enumerable: true,
|
|
19
37
|
configurable: true,
|
|
@@ -59,8 +77,6 @@ class Note {
|
|
|
59
77
|
this.noteNumber = noteNumber;
|
|
60
78
|
this.velocity = velocity;
|
|
61
79
|
this.startTime = startTime;
|
|
62
|
-
this.voice = voice;
|
|
63
|
-
this.voiceParams = voiceParams;
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
const drumExclusiveClasses = new Uint8Array(128);
|
|
@@ -213,7 +229,7 @@ export class MidyGMLite {
|
|
|
213
229
|
enumerable: true,
|
|
214
230
|
configurable: true,
|
|
215
231
|
writable: true,
|
|
216
|
-
value:
|
|
232
|
+
value: Array.from({ length: 128 }, () => [])
|
|
217
233
|
});
|
|
218
234
|
Object.defineProperty(this, "voiceCounter", {
|
|
219
235
|
enumerable: true,
|
|
@@ -227,6 +243,12 @@ export class MidyGMLite {
|
|
|
227
243
|
writable: true,
|
|
228
244
|
value: new Map()
|
|
229
245
|
});
|
|
246
|
+
Object.defineProperty(this, "realtimeVoiceCache", {
|
|
247
|
+
enumerable: true,
|
|
248
|
+
configurable: true,
|
|
249
|
+
writable: true,
|
|
250
|
+
value: new Map()
|
|
251
|
+
});
|
|
230
252
|
Object.defineProperty(this, "isPlaying", {
|
|
231
253
|
enumerable: true,
|
|
232
254
|
configurable: true,
|
|
@@ -300,6 +322,7 @@ export class MidyGMLite {
|
|
|
300
322
|
length: 1,
|
|
301
323
|
sampleRate: audioContext.sampleRate,
|
|
302
324
|
});
|
|
325
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
303
326
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
304
327
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
305
328
|
this.channels = this.createChannels(audioContext);
|
|
@@ -307,21 +330,14 @@ export class MidyGMLite {
|
|
|
307
330
|
this.scheduler.connect(audioContext.destination);
|
|
308
331
|
this.GM1SystemOn();
|
|
309
332
|
}
|
|
310
|
-
initSoundFontTable() {
|
|
311
|
-
const table = new Array(128);
|
|
312
|
-
for (let i = 0; i < 128; i++) {
|
|
313
|
-
table[i] = new Map();
|
|
314
|
-
}
|
|
315
|
-
return table;
|
|
316
|
-
}
|
|
317
333
|
addSoundFont(soundFont) {
|
|
318
334
|
const index = this.soundFonts.length;
|
|
319
335
|
this.soundFonts.push(soundFont);
|
|
320
336
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
337
|
+
const soundFontTable = this.soundFontTable;
|
|
321
338
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
banks.set(presetHeader.bank, index);
|
|
339
|
+
const { preset, bank } = presetHeaders[i];
|
|
340
|
+
soundFontTable[preset][bank] = index;
|
|
325
341
|
}
|
|
326
342
|
}
|
|
327
343
|
async toUint8Array(input) {
|
|
@@ -399,13 +415,16 @@ export class MidyGMLite {
|
|
|
399
415
|
this.GM1SystemOn();
|
|
400
416
|
}
|
|
401
417
|
getVoiceId(channel, noteNumber, velocity) {
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
|
|
418
|
+
const programNumber = channel.programNumber;
|
|
419
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
420
|
+
if (!bankTable)
|
|
421
|
+
return;
|
|
422
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
423
|
+
const soundFontIndex = bankTable[bank];
|
|
405
424
|
if (soundFontIndex === undefined)
|
|
406
425
|
return;
|
|
407
426
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
408
|
-
const voice = soundFont.getVoice(
|
|
427
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
409
428
|
const { instrument, sampleID } = voice.generators;
|
|
410
429
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
411
430
|
}
|
|
@@ -456,19 +475,22 @@ export class MidyGMLite {
|
|
|
456
475
|
}
|
|
457
476
|
return bufferSource;
|
|
458
477
|
}
|
|
459
|
-
async scheduleTimelineEvents(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
478
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
479
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
480
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
481
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
482
|
+
const timeline = this.timeline;
|
|
483
|
+
while (queueIndex < timeline.length) {
|
|
484
|
+
const event = timeline[queueIndex];
|
|
485
|
+
if (lookAheadCheckTime < event.startTime)
|
|
463
486
|
break;
|
|
464
|
-
const
|
|
465
|
-
const startTime = event.startTime + delay;
|
|
487
|
+
const startTime = event.startTime + schedulingOffset;
|
|
466
488
|
switch (event.type) {
|
|
467
489
|
case "noteOn":
|
|
468
|
-
await this.
|
|
490
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
469
491
|
break;
|
|
470
492
|
case "noteOff": {
|
|
471
|
-
const notePromise = this.
|
|
493
|
+
const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
472
494
|
if (notePromise)
|
|
473
495
|
this.notePromises.push(notePromise);
|
|
474
496
|
break;
|
|
@@ -501,6 +523,7 @@ export class MidyGMLite {
|
|
|
501
523
|
this.exclusiveClassNotes.fill(undefined);
|
|
502
524
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
503
525
|
this.voiceCache.clear();
|
|
526
|
+
this.realtimeVoiceCache.clear();
|
|
504
527
|
for (let i = 0; i < this.channels.length; i++) {
|
|
505
528
|
this.channels[i].scheduledNotes = [];
|
|
506
529
|
this.resetChannelStates(i);
|
|
@@ -534,13 +557,10 @@ export class MidyGMLite {
|
|
|
534
557
|
this.isPaused = false;
|
|
535
558
|
this.startTime = this.audioContext.currentTime;
|
|
536
559
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
537
|
-
let resumeTime = this.resumeTime - this.startTime;
|
|
538
560
|
let finished = false;
|
|
539
561
|
this.notePromises = [];
|
|
540
562
|
while (queueIndex < this.timeline.length) {
|
|
541
563
|
const now = this.audioContext.currentTime;
|
|
542
|
-
const t = now + resumeTime;
|
|
543
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
544
564
|
if (this.isPausing) {
|
|
545
565
|
await this.stopNotes(0, true, now);
|
|
546
566
|
await this.audioContext.suspend();
|
|
@@ -559,10 +579,10 @@ export class MidyGMLite {
|
|
|
559
579
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
560
580
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
561
581
|
queueIndex = nextQueueIndex;
|
|
562
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
563
582
|
this.isSeeking = false;
|
|
564
583
|
continue;
|
|
565
584
|
}
|
|
585
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
566
586
|
const waitTime = now + this.noteCheckInterval;
|
|
567
587
|
await this.scheduleTask(() => { }, waitTime);
|
|
568
588
|
}
|
|
@@ -578,16 +598,16 @@ export class MidyGMLite {
|
|
|
578
598
|
secondToTicks(second, secondsPerBeat) {
|
|
579
599
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
580
600
|
}
|
|
601
|
+
getSoundFontId(channel) {
|
|
602
|
+
const programNumber = channel.programNumber;
|
|
603
|
+
const bank = channel.isDrum ? "128" : "000";
|
|
604
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
605
|
+
return `${bank}:${program}`;
|
|
606
|
+
}
|
|
581
607
|
extractMidiData(midi) {
|
|
582
608
|
const instruments = new Set();
|
|
583
609
|
const timeline = [];
|
|
584
|
-
const
|
|
585
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
586
|
-
tmpChannels[i] = {
|
|
587
|
-
programNumber: -1,
|
|
588
|
-
bank: this.channels[i].bank,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
610
|
+
const channels = this.channels;
|
|
591
611
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
592
612
|
const track = midi.tracks[i];
|
|
593
613
|
let currentTicks = 0;
|
|
@@ -597,17 +617,15 @@ export class MidyGMLite {
|
|
|
597
617
|
event.ticks = currentTicks;
|
|
598
618
|
switch (event.type) {
|
|
599
619
|
case "noteOn": {
|
|
600
|
-
const channel =
|
|
601
|
-
|
|
602
|
-
instruments.add(`${channel.bank}:0`);
|
|
603
|
-
channel.programNumber = 0;
|
|
604
|
-
}
|
|
620
|
+
const channel = channels[event.channel];
|
|
621
|
+
instruments.add(this.getSoundFontId(channel));
|
|
605
622
|
break;
|
|
606
623
|
}
|
|
607
624
|
case "programChange": {
|
|
608
|
-
const channel =
|
|
609
|
-
channel
|
|
610
|
-
instruments.add(
|
|
625
|
+
const channel = channels[event.channel];
|
|
626
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
627
|
+
instruments.add(this.getSoundFontId(channel));
|
|
628
|
+
break;
|
|
611
629
|
}
|
|
612
630
|
}
|
|
613
631
|
delete event.deltaTime;
|
|
@@ -642,7 +660,7 @@ export class MidyGMLite {
|
|
|
642
660
|
const channel = this.channels[channelNumber];
|
|
643
661
|
const promises = [];
|
|
644
662
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
645
|
-
const promise = this.
|
|
663
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
646
664
|
this.notePromises.push(promise);
|
|
647
665
|
promises.push(promise);
|
|
648
666
|
});
|
|
@@ -652,7 +670,7 @@ export class MidyGMLite {
|
|
|
652
670
|
const channel = this.channels[channelNumber];
|
|
653
671
|
const promises = [];
|
|
654
672
|
this.processScheduledNotes(channel, (note) => {
|
|
655
|
-
const promise = this.
|
|
673
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
656
674
|
this.notePromises.push(promise);
|
|
657
675
|
promises.push(promise);
|
|
658
676
|
});
|
|
@@ -685,7 +703,7 @@ export class MidyGMLite {
|
|
|
685
703
|
if (!this.isPlaying || this.isPaused)
|
|
686
704
|
return;
|
|
687
705
|
const now = this.audioContext.currentTime;
|
|
688
|
-
this.resumeTime
|
|
706
|
+
this.resumeTime = now - this.startTime - this.startDelay;
|
|
689
707
|
this.isPausing = true;
|
|
690
708
|
await this.playPromise;
|
|
691
709
|
this.isPausing = false;
|
|
@@ -711,11 +729,13 @@ export class MidyGMLite {
|
|
|
711
729
|
if (totalTime < event.startTime)
|
|
712
730
|
totalTime = event.startTime;
|
|
713
731
|
}
|
|
714
|
-
return totalTime;
|
|
732
|
+
return totalTime + this.startDelay;
|
|
715
733
|
}
|
|
716
734
|
currentTime() {
|
|
735
|
+
if (!this.isPlaying)
|
|
736
|
+
return this.resumeTime;
|
|
717
737
|
const now = this.audioContext.currentTime;
|
|
718
|
-
return
|
|
738
|
+
return now + this.resumeTime - this.startTime;
|
|
719
739
|
}
|
|
720
740
|
processScheduledNotes(channel, callback) {
|
|
721
741
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -852,32 +872,43 @@ export class MidyGMLite {
|
|
|
852
872
|
note.modulationLFO.connect(note.volumeDepth);
|
|
853
873
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
854
874
|
}
|
|
855
|
-
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
875
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
856
876
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
this.voiceCache.delete(audioBufferId);
|
|
862
|
-
}
|
|
863
|
-
return cache.audioBuffer;
|
|
864
|
-
}
|
|
865
|
-
else {
|
|
866
|
-
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
877
|
+
if (realtime) {
|
|
878
|
+
const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
|
|
879
|
+
if (cachedAudioBuffer)
|
|
880
|
+
return cachedAudioBuffer;
|
|
867
881
|
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
868
|
-
|
|
869
|
-
this.voiceCache.set(audioBufferId, cache);
|
|
882
|
+
this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
|
|
870
883
|
return audioBuffer;
|
|
871
884
|
}
|
|
885
|
+
else {
|
|
886
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
887
|
+
if (cache) {
|
|
888
|
+
cache.counter += 1;
|
|
889
|
+
if (cache.maxCount <= cache.counter) {
|
|
890
|
+
this.voiceCache.delete(audioBufferId);
|
|
891
|
+
}
|
|
892
|
+
return cache.audioBuffer;
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
896
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
897
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
898
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
899
|
+
return audioBuffer;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
872
902
|
}
|
|
873
|
-
async
|
|
903
|
+
async setNoteAudioNode(channel, note, realtime) {
|
|
874
904
|
const now = this.audioContext.currentTime;
|
|
905
|
+
const { noteNumber, velocity, startTime } = note;
|
|
875
906
|
const state = channel.state;
|
|
876
907
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
877
|
-
const voiceParams = voice.getAllParams(controllerState);
|
|
878
|
-
|
|
879
|
-
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
880
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
908
|
+
const voiceParams = note.voice.getAllParams(controllerState);
|
|
909
|
+
note.voiceParams = voiceParams;
|
|
910
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
911
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
881
912
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
882
913
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
883
914
|
type: "lowpass",
|
|
@@ -903,7 +934,7 @@ export class MidyGMLite {
|
|
|
903
934
|
if (prev) {
|
|
904
935
|
const [prevNote, prevChannelNumber] = prev;
|
|
905
936
|
if (prevNote && !prevNote.ending) {
|
|
906
|
-
this.
|
|
937
|
+
this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
907
938
|
startTime, true);
|
|
908
939
|
}
|
|
909
940
|
}
|
|
@@ -913,43 +944,56 @@ export class MidyGMLite {
|
|
|
913
944
|
const channel = this.channels[channelNumber];
|
|
914
945
|
if (!channel.isDrum)
|
|
915
946
|
return;
|
|
916
|
-
const drumExclusiveClass = drumExclusiveClasses[noteNumber];
|
|
947
|
+
const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
|
|
917
948
|
if (drumExclusiveClass === 0)
|
|
918
949
|
return;
|
|
919
950
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
920
951
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
921
952
|
if (prevNote && !prevNote.ending) {
|
|
922
|
-
this.
|
|
953
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
923
954
|
startTime, true);
|
|
924
955
|
}
|
|
925
956
|
this.drumExclusiveClassNotes[index] = note;
|
|
926
957
|
}
|
|
927
|
-
|
|
958
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
928
959
|
const channel = this.channels[channelNumber];
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (soundFontIndex === undefined)
|
|
933
|
-
return;
|
|
934
|
-
const soundFont = this.soundFonts[soundFontIndex];
|
|
935
|
-
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
936
|
-
if (!voice)
|
|
937
|
-
return;
|
|
938
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
939
|
-
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
940
|
-
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
960
|
+
const volumeEnvelopeNode = note.volumeEnvelopeNode;
|
|
961
|
+
volumeEnvelopeNode.connect(channel.gainL);
|
|
962
|
+
volumeEnvelopeNode.connect(channel.gainR);
|
|
941
963
|
if (0.5 <= channel.state.sustainPedal) {
|
|
942
964
|
channel.sustainNotes.push(note);
|
|
943
965
|
}
|
|
944
966
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
945
967
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
968
|
+
}
|
|
969
|
+
async noteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
970
|
+
const channel = this.channels[channelNumber];
|
|
971
|
+
const realtime = startTime === undefined;
|
|
972
|
+
if (realtime)
|
|
973
|
+
startTime = this.audioContext.currentTime;
|
|
974
|
+
const note = new Note(noteNumber, velocity, startTime);
|
|
946
975
|
const scheduledNotes = channel.scheduledNotes;
|
|
947
976
|
note.index = scheduledNotes.length;
|
|
948
977
|
scheduledNotes.push(note);
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
978
|
+
const programNumber = channel.programNumber;
|
|
979
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
980
|
+
if (!bankTable)
|
|
981
|
+
return;
|
|
982
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
983
|
+
const soundFontIndex = bankTable[bank];
|
|
984
|
+
if (soundFontIndex === undefined)
|
|
985
|
+
return;
|
|
986
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
987
|
+
note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
988
|
+
if (!note.voice)
|
|
989
|
+
return;
|
|
990
|
+
await this.setNoteAudioNode(channel, note, realtime);
|
|
991
|
+
this.setNoteRouting(channelNumber, note, startTime);
|
|
992
|
+
note.pending = false;
|
|
993
|
+
const off = note.offEvent;
|
|
994
|
+
if (off) {
|
|
995
|
+
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
996
|
+
}
|
|
953
997
|
}
|
|
954
998
|
disconnectNote(note) {
|
|
955
999
|
note.bufferSource.disconnect();
|
|
@@ -962,6 +1006,7 @@ export class MidyGMLite {
|
|
|
962
1006
|
}
|
|
963
1007
|
}
|
|
964
1008
|
releaseNote(channel, note, endTime) {
|
|
1009
|
+
endTime ??= this.audioContext.currentTime;
|
|
965
1010
|
const volRelease = endTime + note.voiceParams.volRelease;
|
|
966
1011
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
967
1012
|
const stopTime = Math.min(volRelease, modRelease);
|
|
@@ -982,7 +1027,7 @@ export class MidyGMLite {
|
|
|
982
1027
|
}, stopTime);
|
|
983
1028
|
});
|
|
984
1029
|
}
|
|
985
|
-
|
|
1030
|
+
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
986
1031
|
const channel = this.channels[channelNumber];
|
|
987
1032
|
if (!force) {
|
|
988
1033
|
if (channel.isDrum)
|
|
@@ -994,6 +1039,10 @@ export class MidyGMLite {
|
|
|
994
1039
|
if (index < 0)
|
|
995
1040
|
return;
|
|
996
1041
|
const note = channel.scheduledNotes[index];
|
|
1042
|
+
if (note.pending) {
|
|
1043
|
+
note.offEvent = { velocity, startTime: endTime };
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
997
1046
|
note.ending = true;
|
|
998
1047
|
this.setNoteIndex(channel, index);
|
|
999
1048
|
this.releaseNote(channel, note, endTime);
|
|
@@ -1024,22 +1073,37 @@ export class MidyGMLite {
|
|
|
1024
1073
|
}
|
|
1025
1074
|
return -1;
|
|
1026
1075
|
}
|
|
1027
|
-
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1028
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1029
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1030
|
-
}
|
|
1031
1076
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1032
1077
|
const velocity = halfVelocity * 2;
|
|
1033
1078
|
const channel = this.channels[channelNumber];
|
|
1034
1079
|
const promises = [];
|
|
1035
1080
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1036
|
-
const promise = this.
|
|
1081
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1037
1082
|
promises.push(promise);
|
|
1038
1083
|
}
|
|
1039
1084
|
channel.sustainNotes = [];
|
|
1040
1085
|
return promises;
|
|
1041
1086
|
}
|
|
1042
|
-
|
|
1087
|
+
createMessageHandlers() {
|
|
1088
|
+
const handlers = new Array(256);
|
|
1089
|
+
// Channel Message
|
|
1090
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1091
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1092
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1093
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1094
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1095
|
+
return handlers;
|
|
1096
|
+
}
|
|
1097
|
+
handleMessage(data, scheduleTime) {
|
|
1098
|
+
const status = data[0];
|
|
1099
|
+
if (status === 0xF0) {
|
|
1100
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1101
|
+
}
|
|
1102
|
+
const handler = this.messageHandlers[status];
|
|
1103
|
+
if (handler)
|
|
1104
|
+
handler(data, scheduleTime);
|
|
1105
|
+
}
|
|
1106
|
+
handleChannelMessage(statusByte, data1, data2, scheduleTime) {
|
|
1043
1107
|
const channelNumber = statusByte & 0x0F;
|
|
1044
1108
|
const messageType = statusByte & 0xF0;
|
|
1045
1109
|
switch (messageType) {
|
|
@@ -1422,10 +1486,8 @@ export class MidyGMLite {
|
|
|
1422
1486
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1423
1487
|
this.allSoundOff(i, 0, scheduleTime);
|
|
1424
1488
|
const channel = this.channels[i];
|
|
1425
|
-
channel.bank = 0;
|
|
1426
1489
|
channel.isDrum = false;
|
|
1427
1490
|
}
|
|
1428
|
-
this.channels[9].bank = 128;
|
|
1429
1491
|
this.channels[9].isDrum = true;
|
|
1430
1492
|
}
|
|
1431
1493
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -1446,16 +1508,11 @@ export class MidyGMLite {
|
|
|
1446
1508
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1447
1509
|
this.setMasterVolume(volume, scheduleTime);
|
|
1448
1510
|
}
|
|
1449
|
-
setMasterVolume(
|
|
1511
|
+
setMasterVolume(value, scheduleTime) {
|
|
1450
1512
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
else {
|
|
1455
|
-
this.masterVolume.gain
|
|
1456
|
-
.cancelScheduledValues(scheduleTime)
|
|
1457
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
1458
|
-
}
|
|
1513
|
+
this.masterVolume.gain
|
|
1514
|
+
.cancelScheduledValues(scheduleTime)
|
|
1515
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
1459
1516
|
}
|
|
1460
1517
|
handleSysEx(data, scheduleTime) {
|
|
1461
1518
|
switch (data[0]) {
|
|
@@ -1495,7 +1552,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1495
1552
|
scheduleIndex: 0,
|
|
1496
1553
|
detune: 0,
|
|
1497
1554
|
programNumber: 0,
|
|
1498
|
-
bank: 0,
|
|
1499
1555
|
dataMSB: 0,
|
|
1500
1556
|
dataLSB: 0,
|
|
1501
1557
|
rpnMSB: 127,
|