@marmooo/midy 0.3.6 → 0.3.8
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 +38 -20
- package/esm/midy-GM1.d.ts +36 -30
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +184 -142
- package/esm/midy-GM2.d.ts +43 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +324 -279
- package/esm/midy-GMLite.d.ts +35 -30
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +178 -139
- package/esm/midy.d.ts +45 -34
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +414 -302
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +36 -30
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +184 -142
- package/script/midy-GM2.d.ts +43 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +324 -279
- package/script/midy-GMLite.d.ts +35 -30
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +178 -139
- package/script/midy.d.ts +45 -34
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +414 -302
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,7 +20,7 @@ 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>;
|
|
27
26
|
isPlaying: boolean;
|
|
@@ -29,6 +28,7 @@ export class MidyGMLite {
|
|
|
29
28
|
isPaused: boolean;
|
|
30
29
|
isStopping: boolean;
|
|
31
30
|
isSeeking: boolean;
|
|
31
|
+
playPromise: any;
|
|
32
32
|
timeline: any[];
|
|
33
33
|
notePromises: any[];
|
|
34
34
|
instruments: Set<any>;
|
|
@@ -38,21 +38,21 @@ export class MidyGMLite {
|
|
|
38
38
|
masterVolume: any;
|
|
39
39
|
scheduler: any;
|
|
40
40
|
schedulerBuffer: any;
|
|
41
|
+
messageHandlers: any[];
|
|
41
42
|
voiceParamsHandlers: {
|
|
42
|
-
modLfoToPitch: (channel: any, note: any,
|
|
43
|
-
vibLfoToPitch: (_channel: any, _note: any,
|
|
44
|
-
modLfoToFilterFc: (channel: any, note: any,
|
|
45
|
-
modLfoToVolume: (channel: any, note: any,
|
|
46
|
-
chorusEffectsSend: (_channel: any, _note: any,
|
|
47
|
-
reverbEffectsSend: (_channel: any, _note: any,
|
|
48
|
-
delayModLFO: (_channel: any, note: any,
|
|
49
|
-
freqModLFO: (_channel: any, note: any,
|
|
50
|
-
delayVibLFO: (_channel: any, _note: any,
|
|
51
|
-
freqVibLFO: (_channel: any, _note: any,
|
|
43
|
+
modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
|
|
44
|
+
vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
45
|
+
modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
|
|
46
|
+
modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
|
|
47
|
+
chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
48
|
+
reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
49
|
+
delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
|
|
50
|
+
freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
|
|
51
|
+
delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
52
|
+
freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
52
53
|
};
|
|
53
54
|
controlChangeHandlers: any[];
|
|
54
55
|
channels: any[];
|
|
55
|
-
initSoundFontTable(): any[];
|
|
56
56
|
addSoundFont(soundFont: any): void;
|
|
57
57
|
toUint8Array(input: any): Promise<Uint8Array<ArrayBuffer>>;
|
|
58
58
|
loadSoundFont(input: any): Promise<void>;
|
|
@@ -67,11 +67,14 @@ export class MidyGMLite {
|
|
|
67
67
|
createChannels(audioContext: any): any[];
|
|
68
68
|
createAudioBuffer(voiceParams: any): Promise<any>;
|
|
69
69
|
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
70
|
-
scheduleTimelineEvents(
|
|
70
|
+
scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
|
|
71
71
|
getQueueIndex(second: any): number;
|
|
72
|
-
|
|
72
|
+
resetAllStates(): void;
|
|
73
|
+
updateStates(queueIndex: any, nextQueueIndex: any): void;
|
|
74
|
+
playNotes(): Promise<void>;
|
|
73
75
|
ticksToSecond(ticks: any, secondsPerBeat: any): number;
|
|
74
76
|
secondToTicks(second: any, secondsPerBeat: any): number;
|
|
77
|
+
getSoundFontId(channel: any): string;
|
|
75
78
|
extractMidiData(midi: any): {
|
|
76
79
|
instruments: Set<any>;
|
|
77
80
|
timeline: any[];
|
|
@@ -80,8 +83,8 @@ export class MidyGMLite {
|
|
|
80
83
|
stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
|
|
81
84
|
stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
|
|
82
85
|
start(): Promise<void>;
|
|
83
|
-
stop(): void
|
|
84
|
-
pause(): void
|
|
86
|
+
stop(): Promise<void>;
|
|
87
|
+
pause(): Promise<void>;
|
|
85
88
|
resume(): Promise<void>;
|
|
86
89
|
seekTo(second: any): void;
|
|
87
90
|
calcTotalTime(): number;
|
|
@@ -113,7 +116,9 @@ export class MidyGMLite {
|
|
|
113
116
|
findNoteOffIndex(channel: any, noteNumber: any): any;
|
|
114
117
|
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
|
|
115
118
|
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
|
|
116
|
-
|
|
119
|
+
createMessageHandlers(): any[];
|
|
120
|
+
handleMessage(data: any, scheduleTime: any): void;
|
|
121
|
+
handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
|
|
117
122
|
setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
118
123
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
119
124
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
|
@@ -123,16 +128,16 @@ export class MidyGMLite {
|
|
|
123
128
|
setDelayModLFO(note: any, scheduleTime: any): void;
|
|
124
129
|
setFreqModLFO(note: any, scheduleTime: any): void;
|
|
125
130
|
createVoiceParamsHandlers(): {
|
|
126
|
-
modLfoToPitch: (channel: any, note: any,
|
|
127
|
-
vibLfoToPitch: (_channel: any, _note: any,
|
|
128
|
-
modLfoToFilterFc: (channel: any, note: any,
|
|
129
|
-
modLfoToVolume: (channel: any, note: any,
|
|
130
|
-
chorusEffectsSend: (_channel: any, _note: any,
|
|
131
|
-
reverbEffectsSend: (_channel: any, _note: any,
|
|
132
|
-
delayModLFO: (_channel: any, note: any,
|
|
133
|
-
freqModLFO: (_channel: any, note: any,
|
|
134
|
-
delayVibLFO: (_channel: any, _note: any,
|
|
135
|
-
freqVibLFO: (_channel: any, _note: any,
|
|
131
|
+
modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
|
|
132
|
+
vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
133
|
+
modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
|
|
134
|
+
modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
|
|
135
|
+
chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
136
|
+
reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
137
|
+
delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
|
|
138
|
+
freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
|
|
139
|
+
delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
140
|
+
freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
|
|
136
141
|
};
|
|
137
142
|
getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
|
|
138
143
|
applyVoiceParams(channel: any, controllerType: any, scheduleTime: any): void;
|
|
@@ -158,14 +163,14 @@ export class MidyGMLite {
|
|
|
158
163
|
handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
|
|
159
164
|
setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
|
|
160
165
|
allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
161
|
-
|
|
166
|
+
resetChannelStates(channelNumber: any): void;
|
|
162
167
|
resetAllControllers(channelNumber: any, _value: any, scheduleTime: any): void;
|
|
163
168
|
allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
164
169
|
handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
165
170
|
GM1SystemOn(scheduleTime: any): void;
|
|
166
171
|
handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
167
172
|
handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
|
|
168
|
-
setMasterVolume(
|
|
173
|
+
setMasterVolume(value: any, scheduleTime: any): void;
|
|
169
174
|
handleSysEx(data: any, scheduleTime: any): void;
|
|
170
175
|
scheduleTask(callback: any, scheduleTime: any): Promise<any>;
|
|
171
176
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA4BE;;;;;;;;;MASE;IAEF,+BAeC;IArDD,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,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,uBAQC;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,sBAGC;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,6FAyBC;IAED,oGA2CC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAiCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;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;AA9hDD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -216,7 +216,7 @@ class MidyGMLite {
|
|
|
216
216
|
enumerable: true,
|
|
217
217
|
configurable: true,
|
|
218
218
|
writable: true,
|
|
219
|
-
value:
|
|
219
|
+
value: Array.from({ length: 128 }, () => [])
|
|
220
220
|
});
|
|
221
221
|
Object.defineProperty(this, "voiceCounter", {
|
|
222
222
|
enumerable: true,
|
|
@@ -260,6 +260,12 @@ class MidyGMLite {
|
|
|
260
260
|
writable: true,
|
|
261
261
|
value: false
|
|
262
262
|
});
|
|
263
|
+
Object.defineProperty(this, "playPromise", {
|
|
264
|
+
enumerable: true,
|
|
265
|
+
configurable: true,
|
|
266
|
+
writable: true,
|
|
267
|
+
value: void 0
|
|
268
|
+
});
|
|
263
269
|
Object.defineProperty(this, "timeline", {
|
|
264
270
|
enumerable: true,
|
|
265
271
|
configurable: true,
|
|
@@ -297,6 +303,7 @@ class MidyGMLite {
|
|
|
297
303
|
length: 1,
|
|
298
304
|
sampleRate: audioContext.sampleRate,
|
|
299
305
|
});
|
|
306
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
300
307
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
301
308
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
302
309
|
this.channels = this.createChannels(audioContext);
|
|
@@ -304,21 +311,14 @@ class MidyGMLite {
|
|
|
304
311
|
this.scheduler.connect(audioContext.destination);
|
|
305
312
|
this.GM1SystemOn();
|
|
306
313
|
}
|
|
307
|
-
initSoundFontTable() {
|
|
308
|
-
const table = new Array(128);
|
|
309
|
-
for (let i = 0; i < 128; i++) {
|
|
310
|
-
table[i] = new Map();
|
|
311
|
-
}
|
|
312
|
-
return table;
|
|
313
|
-
}
|
|
314
314
|
addSoundFont(soundFont) {
|
|
315
315
|
const index = this.soundFonts.length;
|
|
316
316
|
this.soundFonts.push(soundFont);
|
|
317
317
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
318
|
+
const soundFontTable = this.soundFontTable;
|
|
318
319
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
banks.set(presetHeader.bank, index);
|
|
320
|
+
const { preset, bank } = presetHeaders[i];
|
|
321
|
+
soundFontTable[preset][bank] = index;
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
async toUint8Array(input) {
|
|
@@ -396,13 +396,16 @@ class MidyGMLite {
|
|
|
396
396
|
this.GM1SystemOn();
|
|
397
397
|
}
|
|
398
398
|
getVoiceId(channel, noteNumber, velocity) {
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
|
|
399
|
+
const programNumber = channel.programNumber;
|
|
400
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
401
|
+
if (!bankTable)
|
|
402
|
+
return;
|
|
403
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
404
|
+
const soundFontIndex = bankTable[bank];
|
|
402
405
|
if (soundFontIndex === undefined)
|
|
403
406
|
return;
|
|
404
407
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
405
|
-
const voice = soundFont.getVoice(
|
|
408
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
406
409
|
const { instrument, sampleID } = voice.generators;
|
|
407
410
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
408
411
|
}
|
|
@@ -453,13 +456,16 @@ class MidyGMLite {
|
|
|
453
456
|
}
|
|
454
457
|
return bufferSource;
|
|
455
458
|
}
|
|
456
|
-
async scheduleTimelineEvents(
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
459
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
460
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
461
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
462
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
463
|
+
const timeline = this.timeline;
|
|
464
|
+
while (queueIndex < timeline.length) {
|
|
465
|
+
const event = timeline[queueIndex];
|
|
466
|
+
if (lookAheadCheckTime < event.startTime)
|
|
460
467
|
break;
|
|
461
|
-
const
|
|
462
|
-
const startTime = event.startTime + delay;
|
|
468
|
+
const startTime = event.startTime + schedulingOffset;
|
|
463
469
|
switch (event.type) {
|
|
464
470
|
case "noteOn":
|
|
465
471
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
@@ -494,70 +500,77 @@ class MidyGMLite {
|
|
|
494
500
|
}
|
|
495
501
|
return 0;
|
|
496
502
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
503
|
+
resetAllStates() {
|
|
504
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
505
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
506
|
+
this.voiceCache.clear();
|
|
507
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
508
|
+
this.channels[i].scheduledNotes = [];
|
|
509
|
+
this.resetChannelStates(i);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
updateStates(queueIndex, nextQueueIndex) {
|
|
513
|
+
if (nextQueueIndex < queueIndex)
|
|
514
|
+
queueIndex = 0;
|
|
515
|
+
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
516
|
+
const event = this.timeline[i];
|
|
517
|
+
switch (event.type) {
|
|
518
|
+
case "controller":
|
|
519
|
+
this.setControlChange(event.channel, event.controllerType, event.value, 0);
|
|
520
|
+
break;
|
|
521
|
+
case "programChange":
|
|
522
|
+
this.setProgramChange(event.channel, event.programNumber, 0);
|
|
523
|
+
break;
|
|
524
|
+
case "pitchBend":
|
|
525
|
+
this.setPitchBend(event.channel, event.value + 8192, 0);
|
|
526
|
+
break;
|
|
527
|
+
case "sysEx":
|
|
528
|
+
this.handleSysEx(event.data, 0);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async playNotes() {
|
|
533
|
+
if (this.audioContext.state === "suspended") {
|
|
534
|
+
await this.audioContext.resume();
|
|
535
|
+
}
|
|
536
|
+
this.isPlaying = true;
|
|
537
|
+
this.isPaused = false;
|
|
538
|
+
this.startTime = this.audioContext.currentTime;
|
|
539
|
+
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
540
|
+
let finished = false;
|
|
541
|
+
this.notePromises = [];
|
|
542
|
+
while (queueIndex < this.timeline.length) {
|
|
543
|
+
const now = this.audioContext.currentTime;
|
|
544
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
545
|
+
if (this.isPausing) {
|
|
546
|
+
await this.stopNotes(0, true, now);
|
|
547
|
+
await this.audioContext.suspend();
|
|
548
|
+
this.notePromises = [];
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
else if (this.isStopping) {
|
|
552
|
+
await this.stopNotes(0, true, now);
|
|
553
|
+
await this.audioContext.suspend();
|
|
554
|
+
finished = true;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
else if (this.isSeeking) {
|
|
558
|
+
await this.stopNotes(0, true, now);
|
|
559
|
+
this.startTime = this.audioContext.currentTime;
|
|
560
|
+
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
561
|
+
this.updateStates(queueIndex, nextQueueIndex);
|
|
562
|
+
queueIndex = nextQueueIndex;
|
|
563
|
+
this.isSeeking = false;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const waitTime = now + this.noteCheckInterval;
|
|
567
|
+
await this.scheduleTask(() => { }, waitTime);
|
|
568
|
+
}
|
|
569
|
+
if (finished) {
|
|
504
570
|
this.notePromises = [];
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
this.notePromises = [];
|
|
509
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
510
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
511
|
-
this.voiceCache.clear();
|
|
512
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
513
|
-
this.resetAllStates(i);
|
|
514
|
-
}
|
|
515
|
-
resolve();
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
const now = this.audioContext.currentTime;
|
|
519
|
-
const t = now + resumeTime;
|
|
520
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
521
|
-
if (this.isPausing) {
|
|
522
|
-
await this.stopNotes(0, true, now);
|
|
523
|
-
this.notePromises = [];
|
|
524
|
-
this.isPausing = false;
|
|
525
|
-
this.isPaused = true;
|
|
526
|
-
resolve();
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
else if (this.isStopping) {
|
|
530
|
-
await this.stopNotes(0, true, now);
|
|
531
|
-
this.notePromises = [];
|
|
532
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
533
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
534
|
-
this.voiceCache.clear();
|
|
535
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
536
|
-
this.resetAllStates(i);
|
|
537
|
-
}
|
|
538
|
-
this.isStopping = false;
|
|
539
|
-
this.isPaused = false;
|
|
540
|
-
resolve();
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
else if (this.isSeeking) {
|
|
544
|
-
this.stopNotes(0, true, now);
|
|
545
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
546
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
547
|
-
this.startTime = this.audioContext.currentTime;
|
|
548
|
-
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
549
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
550
|
-
this.isSeeking = false;
|
|
551
|
-
await schedulePlayback();
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
const waitTime = now + this.noteCheckInterval;
|
|
555
|
-
await this.scheduleTask(() => { }, waitTime);
|
|
556
|
-
await schedulePlayback();
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
schedulePlayback();
|
|
560
|
-
});
|
|
571
|
+
this.resetAllStates();
|
|
572
|
+
}
|
|
573
|
+
this.isPlaying = false;
|
|
561
574
|
}
|
|
562
575
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
563
576
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -565,16 +578,16 @@ class MidyGMLite {
|
|
|
565
578
|
secondToTicks(second, secondsPerBeat) {
|
|
566
579
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
567
580
|
}
|
|
581
|
+
getSoundFontId(channel) {
|
|
582
|
+
const programNumber = channel.programNumber;
|
|
583
|
+
const bank = channel.isDrum ? "128" : "000";
|
|
584
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
585
|
+
return `${bank}:${program}`;
|
|
586
|
+
}
|
|
568
587
|
extractMidiData(midi) {
|
|
569
588
|
const instruments = new Set();
|
|
570
589
|
const timeline = [];
|
|
571
|
-
const
|
|
572
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
573
|
-
tmpChannels[i] = {
|
|
574
|
-
programNumber: -1,
|
|
575
|
-
bank: this.channels[i].bank,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
590
|
+
const channels = this.channels;
|
|
578
591
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
579
592
|
const track = midi.tracks[i];
|
|
580
593
|
let currentTicks = 0;
|
|
@@ -584,17 +597,15 @@ class MidyGMLite {
|
|
|
584
597
|
event.ticks = currentTicks;
|
|
585
598
|
switch (event.type) {
|
|
586
599
|
case "noteOn": {
|
|
587
|
-
const channel =
|
|
588
|
-
|
|
589
|
-
instruments.add(`${channel.bank}:0`);
|
|
590
|
-
channel.programNumber = 0;
|
|
591
|
-
}
|
|
600
|
+
const channel = channels[event.channel];
|
|
601
|
+
instruments.add(this.getSoundFontId(channel));
|
|
592
602
|
break;
|
|
593
603
|
}
|
|
594
604
|
case "programChange": {
|
|
595
|
-
const channel =
|
|
596
|
-
channel
|
|
597
|
-
instruments.add(
|
|
605
|
+
const channel = channels[event.channel];
|
|
606
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
607
|
+
instruments.add(this.getSoundFontId(channel));
|
|
608
|
+
break;
|
|
598
609
|
}
|
|
599
610
|
}
|
|
600
611
|
delete event.deltaTime;
|
|
@@ -658,26 +669,32 @@ class MidyGMLite {
|
|
|
658
669
|
this.resumeTime = 0;
|
|
659
670
|
if (this.voiceCounter.size === 0)
|
|
660
671
|
this.cacheVoiceIds();
|
|
661
|
-
|
|
662
|
-
this.
|
|
672
|
+
this.playPromise = this.playNotes();
|
|
673
|
+
await this.playPromise;
|
|
663
674
|
}
|
|
664
|
-
stop() {
|
|
675
|
+
async stop() {
|
|
665
676
|
if (!this.isPlaying)
|
|
666
677
|
return;
|
|
667
678
|
this.isStopping = true;
|
|
679
|
+
await this.playPromise;
|
|
680
|
+
this.isStopping = false;
|
|
668
681
|
}
|
|
669
|
-
pause() {
|
|
682
|
+
async pause() {
|
|
670
683
|
if (!this.isPlaying || this.isPaused)
|
|
671
684
|
return;
|
|
672
685
|
const now = this.audioContext.currentTime;
|
|
673
686
|
this.resumeTime += now - this.startTime - this.startDelay;
|
|
674
687
|
this.isPausing = true;
|
|
688
|
+
await this.playPromise;
|
|
689
|
+
this.isPausing = false;
|
|
690
|
+
this.isPaused = true;
|
|
675
691
|
}
|
|
676
692
|
async resume() {
|
|
677
693
|
if (!this.isPaused)
|
|
678
694
|
return;
|
|
679
|
-
|
|
680
|
-
this.
|
|
695
|
+
this.playPromise = this.playNotes();
|
|
696
|
+
await this.playPromise;
|
|
697
|
+
this.isPaused = false;
|
|
681
698
|
}
|
|
682
699
|
seekTo(second) {
|
|
683
700
|
this.resumeTime = second;
|
|
@@ -858,7 +875,7 @@ class MidyGMLite {
|
|
|
858
875
|
const voiceParams = voice.getAllParams(controllerState);
|
|
859
876
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
860
877
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
861
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
878
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
862
879
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
863
880
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
864
881
|
type: "lowpass",
|
|
@@ -894,7 +911,7 @@ class MidyGMLite {
|
|
|
894
911
|
const channel = this.channels[channelNumber];
|
|
895
912
|
if (!channel.isDrum)
|
|
896
913
|
return;
|
|
897
|
-
const drumExclusiveClass = drumExclusiveClasses[noteNumber];
|
|
914
|
+
const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
|
|
898
915
|
if (drumExclusiveClass === 0)
|
|
899
916
|
return;
|
|
900
917
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
@@ -907,13 +924,16 @@ class MidyGMLite {
|
|
|
907
924
|
}
|
|
908
925
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
909
926
|
const channel = this.channels[channelNumber];
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
927
|
+
const programNumber = channel.programNumber;
|
|
928
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
929
|
+
if (!bankTable)
|
|
930
|
+
return;
|
|
931
|
+
const bank = channel.isDrum ? 128 : 0;
|
|
932
|
+
const soundFontIndex = bankTable[bank];
|
|
913
933
|
if (soundFontIndex === undefined)
|
|
914
934
|
return;
|
|
915
935
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
916
|
-
const voice = soundFont.getVoice(
|
|
936
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
917
937
|
if (!voice)
|
|
918
938
|
return;
|
|
919
939
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
@@ -1020,7 +1040,26 @@ class MidyGMLite {
|
|
|
1020
1040
|
channel.sustainNotes = [];
|
|
1021
1041
|
return promises;
|
|
1022
1042
|
}
|
|
1023
|
-
|
|
1043
|
+
createMessageHandlers() {
|
|
1044
|
+
const handlers = new Array(256);
|
|
1045
|
+
// Channel Message
|
|
1046
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1047
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1048
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1049
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1050
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1051
|
+
return handlers;
|
|
1052
|
+
}
|
|
1053
|
+
handleMessage(data, scheduleTime) {
|
|
1054
|
+
const status = data[0];
|
|
1055
|
+
if (status === 0xF0) {
|
|
1056
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1057
|
+
}
|
|
1058
|
+
const handler = this.messageHandlers[status];
|
|
1059
|
+
if (handler)
|
|
1060
|
+
handler(data, scheduleTime);
|
|
1061
|
+
}
|
|
1062
|
+
handleChannelMessage(statusByte, data1, data2, scheduleTime) {
|
|
1024
1063
|
const channelNumber = statusByte & 0x0F;
|
|
1025
1064
|
const messageType = statusByte & 0xF0;
|
|
1026
1065
|
switch (messageType) {
|
|
@@ -1100,28 +1139,36 @@ class MidyGMLite {
|
|
|
1100
1139
|
}
|
|
1101
1140
|
createVoiceParamsHandlers() {
|
|
1102
1141
|
return {
|
|
1103
|
-
modLfoToPitch: (channel, note,
|
|
1142
|
+
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1104
1143
|
if (0 < channel.state.modulationDepth) {
|
|
1105
1144
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1106
1145
|
}
|
|
1107
1146
|
},
|
|
1108
|
-
vibLfoToPitch: (_channel, _note,
|
|
1109
|
-
modLfoToFilterFc: (channel, note,
|
|
1147
|
+
vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
|
|
1148
|
+
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1110
1149
|
if (0 < channel.state.modulationDepth) {
|
|
1111
1150
|
this.setModLfoToFilterFc(note, scheduleTime);
|
|
1112
1151
|
}
|
|
1113
1152
|
},
|
|
1114
|
-
modLfoToVolume: (channel, note,
|
|
1153
|
+
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1115
1154
|
if (0 < channel.state.modulationDepth) {
|
|
1116
1155
|
this.setModLfoToVolume(note, scheduleTime);
|
|
1117
1156
|
}
|
|
1118
1157
|
},
|
|
1119
|
-
chorusEffectsSend: (_channel, _note,
|
|
1120
|
-
reverbEffectsSend: (_channel, _note,
|
|
1121
|
-
delayModLFO: (_channel, note,
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1158
|
+
chorusEffectsSend: (_channel, _note, _scheduleTime) => { },
|
|
1159
|
+
reverbEffectsSend: (_channel, _note, _scheduleTime) => { },
|
|
1160
|
+
delayModLFO: (_channel, note, scheduleTime) => {
|
|
1161
|
+
if (0 < channel.state.modulationDepth) {
|
|
1162
|
+
this.setDelayModLFO(note, scheduleTime);
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1166
|
+
if (0 < channel.state.modulationDepth) {
|
|
1167
|
+
this.setFreqModLFO(note, scheduleTime);
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
delayVibLFO: (_channel, _note, _scheduleTime) => { },
|
|
1171
|
+
freqVibLFO: (_channel, _note, _scheduleTime) => { },
|
|
1125
1172
|
};
|
|
1126
1173
|
}
|
|
1127
1174
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1144,7 +1191,7 @@ class MidyGMLite {
|
|
|
1144
1191
|
continue;
|
|
1145
1192
|
note.voiceParams[key] = value;
|
|
1146
1193
|
if (key in this.voiceParamsHandlers) {
|
|
1147
|
-
this.voiceParamsHandlers[key](channel, note,
|
|
1194
|
+
this.voiceParamsHandlers[key](channel, note, scheduleTime);
|
|
1148
1195
|
}
|
|
1149
1196
|
else {
|
|
1150
1197
|
if (volumeEnvelopeKeySet.has(key))
|
|
@@ -1301,8 +1348,8 @@ class MidyGMLite {
|
|
|
1301
1348
|
}
|
|
1302
1349
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1303
1350
|
const channel = this.channels[channelNumber];
|
|
1304
|
-
this.limitData(channel, 0, 127, 0,
|
|
1305
|
-
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1351
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
1352
|
+
const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
1306
1353
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1307
1354
|
}
|
|
1308
1355
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
@@ -1310,7 +1357,7 @@ class MidyGMLite {
|
|
|
1310
1357
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1311
1358
|
const state = channel.state;
|
|
1312
1359
|
const prev = state.pitchWheelSensitivity;
|
|
1313
|
-
const next = value /
|
|
1360
|
+
const next = value / 12800;
|
|
1314
1361
|
state.pitchWheelSensitivity = next;
|
|
1315
1362
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1316
1363
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -1320,7 +1367,7 @@ class MidyGMLite {
|
|
|
1320
1367
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1321
1368
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1322
1369
|
}
|
|
1323
|
-
|
|
1370
|
+
resetChannelStates(channelNumber) {
|
|
1324
1371
|
const scheduleTime = this.audioContext.currentTime;
|
|
1325
1372
|
const channel = this.channels[channelNumber];
|
|
1326
1373
|
const state = channel.state;
|
|
@@ -1395,10 +1442,8 @@ class MidyGMLite {
|
|
|
1395
1442
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1396
1443
|
this.allSoundOff(i, 0, scheduleTime);
|
|
1397
1444
|
const channel = this.channels[i];
|
|
1398
|
-
channel.bank = 0;
|
|
1399
1445
|
channel.isDrum = false;
|
|
1400
1446
|
}
|
|
1401
|
-
this.channels[9].bank = 128;
|
|
1402
1447
|
this.channels[9].isDrum = true;
|
|
1403
1448
|
}
|
|
1404
1449
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -1419,16 +1464,11 @@ class MidyGMLite {
|
|
|
1419
1464
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1420
1465
|
this.setMasterVolume(volume, scheduleTime);
|
|
1421
1466
|
}
|
|
1422
|
-
setMasterVolume(
|
|
1467
|
+
setMasterVolume(value, scheduleTime) {
|
|
1423
1468
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
else {
|
|
1428
|
-
this.masterVolume.gain
|
|
1429
|
-
.cancelScheduledValues(scheduleTime)
|
|
1430
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
1431
|
-
}
|
|
1469
|
+
this.masterVolume.gain
|
|
1470
|
+
.cancelScheduledValues(scheduleTime)
|
|
1471
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
1432
1472
|
}
|
|
1433
1473
|
handleSysEx(data, scheduleTime) {
|
|
1434
1474
|
switch (data[0]) {
|
|
@@ -1469,7 +1509,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1469
1509
|
scheduleIndex: 0,
|
|
1470
1510
|
detune: 0,
|
|
1471
1511
|
programNumber: 0,
|
|
1472
|
-
bank: 0,
|
|
1473
1512
|
dataMSB: 0,
|
|
1474
1513
|
dataLSB: 0,
|
|
1475
1514
|
rpnMSB: 127,
|