@marmooo/midy 0.2.4 → 0.2.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/esm/midy-GM1.d.ts +20 -3
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +104 -10
- package/esm/midy-GM2.d.ts +22 -5
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +116 -25
- package/esm/midy-GMLite.d.ts +20 -3
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +104 -10
- package/esm/midy.d.ts +23 -5
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +145 -27
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +20 -3
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +104 -10
- package/script/midy-GM2.d.ts +22 -5
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +116 -25
- package/script/midy-GMLite.d.ts +20 -3
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +104 -10
- package/script/midy.d.ts +23 -5
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +145 -27
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", {
|
|
@@ -169,6 +221,18 @@ class MidyGMLite {
|
|
|
169
221
|
writable: true,
|
|
170
222
|
value: this.initSoundFontTable()
|
|
171
223
|
});
|
|
224
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
225
|
+
enumerable: true,
|
|
226
|
+
configurable: true,
|
|
227
|
+
writable: true,
|
|
228
|
+
value: new Map()
|
|
229
|
+
});
|
|
230
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
231
|
+
enumerable: true,
|
|
232
|
+
configurable: true,
|
|
233
|
+
writable: true,
|
|
234
|
+
value: new Map()
|
|
235
|
+
});
|
|
172
236
|
Object.defineProperty(this, "isPlaying", {
|
|
173
237
|
enumerable: true,
|
|
174
238
|
configurable: true,
|
|
@@ -221,7 +285,7 @@ class MidyGMLite {
|
|
|
221
285
|
enumerable: true,
|
|
222
286
|
configurable: true,
|
|
223
287
|
writable: true,
|
|
224
|
-
value: new
|
|
288
|
+
value: new SparseMap(128)
|
|
225
289
|
});
|
|
226
290
|
this.audioContext = audioContext;
|
|
227
291
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -234,7 +298,7 @@ class MidyGMLite {
|
|
|
234
298
|
initSoundFontTable() {
|
|
235
299
|
const table = new Array(128);
|
|
236
300
|
for (let i = 0; i < 128; i++) {
|
|
237
|
-
table[i] = new
|
|
301
|
+
table[i] = new SparseMap(128);
|
|
238
302
|
}
|
|
239
303
|
return table;
|
|
240
304
|
}
|
|
@@ -287,7 +351,7 @@ class MidyGMLite {
|
|
|
287
351
|
...this.constructor.channelSettings,
|
|
288
352
|
state: new ControllerState(),
|
|
289
353
|
...this.setChannelAudioNodes(audioContext),
|
|
290
|
-
scheduledNotes: new
|
|
354
|
+
scheduledNotes: new SparseMap(128),
|
|
291
355
|
};
|
|
292
356
|
});
|
|
293
357
|
return channels;
|
|
@@ -321,9 +385,8 @@ class MidyGMLite {
|
|
|
321
385
|
return audioBuffer;
|
|
322
386
|
}
|
|
323
387
|
}
|
|
324
|
-
|
|
388
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
325
389
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
326
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
327
390
|
bufferSource.buffer = audioBuffer;
|
|
328
391
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
329
392
|
if (bufferSource.loop) {
|
|
@@ -388,6 +451,7 @@ class MidyGMLite {
|
|
|
388
451
|
await Promise.all(this.notePromises);
|
|
389
452
|
this.notePromises = [];
|
|
390
453
|
this.exclusiveClassMap.clear();
|
|
454
|
+
this.audioBufferCache.clear();
|
|
391
455
|
resolve();
|
|
392
456
|
return;
|
|
393
457
|
}
|
|
@@ -403,8 +467,9 @@ class MidyGMLite {
|
|
|
403
467
|
}
|
|
404
468
|
else if (this.isStopping) {
|
|
405
469
|
await this.stopNotes(0, true);
|
|
406
|
-
this.exclusiveClassMap.clear();
|
|
407
470
|
this.notePromises = [];
|
|
471
|
+
this.exclusiveClassMap.clear();
|
|
472
|
+
this.audioBufferCache.clear();
|
|
408
473
|
resolve();
|
|
409
474
|
this.isStopping = false;
|
|
410
475
|
this.isPaused = false;
|
|
@@ -435,6 +500,9 @@ class MidyGMLite {
|
|
|
435
500
|
secondToTicks(second, secondsPerBeat) {
|
|
436
501
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
437
502
|
}
|
|
503
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
504
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
505
|
+
}
|
|
438
506
|
extractMidiData(midi) {
|
|
439
507
|
const instruments = new Set();
|
|
440
508
|
const timeline = [];
|
|
@@ -455,6 +523,8 @@ class MidyGMLite {
|
|
|
455
523
|
switch (event.type) {
|
|
456
524
|
case "noteOn": {
|
|
457
525
|
const channel = tmpChannels[event.channel];
|
|
526
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
527
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
458
528
|
if (channel.programNumber < 0) {
|
|
459
529
|
instruments.add(`${channel.bank}:0`);
|
|
460
530
|
channel.programNumber = 0;
|
|
@@ -471,6 +541,10 @@ class MidyGMLite {
|
|
|
471
541
|
timeline.push(event);
|
|
472
542
|
}
|
|
473
543
|
}
|
|
544
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
545
|
+
if (count === 1)
|
|
546
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
547
|
+
}
|
|
474
548
|
const priority = {
|
|
475
549
|
controller: 0,
|
|
476
550
|
sysEx: 1,
|
|
@@ -561,7 +635,7 @@ class MidyGMLite {
|
|
|
561
635
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
562
636
|
}
|
|
563
637
|
getActiveNotes(channel, time) {
|
|
564
|
-
const activeNotes = new
|
|
638
|
+
const activeNotes = new SparseMap(128);
|
|
565
639
|
channel.scheduledNotes.forEach((noteList) => {
|
|
566
640
|
const activeNote = this.getActiveNote(noteList, time);
|
|
567
641
|
if (activeNote) {
|
|
@@ -701,11 +775,31 @@ class MidyGMLite {
|
|
|
701
775
|
note.modulationLFO.connect(note.volumeDepth);
|
|
702
776
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
703
777
|
}
|
|
778
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
779
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
780
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
781
|
+
if (cache) {
|
|
782
|
+
cache.counter += 1;
|
|
783
|
+
if (cache.maxCount <= cache.counter) {
|
|
784
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
785
|
+
}
|
|
786
|
+
return cache.audioBuffer;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
790
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
791
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
792
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
793
|
+
return audioBuffer;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
704
796
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
705
797
|
const state = channel.state;
|
|
706
|
-
const
|
|
798
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
799
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
707
800
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
708
|
-
|
|
801
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
802
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
709
803
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
710
804
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
711
805
|
type: "lowpass",
|
|
@@ -729,10 +823,10 @@ class MidyGMLite {
|
|
|
729
823
|
if (soundFontIndex === undefined)
|
|
730
824
|
return;
|
|
731
825
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
732
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
733
826
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
734
827
|
if (!voice)
|
|
735
828
|
return;
|
|
829
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
736
830
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
737
831
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
738
832
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
package/script/midy.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export class Midy {
|
|
|
2
2
|
static channelSettings: {
|
|
3
3
|
currentBufferSource: null;
|
|
4
4
|
detune: number;
|
|
5
|
-
scaleOctaveTuningTable:
|
|
5
|
+
scaleOctaveTuningTable: Float32Array<ArrayBuffer>;
|
|
6
6
|
channelPressureTable: Uint8Array<ArrayBuffer>;
|
|
7
7
|
polyphonicKeyPressureTable: Uint8Array<ArrayBuffer>;
|
|
8
8
|
keyBasedInstrumentControlTable: Int8Array<ArrayBuffer>;
|
|
@@ -48,6 +48,8 @@ export class Midy {
|
|
|
48
48
|
resumeTime: number;
|
|
49
49
|
soundFonts: any[];
|
|
50
50
|
soundFontTable: any[];
|
|
51
|
+
audioBufferCounter: Map<any, any>;
|
|
52
|
+
audioBufferCache: Map<any, any>;
|
|
51
53
|
isPlaying: boolean;
|
|
52
54
|
isPausing: boolean;
|
|
53
55
|
isPaused: boolean;
|
|
@@ -56,7 +58,7 @@ export class Midy {
|
|
|
56
58
|
timeline: any[];
|
|
57
59
|
instruments: any[];
|
|
58
60
|
notePromises: any[];
|
|
59
|
-
exclusiveClassMap:
|
|
61
|
+
exclusiveClassMap: SparseMap;
|
|
60
62
|
defaultOptions: {
|
|
61
63
|
reverbAlgorithm: (audioContext: any) => {
|
|
62
64
|
input: any;
|
|
@@ -144,13 +146,14 @@ export class Midy {
|
|
|
144
146
|
};
|
|
145
147
|
createChannels(audioContext: any): any[];
|
|
146
148
|
createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
|
|
147
|
-
createNoteBufferNode(
|
|
149
|
+
createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
|
|
148
150
|
findPortamentoTarget(queueIndex: any): any;
|
|
149
151
|
scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
|
|
150
152
|
getQueueIndex(second: any): number;
|
|
151
153
|
playNotes(): Promise<any>;
|
|
152
154
|
ticksToSecond(ticks: any, secondsPerBeat: any): number;
|
|
153
155
|
secondToTicks(second: any, secondsPerBeat: any): number;
|
|
156
|
+
getAudioBufferId(programNumber: any, noteNumber: any, velocity: any): string;
|
|
154
157
|
extractMidiData(midi: any): {
|
|
155
158
|
instruments: Set<any>;
|
|
156
159
|
timeline: any[];
|
|
@@ -164,7 +167,7 @@ export class Midy {
|
|
|
164
167
|
seekTo(second: any): void;
|
|
165
168
|
calcTotalTime(): number;
|
|
166
169
|
currentTime(): number;
|
|
167
|
-
getActiveNotes(channel: any, time: any):
|
|
170
|
+
getActiveNotes(channel: any, time: any): SparseMap;
|
|
168
171
|
getActiveNote(noteList: any, time: any): any;
|
|
169
172
|
createConvolutionReverbImpulse(audioContext: any, decay: any, preDecay: any): any;
|
|
170
173
|
createConvolutionReverb(audioContext: any, impulse: any): {
|
|
@@ -205,6 +208,7 @@ export class Midy {
|
|
|
205
208
|
setFilterEnvelope(channel: any, note: any, pressure: any): void;
|
|
206
209
|
startModulation(channel: any, note: any, startTime: any): void;
|
|
207
210
|
startVibrato(channel: any, note: any, startTime: any): void;
|
|
211
|
+
getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
|
|
208
212
|
createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, portamento: any, isSF3: any): Promise<Note>;
|
|
209
213
|
calcBank(channel: any, channelNumber: any): any;
|
|
210
214
|
scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, portamento: any): Promise<void>;
|
|
@@ -362,7 +366,8 @@ export class Midy {
|
|
|
362
366
|
setChorusSendToReverb(value: any): void;
|
|
363
367
|
getChorusSendToReverb(value: any): number;
|
|
364
368
|
getChannelBitmap(data: any): any[];
|
|
365
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data: any): void;
|
|
369
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data: any, realtime: any): void;
|
|
370
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data: any, realtime: any): void;
|
|
366
371
|
applyDestinationSettings(channel: any, note: any, table: any): void;
|
|
367
372
|
handleChannelPressureSysEx(data: any, tableName: any): void;
|
|
368
373
|
initControlTable(): Uint8Array<ArrayBuffer>;
|
|
@@ -374,6 +379,19 @@ export class Midy {
|
|
|
374
379
|
handleSysEx(data: any): any;
|
|
375
380
|
scheduleTask(callback: any, startTime: any): Promise<any>;
|
|
376
381
|
}
|
|
382
|
+
declare class SparseMap {
|
|
383
|
+
constructor(size: any);
|
|
384
|
+
data: any[];
|
|
385
|
+
activeIndices: any[];
|
|
386
|
+
set(key: any, value: any): void;
|
|
387
|
+
get(key: any): any;
|
|
388
|
+
delete(key: any): boolean;
|
|
389
|
+
has(key: any): boolean;
|
|
390
|
+
get size(): number;
|
|
391
|
+
clear(): void;
|
|
392
|
+
forEach(callback: any): void;
|
|
393
|
+
[Symbol.iterator](): Generator<any[], void, unknown>;
|
|
394
|
+
}
|
|
377
395
|
declare class Note {
|
|
378
396
|
constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
|
|
379
397
|
bufferSource: any;
|
package/script/midy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
|
package/script/midy.js
CHANGED
|
@@ -3,6 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Midy = 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", {
|
|
@@ -290,6 +342,18 @@ class Midy {
|
|
|
290
342
|
writable: true,
|
|
291
343
|
value: this.initSoundFontTable()
|
|
292
344
|
});
|
|
345
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
346
|
+
enumerable: true,
|
|
347
|
+
configurable: true,
|
|
348
|
+
writable: true,
|
|
349
|
+
value: new Map()
|
|
350
|
+
});
|
|
351
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
352
|
+
enumerable: true,
|
|
353
|
+
configurable: true,
|
|
354
|
+
writable: true,
|
|
355
|
+
value: new Map()
|
|
356
|
+
});
|
|
293
357
|
Object.defineProperty(this, "isPlaying", {
|
|
294
358
|
enumerable: true,
|
|
295
359
|
configurable: true,
|
|
@@ -342,7 +406,7 @@ class Midy {
|
|
|
342
406
|
enumerable: true,
|
|
343
407
|
configurable: true,
|
|
344
408
|
writable: true,
|
|
345
|
-
value: new
|
|
409
|
+
value: new SparseMap(128)
|
|
346
410
|
});
|
|
347
411
|
Object.defineProperty(this, "defaultOptions", {
|
|
348
412
|
enumerable: true,
|
|
@@ -382,7 +446,7 @@ class Midy {
|
|
|
382
446
|
initSoundFontTable() {
|
|
383
447
|
const table = new Array(128);
|
|
384
448
|
for (let i = 0; i < 128; i++) {
|
|
385
|
-
table[i] = new
|
|
449
|
+
table[i] = new SparseMap(128);
|
|
386
450
|
}
|
|
387
451
|
return table;
|
|
388
452
|
}
|
|
@@ -436,8 +500,8 @@ class Midy {
|
|
|
436
500
|
state: new ControllerState(),
|
|
437
501
|
controlTable: this.initControlTable(),
|
|
438
502
|
...this.setChannelAudioNodes(audioContext),
|
|
439
|
-
scheduledNotes: new
|
|
440
|
-
sostenutoNotes: new
|
|
503
|
+
scheduledNotes: new SparseMap(128),
|
|
504
|
+
sostenutoNotes: new SparseMap(128),
|
|
441
505
|
};
|
|
442
506
|
});
|
|
443
507
|
return channels;
|
|
@@ -471,9 +535,8 @@ class Midy {
|
|
|
471
535
|
return audioBuffer;
|
|
472
536
|
}
|
|
473
537
|
}
|
|
474
|
-
|
|
538
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
475
539
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
476
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
477
540
|
bufferSource.buffer = audioBuffer;
|
|
478
541
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
479
542
|
if (bufferSource.loop) {
|
|
@@ -565,6 +628,7 @@ class Midy {
|
|
|
565
628
|
await Promise.all(this.notePromises);
|
|
566
629
|
this.notePromises = [];
|
|
567
630
|
this.exclusiveClassMap.clear();
|
|
631
|
+
this.audioBufferCache.clear();
|
|
568
632
|
resolve();
|
|
569
633
|
return;
|
|
570
634
|
}
|
|
@@ -580,8 +644,9 @@ class Midy {
|
|
|
580
644
|
}
|
|
581
645
|
else if (this.isStopping) {
|
|
582
646
|
await this.stopNotes(0, true);
|
|
583
|
-
this.exclusiveClassMap.clear();
|
|
584
647
|
this.notePromises = [];
|
|
648
|
+
this.exclusiveClassMap.clear();
|
|
649
|
+
this.audioBufferCache.clear();
|
|
585
650
|
resolve();
|
|
586
651
|
this.isStopping = false;
|
|
587
652
|
this.isPaused = false;
|
|
@@ -612,6 +677,9 @@ class Midy {
|
|
|
612
677
|
secondToTicks(second, secondsPerBeat) {
|
|
613
678
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
614
679
|
}
|
|
680
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
681
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
682
|
+
}
|
|
615
683
|
extractMidiData(midi) {
|
|
616
684
|
const instruments = new Set();
|
|
617
685
|
const timeline = [];
|
|
@@ -633,6 +701,8 @@ class Midy {
|
|
|
633
701
|
switch (event.type) {
|
|
634
702
|
case "noteOn": {
|
|
635
703
|
const channel = tmpChannels[event.channel];
|
|
704
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
705
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
636
706
|
if (channel.programNumber < 0) {
|
|
637
707
|
channel.programNumber = event.programNumber;
|
|
638
708
|
switch (channel.bankMSB) {
|
|
@@ -682,6 +752,10 @@ class Midy {
|
|
|
682
752
|
timeline.push(event);
|
|
683
753
|
}
|
|
684
754
|
}
|
|
755
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
756
|
+
if (count === 1)
|
|
757
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
758
|
+
}
|
|
685
759
|
const priority = {
|
|
686
760
|
controller: 0,
|
|
687
761
|
sysEx: 1,
|
|
@@ -775,7 +849,7 @@ class Midy {
|
|
|
775
849
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
776
850
|
}
|
|
777
851
|
getActiveNotes(channel, time) {
|
|
778
|
-
const activeNotes = new
|
|
852
|
+
const activeNotes = new SparseMap(128);
|
|
779
853
|
channel.scheduledNotes.forEach((noteList) => {
|
|
780
854
|
const activeNote = this.getActiveNote(noteList, time);
|
|
781
855
|
if (activeNote) {
|
|
@@ -1113,12 +1187,31 @@ class Midy {
|
|
|
1113
1187
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1114
1188
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1115
1189
|
}
|
|
1190
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1191
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1192
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1193
|
+
if (cache) {
|
|
1194
|
+
cache.counter += 1;
|
|
1195
|
+
if (cache.maxCount <= cache.counter) {
|
|
1196
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1197
|
+
}
|
|
1198
|
+
return cache.audioBuffer;
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1202
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1203
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1204
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1205
|
+
return audioBuffer;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1116
1208
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1117
1209
|
const state = channel.state;
|
|
1118
1210
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1119
1211
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1120
1212
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1121
|
-
|
|
1213
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1214
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1122
1215
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1123
1216
|
note.gainL = new GainNode(this.audioContext);
|
|
1124
1217
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1178,10 +1271,10 @@ class Midy {
|
|
|
1178
1271
|
if (soundFontIndex === undefined)
|
|
1179
1272
|
return;
|
|
1180
1273
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1181
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1182
1274
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1183
1275
|
if (!voice)
|
|
1184
1276
|
return;
|
|
1277
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1185
1278
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1186
1279
|
note.gainL.connect(channel.gainL);
|
|
1187
1280
|
note.gainR.connect(channel.gainR);
|
|
@@ -1365,6 +1458,7 @@ class Midy {
|
|
|
1365
1458
|
channel.program = program;
|
|
1366
1459
|
}
|
|
1367
1460
|
handleChannelPressure(channelNumber, value) {
|
|
1461
|
+
const now = this.audioContext.currentTime;
|
|
1368
1462
|
const channel = this.channels[channelNumber];
|
|
1369
1463
|
const prev = channel.state.channelPressure;
|
|
1370
1464
|
const next = value / 127;
|
|
@@ -1374,13 +1468,8 @@ class Midy {
|
|
|
1374
1468
|
channel.detune += pressureDepth * (next - prev);
|
|
1375
1469
|
}
|
|
1376
1470
|
const table = channel.channelPressureTable;
|
|
1377
|
-
channel.
|
|
1378
|
-
|
|
1379
|
-
const note = noteList[i];
|
|
1380
|
-
if (!note)
|
|
1381
|
-
continue;
|
|
1382
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1383
|
-
}
|
|
1471
|
+
this.getActiveNotes(channel, now).forEach((note) => {
|
|
1472
|
+
this.applyDestinationSettings(channel, note, table);
|
|
1384
1473
|
});
|
|
1385
1474
|
// this.applyVoiceParams(channel, 13);
|
|
1386
1475
|
}
|
|
@@ -1791,8 +1880,7 @@ class Midy {
|
|
|
1791
1880
|
channel.state.sostenutoPedal = value / 127;
|
|
1792
1881
|
if (64 <= value) {
|
|
1793
1882
|
const now = this.audioContext.currentTime;
|
|
1794
|
-
|
|
1795
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1883
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1796
1884
|
}
|
|
1797
1885
|
else {
|
|
1798
1886
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -2173,7 +2261,10 @@ class Midy {
|
|
|
2173
2261
|
switch (data[3]) {
|
|
2174
2262
|
case 8:
|
|
2175
2263
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2176
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2264
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2265
|
+
case 9:
|
|
2266
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2267
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
|
|
2177
2268
|
default:
|
|
2178
2269
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2179
2270
|
}
|
|
@@ -2235,8 +2326,10 @@ class Midy {
|
|
|
2235
2326
|
case 8:
|
|
2236
2327
|
switch (data[3]) {
|
|
2237
2328
|
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2238
|
-
|
|
2239
|
-
|
|
2329
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
|
|
2330
|
+
case 9:
|
|
2331
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2332
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
|
|
2240
2333
|
default:
|
|
2241
2334
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2242
2335
|
}
|
|
@@ -2506,8 +2599,26 @@ class Midy {
|
|
|
2506
2599
|
}
|
|
2507
2600
|
return bitmap;
|
|
2508
2601
|
}
|
|
2509
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2510
|
-
if (data.length <
|
|
2602
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2603
|
+
if (data.length < 19) {
|
|
2604
|
+
console.error("Data length is too short");
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2608
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2609
|
+
if (!channelBitmap[i])
|
|
2610
|
+
continue;
|
|
2611
|
+
const channel = this.channels[i];
|
|
2612
|
+
for (let j = 0; j < 12; j++) {
|
|
2613
|
+
const centValue = data[j + 7] - 64;
|
|
2614
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2615
|
+
}
|
|
2616
|
+
if (realtime)
|
|
2617
|
+
this.updateChannelDetune(channel);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
|
|
2621
|
+
if (data.length < 31) {
|
|
2511
2622
|
console.error("Data length is too short");
|
|
2512
2623
|
return;
|
|
2513
2624
|
}
|
|
@@ -2515,10 +2626,17 @@ class Midy {
|
|
|
2515
2626
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2516
2627
|
if (!channelBitmap[i])
|
|
2517
2628
|
continue;
|
|
2629
|
+
const channel = this.channels[i];
|
|
2518
2630
|
for (let j = 0; j < 12; j++) {
|
|
2519
|
-
const
|
|
2520
|
-
|
|
2631
|
+
const index = 7 + j * 2;
|
|
2632
|
+
const msb = data[index] & 0x7F;
|
|
2633
|
+
const lsb = data[index + 1] & 0x7F;
|
|
2634
|
+
const value14bit = msb * 128 + lsb;
|
|
2635
|
+
const centValue = (value14bit - 8192) / 8.192;
|
|
2636
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2521
2637
|
}
|
|
2638
|
+
if (realtime)
|
|
2639
|
+
this.updateChannelDetune(channel);
|
|
2522
2640
|
}
|
|
2523
2641
|
}
|
|
2524
2642
|
applyDestinationSettings(channel, note, table) {
|
|
@@ -2670,7 +2788,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2670
2788
|
value: {
|
|
2671
2789
|
currentBufferSource: null,
|
|
2672
2790
|
detune: 0,
|
|
2673
|
-
scaleOctaveTuningTable: new
|
|
2791
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
2674
2792
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2675
2793
|
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2676
2794
|
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|