@marmooo/midy 0.3.4 → 0.3.6
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 +19 -11
- package/esm/midy-GM1.d.ts +14 -11
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +140 -106
- package/esm/midy-GM2.d.ts +26 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +285 -279
- package/esm/midy-GMLite.d.ts +15 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +139 -107
- package/esm/midy.d.ts +31 -27
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +304 -298
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +14 -11
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +140 -106
- package/script/midy-GM2.d.ts +26 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +285 -279
- package/script/midy-GMLite.d.ts +15 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +139 -107
- package/script/midy.d.ts +31 -27
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +304 -298
package/esm/midy-GMLite.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export class MidyGMLite {
|
|
2
2
|
static channelSettings: {
|
|
3
|
+
scheduleIndex: number;
|
|
3
4
|
detune: number;
|
|
4
5
|
programNumber: number;
|
|
5
6
|
bank: number;
|
|
@@ -21,16 +22,16 @@ export class MidyGMLite {
|
|
|
21
22
|
resumeTime: number;
|
|
22
23
|
soundFonts: any[];
|
|
23
24
|
soundFontTable: any[];
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
voiceCounter: Map<any, any>;
|
|
26
|
+
voiceCache: Map<any, any>;
|
|
26
27
|
isPlaying: boolean;
|
|
27
28
|
isPausing: boolean;
|
|
28
29
|
isPaused: boolean;
|
|
29
30
|
isStopping: boolean;
|
|
30
31
|
isSeeking: boolean;
|
|
31
32
|
timeline: any[];
|
|
32
|
-
instruments: any[];
|
|
33
33
|
notePromises: any[];
|
|
34
|
+
instruments: Set<any>;
|
|
34
35
|
exclusiveClassNotes: any[];
|
|
35
36
|
drumExclusiveClassNotes: any[];
|
|
36
37
|
audioContext: any;
|
|
@@ -53,22 +54,24 @@ export class MidyGMLite {
|
|
|
53
54
|
channels: any[];
|
|
54
55
|
initSoundFontTable(): any[];
|
|
55
56
|
addSoundFont(soundFont: any): void;
|
|
57
|
+
toUint8Array(input: any): Promise<Uint8Array<ArrayBuffer>>;
|
|
56
58
|
loadSoundFont(input: any): Promise<void>;
|
|
57
59
|
loadMIDI(input: any): Promise<void>;
|
|
60
|
+
cacheVoiceIds(): void;
|
|
61
|
+
getVoiceId(channel: any, noteNumber: any, velocity: any): any;
|
|
58
62
|
createChannelAudioNodes(audioContext: any): {
|
|
59
63
|
gainL: any;
|
|
60
64
|
gainR: any;
|
|
61
65
|
merger: any;
|
|
62
66
|
};
|
|
63
67
|
createChannels(audioContext: any): any[];
|
|
64
|
-
|
|
68
|
+
createAudioBuffer(voiceParams: any): Promise<any>;
|
|
65
69
|
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
66
70
|
scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
|
|
67
71
|
getQueueIndex(second: any): number;
|
|
68
72
|
playNotes(): Promise<any>;
|
|
69
73
|
ticksToSecond(ticks: any, secondsPerBeat: any): number;
|
|
70
74
|
secondToTicks(second: any, secondsPerBeat: any): number;
|
|
71
|
-
getAudioBufferId(programNumber: any, noteNumber: any, velocity: any): string;
|
|
72
75
|
extractMidiData(midi: any): {
|
|
73
76
|
instruments: Set<any>;
|
|
74
77
|
timeline: any[];
|
|
@@ -83,7 +86,7 @@ export class MidyGMLite {
|
|
|
83
86
|
seekTo(second: any): void;
|
|
84
87
|
calcTotalTime(): number;
|
|
85
88
|
currentTime(): number;
|
|
86
|
-
processScheduledNotes(channel: any,
|
|
89
|
+
processScheduledNotes(channel: any, callback: any): void;
|
|
87
90
|
processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
|
|
88
91
|
cbToRatio(cb: any): number;
|
|
89
92
|
rateToCent(rate: any): number;
|
|
@@ -97,8 +100,8 @@ export class MidyGMLite {
|
|
|
97
100
|
clampCutoffFrequency(frequency: any): number;
|
|
98
101
|
setFilterEnvelope(note: any, scheduleTime: any): void;
|
|
99
102
|
startModulation(channel: any, note: any, scheduleTime: any): void;
|
|
100
|
-
getAudioBuffer(
|
|
101
|
-
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any
|
|
103
|
+
getAudioBuffer(channel: any, noteNumber: any, velocity: any, voiceParams: any): Promise<any>;
|
|
104
|
+
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any): Promise<Note>;
|
|
102
105
|
handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
103
106
|
handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
|
|
104
107
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
@@ -106,11 +109,12 @@ export class MidyGMLite {
|
|
|
106
109
|
disconnectNote(note: any): void;
|
|
107
110
|
releaseNote(channel: any, note: any, endTime: any): Promise<any>;
|
|
108
111
|
scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): void;
|
|
109
|
-
|
|
112
|
+
setNoteIndex(channel: any, index: any): void;
|
|
113
|
+
findNoteOffIndex(channel: any, noteNumber: any): any;
|
|
110
114
|
noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
|
|
111
115
|
releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
|
|
112
116
|
handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
|
|
113
|
-
|
|
117
|
+
setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
|
|
114
118
|
handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
|
|
115
119
|
setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
|
|
116
120
|
setModLfoToPitch(channel: any, note: any, scheduleTime: any): void;
|
|
@@ -133,7 +137,7 @@ export class MidyGMLite {
|
|
|
133
137
|
getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
|
|
134
138
|
applyVoiceParams(channel: any, controllerType: any, scheduleTime: any): void;
|
|
135
139
|
createControlChangeHandlers(): any[];
|
|
136
|
-
|
|
140
|
+
setControlChange(channelNumber: any, controllerType: any, value: any, scheduleTime: any): void;
|
|
137
141
|
updateModulation(channel: any, scheduleTime: any): void;
|
|
138
142
|
setModulationDepth(channelNumber: any, modulation: any, scheduleTime: any): void;
|
|
139
143
|
setVolume(channelNumber: any, volume: any, scheduleTime: any): void;
|
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":"AA0GA;IA2BE
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA2BE;;;;;;;;;;MAUE;IAEF,+BAcC;IApDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;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,oGAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;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,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAx/CD;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/esm/midy-GMLite.js
CHANGED
|
@@ -215,13 +215,13 @@ export class MidyGMLite {
|
|
|
215
215
|
writable: true,
|
|
216
216
|
value: this.initSoundFontTable()
|
|
217
217
|
});
|
|
218
|
-
Object.defineProperty(this, "
|
|
218
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
219
219
|
enumerable: true,
|
|
220
220
|
configurable: true,
|
|
221
221
|
writable: true,
|
|
222
222
|
value: new Map()
|
|
223
223
|
});
|
|
224
|
-
Object.defineProperty(this, "
|
|
224
|
+
Object.defineProperty(this, "voiceCache", {
|
|
225
225
|
enumerable: true,
|
|
226
226
|
configurable: true,
|
|
227
227
|
writable: true,
|
|
@@ -263,17 +263,17 @@ export class MidyGMLite {
|
|
|
263
263
|
writable: true,
|
|
264
264
|
value: []
|
|
265
265
|
});
|
|
266
|
-
Object.defineProperty(this, "
|
|
266
|
+
Object.defineProperty(this, "notePromises", {
|
|
267
267
|
enumerable: true,
|
|
268
268
|
configurable: true,
|
|
269
269
|
writable: true,
|
|
270
270
|
value: []
|
|
271
271
|
});
|
|
272
|
-
Object.defineProperty(this, "
|
|
272
|
+
Object.defineProperty(this, "instruments", {
|
|
273
273
|
enumerable: true,
|
|
274
274
|
configurable: true,
|
|
275
275
|
writable: true,
|
|
276
|
-
value:
|
|
276
|
+
value: new Set()
|
|
277
277
|
});
|
|
278
278
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
279
279
|
enumerable: true,
|
|
@@ -314,13 +314,11 @@ export class MidyGMLite {
|
|
|
314
314
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
315
315
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
316
316
|
const presetHeader = presetHeaders[i];
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
banks.set(presetHeader.bank, index);
|
|
320
|
-
}
|
|
317
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
318
|
+
banks.set(presetHeader.bank, index);
|
|
321
319
|
}
|
|
322
320
|
}
|
|
323
|
-
async
|
|
321
|
+
async toUint8Array(input) {
|
|
324
322
|
let uint8Array;
|
|
325
323
|
if (typeof input === "string") {
|
|
326
324
|
const response = await fetch(input);
|
|
@@ -333,23 +331,32 @@ export class MidyGMLite {
|
|
|
333
331
|
else {
|
|
334
332
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
335
333
|
}
|
|
336
|
-
|
|
337
|
-
const soundFont = new SoundFont(parsed);
|
|
338
|
-
this.addSoundFont(soundFont);
|
|
334
|
+
return uint8Array;
|
|
339
335
|
}
|
|
340
|
-
async
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
async loadSoundFont(input) {
|
|
337
|
+
this.voiceCounter.clear();
|
|
338
|
+
if (Array.isArray(input)) {
|
|
339
|
+
const promises = new Array(input.length);
|
|
340
|
+
for (let i = 0; i < input.length; i++) {
|
|
341
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
342
|
+
}
|
|
343
|
+
const uint8Arrays = await Promise.all(promises);
|
|
344
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
345
|
+
const parsed = parse(uint8Arrays[i]);
|
|
346
|
+
const soundFont = new SoundFont(parsed);
|
|
347
|
+
this.addSoundFont(soundFont);
|
|
348
|
+
}
|
|
349
349
|
}
|
|
350
350
|
else {
|
|
351
|
-
|
|
351
|
+
const uint8Array = await this.toUint8Array(input);
|
|
352
|
+
const parsed = parse(uint8Array);
|
|
353
|
+
const soundFont = new SoundFont(parsed);
|
|
354
|
+
this.addSoundFont(soundFont);
|
|
352
355
|
}
|
|
356
|
+
}
|
|
357
|
+
async loadMIDI(input) {
|
|
358
|
+
this.voiceCounter.clear();
|
|
359
|
+
const uint8Array = await this.toUint8Array(input);
|
|
353
360
|
const midi = parseMidi(uint8Array);
|
|
354
361
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
355
362
|
const midiData = this.extractMidiData(midi);
|
|
@@ -357,6 +364,45 @@ export class MidyGMLite {
|
|
|
357
364
|
this.timeline = midiData.timeline;
|
|
358
365
|
this.totalTime = this.calcTotalTime();
|
|
359
366
|
}
|
|
367
|
+
cacheVoiceIds() {
|
|
368
|
+
const timeline = this.timeline;
|
|
369
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
370
|
+
const event = timeline[i];
|
|
371
|
+
switch (event.type) {
|
|
372
|
+
case "noteOn": {
|
|
373
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
374
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
case "controller":
|
|
378
|
+
if (event.controllerType === 0) {
|
|
379
|
+
this.setBankMSB(event.channel, event.value);
|
|
380
|
+
}
|
|
381
|
+
else if (event.controllerType === 32) {
|
|
382
|
+
this.setBankLSB(event.channel, event.value);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
case "programChange":
|
|
386
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
390
|
+
if (count === 1)
|
|
391
|
+
this.voiceCounter.delete(audioBufferId);
|
|
392
|
+
}
|
|
393
|
+
this.GM1SystemOn();
|
|
394
|
+
}
|
|
395
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
396
|
+
const bankNumber = this.calcBank(channel);
|
|
397
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
398
|
+
.get(bankNumber);
|
|
399
|
+
if (soundFontIndex === undefined)
|
|
400
|
+
return;
|
|
401
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
402
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
403
|
+
const { instrument, sampleID } = voice.generators;
|
|
404
|
+
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
405
|
+
}
|
|
360
406
|
createChannelAudioNodes(audioContext) {
|
|
361
407
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
362
408
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -385,34 +431,12 @@ export class MidyGMLite {
|
|
|
385
431
|
});
|
|
386
432
|
return channels;
|
|
387
433
|
}
|
|
388
|
-
async
|
|
434
|
+
async createAudioBuffer(voiceParams) {
|
|
435
|
+
const sample = voiceParams.sample;
|
|
389
436
|
const sampleStart = voiceParams.start;
|
|
390
|
-
const sampleEnd =
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const start = sample.byteOffset + sampleStart;
|
|
394
|
-
const end = sample.byteOffset + sampleEnd;
|
|
395
|
-
const buffer = sample.buffer.slice(start, end);
|
|
396
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
397
|
-
return audioBuffer;
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
const sample = voiceParams.sample;
|
|
401
|
-
const start = sample.byteOffset + sampleStart;
|
|
402
|
-
const end = sample.byteOffset + sampleEnd;
|
|
403
|
-
const buffer = sample.buffer.slice(start, end);
|
|
404
|
-
const audioBuffer = new AudioBuffer({
|
|
405
|
-
numberOfChannels: 1,
|
|
406
|
-
length: sample.length,
|
|
407
|
-
sampleRate: voiceParams.sampleRate,
|
|
408
|
-
});
|
|
409
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
410
|
-
const int16Array = new Int16Array(buffer);
|
|
411
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
412
|
-
channelData[i] = int16Array[i] / 32768;
|
|
413
|
-
}
|
|
414
|
-
return audioBuffer;
|
|
415
|
-
}
|
|
437
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
438
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
439
|
+
return audioBuffer;
|
|
416
440
|
}
|
|
417
441
|
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
418
442
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
@@ -444,10 +468,10 @@ export class MidyGMLite {
|
|
|
444
468
|
break;
|
|
445
469
|
}
|
|
446
470
|
case "controller":
|
|
447
|
-
this.
|
|
471
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
448
472
|
break;
|
|
449
473
|
case "programChange":
|
|
450
|
-
this.
|
|
474
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
451
475
|
break;
|
|
452
476
|
case "pitchBend":
|
|
453
477
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -481,7 +505,7 @@ export class MidyGMLite {
|
|
|
481
505
|
this.notePromises = [];
|
|
482
506
|
this.exclusiveClassNotes.fill(undefined);
|
|
483
507
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
484
|
-
this.
|
|
508
|
+
this.voiceCache.clear();
|
|
485
509
|
for (let i = 0; i < this.channels.length; i++) {
|
|
486
510
|
this.resetAllStates(i);
|
|
487
511
|
}
|
|
@@ -504,7 +528,7 @@ export class MidyGMLite {
|
|
|
504
528
|
this.notePromises = [];
|
|
505
529
|
this.exclusiveClassNotes.fill(undefined);
|
|
506
530
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
507
|
-
this.
|
|
531
|
+
this.voiceCache.clear();
|
|
508
532
|
for (let i = 0; i < this.channels.length; i++) {
|
|
509
533
|
this.resetAllStates(i);
|
|
510
534
|
}
|
|
@@ -538,11 +562,7 @@ export class MidyGMLite {
|
|
|
538
562
|
secondToTicks(second, secondsPerBeat) {
|
|
539
563
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
540
564
|
}
|
|
541
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
542
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
543
|
-
}
|
|
544
565
|
extractMidiData(midi) {
|
|
545
|
-
this.audioBufferCounter.clear();
|
|
546
566
|
const instruments = new Set();
|
|
547
567
|
const timeline = [];
|
|
548
568
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -562,8 +582,6 @@ export class MidyGMLite {
|
|
|
562
582
|
switch (event.type) {
|
|
563
583
|
case "noteOn": {
|
|
564
584
|
const channel = tmpChannels[event.channel];
|
|
565
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
566
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
567
585
|
if (channel.programNumber < 0) {
|
|
568
586
|
instruments.add(`${channel.bank}:0`);
|
|
569
587
|
channel.programNumber = 0;
|
|
@@ -580,10 +598,6 @@ export class MidyGMLite {
|
|
|
580
598
|
timeline.push(event);
|
|
581
599
|
}
|
|
582
600
|
}
|
|
583
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
584
|
-
if (count === 1)
|
|
585
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
586
|
-
}
|
|
587
601
|
const priority = {
|
|
588
602
|
controller: 0,
|
|
589
603
|
sysEx: 1,
|
|
@@ -621,12 +635,11 @@ export class MidyGMLite {
|
|
|
621
635
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
622
636
|
const channel = this.channels[channelNumber];
|
|
623
637
|
const promises = [];
|
|
624
|
-
this.processScheduledNotes(channel,
|
|
638
|
+
this.processScheduledNotes(channel, (note) => {
|
|
625
639
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
626
640
|
this.notePromises.push(promise);
|
|
627
641
|
promises.push(promise);
|
|
628
642
|
});
|
|
629
|
-
channel.scheduledNotes = [];
|
|
630
643
|
return Promise.all(promises);
|
|
631
644
|
}
|
|
632
645
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -640,6 +653,8 @@ export class MidyGMLite {
|
|
|
640
653
|
if (this.isPlaying || this.isPaused)
|
|
641
654
|
return;
|
|
642
655
|
this.resumeTime = 0;
|
|
656
|
+
if (this.voiceCounter.size === 0)
|
|
657
|
+
this.cacheVoiceIds();
|
|
643
658
|
await this.playNotes();
|
|
644
659
|
this.isPlaying = false;
|
|
645
660
|
}
|
|
@@ -680,22 +695,20 @@ export class MidyGMLite {
|
|
|
680
695
|
const now = this.audioContext.currentTime;
|
|
681
696
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
682
697
|
}
|
|
683
|
-
processScheduledNotes(channel,
|
|
698
|
+
processScheduledNotes(channel, callback) {
|
|
684
699
|
const scheduledNotes = channel.scheduledNotes;
|
|
685
|
-
for (let i =
|
|
700
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
686
701
|
const note = scheduledNotes[i];
|
|
687
702
|
if (!note)
|
|
688
703
|
continue;
|
|
689
704
|
if (note.ending)
|
|
690
705
|
continue;
|
|
691
|
-
if (note.startTime < scheduleTime)
|
|
692
|
-
continue;
|
|
693
706
|
callback(note);
|
|
694
707
|
}
|
|
695
708
|
}
|
|
696
709
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
697
710
|
const scheduledNotes = channel.scheduledNotes;
|
|
698
|
-
for (let i =
|
|
711
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
699
712
|
const note = scheduledNotes[i];
|
|
700
713
|
if (!note)
|
|
701
714
|
continue;
|
|
@@ -724,7 +737,7 @@ export class MidyGMLite {
|
|
|
724
737
|
return pitchWheel * pitchWheelSensitivity;
|
|
725
738
|
}
|
|
726
739
|
updateChannelDetune(channel, scheduleTime) {
|
|
727
|
-
this.processScheduledNotes(channel,
|
|
740
|
+
this.processScheduledNotes(channel, (note) => {
|
|
728
741
|
this.updateDetune(channel, note, scheduleTime);
|
|
729
742
|
});
|
|
730
743
|
}
|
|
@@ -817,32 +830,32 @@ export class MidyGMLite {
|
|
|
817
830
|
note.modulationLFO.connect(note.volumeDepth);
|
|
818
831
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
819
832
|
}
|
|
820
|
-
async getAudioBuffer(
|
|
821
|
-
const audioBufferId = this.
|
|
822
|
-
const cache = this.
|
|
833
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
834
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
835
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
823
836
|
if (cache) {
|
|
824
837
|
cache.counter += 1;
|
|
825
838
|
if (cache.maxCount <= cache.counter) {
|
|
826
|
-
this.
|
|
839
|
+
this.voiceCache.delete(audioBufferId);
|
|
827
840
|
}
|
|
828
841
|
return cache.audioBuffer;
|
|
829
842
|
}
|
|
830
843
|
else {
|
|
831
|
-
const maxCount = this.
|
|
832
|
-
const audioBuffer = await this.
|
|
844
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
845
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
833
846
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
834
|
-
this.
|
|
847
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
835
848
|
return audioBuffer;
|
|
836
849
|
}
|
|
837
850
|
}
|
|
838
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
851
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
839
852
|
const now = this.audioContext.currentTime;
|
|
840
853
|
const state = channel.state;
|
|
841
854
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
842
855
|
const voiceParams = voice.getAllParams(controllerState);
|
|
843
856
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
844
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
845
|
-
note.bufferSource = this.createBufferSource(
|
|
857
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
858
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
846
859
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
847
860
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
848
861
|
type: "lowpass",
|
|
@@ -892,15 +905,15 @@ export class MidyGMLite {
|
|
|
892
905
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
893
906
|
const channel = this.channels[channelNumber];
|
|
894
907
|
const bankNumber = channel.bank;
|
|
895
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
908
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
909
|
+
.get(bankNumber);
|
|
896
910
|
if (soundFontIndex === undefined)
|
|
897
911
|
return;
|
|
898
912
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
899
913
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
900
914
|
if (!voice)
|
|
901
915
|
return;
|
|
902
|
-
const
|
|
903
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
916
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
904
917
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
905
918
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
906
919
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -955,15 +968,29 @@ export class MidyGMLite {
|
|
|
955
968
|
if (0.5 <= channel.state.sustainPedal)
|
|
956
969
|
return;
|
|
957
970
|
}
|
|
958
|
-
const
|
|
959
|
-
if (
|
|
971
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
972
|
+
if (index < 0)
|
|
960
973
|
return;
|
|
974
|
+
const note = channel.scheduledNotes[index];
|
|
961
975
|
note.ending = true;
|
|
976
|
+
this.setNoteIndex(channel, index);
|
|
962
977
|
this.releaseNote(channel, note, endTime);
|
|
963
978
|
}
|
|
964
|
-
|
|
979
|
+
setNoteIndex(channel, index) {
|
|
980
|
+
let allEnds = true;
|
|
981
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
982
|
+
const note = channel.scheduledNotes[i];
|
|
983
|
+
if (note && !note.ending) {
|
|
984
|
+
allEnds = false;
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (allEnds)
|
|
989
|
+
channel.scheduleIndex = index + 1;
|
|
990
|
+
}
|
|
991
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
965
992
|
const scheduledNotes = channel.scheduledNotes;
|
|
966
|
-
for (let i =
|
|
993
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
967
994
|
const note = scheduledNotes[i];
|
|
968
995
|
if (!note)
|
|
969
996
|
continue;
|
|
@@ -971,8 +998,9 @@ export class MidyGMLite {
|
|
|
971
998
|
continue;
|
|
972
999
|
if (note.noteNumber !== noteNumber)
|
|
973
1000
|
continue;
|
|
974
|
-
return
|
|
1001
|
+
return i;
|
|
975
1002
|
}
|
|
1003
|
+
return -1;
|
|
976
1004
|
}
|
|
977
1005
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
978
1006
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -998,16 +1026,16 @@ export class MidyGMLite {
|
|
|
998
1026
|
case 0x90:
|
|
999
1027
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1000
1028
|
case 0xB0:
|
|
1001
|
-
return this.
|
|
1029
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1002
1030
|
case 0xC0:
|
|
1003
|
-
return this.
|
|
1031
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1004
1032
|
case 0xE0:
|
|
1005
1033
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1006
1034
|
default:
|
|
1007
1035
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1008
1036
|
}
|
|
1009
1037
|
}
|
|
1010
|
-
|
|
1038
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1011
1039
|
const channel = this.channels[channelNumber];
|
|
1012
1040
|
channel.programNumber = programNumber;
|
|
1013
1041
|
}
|
|
@@ -1027,13 +1055,17 @@ export class MidyGMLite {
|
|
|
1027
1055
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1028
1056
|
}
|
|
1029
1057
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
channel.state.modulationDepth;
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1058
|
+
if (note.modulationDepth) {
|
|
1059
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1060
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1061
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1062
|
+
note.modulationDepth.gain
|
|
1063
|
+
.cancelScheduledValues(scheduleTime)
|
|
1064
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1068
|
+
}
|
|
1037
1069
|
}
|
|
1038
1070
|
setModLfoToFilterFc(note, scheduleTime) {
|
|
1039
1071
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
@@ -1097,7 +1129,7 @@ export class MidyGMLite {
|
|
|
1097
1129
|
return state;
|
|
1098
1130
|
}
|
|
1099
1131
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1100
|
-
this.processScheduledNotes(channel,
|
|
1132
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1101
1133
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1102
1134
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1103
1135
|
let applyVolumeEnvelope = false;
|
|
@@ -1144,7 +1176,7 @@ export class MidyGMLite {
|
|
|
1144
1176
|
handlers[123] = this.allNotesOff;
|
|
1145
1177
|
return handlers;
|
|
1146
1178
|
}
|
|
1147
|
-
|
|
1179
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1148
1180
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1149
1181
|
if (handler) {
|
|
1150
1182
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1157,12 +1189,11 @@ export class MidyGMLite {
|
|
|
1157
1189
|
}
|
|
1158
1190
|
updateModulation(channel, scheduleTime) {
|
|
1159
1191
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1160
|
-
this.processScheduledNotes(channel,
|
|
1192
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1161
1193
|
if (note.modulationDepth) {
|
|
1162
1194
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1163
1195
|
}
|
|
1164
1196
|
else {
|
|
1165
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1166
1197
|
this.startModulation(channel, note, scheduleTime);
|
|
1167
1198
|
}
|
|
1168
1199
|
});
|
|
@@ -1218,7 +1249,7 @@ export class MidyGMLite {
|
|
|
1218
1249
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1219
1250
|
channel.state.sustainPedal = value / 127;
|
|
1220
1251
|
if (64 <= value) {
|
|
1221
|
-
this.processScheduledNotes(channel,
|
|
1252
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1222
1253
|
channel.sustainNotes.push(note);
|
|
1223
1254
|
});
|
|
1224
1255
|
}
|
|
@@ -1293,7 +1324,7 @@ export class MidyGMLite {
|
|
|
1293
1324
|
const entries = Object.entries(defaultControllerState);
|
|
1294
1325
|
for (const [key, { type, defaultValue }] of entries) {
|
|
1295
1326
|
if (128 <= type) {
|
|
1296
|
-
this.
|
|
1327
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1297
1328
|
}
|
|
1298
1329
|
else {
|
|
1299
1330
|
state[key] = defaultValue;
|
|
@@ -1318,7 +1349,7 @@ export class MidyGMLite {
|
|
|
1318
1349
|
const key = keys[i];
|
|
1319
1350
|
const { type, defaultValue } = defaultControllerState[key];
|
|
1320
1351
|
if (128 <= type) {
|
|
1321
|
-
this.
|
|
1352
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1322
1353
|
}
|
|
1323
1354
|
else {
|
|
1324
1355
|
state[key] = defaultValue;
|
|
@@ -1431,6 +1462,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1431
1462
|
configurable: true,
|
|
1432
1463
|
writable: true,
|
|
1433
1464
|
value: {
|
|
1465
|
+
scheduleIndex: 0,
|
|
1434
1466
|
detune: 0,
|
|
1435
1467
|
programNumber: 0,
|
|
1436
1468
|
bank: 0,
|