@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/script/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;
|
|
@@ -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/script/midy-GMLite.js
CHANGED
|
@@ -3,6 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGMLite = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
+
// 2-3 times faster than Map
|
|
7
|
+
class SparseMap {
|
|
8
|
+
constructor(size) {
|
|
9
|
+
this.data = new Array(size);
|
|
10
|
+
this.activeIndices = [];
|
|
11
|
+
}
|
|
12
|
+
set(key, value) {
|
|
13
|
+
if (this.data[key] === undefined) {
|
|
14
|
+
this.activeIndices.push(key);
|
|
15
|
+
}
|
|
16
|
+
this.data[key] = value;
|
|
17
|
+
}
|
|
18
|
+
get(key) {
|
|
19
|
+
return this.data[key];
|
|
20
|
+
}
|
|
21
|
+
delete(key) {
|
|
22
|
+
if (this.data[key] !== undefined) {
|
|
23
|
+
this.data[key] = undefined;
|
|
24
|
+
const index = this.activeIndices.indexOf(key);
|
|
25
|
+
if (index !== -1) {
|
|
26
|
+
this.activeIndices.splice(index, 1);
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
has(key) {
|
|
33
|
+
return this.data[key] !== undefined;
|
|
34
|
+
}
|
|
35
|
+
get size() {
|
|
36
|
+
return this.activeIndices.length;
|
|
37
|
+
}
|
|
38
|
+
clear() {
|
|
39
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
40
|
+
const key = this.activeIndices[i];
|
|
41
|
+
this.data[key] = undefined;
|
|
42
|
+
}
|
|
43
|
+
this.activeIndices = [];
|
|
44
|
+
}
|
|
45
|
+
*[Symbol.iterator]() {
|
|
46
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
47
|
+
const key = this.activeIndices[i];
|
|
48
|
+
yield [key, this.data[key]];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
forEach(callback) {
|
|
52
|
+
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
53
|
+
const key = this.activeIndices[i];
|
|
54
|
+
callback(this.data[key], key, this);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
6
58
|
class Note {
|
|
7
59
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
60
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -17,6 +69,12 @@ class Note {
|
|
|
17
69
|
writable: true,
|
|
18
70
|
value: void 0
|
|
19
71
|
});
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
20
78
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
21
79
|
enumerable: true,
|
|
22
80
|
configurable: true,
|
|
@@ -169,6 +227,18 @@ class MidyGMLite {
|
|
|
169
227
|
writable: true,
|
|
170
228
|
value: this.initSoundFontTable()
|
|
171
229
|
});
|
|
230
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
231
|
+
enumerable: true,
|
|
232
|
+
configurable: true,
|
|
233
|
+
writable: true,
|
|
234
|
+
value: new Map()
|
|
235
|
+
});
|
|
236
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
237
|
+
enumerable: true,
|
|
238
|
+
configurable: true,
|
|
239
|
+
writable: true,
|
|
240
|
+
value: new Map()
|
|
241
|
+
});
|
|
172
242
|
Object.defineProperty(this, "isPlaying", {
|
|
173
243
|
enumerable: true,
|
|
174
244
|
configurable: true,
|
|
@@ -221,7 +291,7 @@ class MidyGMLite {
|
|
|
221
291
|
enumerable: true,
|
|
222
292
|
configurable: true,
|
|
223
293
|
writable: true,
|
|
224
|
-
value: new
|
|
294
|
+
value: new SparseMap(128)
|
|
225
295
|
});
|
|
226
296
|
this.audioContext = audioContext;
|
|
227
297
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -234,7 +304,7 @@ class MidyGMLite {
|
|
|
234
304
|
initSoundFontTable() {
|
|
235
305
|
const table = new Array(128);
|
|
236
306
|
for (let i = 0; i < 128; i++) {
|
|
237
|
-
table[i] = new
|
|
307
|
+
table[i] = new SparseMap(128);
|
|
238
308
|
}
|
|
239
309
|
return table;
|
|
240
310
|
}
|
|
@@ -287,7 +357,7 @@ class MidyGMLite {
|
|
|
287
357
|
...this.constructor.channelSettings,
|
|
288
358
|
state: new ControllerState(),
|
|
289
359
|
...this.setChannelAudioNodes(audioContext),
|
|
290
|
-
scheduledNotes: new
|
|
360
|
+
scheduledNotes: new SparseMap(128),
|
|
291
361
|
};
|
|
292
362
|
});
|
|
293
363
|
return channels;
|
|
@@ -321,9 +391,8 @@ class MidyGMLite {
|
|
|
321
391
|
return audioBuffer;
|
|
322
392
|
}
|
|
323
393
|
}
|
|
324
|
-
|
|
394
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
325
395
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
326
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
327
396
|
bufferSource.buffer = audioBuffer;
|
|
328
397
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
329
398
|
if (bufferSource.loop) {
|
|
@@ -337,31 +406,32 @@ class MidyGMLite {
|
|
|
337
406
|
const event = this.timeline[queueIndex];
|
|
338
407
|
if (event.startTime > t + this.lookAhead)
|
|
339
408
|
break;
|
|
409
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
340
410
|
switch (event.type) {
|
|
341
411
|
case "noteOn":
|
|
342
412
|
if (event.velocity !== 0) {
|
|
343
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
413
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
344
414
|
break;
|
|
345
415
|
}
|
|
346
416
|
/* falls through */
|
|
347
417
|
case "noteOff": {
|
|
348
|
-
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity,
|
|
418
|
+
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, startTime);
|
|
349
419
|
if (notePromise) {
|
|
350
420
|
this.notePromises.push(notePromise);
|
|
351
421
|
}
|
|
352
422
|
break;
|
|
353
423
|
}
|
|
354
424
|
case "controller":
|
|
355
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
425
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
356
426
|
break;
|
|
357
427
|
case "programChange":
|
|
358
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
428
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
359
429
|
break;
|
|
360
430
|
case "pitchBend":
|
|
361
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
431
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
362
432
|
break;
|
|
363
433
|
case "sysEx":
|
|
364
|
-
this.handleSysEx(event.data);
|
|
434
|
+
this.handleSysEx(event.data, startTime);
|
|
365
435
|
}
|
|
366
436
|
queueIndex++;
|
|
367
437
|
}
|
|
@@ -388,6 +458,7 @@ class MidyGMLite {
|
|
|
388
458
|
await Promise.all(this.notePromises);
|
|
389
459
|
this.notePromises = [];
|
|
390
460
|
this.exclusiveClassMap.clear();
|
|
461
|
+
this.audioBufferCache.clear();
|
|
391
462
|
resolve();
|
|
392
463
|
return;
|
|
393
464
|
}
|
|
@@ -403,8 +474,9 @@ class MidyGMLite {
|
|
|
403
474
|
}
|
|
404
475
|
else if (this.isStopping) {
|
|
405
476
|
await this.stopNotes(0, true);
|
|
406
|
-
this.exclusiveClassMap.clear();
|
|
407
477
|
this.notePromises = [];
|
|
478
|
+
this.exclusiveClassMap.clear();
|
|
479
|
+
this.audioBufferCache.clear();
|
|
408
480
|
resolve();
|
|
409
481
|
this.isStopping = false;
|
|
410
482
|
this.isPaused = false;
|
|
@@ -435,6 +507,9 @@ class MidyGMLite {
|
|
|
435
507
|
secondToTicks(second, secondsPerBeat) {
|
|
436
508
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
437
509
|
}
|
|
510
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
511
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
512
|
+
}
|
|
438
513
|
extractMidiData(midi) {
|
|
439
514
|
const instruments = new Set();
|
|
440
515
|
const timeline = [];
|
|
@@ -455,6 +530,8 @@ class MidyGMLite {
|
|
|
455
530
|
switch (event.type) {
|
|
456
531
|
case "noteOn": {
|
|
457
532
|
const channel = tmpChannels[event.channel];
|
|
533
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
534
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
458
535
|
if (channel.programNumber < 0) {
|
|
459
536
|
instruments.add(`${channel.bank}:0`);
|
|
460
537
|
channel.programNumber = 0;
|
|
@@ -471,6 +548,10 @@ class MidyGMLite {
|
|
|
471
548
|
timeline.push(event);
|
|
472
549
|
}
|
|
473
550
|
}
|
|
551
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
552
|
+
if (count === 1)
|
|
553
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
554
|
+
}
|
|
474
555
|
const priority = {
|
|
475
556
|
controller: 0,
|
|
476
557
|
sysEx: 1,
|
|
@@ -560,8 +641,20 @@ class MidyGMLite {
|
|
|
560
641
|
const now = this.audioContext.currentTime;
|
|
561
642
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
562
643
|
}
|
|
644
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
645
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
646
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
647
|
+
const note = noteList[i];
|
|
648
|
+
if (!note)
|
|
649
|
+
continue;
|
|
650
|
+
if (scheduleTime < note.startTime)
|
|
651
|
+
continue;
|
|
652
|
+
callback(note);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
563
656
|
getActiveNotes(channel, time) {
|
|
564
|
-
const activeNotes = new
|
|
657
|
+
const activeNotes = new SparseMap(128);
|
|
565
658
|
channel.scheduledNotes.forEach((noteList) => {
|
|
566
659
|
const activeNote = this.getActiveNote(noteList, time);
|
|
567
660
|
if (activeNote) {
|
|
@@ -631,20 +724,20 @@ class MidyGMLite {
|
|
|
631
724
|
.setValueAtTime(attackVolume, volHold)
|
|
632
725
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
633
726
|
}
|
|
634
|
-
setPitchEnvelope(note) {
|
|
635
|
-
|
|
727
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
728
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
636
729
|
const { voiceParams } = note;
|
|
637
730
|
const baseRate = voiceParams.playbackRate;
|
|
638
731
|
note.bufferSource.playbackRate
|
|
639
|
-
.cancelScheduledValues(
|
|
640
|
-
.setValueAtTime(baseRate,
|
|
732
|
+
.cancelScheduledValues(scheduleTime)
|
|
733
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
641
734
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
642
735
|
if (modEnvToPitch === 0)
|
|
643
736
|
return;
|
|
644
737
|
const basePitch = this.rateToCent(baseRate);
|
|
645
738
|
const peekPitch = basePitch + modEnvToPitch;
|
|
646
739
|
const peekRate = this.centToRate(peekPitch);
|
|
647
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
740
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
648
741
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
649
742
|
const modHold = modAttack + voiceParams.modHold;
|
|
650
743
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -701,11 +794,31 @@ class MidyGMLite {
|
|
|
701
794
|
note.modulationLFO.connect(note.volumeDepth);
|
|
702
795
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
703
796
|
}
|
|
797
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
798
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
799
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
800
|
+
if (cache) {
|
|
801
|
+
cache.counter += 1;
|
|
802
|
+
if (cache.maxCount <= cache.counter) {
|
|
803
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
804
|
+
}
|
|
805
|
+
return cache.audioBuffer;
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
809
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
810
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
811
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
812
|
+
return audioBuffer;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
704
815
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
705
816
|
const state = channel.state;
|
|
706
|
-
const
|
|
817
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
818
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
707
819
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
708
|
-
|
|
820
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
821
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
709
822
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
710
823
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
711
824
|
type: "lowpass",
|
|
@@ -729,10 +842,10 @@ class MidyGMLite {
|
|
|
729
842
|
if (soundFontIndex === undefined)
|
|
730
843
|
return;
|
|
731
844
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
732
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
733
845
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
734
846
|
if (!voice)
|
|
735
847
|
return;
|
|
848
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
736
849
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
737
850
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
738
851
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
@@ -1001,10 +1114,10 @@ class MidyGMLite {
|
|
|
1001
1114
|
123: this.allNotesOff,
|
|
1002
1115
|
};
|
|
1003
1116
|
}
|
|
1004
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1117
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1005
1118
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1006
1119
|
if (handler) {
|
|
1007
|
-
handler.call(this, channelNumber, value);
|
|
1120
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1008
1121
|
const channel = this.channels[channelNumber];
|
|
1009
1122
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1010
1123
|
}
|
|
@@ -1012,33 +1125,28 @@ class MidyGMLite {
|
|
|
1012
1125
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1013
1126
|
}
|
|
1014
1127
|
}
|
|
1015
|
-
updateModulation(channel) {
|
|
1016
|
-
|
|
1128
|
+
updateModulation(channel, scheduleTime) {
|
|
1129
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1017
1130
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
else {
|
|
1027
|
-
this.setPitchEnvelope(note);
|
|
1028
|
-
this.startModulation(channel, note, now);
|
|
1029
|
-
}
|
|
1131
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1132
|
+
if (note.modulationDepth) {
|
|
1133
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1137
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1030
1138
|
}
|
|
1031
1139
|
});
|
|
1032
1140
|
}
|
|
1033
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1141
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1034
1142
|
const channel = this.channels[channelNumber];
|
|
1035
1143
|
channel.state.modulationDepth = modulation / 127;
|
|
1036
|
-
this.updateModulation(channel);
|
|
1144
|
+
this.updateModulation(channel, scheduleTime);
|
|
1037
1145
|
}
|
|
1038
|
-
setVolume(channelNumber, volume) {
|
|
1146
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1039
1147
|
const channel = this.channels[channelNumber];
|
|
1040
1148
|
channel.state.volume = volume / 127;
|
|
1041
|
-
this.updateChannelVolume(channel);
|
|
1149
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1042
1150
|
}
|
|
1043
1151
|
panToGain(pan) {
|
|
1044
1152
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1047,31 +1155,31 @@ class MidyGMLite {
|
|
|
1047
1155
|
gainRight: Math.sin(theta),
|
|
1048
1156
|
};
|
|
1049
1157
|
}
|
|
1050
|
-
setPan(channelNumber, pan) {
|
|
1158
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1051
1159
|
const channel = this.channels[channelNumber];
|
|
1052
1160
|
channel.state.pan = pan / 127;
|
|
1053
|
-
this.updateChannelVolume(channel);
|
|
1161
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1054
1162
|
}
|
|
1055
|
-
setExpression(channelNumber, expression) {
|
|
1163
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1056
1164
|
const channel = this.channels[channelNumber];
|
|
1057
1165
|
channel.state.expression = expression / 127;
|
|
1058
|
-
this.updateChannelVolume(channel);
|
|
1166
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1059
1167
|
}
|
|
1060
1168
|
dataEntryLSB(channelNumber, value) {
|
|
1061
1169
|
this.channels[channelNumber].dataLSB = value;
|
|
1062
1170
|
this.handleRPN(channelNumber);
|
|
1063
1171
|
}
|
|
1064
|
-
updateChannelVolume(channel) {
|
|
1065
|
-
|
|
1172
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1173
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1066
1174
|
const state = channel.state;
|
|
1067
1175
|
const volume = state.volume * state.expression;
|
|
1068
1176
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1069
1177
|
channel.gainL.gain
|
|
1070
1178
|
.cancelScheduledValues(now)
|
|
1071
|
-
.setValueAtTime(volume * gainLeft,
|
|
1179
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1072
1180
|
channel.gainR.gain
|
|
1073
1181
|
.cancelScheduledValues(now)
|
|
1074
|
-
.setValueAtTime(volume * gainRight,
|
|
1182
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1075
1183
|
}
|
|
1076
1184
|
setSustainPedal(channelNumber, value) {
|
|
1077
1185
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|