@marmooo/midy 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/esm/midy-GM1.d.ts +13 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +126 -95
- package/esm/midy-GM2.d.ts +17 -13
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +157 -124
- package/esm/midy-GMLite.d.ts +14 -10
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +125 -96
- package/esm/midy.d.ts +18 -14
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +172 -139
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +13 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +126 -95
- package/script/midy-GM2.d.ts +17 -13
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +157 -124
- package/script/midy-GMLite.d.ts +14 -10
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +125 -96
- package/script/midy.d.ts +18 -14
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +172 -139
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,8 +22,8 @@ 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;
|
|
@@ -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): string | undefined;
|
|
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,mBAAiB;IACjB,oBAAkB;IAClB,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,6EAcC;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,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;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;AAt/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,
|
|
@@ -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}:${instrument}:${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
|
}
|
|
@@ -1097,7 +1125,7 @@ export class MidyGMLite {
|
|
|
1097
1125
|
return state;
|
|
1098
1126
|
}
|
|
1099
1127
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1100
|
-
this.processScheduledNotes(channel,
|
|
1128
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1101
1129
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1102
1130
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1103
1131
|
let applyVolumeEnvelope = false;
|
|
@@ -1144,7 +1172,7 @@ export class MidyGMLite {
|
|
|
1144
1172
|
handlers[123] = this.allNotesOff;
|
|
1145
1173
|
return handlers;
|
|
1146
1174
|
}
|
|
1147
|
-
|
|
1175
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1148
1176
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1149
1177
|
if (handler) {
|
|
1150
1178
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1157,7 +1185,7 @@ export class MidyGMLite {
|
|
|
1157
1185
|
}
|
|
1158
1186
|
updateModulation(channel, scheduleTime) {
|
|
1159
1187
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1160
|
-
this.processScheduledNotes(channel,
|
|
1188
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1161
1189
|
if (note.modulationDepth) {
|
|
1162
1190
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1163
1191
|
}
|
|
@@ -1218,7 +1246,7 @@ export class MidyGMLite {
|
|
|
1218
1246
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1219
1247
|
channel.state.sustainPedal = value / 127;
|
|
1220
1248
|
if (64 <= value) {
|
|
1221
|
-
this.processScheduledNotes(channel,
|
|
1249
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1222
1250
|
channel.sustainNotes.push(note);
|
|
1223
1251
|
});
|
|
1224
1252
|
}
|
|
@@ -1293,7 +1321,7 @@ export class MidyGMLite {
|
|
|
1293
1321
|
const entries = Object.entries(defaultControllerState);
|
|
1294
1322
|
for (const [key, { type, defaultValue }] of entries) {
|
|
1295
1323
|
if (128 <= type) {
|
|
1296
|
-
this.
|
|
1324
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1297
1325
|
}
|
|
1298
1326
|
else {
|
|
1299
1327
|
state[key] = defaultValue;
|
|
@@ -1318,7 +1346,7 @@ export class MidyGMLite {
|
|
|
1318
1346
|
const key = keys[i];
|
|
1319
1347
|
const { type, defaultValue } = defaultControllerState[key];
|
|
1320
1348
|
if (128 <= type) {
|
|
1321
|
-
this.
|
|
1349
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1322
1350
|
}
|
|
1323
1351
|
else {
|
|
1324
1352
|
state[key] = defaultValue;
|
|
@@ -1431,6 +1459,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1431
1459
|
configurable: true,
|
|
1432
1460
|
writable: true,
|
|
1433
1461
|
value: {
|
|
1462
|
+
scheduleIndex: 0,
|
|
1434
1463
|
detune: 0,
|
|
1435
1464
|
programNumber: 0,
|
|
1436
1465
|
bank: 0,
|