@marmooo/midy 0.2.4 → 0.2.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/esm/midy-GM1.d.ts +44 -28
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -101
- package/esm/midy-GM2.d.ts +54 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +260 -156
- package/esm/midy-GMLite.d.ts +38 -19
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +157 -49
- package/esm/midy.d.ts +57 -35
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +312 -183
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +44 -28
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -101
- package/script/midy-GM2.d.ts +54 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +260 -156
- package/script/midy-GMLite.d.ts +38 -19
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +157 -49
- package/script/midy.d.ts +57 -35
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +312 -183
package/esm/midy-GMLite.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export class MidyGMLite {
|
|
|
19
19
|
resumeTime: number;
|
|
20
20
|
soundFonts: any[];
|
|
21
21
|
soundFontTable: any[];
|
|
22
|
+
audioBufferCounter: Map<any, any>;
|
|
23
|
+
audioBufferCache: Map<any, any>;
|
|
22
24
|
isPlaying: boolean;
|
|
23
25
|
isPausing: boolean;
|
|
24
26
|
isPaused: boolean;
|
|
@@ -27,7 +29,7 @@ export class MidyGMLite {
|
|
|
27
29
|
timeline: any[];
|
|
28
30
|
instruments: any[];
|
|
29
31
|
notePromises: any[];
|
|
30
|
-
exclusiveClassMap:
|
|
32
|
+
exclusiveClassMap: SparseMap;
|
|
31
33
|
audioContext: any;
|
|
32
34
|
masterVolume: any;
|
|
33
35
|
voiceParamsHandlers: {
|
|
@@ -43,11 +45,11 @@ export class MidyGMLite {
|
|
|
43
45
|
freqVibLFO: (_channel: any, _note: any, _prevValue: any) => void;
|
|
44
46
|
};
|
|
45
47
|
controlChangeHandlers: {
|
|
46
|
-
1: (channelNumber: any, modulation: any) => void;
|
|
48
|
+
1: (channelNumber: any, modulation: any, scheduleTime: any) => void;
|
|
47
49
|
6: (channelNumber: any, value: any) => void;
|
|
48
|
-
7: (channelNumber: any, volume: any) => void;
|
|
49
|
-
10: (channelNumber: any, pan: any) => void;
|
|
50
|
-
11: (channelNumber: any, expression: any) => void;
|
|
50
|
+
7: (channelNumber: any, volume: any, scheduleTime: any) => void;
|
|
51
|
+
10: (channelNumber: any, pan: any, scheduleTime: any) => void;
|
|
52
|
+
11: (channelNumber: any, expression: any, scheduleTime: any) => void;
|
|
51
53
|
38: (channelNumber: any, value: any) => void;
|
|
52
54
|
64: (channelNumber: any, value: any) => void;
|
|
53
55
|
100: (channelNumber: any, value: any) => void;
|
|
@@ -68,12 +70,13 @@ export class MidyGMLite {
|
|
|
68
70
|
};
|
|
69
71
|
createChannels(audioContext: any): any[];
|
|
70
72
|
createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
|
|
71
|
-
createNoteBufferNode(
|
|
73
|
+
createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
|
|
72
74
|
scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
|
|
73
75
|
getQueueIndex(second: any): number;
|
|
74
76
|
playNotes(): Promise<any>;
|
|
75
77
|
ticksToSecond(ticks: any, secondsPerBeat: any): number;
|
|
76
78
|
secondToTicks(second: any, secondsPerBeat: any): number;
|
|
79
|
+
getAudioBufferId(programNumber: any, noteNumber: any, velocity: any): string;
|
|
77
80
|
extractMidiData(midi: any): {
|
|
78
81
|
instruments: Set<any>;
|
|
79
82
|
timeline: any[];
|
|
@@ -87,7 +90,8 @@ export class MidyGMLite {
|
|
|
87
90
|
seekTo(second: any): void;
|
|
88
91
|
calcTotalTime(): number;
|
|
89
92
|
currentTime(): number;
|
|
90
|
-
|
|
93
|
+
processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
|
|
94
|
+
getActiveNotes(channel: any, time: any): SparseMap;
|
|
91
95
|
getActiveNote(noteList: any, time: any): any;
|
|
92
96
|
cbToRatio(cb: any): number;
|
|
93
97
|
rateToCent(rate: any): number;
|
|
@@ -97,10 +101,11 @@ export class MidyGMLite {
|
|
|
97
101
|
updateChannelDetune(channel: any): void;
|
|
98
102
|
updateDetune(channel: any, note: any): void;
|
|
99
103
|
setVolumeEnvelope(note: any): void;
|
|
100
|
-
setPitchEnvelope(note: any): void;
|
|
104
|
+
setPitchEnvelope(note: any, scheduleTime: any): void;
|
|
101
105
|
clampCutoffFrequency(frequency: any): number;
|
|
102
106
|
setFilterEnvelope(note: any): void;
|
|
103
107
|
startModulation(channel: any, note: any, startTime: any): void;
|
|
108
|
+
getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
|
|
104
109
|
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
|
|
105
110
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
|
|
106
111
|
noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
|
|
@@ -132,11 +137,11 @@ export class MidyGMLite {
|
|
|
132
137
|
getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
|
|
133
138
|
applyVoiceParams(channel: any, controllerType: any): void;
|
|
134
139
|
createControlChangeHandlers(): {
|
|
135
|
-
1: (channelNumber: any, modulation: any) => void;
|
|
140
|
+
1: (channelNumber: any, modulation: any, scheduleTime: any) => void;
|
|
136
141
|
6: (channelNumber: any, value: any) => void;
|
|
137
|
-
7: (channelNumber: any, volume: any) => void;
|
|
138
|
-
10: (channelNumber: any, pan: any) => void;
|
|
139
|
-
11: (channelNumber: any, expression: any) => void;
|
|
142
|
+
7: (channelNumber: any, volume: any, scheduleTime: any) => void;
|
|
143
|
+
10: (channelNumber: any, pan: any, scheduleTime: any) => void;
|
|
144
|
+
11: (channelNumber: any, expression: any, scheduleTime: any) => void;
|
|
140
145
|
38: (channelNumber: any, value: any) => void;
|
|
141
146
|
64: (channelNumber: any, value: any) => void;
|
|
142
147
|
100: (channelNumber: any, value: any) => void;
|
|
@@ -145,18 +150,18 @@ export class MidyGMLite {
|
|
|
145
150
|
121: (channelNumber: any) => void;
|
|
146
151
|
123: (channelNumber: any) => Promise<void>;
|
|
147
152
|
};
|
|
148
|
-
handleControlChange(channelNumber: any, controllerType: any, value: any): void;
|
|
149
|
-
updateModulation(channel: any): void;
|
|
150
|
-
setModulationDepth(channelNumber: any, modulation: any): void;
|
|
151
|
-
setVolume(channelNumber: any, volume: any): void;
|
|
153
|
+
handleControlChange(channelNumber: any, controllerType: any, value: any, startTime: any): void;
|
|
154
|
+
updateModulation(channel: any, scheduleTime: any): void;
|
|
155
|
+
setModulationDepth(channelNumber: any, modulation: any, scheduleTime: any): void;
|
|
156
|
+
setVolume(channelNumber: any, volume: any, scheduleTime: any): void;
|
|
152
157
|
panToGain(pan: any): {
|
|
153
158
|
gainLeft: number;
|
|
154
159
|
gainRight: number;
|
|
155
160
|
};
|
|
156
|
-
setPan(channelNumber: any, pan: any): void;
|
|
157
|
-
setExpression(channelNumber: any, expression: any): void;
|
|
161
|
+
setPan(channelNumber: any, pan: any, scheduleTime: any): void;
|
|
162
|
+
setExpression(channelNumber: any, expression: any, scheduleTime: any): void;
|
|
158
163
|
dataEntryLSB(channelNumber: any, value: any): void;
|
|
159
|
-
updateChannelVolume(channel: any): void;
|
|
164
|
+
updateChannelVolume(channel: any, scheduleTime: any): void;
|
|
160
165
|
setSustainPedal(channelNumber: any, value: any): void;
|
|
161
166
|
limitData(channel: any, minMSB: any, maxMSB: any, minLSB: any, maxLSB: any): void;
|
|
162
167
|
handleRPN(channelNumber: any): void;
|
|
@@ -177,10 +182,24 @@ export class MidyGMLite {
|
|
|
177
182
|
handleSysEx(data: any): void;
|
|
178
183
|
scheduleTask(callback: any, startTime: any): Promise<any>;
|
|
179
184
|
}
|
|
185
|
+
declare class SparseMap {
|
|
186
|
+
constructor(size: any);
|
|
187
|
+
data: any[];
|
|
188
|
+
activeIndices: any[];
|
|
189
|
+
set(key: any, value: any): void;
|
|
190
|
+
get(key: any): any;
|
|
191
|
+
delete(key: any): boolean;
|
|
192
|
+
has(key: any): boolean;
|
|
193
|
+
get size(): number;
|
|
194
|
+
clear(): void;
|
|
195
|
+
forEach(callback: any): void;
|
|
196
|
+
[Symbol.iterator](): Generator<any[], void, unknown>;
|
|
197
|
+
}
|
|
180
198
|
declare class Note {
|
|
181
199
|
constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
|
|
182
200
|
bufferSource: any;
|
|
183
201
|
filterNode: any;
|
|
202
|
+
filterDepth: any;
|
|
184
203
|
volumeEnvelopeNode: any;
|
|
185
204
|
volumeDepth: any;
|
|
186
205
|
modulationLFO: any;
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAsBE;;;;;;;;;MASE;IAEF,+BAQC;IAxCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAcrC,kBAAgC;IAChC,kBAA8C;IAC9C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,8DASC;IAED,2EAqDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4EASC;IAED,mDASC;IAED,6CAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,wCAQC;IAED,4CAKC;IAED,mCAgBC;IAED,qDAqBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,yGAgBC;IAED,gHAuCC;IAED,kGAgDC;IAED,0EAGC;IAED,qFAwBC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDASC;IAED,gDASC;IAED,qCAMC;IAED,mCAQC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MAqBC;IAED,oFAMC;IAED,0DA6CC;IAED;;;;;;;;;;;;;MAeC;IAED,+FAWC;IAED,wDAWC;IAED,iFAIC;IAED,oEAIC;IAED;;;MAMC;IAED,8DAIC;IAED,4EAIC;IAED,mDAGC;IAED,2DAWC;IAED,sDAKC;IAED,kFAeC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,kDAKC;IAED,wDASC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAt1CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,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
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
|
+
// 2-3 times faster than Map
|
|
4
|
+
class SparseMap {
|
|
5
|
+
constructor(size) {
|
|
6
|
+
this.data = new Array(size);
|
|
7
|
+
this.activeIndices = [];
|
|
8
|
+
}
|
|
9
|
+
set(key, value) {
|
|
10
|
+
if (this.data[key] === undefined) {
|
|
11
|
+
this.activeIndices.push(key);
|
|
12
|
+
}
|
|
13
|
+
this.data[key] = value;
|
|
14
|
+
}
|
|
15
|
+
get(key) {
|
|
16
|
+
return this.data[key];
|
|
17
|
+
}
|
|
18
|
+
delete(key) {
|
|
19
|
+
if (this.data[key] !== undefined) {
|
|
20
|
+
this.data[key] = undefined;
|
|
21
|
+
const index = this.activeIndices.indexOf(key);
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
this.activeIndices.splice(index, 1);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
has(key) {
|
|
30
|
+
return this.data[key] !== undefined;
|
|
31
|
+
}
|
|
32
|
+
get size() {
|
|
33
|
+
return this.activeIndices.length;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
37
|
+
const key = this.activeIndices[i];
|
|
38
|
+
this.data[key] = undefined;
|
|
39
|
+
}
|
|
40
|
+
this.activeIndices = [];
|
|
41
|
+
}
|
|
42
|
+
*[Symbol.iterator]() {
|
|
43
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
44
|
+
const key = this.activeIndices[i];
|
|
45
|
+
yield [key, this.data[key]];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
forEach(callback) {
|
|
49
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
50
|
+
const key = this.activeIndices[i];
|
|
51
|
+
callback(this.data[key], key, this);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
3
55
|
class Note {
|
|
4
56
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
5
57
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -14,6 +66,12 @@ class Note {
|
|
|
14
66
|
writable: true,
|
|
15
67
|
value: void 0
|
|
16
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
17
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
18
76
|
enumerable: true,
|
|
19
77
|
configurable: true,
|
|
@@ -166,6 +224,18 @@ export class MidyGMLite {
|
|
|
166
224
|
writable: true,
|
|
167
225
|
value: this.initSoundFontTable()
|
|
168
226
|
});
|
|
227
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
configurable: true,
|
|
230
|
+
writable: true,
|
|
231
|
+
value: new Map()
|
|
232
|
+
});
|
|
233
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
234
|
+
enumerable: true,
|
|
235
|
+
configurable: true,
|
|
236
|
+
writable: true,
|
|
237
|
+
value: new Map()
|
|
238
|
+
});
|
|
169
239
|
Object.defineProperty(this, "isPlaying", {
|
|
170
240
|
enumerable: true,
|
|
171
241
|
configurable: true,
|
|
@@ -218,7 +288,7 @@ export class MidyGMLite {
|
|
|
218
288
|
enumerable: true,
|
|
219
289
|
configurable: true,
|
|
220
290
|
writable: true,
|
|
221
|
-
value: new
|
|
291
|
+
value: new SparseMap(128)
|
|
222
292
|
});
|
|
223
293
|
this.audioContext = audioContext;
|
|
224
294
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -231,7 +301,7 @@ export class MidyGMLite {
|
|
|
231
301
|
initSoundFontTable() {
|
|
232
302
|
const table = new Array(128);
|
|
233
303
|
for (let i = 0; i < 128; i++) {
|
|
234
|
-
table[i] = new
|
|
304
|
+
table[i] = new SparseMap(128);
|
|
235
305
|
}
|
|
236
306
|
return table;
|
|
237
307
|
}
|
|
@@ -284,7 +354,7 @@ export class MidyGMLite {
|
|
|
284
354
|
...this.constructor.channelSettings,
|
|
285
355
|
state: new ControllerState(),
|
|
286
356
|
...this.setChannelAudioNodes(audioContext),
|
|
287
|
-
scheduledNotes: new
|
|
357
|
+
scheduledNotes: new SparseMap(128),
|
|
288
358
|
};
|
|
289
359
|
});
|
|
290
360
|
return channels;
|
|
@@ -318,9 +388,8 @@ export class MidyGMLite {
|
|
|
318
388
|
return audioBuffer;
|
|
319
389
|
}
|
|
320
390
|
}
|
|
321
|
-
|
|
391
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
322
392
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
323
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
324
393
|
bufferSource.buffer = audioBuffer;
|
|
325
394
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
326
395
|
if (bufferSource.loop) {
|
|
@@ -334,31 +403,32 @@ export class MidyGMLite {
|
|
|
334
403
|
const event = this.timeline[queueIndex];
|
|
335
404
|
if (event.startTime > t + this.lookAhead)
|
|
336
405
|
break;
|
|
406
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
337
407
|
switch (event.type) {
|
|
338
408
|
case "noteOn":
|
|
339
409
|
if (event.velocity !== 0) {
|
|
340
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
410
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
341
411
|
break;
|
|
342
412
|
}
|
|
343
413
|
/* falls through */
|
|
344
414
|
case "noteOff": {
|
|
345
|
-
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity,
|
|
415
|
+
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, startTime);
|
|
346
416
|
if (notePromise) {
|
|
347
417
|
this.notePromises.push(notePromise);
|
|
348
418
|
}
|
|
349
419
|
break;
|
|
350
420
|
}
|
|
351
421
|
case "controller":
|
|
352
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
422
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
353
423
|
break;
|
|
354
424
|
case "programChange":
|
|
355
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
425
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
356
426
|
break;
|
|
357
427
|
case "pitchBend":
|
|
358
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
428
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
359
429
|
break;
|
|
360
430
|
case "sysEx":
|
|
361
|
-
this.handleSysEx(event.data);
|
|
431
|
+
this.handleSysEx(event.data, startTime);
|
|
362
432
|
}
|
|
363
433
|
queueIndex++;
|
|
364
434
|
}
|
|
@@ -385,6 +455,7 @@ export class MidyGMLite {
|
|
|
385
455
|
await Promise.all(this.notePromises);
|
|
386
456
|
this.notePromises = [];
|
|
387
457
|
this.exclusiveClassMap.clear();
|
|
458
|
+
this.audioBufferCache.clear();
|
|
388
459
|
resolve();
|
|
389
460
|
return;
|
|
390
461
|
}
|
|
@@ -400,8 +471,9 @@ export class MidyGMLite {
|
|
|
400
471
|
}
|
|
401
472
|
else if (this.isStopping) {
|
|
402
473
|
await this.stopNotes(0, true);
|
|
403
|
-
this.exclusiveClassMap.clear();
|
|
404
474
|
this.notePromises = [];
|
|
475
|
+
this.exclusiveClassMap.clear();
|
|
476
|
+
this.audioBufferCache.clear();
|
|
405
477
|
resolve();
|
|
406
478
|
this.isStopping = false;
|
|
407
479
|
this.isPaused = false;
|
|
@@ -432,6 +504,9 @@ export class MidyGMLite {
|
|
|
432
504
|
secondToTicks(second, secondsPerBeat) {
|
|
433
505
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
434
506
|
}
|
|
507
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
508
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
509
|
+
}
|
|
435
510
|
extractMidiData(midi) {
|
|
436
511
|
const instruments = new Set();
|
|
437
512
|
const timeline = [];
|
|
@@ -452,6 +527,8 @@ export class MidyGMLite {
|
|
|
452
527
|
switch (event.type) {
|
|
453
528
|
case "noteOn": {
|
|
454
529
|
const channel = tmpChannels[event.channel];
|
|
530
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
531
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
455
532
|
if (channel.programNumber < 0) {
|
|
456
533
|
instruments.add(`${channel.bank}:0`);
|
|
457
534
|
channel.programNumber = 0;
|
|
@@ -468,6 +545,10 @@ export class MidyGMLite {
|
|
|
468
545
|
timeline.push(event);
|
|
469
546
|
}
|
|
470
547
|
}
|
|
548
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
549
|
+
if (count === 1)
|
|
550
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
551
|
+
}
|
|
471
552
|
const priority = {
|
|
472
553
|
controller: 0,
|
|
473
554
|
sysEx: 1,
|
|
@@ -557,8 +638,20 @@ export class MidyGMLite {
|
|
|
557
638
|
const now = this.audioContext.currentTime;
|
|
558
639
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
559
640
|
}
|
|
641
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
642
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
643
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
644
|
+
const note = noteList[i];
|
|
645
|
+
if (!note)
|
|
646
|
+
continue;
|
|
647
|
+
if (scheduleTime < note.startTime)
|
|
648
|
+
continue;
|
|
649
|
+
callback(note);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}
|
|
560
653
|
getActiveNotes(channel, time) {
|
|
561
|
-
const activeNotes = new
|
|
654
|
+
const activeNotes = new SparseMap(128);
|
|
562
655
|
channel.scheduledNotes.forEach((noteList) => {
|
|
563
656
|
const activeNote = this.getActiveNote(noteList, time);
|
|
564
657
|
if (activeNote) {
|
|
@@ -628,20 +721,20 @@ export class MidyGMLite {
|
|
|
628
721
|
.setValueAtTime(attackVolume, volHold)
|
|
629
722
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
630
723
|
}
|
|
631
|
-
setPitchEnvelope(note) {
|
|
632
|
-
|
|
724
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
725
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
633
726
|
const { voiceParams } = note;
|
|
634
727
|
const baseRate = voiceParams.playbackRate;
|
|
635
728
|
note.bufferSource.playbackRate
|
|
636
|
-
.cancelScheduledValues(
|
|
637
|
-
.setValueAtTime(baseRate,
|
|
729
|
+
.cancelScheduledValues(scheduleTime)
|
|
730
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
638
731
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
639
732
|
if (modEnvToPitch === 0)
|
|
640
733
|
return;
|
|
641
734
|
const basePitch = this.rateToCent(baseRate);
|
|
642
735
|
const peekPitch = basePitch + modEnvToPitch;
|
|
643
736
|
const peekRate = this.centToRate(peekPitch);
|
|
644
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
737
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
645
738
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
646
739
|
const modHold = modAttack + voiceParams.modHold;
|
|
647
740
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -698,11 +791,31 @@ export class MidyGMLite {
|
|
|
698
791
|
note.modulationLFO.connect(note.volumeDepth);
|
|
699
792
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
700
793
|
}
|
|
794
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
795
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
796
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
797
|
+
if (cache) {
|
|
798
|
+
cache.counter += 1;
|
|
799
|
+
if (cache.maxCount <= cache.counter) {
|
|
800
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
801
|
+
}
|
|
802
|
+
return cache.audioBuffer;
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
806
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
807
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
808
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
809
|
+
return audioBuffer;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
701
812
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
702
813
|
const state = channel.state;
|
|
703
|
-
const
|
|
814
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
815
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
704
816
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
705
|
-
|
|
817
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
818
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
706
819
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
707
820
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
708
821
|
type: "lowpass",
|
|
@@ -726,10 +839,10 @@ export class MidyGMLite {
|
|
|
726
839
|
if (soundFontIndex === undefined)
|
|
727
840
|
return;
|
|
728
841
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
729
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
730
842
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
731
843
|
if (!voice)
|
|
732
844
|
return;
|
|
845
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
733
846
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
734
847
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
735
848
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
@@ -998,10 +1111,10 @@ export class MidyGMLite {
|
|
|
998
1111
|
123: this.allNotesOff,
|
|
999
1112
|
};
|
|
1000
1113
|
}
|
|
1001
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1114
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1002
1115
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1003
1116
|
if (handler) {
|
|
1004
|
-
handler.call(this, channelNumber, value);
|
|
1117
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1005
1118
|
const channel = this.channels[channelNumber];
|
|
1006
1119
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1007
1120
|
}
|
|
@@ -1009,33 +1122,28 @@ export class MidyGMLite {
|
|
|
1009
1122
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1010
1123
|
}
|
|
1011
1124
|
}
|
|
1012
|
-
updateModulation(channel) {
|
|
1013
|
-
|
|
1125
|
+
updateModulation(channel, scheduleTime) {
|
|
1126
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1014
1127
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
else {
|
|
1024
|
-
this.setPitchEnvelope(note);
|
|
1025
|
-
this.startModulation(channel, note, now);
|
|
1026
|
-
}
|
|
1128
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1129
|
+
if (note.modulationDepth) {
|
|
1130
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1134
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1027
1135
|
}
|
|
1028
1136
|
});
|
|
1029
1137
|
}
|
|
1030
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1138
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1031
1139
|
const channel = this.channels[channelNumber];
|
|
1032
1140
|
channel.state.modulationDepth = modulation / 127;
|
|
1033
|
-
this.updateModulation(channel);
|
|
1141
|
+
this.updateModulation(channel, scheduleTime);
|
|
1034
1142
|
}
|
|
1035
|
-
setVolume(channelNumber, volume) {
|
|
1143
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1036
1144
|
const channel = this.channels[channelNumber];
|
|
1037
1145
|
channel.state.volume = volume / 127;
|
|
1038
|
-
this.updateChannelVolume(channel);
|
|
1146
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1039
1147
|
}
|
|
1040
1148
|
panToGain(pan) {
|
|
1041
1149
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1044,31 +1152,31 @@ export class MidyGMLite {
|
|
|
1044
1152
|
gainRight: Math.sin(theta),
|
|
1045
1153
|
};
|
|
1046
1154
|
}
|
|
1047
|
-
setPan(channelNumber, pan) {
|
|
1155
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1048
1156
|
const channel = this.channels[channelNumber];
|
|
1049
1157
|
channel.state.pan = pan / 127;
|
|
1050
|
-
this.updateChannelVolume(channel);
|
|
1158
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1051
1159
|
}
|
|
1052
|
-
setExpression(channelNumber, expression) {
|
|
1160
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1053
1161
|
const channel = this.channels[channelNumber];
|
|
1054
1162
|
channel.state.expression = expression / 127;
|
|
1055
|
-
this.updateChannelVolume(channel);
|
|
1163
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1056
1164
|
}
|
|
1057
1165
|
dataEntryLSB(channelNumber, value) {
|
|
1058
1166
|
this.channels[channelNumber].dataLSB = value;
|
|
1059
1167
|
this.handleRPN(channelNumber);
|
|
1060
1168
|
}
|
|
1061
|
-
updateChannelVolume(channel) {
|
|
1062
|
-
|
|
1169
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1170
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1063
1171
|
const state = channel.state;
|
|
1064
1172
|
const volume = state.volume * state.expression;
|
|
1065
1173
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1066
1174
|
channel.gainL.gain
|
|
1067
1175
|
.cancelScheduledValues(now)
|
|
1068
|
-
.setValueAtTime(volume * gainLeft,
|
|
1176
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1069
1177
|
channel.gainR.gain
|
|
1070
1178
|
.cancelScheduledValues(now)
|
|
1071
|
-
.setValueAtTime(volume * gainRight,
|
|
1179
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1072
1180
|
}
|
|
1073
1181
|
setSustainPedal(channelNumber, value) {
|
|
1074
1182
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|