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