@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-GM1.js
CHANGED
|
@@ -3,6 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGM1 = 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,37 +69,31 @@ class Note {
|
|
|
17
69
|
writable: true,
|
|
18
70
|
value: void 0
|
|
19
71
|
});
|
|
20
|
-
Object.defineProperty(this, "
|
|
21
|
-
enumerable: true,
|
|
22
|
-
configurable: true,
|
|
23
|
-
writable: true,
|
|
24
|
-
value: void 0
|
|
25
|
-
});
|
|
26
|
-
Object.defineProperty(this, "volumeDepth", {
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
27
73
|
enumerable: true,
|
|
28
74
|
configurable: true,
|
|
29
75
|
writable: true,
|
|
30
76
|
value: void 0
|
|
31
77
|
});
|
|
32
|
-
Object.defineProperty(this, "
|
|
78
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
33
79
|
enumerable: true,
|
|
34
80
|
configurable: true,
|
|
35
81
|
writable: true,
|
|
36
82
|
value: void 0
|
|
37
83
|
});
|
|
38
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
39
85
|
enumerable: true,
|
|
40
86
|
configurable: true,
|
|
41
87
|
writable: true,
|
|
42
88
|
value: void 0
|
|
43
89
|
});
|
|
44
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "modulationLFO", {
|
|
45
91
|
enumerable: true,
|
|
46
92
|
configurable: true,
|
|
47
93
|
writable: true,
|
|
48
94
|
value: void 0
|
|
49
95
|
});
|
|
50
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "modulationDepth", {
|
|
51
97
|
enumerable: true,
|
|
52
98
|
configurable: true,
|
|
53
99
|
writable: true,
|
|
@@ -181,6 +227,18 @@ class MidyGM1 {
|
|
|
181
227
|
writable: true,
|
|
182
228
|
value: this.initSoundFontTable()
|
|
183
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
|
+
});
|
|
184
242
|
Object.defineProperty(this, "isPlaying", {
|
|
185
243
|
enumerable: true,
|
|
186
244
|
configurable: true,
|
|
@@ -233,7 +291,7 @@ class MidyGM1 {
|
|
|
233
291
|
enumerable: true,
|
|
234
292
|
configurable: true,
|
|
235
293
|
writable: true,
|
|
236
|
-
value: new
|
|
294
|
+
value: new SparseMap(128)
|
|
237
295
|
});
|
|
238
296
|
this.audioContext = audioContext;
|
|
239
297
|
this.masterVolume = new GainNode(audioContext);
|
|
@@ -246,7 +304,7 @@ class MidyGM1 {
|
|
|
246
304
|
initSoundFontTable() {
|
|
247
305
|
const table = new Array(128);
|
|
248
306
|
for (let i = 0; i < 128; i++) {
|
|
249
|
-
table[i] = new
|
|
307
|
+
table[i] = new SparseMap(128);
|
|
250
308
|
}
|
|
251
309
|
return table;
|
|
252
310
|
}
|
|
@@ -299,7 +357,7 @@ class MidyGM1 {
|
|
|
299
357
|
...this.constructor.channelSettings,
|
|
300
358
|
state: new ControllerState(),
|
|
301
359
|
...this.setChannelAudioNodes(audioContext),
|
|
302
|
-
scheduledNotes: new
|
|
360
|
+
scheduledNotes: new SparseMap(128),
|
|
303
361
|
};
|
|
304
362
|
});
|
|
305
363
|
return channels;
|
|
@@ -333,9 +391,8 @@ class MidyGM1 {
|
|
|
333
391
|
return audioBuffer;
|
|
334
392
|
}
|
|
335
393
|
}
|
|
336
|
-
|
|
394
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
337
395
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
338
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
339
396
|
bufferSource.buffer = audioBuffer;
|
|
340
397
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
341
398
|
if (bufferSource.loop) {
|
|
@@ -349,31 +406,32 @@ class MidyGM1 {
|
|
|
349
406
|
const event = this.timeline[queueIndex];
|
|
350
407
|
if (event.startTime > t + this.lookAhead)
|
|
351
408
|
break;
|
|
409
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
352
410
|
switch (event.type) {
|
|
353
411
|
case "noteOn":
|
|
354
412
|
if (event.velocity !== 0) {
|
|
355
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
413
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
356
414
|
break;
|
|
357
415
|
}
|
|
358
416
|
/* falls through */
|
|
359
417
|
case "noteOff": {
|
|
360
|
-
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity,
|
|
418
|
+
const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, startTime);
|
|
361
419
|
if (notePromise) {
|
|
362
420
|
this.notePromises.push(notePromise);
|
|
363
421
|
}
|
|
364
422
|
break;
|
|
365
423
|
}
|
|
366
424
|
case "controller":
|
|
367
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
425
|
+
this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
368
426
|
break;
|
|
369
427
|
case "programChange":
|
|
370
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
428
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
371
429
|
break;
|
|
372
430
|
case "pitchBend":
|
|
373
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
431
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
374
432
|
break;
|
|
375
433
|
case "sysEx":
|
|
376
|
-
this.handleSysEx(event.data);
|
|
434
|
+
this.handleSysEx(event.data, startTime);
|
|
377
435
|
}
|
|
378
436
|
queueIndex++;
|
|
379
437
|
}
|
|
@@ -400,6 +458,7 @@ class MidyGM1 {
|
|
|
400
458
|
await Promise.all(this.notePromises);
|
|
401
459
|
this.notePromises = [];
|
|
402
460
|
this.exclusiveClassMap.clear();
|
|
461
|
+
this.audioBufferCache.clear();
|
|
403
462
|
resolve();
|
|
404
463
|
return;
|
|
405
464
|
}
|
|
@@ -415,8 +474,9 @@ class MidyGM1 {
|
|
|
415
474
|
}
|
|
416
475
|
else if (this.isStopping) {
|
|
417
476
|
await this.stopNotes(0, true);
|
|
418
|
-
this.exclusiveClassMap.clear();
|
|
419
477
|
this.notePromises = [];
|
|
478
|
+
this.exclusiveClassMap.clear();
|
|
479
|
+
this.audioBufferCache.clear();
|
|
420
480
|
resolve();
|
|
421
481
|
this.isStopping = false;
|
|
422
482
|
this.isPaused = false;
|
|
@@ -447,6 +507,9 @@ class MidyGM1 {
|
|
|
447
507
|
secondToTicks(second, secondsPerBeat) {
|
|
448
508
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
449
509
|
}
|
|
510
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
511
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
512
|
+
}
|
|
450
513
|
extractMidiData(midi) {
|
|
451
514
|
const instruments = new Set();
|
|
452
515
|
const timeline = [];
|
|
@@ -467,6 +530,8 @@ class MidyGM1 {
|
|
|
467
530
|
switch (event.type) {
|
|
468
531
|
case "noteOn": {
|
|
469
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);
|
|
470
535
|
if (channel.programNumber < 0) {
|
|
471
536
|
instruments.add(`${channel.bank}:0`);
|
|
472
537
|
channel.programNumber = 0;
|
|
@@ -483,6 +548,10 @@ class MidyGM1 {
|
|
|
483
548
|
timeline.push(event);
|
|
484
549
|
}
|
|
485
550
|
}
|
|
551
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
552
|
+
if (count === 1)
|
|
553
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
554
|
+
}
|
|
486
555
|
const priority = {
|
|
487
556
|
controller: 0,
|
|
488
557
|
sysEx: 1,
|
|
@@ -572,8 +641,20 @@ class MidyGM1 {
|
|
|
572
641
|
const now = this.audioContext.currentTime;
|
|
573
642
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
574
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
|
+
}
|
|
575
656
|
getActiveNotes(channel, time) {
|
|
576
|
-
const activeNotes = new
|
|
657
|
+
const activeNotes = new SparseMap(128);
|
|
577
658
|
channel.scheduledNotes.forEach((noteList) => {
|
|
578
659
|
const activeNote = this.getActiveNote(noteList, time);
|
|
579
660
|
if (activeNote) {
|
|
@@ -645,20 +726,20 @@ class MidyGM1 {
|
|
|
645
726
|
.setValueAtTime(attackVolume, volHold)
|
|
646
727
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
647
728
|
}
|
|
648
|
-
setPitchEnvelope(note) {
|
|
649
|
-
|
|
729
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
730
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
650
731
|
const { voiceParams } = note;
|
|
651
732
|
const baseRate = voiceParams.playbackRate;
|
|
652
733
|
note.bufferSource.playbackRate
|
|
653
|
-
.cancelScheduledValues(
|
|
654
|
-
.setValueAtTime(baseRate,
|
|
734
|
+
.cancelScheduledValues(scheduleTime)
|
|
735
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
655
736
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
656
737
|
if (modEnvToPitch === 0)
|
|
657
738
|
return;
|
|
658
739
|
const basePitch = this.rateToCent(baseRate);
|
|
659
740
|
const peekPitch = basePitch + modEnvToPitch;
|
|
660
741
|
const peekRate = this.centToRate(peekPitch);
|
|
661
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
742
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
662
743
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
663
744
|
const modHold = modAttack + voiceParams.modHold;
|
|
664
745
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -715,11 +796,31 @@ class MidyGM1 {
|
|
|
715
796
|
note.modulationLFO.connect(note.volumeDepth);
|
|
716
797
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
717
798
|
}
|
|
799
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
800
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
801
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
802
|
+
if (cache) {
|
|
803
|
+
cache.counter += 1;
|
|
804
|
+
if (cache.maxCount <= cache.counter) {
|
|
805
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
806
|
+
}
|
|
807
|
+
return cache.audioBuffer;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
811
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
812
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
813
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
814
|
+
return audioBuffer;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
718
817
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
719
818
|
const state = channel.state;
|
|
720
|
-
const
|
|
819
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
820
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
721
821
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
722
|
-
|
|
822
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
823
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
723
824
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
724
825
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
725
826
|
type: "lowpass",
|
|
@@ -743,10 +844,10 @@ class MidyGM1 {
|
|
|
743
844
|
if (soundFontIndex === undefined)
|
|
744
845
|
return;
|
|
745
846
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
746
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
747
847
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
748
848
|
if (!voice)
|
|
749
849
|
return;
|
|
850
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
750
851
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
751
852
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
752
853
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
@@ -795,10 +896,6 @@ class MidyGM1 {
|
|
|
795
896
|
note.modulationDepth.disconnect();
|
|
796
897
|
note.modulationLFO.stop();
|
|
797
898
|
}
|
|
798
|
-
if (note.vibratoDepth) {
|
|
799
|
-
note.vibratoDepth.disconnect();
|
|
800
|
-
note.vibratoLFO.stop();
|
|
801
|
-
}
|
|
802
899
|
resolve();
|
|
803
900
|
};
|
|
804
901
|
note.bufferSource.stop(stopTime);
|
|
@@ -893,16 +990,6 @@ class MidyGM1 {
|
|
|
893
990
|
.cancelScheduledValues(now)
|
|
894
991
|
.setValueAtTime(modulationDepth, now);
|
|
895
992
|
}
|
|
896
|
-
setVibLfoToPitch(channel, note) {
|
|
897
|
-
const now = this.audioContext.currentTime;
|
|
898
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
899
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
900
|
-
2;
|
|
901
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
902
|
-
note.vibratoDepth.gain
|
|
903
|
-
.cancelScheduledValues(now)
|
|
904
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
905
|
-
}
|
|
906
993
|
setModLfoToFilterFc(note) {
|
|
907
994
|
const now = this.audioContext.currentTime;
|
|
908
995
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
@@ -942,11 +1029,7 @@ class MidyGM1 {
|
|
|
942
1029
|
this.setModLfoToPitch(channel, note);
|
|
943
1030
|
}
|
|
944
1031
|
},
|
|
945
|
-
vibLfoToPitch: (
|
|
946
|
-
if (0 < channel.state.vibratoDepth) {
|
|
947
|
-
this.setVibLfoToPitch(channel, note);
|
|
948
|
-
}
|
|
949
|
-
},
|
|
1032
|
+
vibLfoToPitch: (_channel, _note, _prevValue) => { },
|
|
950
1033
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
951
1034
|
if (0 < channel.state.modulationDepth)
|
|
952
1035
|
this.setModLfoToFilterFc(note);
|
|
@@ -959,28 +1042,8 @@ class MidyGM1 {
|
|
|
959
1042
|
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
960
1043
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
961
1044
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
962
|
-
delayVibLFO: (
|
|
963
|
-
|
|
964
|
-
const now = this.audioContext.currentTime;
|
|
965
|
-
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
966
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
967
|
-
if (now < prevStartTime)
|
|
968
|
-
return;
|
|
969
|
-
const value = note.voiceParams.delayVibLFO;
|
|
970
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
971
|
-
note.vibratoLFO.stop(now);
|
|
972
|
-
note.vibratoLFO.start(startTime);
|
|
973
|
-
}
|
|
974
|
-
},
|
|
975
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
976
|
-
if (0 < channel.state.vibratoDepth) {
|
|
977
|
-
const now = this.audioContext.currentTime;
|
|
978
|
-
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
979
|
-
note.vibratoLFO.frequency
|
|
980
|
-
.cancelScheduledValues(now)
|
|
981
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
|
|
982
|
-
}
|
|
983
|
-
},
|
|
1045
|
+
delayVibLFO: (_channel, _note, _prevValue) => { },
|
|
1046
|
+
freqVibLFO: (_channel, _note, _prevValue) => { },
|
|
984
1047
|
};
|
|
985
1048
|
}
|
|
986
1049
|
getControllerState(channel, noteNumber, velocity) {
|
|
@@ -1053,10 +1116,10 @@ class MidyGM1 {
|
|
|
1053
1116
|
123: this.allNotesOff,
|
|
1054
1117
|
};
|
|
1055
1118
|
}
|
|
1056
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1119
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1057
1120
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1058
1121
|
if (handler) {
|
|
1059
|
-
handler.call(this, channelNumber, value);
|
|
1122
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1060
1123
|
const channel = this.channels[channelNumber];
|
|
1061
1124
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1062
1125
|
}
|
|
@@ -1064,33 +1127,28 @@ class MidyGM1 {
|
|
|
1064
1127
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1065
1128
|
}
|
|
1066
1129
|
}
|
|
1067
|
-
updateModulation(channel) {
|
|
1068
|
-
|
|
1130
|
+
updateModulation(channel, scheduleTime) {
|
|
1131
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1069
1132
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
this.setPitchEnvelope(note);
|
|
1080
|
-
this.startModulation(channel, note, now);
|
|
1081
|
-
}
|
|
1133
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1134
|
+
if (note.modulationDepth) {
|
|
1135
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1139
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1082
1140
|
}
|
|
1083
1141
|
});
|
|
1084
1142
|
}
|
|
1085
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1143
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1086
1144
|
const channel = this.channels[channelNumber];
|
|
1087
1145
|
channel.state.modulationDepth = modulation / 127;
|
|
1088
|
-
this.updateModulation(channel);
|
|
1146
|
+
this.updateModulation(channel, scheduleTime);
|
|
1089
1147
|
}
|
|
1090
|
-
setVolume(channelNumber, volume) {
|
|
1148
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1091
1149
|
const channel = this.channels[channelNumber];
|
|
1092
1150
|
channel.state.volume = volume / 127;
|
|
1093
|
-
this.updateChannelVolume(channel);
|
|
1151
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1094
1152
|
}
|
|
1095
1153
|
panToGain(pan) {
|
|
1096
1154
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1099,31 +1157,31 @@ class MidyGM1 {
|
|
|
1099
1157
|
gainRight: Math.sin(theta),
|
|
1100
1158
|
};
|
|
1101
1159
|
}
|
|
1102
|
-
setPan(channelNumber, pan) {
|
|
1160
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1103
1161
|
const channel = this.channels[channelNumber];
|
|
1104
1162
|
channel.state.pan = pan / 127;
|
|
1105
|
-
this.updateChannelVolume(channel);
|
|
1163
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1106
1164
|
}
|
|
1107
|
-
setExpression(channelNumber, expression) {
|
|
1165
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1108
1166
|
const channel = this.channels[channelNumber];
|
|
1109
1167
|
channel.state.expression = expression / 127;
|
|
1110
|
-
this.updateChannelVolume(channel);
|
|
1168
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1111
1169
|
}
|
|
1112
1170
|
dataEntryLSB(channelNumber, value) {
|
|
1113
1171
|
this.channels[channelNumber].dataLSB = value;
|
|
1114
1172
|
this.handleRPN(channelNumber, 0);
|
|
1115
1173
|
}
|
|
1116
|
-
updateChannelVolume(channel) {
|
|
1117
|
-
|
|
1174
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1175
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1118
1176
|
const state = channel.state;
|
|
1119
1177
|
const volume = state.volume * state.expression;
|
|
1120
1178
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1121
1179
|
channel.gainL.gain
|
|
1122
1180
|
.cancelScheduledValues(now)
|
|
1123
|
-
.setValueAtTime(volume * gainLeft,
|
|
1181
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1124
1182
|
channel.gainR.gain
|
|
1125
1183
|
.cancelScheduledValues(now)
|
|
1126
|
-
.setValueAtTime(volume * gainRight,
|
|
1184
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1127
1185
|
}
|
|
1128
1186
|
setSustainPedal(channelNumber, value) {
|
|
1129
1187
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|