@marmooo/midy 0.3.4 → 0.3.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/README.md +19 -11
- package/esm/midy-GM1.d.ts +14 -11
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +140 -106
- package/esm/midy-GM2.d.ts +26 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +285 -279
- package/esm/midy-GMLite.d.ts +15 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +139 -107
- package/esm/midy.d.ts +31 -27
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +304 -298
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +14 -11
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +140 -106
- package/script/midy-GM2.d.ts +26 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +285 -279
- package/script/midy-GMLite.d.ts +15 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +139 -107
- package/script/midy.d.ts +31 -27
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +304 -298
package/script/midy-GM2.js
CHANGED
|
@@ -71,13 +71,13 @@ class Note {
|
|
|
71
71
|
writable: true,
|
|
72
72
|
value: void 0
|
|
73
73
|
});
|
|
74
|
-
Object.defineProperty(this, "
|
|
74
|
+
Object.defineProperty(this, "reverbSend", {
|
|
75
75
|
enumerable: true,
|
|
76
76
|
configurable: true,
|
|
77
77
|
writable: true,
|
|
78
78
|
value: void 0
|
|
79
79
|
});
|
|
80
|
-
Object.defineProperty(this, "
|
|
80
|
+
Object.defineProperty(this, "chorusSend", {
|
|
81
81
|
enumerable: true,
|
|
82
82
|
configurable: true,
|
|
83
83
|
writable: true,
|
|
@@ -317,13 +317,13 @@ class MidyGM2 {
|
|
|
317
317
|
writable: true,
|
|
318
318
|
value: this.initSoundFontTable()
|
|
319
319
|
});
|
|
320
|
-
Object.defineProperty(this, "
|
|
320
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
321
321
|
enumerable: true,
|
|
322
322
|
configurable: true,
|
|
323
323
|
writable: true,
|
|
324
324
|
value: new Map()
|
|
325
325
|
});
|
|
326
|
-
Object.defineProperty(this, "
|
|
326
|
+
Object.defineProperty(this, "voiceCache", {
|
|
327
327
|
enumerable: true,
|
|
328
328
|
configurable: true,
|
|
329
329
|
writable: true,
|
|
@@ -365,17 +365,17 @@ class MidyGM2 {
|
|
|
365
365
|
writable: true,
|
|
366
366
|
value: []
|
|
367
367
|
});
|
|
368
|
-
Object.defineProperty(this, "
|
|
368
|
+
Object.defineProperty(this, "notePromises", {
|
|
369
369
|
enumerable: true,
|
|
370
370
|
configurable: true,
|
|
371
371
|
writable: true,
|
|
372
372
|
value: []
|
|
373
373
|
});
|
|
374
|
-
Object.defineProperty(this, "
|
|
374
|
+
Object.defineProperty(this, "instruments", {
|
|
375
375
|
enumerable: true,
|
|
376
376
|
configurable: true,
|
|
377
377
|
writable: true,
|
|
378
|
-
value:
|
|
378
|
+
value: new Set()
|
|
379
379
|
});
|
|
380
380
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
381
381
|
enumerable: true,
|
|
@@ -420,13 +420,11 @@ class MidyGM2 {
|
|
|
420
420
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
421
421
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
422
422
|
const presetHeader = presetHeaders[i];
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
banks.set(presetHeader.bank, index);
|
|
426
|
-
}
|
|
423
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
424
|
+
banks.set(presetHeader.bank, index);
|
|
427
425
|
}
|
|
428
426
|
}
|
|
429
|
-
async
|
|
427
|
+
async toUint8Array(input) {
|
|
430
428
|
let uint8Array;
|
|
431
429
|
if (typeof input === "string") {
|
|
432
430
|
const response = await fetch(input);
|
|
@@ -439,23 +437,32 @@ class MidyGM2 {
|
|
|
439
437
|
else {
|
|
440
438
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
441
439
|
}
|
|
442
|
-
|
|
443
|
-
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
444
|
-
this.addSoundFont(soundFont);
|
|
440
|
+
return uint8Array;
|
|
445
441
|
}
|
|
446
|
-
async
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
442
|
+
async loadSoundFont(input) {
|
|
443
|
+
this.voiceCounter.clear();
|
|
444
|
+
if (Array.isArray(input)) {
|
|
445
|
+
const promises = new Array(input.length);
|
|
446
|
+
for (let i = 0; i < input.length; i++) {
|
|
447
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
448
|
+
}
|
|
449
|
+
const uint8Arrays = await Promise.all(promises);
|
|
450
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
451
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
|
|
452
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
453
|
+
this.addSoundFont(soundFont);
|
|
454
|
+
}
|
|
455
455
|
}
|
|
456
456
|
else {
|
|
457
|
-
|
|
457
|
+
const uint8Array = await this.toUint8Array(input);
|
|
458
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
459
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
460
|
+
this.addSoundFont(soundFont);
|
|
458
461
|
}
|
|
462
|
+
}
|
|
463
|
+
async loadMIDI(input) {
|
|
464
|
+
this.voiceCounter.clear();
|
|
465
|
+
const uint8Array = await this.toUint8Array(input);
|
|
459
466
|
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
460
467
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
461
468
|
const midiData = this.extractMidiData(midi);
|
|
@@ -463,6 +470,45 @@ class MidyGM2 {
|
|
|
463
470
|
this.timeline = midiData.timeline;
|
|
464
471
|
this.totalTime = this.calcTotalTime();
|
|
465
472
|
}
|
|
473
|
+
cacheVoiceIds() {
|
|
474
|
+
const timeline = this.timeline;
|
|
475
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
476
|
+
const event = timeline[i];
|
|
477
|
+
switch (event.type) {
|
|
478
|
+
case "noteOn": {
|
|
479
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
480
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "controller":
|
|
484
|
+
if (event.controllerType === 0) {
|
|
485
|
+
this.setBankMSB(event.channel, event.value);
|
|
486
|
+
}
|
|
487
|
+
else if (event.controllerType === 32) {
|
|
488
|
+
this.setBankLSB(event.channel, event.value);
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
case "programChange":
|
|
492
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
496
|
+
if (count === 1)
|
|
497
|
+
this.voiceCounter.delete(audioBufferId);
|
|
498
|
+
}
|
|
499
|
+
this.GM2SystemOn();
|
|
500
|
+
}
|
|
501
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
502
|
+
const bankNumber = this.calcBank(channel);
|
|
503
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
504
|
+
.get(bankNumber);
|
|
505
|
+
if (soundFontIndex === undefined)
|
|
506
|
+
return;
|
|
507
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
508
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
509
|
+
const { instrument, sampleID } = voice.generators;
|
|
510
|
+
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
511
|
+
}
|
|
466
512
|
createChannelAudioNodes(audioContext) {
|
|
467
513
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
468
514
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -504,34 +550,12 @@ class MidyGM2 {
|
|
|
504
550
|
});
|
|
505
551
|
return channels;
|
|
506
552
|
}
|
|
507
|
-
async
|
|
553
|
+
async createAudioBuffer(voiceParams) {
|
|
554
|
+
const sample = voiceParams.sample;
|
|
508
555
|
const sampleStart = voiceParams.start;
|
|
509
|
-
const sampleEnd =
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const start = sample.byteOffset + sampleStart;
|
|
513
|
-
const end = sample.byteOffset + sampleEnd;
|
|
514
|
-
const buffer = sample.buffer.slice(start, end);
|
|
515
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
516
|
-
return audioBuffer;
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
const sample = voiceParams.sample;
|
|
520
|
-
const start = sample.byteOffset + sampleStart;
|
|
521
|
-
const end = sample.byteOffset + sampleEnd;
|
|
522
|
-
const buffer = sample.buffer.slice(start, end);
|
|
523
|
-
const audioBuffer = new AudioBuffer({
|
|
524
|
-
numberOfChannels: 1,
|
|
525
|
-
length: sample.length,
|
|
526
|
-
sampleRate: voiceParams.sampleRate,
|
|
527
|
-
});
|
|
528
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
529
|
-
const int16Array = new Int16Array(buffer);
|
|
530
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
531
|
-
channelData[i] = int16Array[i] / 32768;
|
|
532
|
-
}
|
|
533
|
-
return audioBuffer;
|
|
534
|
-
}
|
|
556
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
557
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
558
|
+
return audioBuffer;
|
|
535
559
|
}
|
|
536
560
|
isLoopDrum(channel, noteNumber) {
|
|
537
561
|
const programNumber = channel.programNumber;
|
|
@@ -568,13 +592,13 @@ class MidyGM2 {
|
|
|
568
592
|
break;
|
|
569
593
|
}
|
|
570
594
|
case "controller":
|
|
571
|
-
this.
|
|
595
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
572
596
|
break;
|
|
573
597
|
case "programChange":
|
|
574
|
-
this.
|
|
598
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
575
599
|
break;
|
|
576
600
|
case "channelAftertouch":
|
|
577
|
-
this.
|
|
601
|
+
this.setChannelPressure(event.channel, event.amount, startTime);
|
|
578
602
|
break;
|
|
579
603
|
case "pitchBend":
|
|
580
604
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -608,8 +632,9 @@ class MidyGM2 {
|
|
|
608
632
|
this.notePromises = [];
|
|
609
633
|
this.exclusiveClassNotes.fill(undefined);
|
|
610
634
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
611
|
-
this.
|
|
635
|
+
this.voiceCache.clear();
|
|
612
636
|
for (let i = 0; i < this.channels.length; i++) {
|
|
637
|
+
this.channels[i].scheduledNotes = [];
|
|
613
638
|
this.resetAllStates(i);
|
|
614
639
|
}
|
|
615
640
|
resolve();
|
|
@@ -631,8 +656,9 @@ class MidyGM2 {
|
|
|
631
656
|
this.notePromises = [];
|
|
632
657
|
this.exclusiveClassNotes.fill(undefined);
|
|
633
658
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
634
|
-
this.
|
|
659
|
+
this.voiceCache.clear();
|
|
635
660
|
for (let i = 0; i < this.channels.length; i++) {
|
|
661
|
+
this.channels[i].scheduledNotes = [];
|
|
636
662
|
this.resetAllStates(i);
|
|
637
663
|
}
|
|
638
664
|
this.isStopping = false;
|
|
@@ -665,11 +691,7 @@ class MidyGM2 {
|
|
|
665
691
|
secondToTicks(second, secondsPerBeat) {
|
|
666
692
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
667
693
|
}
|
|
668
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
669
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
670
|
-
}
|
|
671
694
|
extractMidiData(midi) {
|
|
672
|
-
this.audioBufferCounter.clear();
|
|
673
695
|
const instruments = new Set();
|
|
674
696
|
const timeline = [];
|
|
675
697
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -690,8 +712,6 @@ class MidyGM2 {
|
|
|
690
712
|
switch (event.type) {
|
|
691
713
|
case "noteOn": {
|
|
692
714
|
const channel = tmpChannels[event.channel];
|
|
693
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
694
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
695
715
|
if (channel.programNumber < 0) {
|
|
696
716
|
channel.programNumber = event.programNumber;
|
|
697
717
|
switch (channel.bankMSB) {
|
|
@@ -741,10 +761,6 @@ class MidyGM2 {
|
|
|
741
761
|
timeline.push(event);
|
|
742
762
|
}
|
|
743
763
|
}
|
|
744
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
745
|
-
if (count === 1)
|
|
746
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
747
|
-
}
|
|
748
764
|
const priority = {
|
|
749
765
|
controller: 0,
|
|
750
766
|
sysEx: 1,
|
|
@@ -784,12 +800,11 @@ class MidyGM2 {
|
|
|
784
800
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
785
801
|
const channel = this.channels[channelNumber];
|
|
786
802
|
const promises = [];
|
|
787
|
-
this.processScheduledNotes(channel,
|
|
803
|
+
this.processScheduledNotes(channel, (note) => {
|
|
788
804
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
789
805
|
this.notePromises.push(promise);
|
|
790
806
|
promises.push(promise);
|
|
791
807
|
});
|
|
792
|
-
channel.scheduledNotes = [];
|
|
793
808
|
return Promise.all(promises);
|
|
794
809
|
}
|
|
795
810
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -803,6 +818,8 @@ class MidyGM2 {
|
|
|
803
818
|
if (this.isPlaying || this.isPaused)
|
|
804
819
|
return;
|
|
805
820
|
this.resumeTime = 0;
|
|
821
|
+
if (this.voiceCounter.size === 0)
|
|
822
|
+
this.cacheVoiceIds();
|
|
806
823
|
await this.playNotes();
|
|
807
824
|
this.isPlaying = false;
|
|
808
825
|
}
|
|
@@ -843,22 +860,20 @@ class MidyGM2 {
|
|
|
843
860
|
const now = this.audioContext.currentTime;
|
|
844
861
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
845
862
|
}
|
|
846
|
-
processScheduledNotes(channel,
|
|
863
|
+
processScheduledNotes(channel, callback) {
|
|
847
864
|
const scheduledNotes = channel.scheduledNotes;
|
|
848
|
-
for (let i =
|
|
865
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
849
866
|
const note = scheduledNotes[i];
|
|
850
867
|
if (!note)
|
|
851
868
|
continue;
|
|
852
869
|
if (note.ending)
|
|
853
870
|
continue;
|
|
854
|
-
if (note.startTime < scheduleTime)
|
|
855
|
-
continue;
|
|
856
871
|
callback(note);
|
|
857
872
|
}
|
|
858
873
|
}
|
|
859
874
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
860
875
|
const scheduledNotes = channel.scheduledNotes;
|
|
861
|
-
for (let i =
|
|
876
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
862
877
|
const note = scheduledNotes[i];
|
|
863
878
|
if (!note)
|
|
864
879
|
continue;
|
|
@@ -892,13 +907,11 @@ class MidyGM2 {
|
|
|
892
907
|
return impulse;
|
|
893
908
|
}
|
|
894
909
|
createConvolutionReverb(audioContext, impulse) {
|
|
895
|
-
const input = new GainNode(audioContext);
|
|
896
910
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
897
911
|
buffer: impulse,
|
|
898
912
|
});
|
|
899
|
-
input.connect(convolverNode);
|
|
900
913
|
return {
|
|
901
|
-
input,
|
|
914
|
+
input: convolverNode,
|
|
902
915
|
output: convolverNode,
|
|
903
916
|
convolverNode,
|
|
904
917
|
};
|
|
@@ -1049,7 +1062,7 @@ class MidyGM2 {
|
|
|
1049
1062
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1050
1063
|
}
|
|
1051
1064
|
updateChannelDetune(channel, scheduleTime) {
|
|
1052
|
-
this.processScheduledNotes(channel,
|
|
1065
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1053
1066
|
this.updateDetune(channel, note, scheduleTime);
|
|
1054
1067
|
});
|
|
1055
1068
|
}
|
|
@@ -1270,31 +1283,31 @@ class MidyGM2 {
|
|
|
1270
1283
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1271
1284
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1272
1285
|
}
|
|
1273
|
-
async getAudioBuffer(
|
|
1274
|
-
const audioBufferId = this.
|
|
1275
|
-
const cache = this.
|
|
1286
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1287
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1288
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1276
1289
|
if (cache) {
|
|
1277
1290
|
cache.counter += 1;
|
|
1278
1291
|
if (cache.maxCount <= cache.counter) {
|
|
1279
|
-
this.
|
|
1292
|
+
this.voiceCache.delete(audioBufferId);
|
|
1280
1293
|
}
|
|
1281
1294
|
return cache.audioBuffer;
|
|
1282
1295
|
}
|
|
1283
1296
|
else {
|
|
1284
|
-
const maxCount = this.
|
|
1285
|
-
const audioBuffer = await this.
|
|
1297
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1298
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1286
1299
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1287
|
-
this.
|
|
1300
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1288
1301
|
return audioBuffer;
|
|
1289
1302
|
}
|
|
1290
1303
|
}
|
|
1291
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1304
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1292
1305
|
const now = this.audioContext.currentTime;
|
|
1293
1306
|
const state = channel.state;
|
|
1294
1307
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1295
1308
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1296
1309
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1297
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1310
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1298
1311
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1299
1312
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1300
1313
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1328,12 +1341,8 @@ class MidyGM2 {
|
|
|
1328
1341
|
}
|
|
1329
1342
|
note.bufferSource.connect(note.filterNode);
|
|
1330
1343
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1334
|
-
if (0 < state.reverbSendLevel) {
|
|
1335
|
-
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1336
|
-
}
|
|
1344
|
+
this.setChorusSend(channel, note, now);
|
|
1345
|
+
this.setReverbSend(channel, note, now);
|
|
1337
1346
|
note.bufferSource.start(startTime);
|
|
1338
1347
|
return note;
|
|
1339
1348
|
}
|
|
@@ -1389,20 +1398,24 @@ class MidyGM2 {
|
|
|
1389
1398
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1390
1399
|
const channel = this.channels[channelNumber];
|
|
1391
1400
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1392
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1401
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1402
|
+
.get(bankNumber);
|
|
1393
1403
|
if (soundFontIndex === undefined)
|
|
1394
1404
|
return;
|
|
1395
1405
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1396
1406
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1397
1407
|
if (!voice)
|
|
1398
1408
|
return;
|
|
1399
|
-
const
|
|
1400
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1409
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1401
1410
|
if (channel.isDrum) {
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1411
|
+
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1412
|
+
let gainL = keyBasedGainLs[noteNumber];
|
|
1413
|
+
let gainR = keyBasedGainRs[noteNumber];
|
|
1414
|
+
if (!gainL) {
|
|
1415
|
+
const audioNodes = this.createChannelAudioNodes(this.audioContext);
|
|
1416
|
+
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1417
|
+
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1418
|
+
}
|
|
1406
1419
|
note.volumeEnvelopeNode.connect(gainL);
|
|
1407
1420
|
note.volumeEnvelopeNode.connect(gainR);
|
|
1408
1421
|
}
|
|
@@ -1436,11 +1449,11 @@ class MidyGM2 {
|
|
|
1436
1449
|
note.vibratoDepth.disconnect();
|
|
1437
1450
|
note.vibratoLFO.stop();
|
|
1438
1451
|
}
|
|
1439
|
-
if (note.
|
|
1440
|
-
note.
|
|
1452
|
+
if (note.reverbSend) {
|
|
1453
|
+
note.reverbSend.disconnect();
|
|
1441
1454
|
}
|
|
1442
|
-
if (note.
|
|
1443
|
-
note.
|
|
1455
|
+
if (note.chorusSend) {
|
|
1456
|
+
note.chorusSend.disconnect();
|
|
1444
1457
|
}
|
|
1445
1458
|
}
|
|
1446
1459
|
releaseNote(channel, note, endTime) {
|
|
@@ -1479,15 +1492,29 @@ class MidyGM2 {
|
|
|
1479
1492
|
return;
|
|
1480
1493
|
}
|
|
1481
1494
|
}
|
|
1482
|
-
const
|
|
1483
|
-
if (
|
|
1495
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1496
|
+
if (index < 0)
|
|
1484
1497
|
return;
|
|
1498
|
+
const note = channel.scheduledNotes[index];
|
|
1485
1499
|
note.ending = true;
|
|
1500
|
+
this.setNoteIndex(channel, index);
|
|
1486
1501
|
this.releaseNote(channel, note, endTime);
|
|
1487
1502
|
}
|
|
1488
|
-
|
|
1503
|
+
setNoteIndex(channel, index) {
|
|
1504
|
+
let allEnds = true;
|
|
1505
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1506
|
+
const note = channel.scheduledNotes[i];
|
|
1507
|
+
if (note && !note.ending) {
|
|
1508
|
+
allEnds = false;
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (allEnds)
|
|
1513
|
+
channel.scheduleIndex = index + 1;
|
|
1514
|
+
}
|
|
1515
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1489
1516
|
const scheduledNotes = channel.scheduledNotes;
|
|
1490
|
-
for (let i =
|
|
1517
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1491
1518
|
const note = scheduledNotes[i];
|
|
1492
1519
|
if (!note)
|
|
1493
1520
|
continue;
|
|
@@ -1495,8 +1522,9 @@ class MidyGM2 {
|
|
|
1495
1522
|
continue;
|
|
1496
1523
|
if (note.noteNumber !== noteNumber)
|
|
1497
1524
|
continue;
|
|
1498
|
-
return
|
|
1525
|
+
return i;
|
|
1499
1526
|
}
|
|
1527
|
+
return -1;
|
|
1500
1528
|
}
|
|
1501
1529
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1502
1530
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1536,18 +1564,18 @@ class MidyGM2 {
|
|
|
1536
1564
|
case 0x90:
|
|
1537
1565
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1538
1566
|
case 0xB0:
|
|
1539
|
-
return this.
|
|
1567
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1540
1568
|
case 0xC0:
|
|
1541
|
-
return this.
|
|
1569
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1542
1570
|
case 0xD0:
|
|
1543
|
-
return this.
|
|
1571
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1544
1572
|
case 0xE0:
|
|
1545
1573
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1546
1574
|
default:
|
|
1547
1575
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1548
1576
|
}
|
|
1549
1577
|
}
|
|
1550
|
-
|
|
1578
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1551
1579
|
const channel = this.channels[channelNumber];
|
|
1552
1580
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1553
1581
|
channel.programNumber = programNumber;
|
|
@@ -1562,7 +1590,7 @@ class MidyGM2 {
|
|
|
1562
1590
|
}
|
|
1563
1591
|
}
|
|
1564
1592
|
}
|
|
1565
|
-
|
|
1593
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1566
1594
|
const channel = this.channels[channelNumber];
|
|
1567
1595
|
if (channel.isDrum)
|
|
1568
1596
|
return;
|
|
@@ -1576,7 +1604,7 @@ class MidyGM2 {
|
|
|
1576
1604
|
}
|
|
1577
1605
|
const table = channel.channelPressureTable;
|
|
1578
1606
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1579
|
-
this.
|
|
1607
|
+
this.setEffects(channel, note, table);
|
|
1580
1608
|
});
|
|
1581
1609
|
this.applyVoiceParams(channel, 13);
|
|
1582
1610
|
}
|
|
@@ -1598,13 +1626,18 @@ class MidyGM2 {
|
|
|
1598
1626
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1599
1627
|
}
|
|
1600
1628
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
.
|
|
1607
|
-
|
|
1629
|
+
if (note.modulationDepth) {
|
|
1630
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1631
|
+
this.getLFOPitchDepth(channel, note);
|
|
1632
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1633
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1634
|
+
note.modulationDepth.gain
|
|
1635
|
+
.cancelScheduledValues(scheduleTime)
|
|
1636
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1637
|
+
}
|
|
1638
|
+
else {
|
|
1639
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1640
|
+
}
|
|
1608
1641
|
}
|
|
1609
1642
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1610
1643
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
@@ -1631,63 +1664,63 @@ class MidyGM2 {
|
|
|
1631
1664
|
.cancelScheduledValues(scheduleTime)
|
|
1632
1665
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1633
1666
|
}
|
|
1634
|
-
|
|
1635
|
-
let value = note.voiceParams.reverbEffectsSend
|
|
1667
|
+
setReverbSend(channel, note, scheduleTime) {
|
|
1668
|
+
let value = note.voiceParams.reverbEffectsSend *
|
|
1669
|
+
channel.state.reverbSendLevel;
|
|
1636
1670
|
if (channel.isDrum) {
|
|
1637
|
-
const keyBasedValue = this.
|
|
1638
|
-
if (0 <= keyBasedValue)
|
|
1639
|
-
value
|
|
1640
|
-
}
|
|
1671
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1672
|
+
if (0 <= keyBasedValue)
|
|
1673
|
+
value = keyBasedValue / 127;
|
|
1641
1674
|
}
|
|
1642
|
-
if (
|
|
1675
|
+
if (!note.reverbSend) {
|
|
1643
1676
|
if (0 < value) {
|
|
1644
|
-
note.
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
}
|
|
1648
|
-
else {
|
|
1649
|
-
note.reverbEffectsSend.disconnect();
|
|
1677
|
+
note.reverbSend = new GainNode(this.audioContext, { gain: value });
|
|
1678
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1679
|
+
note.reverbSend.connect(this.reverbEffect.input);
|
|
1650
1680
|
}
|
|
1651
1681
|
}
|
|
1652
1682
|
else {
|
|
1683
|
+
note.reverbSend.gain
|
|
1684
|
+
.cancelScheduledValues(scheduleTime)
|
|
1685
|
+
.setValueAtTime(value, scheduleTime);
|
|
1653
1686
|
if (0 < value) {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
note.
|
|
1687
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
try {
|
|
1691
|
+
note.volumeEnvelopeNode.disconnect(note.reverbSend);
|
|
1659
1692
|
}
|
|
1660
|
-
|
|
1693
|
+
catch { /* empty */ }
|
|
1661
1694
|
}
|
|
1662
1695
|
}
|
|
1663
1696
|
}
|
|
1664
|
-
|
|
1665
|
-
let value = note.voiceParams.chorusEffectsSend
|
|
1697
|
+
setChorusSend(channel, note, scheduleTime) {
|
|
1698
|
+
let value = note.voiceParams.chorusEffectsSend *
|
|
1699
|
+
channel.state.chorusSendLevel;
|
|
1666
1700
|
if (channel.isDrum) {
|
|
1667
|
-
const keyBasedValue = this.
|
|
1668
|
-
if (0 <= keyBasedValue)
|
|
1669
|
-
value
|
|
1670
|
-
}
|
|
1701
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1702
|
+
if (0 <= keyBasedValue)
|
|
1703
|
+
value = keyBasedValue / 127;
|
|
1671
1704
|
}
|
|
1672
|
-
if (
|
|
1673
|
-
if (0 <
|
|
1674
|
-
note.
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
}
|
|
1678
|
-
else {
|
|
1679
|
-
note.chorusEffectsSend.disconnect();
|
|
1705
|
+
if (!note.chorusSend) {
|
|
1706
|
+
if (0 < value) {
|
|
1707
|
+
note.chorusSend = new GainNode(this.audioContext, { gain: value });
|
|
1708
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1709
|
+
note.chorusSend.connect(this.chorusEffect.input);
|
|
1680
1710
|
}
|
|
1681
1711
|
}
|
|
1682
1712
|
else {
|
|
1713
|
+
note.chorusSend.gain
|
|
1714
|
+
.cancelScheduledValues(scheduleTime)
|
|
1715
|
+
.setValueAtTime(value, scheduleTime);
|
|
1683
1716
|
if (0 < value) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
note.volumeEnvelopeNode.
|
|
1717
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
try {
|
|
1721
|
+
note.volumeEnvelopeNode.disconnect(note.chorusSend);
|
|
1689
1722
|
}
|
|
1690
|
-
|
|
1723
|
+
catch { /* empty */ }
|
|
1691
1724
|
}
|
|
1692
1725
|
}
|
|
1693
1726
|
}
|
|
@@ -1769,7 +1802,7 @@ class MidyGM2 {
|
|
|
1769
1802
|
return state;
|
|
1770
1803
|
}
|
|
1771
1804
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1772
|
-
this.processScheduledNotes(channel,
|
|
1805
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1773
1806
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1774
1807
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1775
1808
|
let applyVolumeEnvelope = false;
|
|
@@ -1830,13 +1863,13 @@ class MidyGM2 {
|
|
|
1830
1863
|
handlers[127] = this.polyOn;
|
|
1831
1864
|
return handlers;
|
|
1832
1865
|
}
|
|
1833
|
-
|
|
1866
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1834
1867
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1835
1868
|
if (handler) {
|
|
1836
1869
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1837
1870
|
const channel = this.channels[channelNumber];
|
|
1838
1871
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1839
|
-
this.
|
|
1872
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
1840
1873
|
}
|
|
1841
1874
|
else {
|
|
1842
1875
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1847,12 +1880,11 @@ class MidyGM2 {
|
|
|
1847
1880
|
}
|
|
1848
1881
|
updateModulation(channel, scheduleTime) {
|
|
1849
1882
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1850
|
-
this.processScheduledNotes(channel,
|
|
1883
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1851
1884
|
if (note.modulationDepth) {
|
|
1852
1885
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1853
1886
|
}
|
|
1854
1887
|
else {
|
|
1855
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1856
1888
|
this.startModulation(channel, note, scheduleTime);
|
|
1857
1889
|
}
|
|
1858
1890
|
});
|
|
@@ -1866,7 +1898,7 @@ class MidyGM2 {
|
|
|
1866
1898
|
this.updateModulation(channel, scheduleTime);
|
|
1867
1899
|
}
|
|
1868
1900
|
updatePortamento(channel, scheduleTime) {
|
|
1869
|
-
this.processScheduledNotes(channel,
|
|
1901
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1870
1902
|
if (0.5 <= channel.state.portamento) {
|
|
1871
1903
|
if (0 <= note.portamentoNoteNumber) {
|
|
1872
1904
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1897,8 +1929,14 @@ class MidyGM2 {
|
|
|
1897
1929
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1898
1930
|
const channel = this.channels[channelNumber];
|
|
1899
1931
|
channel.state.volume = volume / 127;
|
|
1900
|
-
|
|
1901
|
-
|
|
1932
|
+
if (channel.isDrum) {
|
|
1933
|
+
for (let i = 0; i < 128; i++) {
|
|
1934
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
else {
|
|
1938
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1939
|
+
}
|
|
1902
1940
|
}
|
|
1903
1941
|
panToGain(pan) {
|
|
1904
1942
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1911,8 +1949,14 @@ class MidyGM2 {
|
|
|
1911
1949
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1912
1950
|
const channel = this.channels[channelNumber];
|
|
1913
1951
|
channel.state.pan = pan / 127;
|
|
1914
|
-
|
|
1915
|
-
|
|
1952
|
+
if (channel.isDrum) {
|
|
1953
|
+
for (let i = 0; i < 128; i++) {
|
|
1954
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
else {
|
|
1958
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1959
|
+
}
|
|
1916
1960
|
}
|
|
1917
1961
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1918
1962
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1938,33 +1982,27 @@ class MidyGM2 {
|
|
|
1938
1982
|
.cancelScheduledValues(scheduleTime)
|
|
1939
1983
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1940
1984
|
}
|
|
1941
|
-
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1942
|
-
if (!channel.isDrum)
|
|
1943
|
-
return;
|
|
1985
|
+
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
1944
1986
|
const state = channel.state;
|
|
1945
1987
|
const defaultVolume = state.volume * state.expression;
|
|
1946
1988
|
const defaultPan = state.pan;
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
gainR.gain
|
|
1965
|
-
.cancelScheduledValues(scheduleTime)
|
|
1966
|
-
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1967
|
-
}
|
|
1989
|
+
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
1990
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
1991
|
+
if (!gainL)
|
|
1992
|
+
return;
|
|
1993
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
1994
|
+
const volume = (0 <= keyBasedVolume)
|
|
1995
|
+
? defaultVolume * keyBasedVolume / 64
|
|
1996
|
+
: defaultVolume;
|
|
1997
|
+
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
1998
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1999
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2000
|
+
gainL.gain
|
|
2001
|
+
.cancelScheduledValues(scheduleTime)
|
|
2002
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2003
|
+
gainR.gain
|
|
2004
|
+
.cancelScheduledValues(scheduleTime)
|
|
2005
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1968
2006
|
}
|
|
1969
2007
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1970
2008
|
const channel = this.channels[channelNumber];
|
|
@@ -1973,7 +2011,7 @@ class MidyGM2 {
|
|
|
1973
2011
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1974
2012
|
channel.state.sustainPedal = value / 127;
|
|
1975
2013
|
if (64 <= value) {
|
|
1976
|
-
this.processScheduledNotes(channel,
|
|
2014
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1977
2015
|
channel.sustainNotes.push(note);
|
|
1978
2016
|
});
|
|
1979
2017
|
}
|
|
@@ -2016,7 +2054,7 @@ class MidyGM2 {
|
|
|
2016
2054
|
const state = channel.state;
|
|
2017
2055
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2018
2056
|
state.softPedal = softPedal / 127;
|
|
2019
|
-
this.processScheduledNotes(channel,
|
|
2057
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2020
2058
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2021
2059
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2022
2060
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2031,67 +2069,19 @@ class MidyGM2 {
|
|
|
2031
2069
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2032
2070
|
const channel = this.channels[channelNumber];
|
|
2033
2071
|
const state = channel.state;
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
reverbEffect.input.gain
|
|
2039
|
-
.cancelScheduledValues(scheduleTime)
|
|
2040
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2041
|
-
}
|
|
2042
|
-
else {
|
|
2043
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2044
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2045
|
-
return false;
|
|
2046
|
-
if (note.reverbEffectsSend)
|
|
2047
|
-
note.reverbEffectsSend.disconnect();
|
|
2048
|
-
});
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
else {
|
|
2052
|
-
if (0 < reverbSendLevel) {
|
|
2053
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2054
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2055
|
-
});
|
|
2056
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2057
|
-
reverbEffect.input.gain
|
|
2058
|
-
.cancelScheduledValues(scheduleTime)
|
|
2059
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2072
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2073
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2074
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2075
|
+
});
|
|
2062
2076
|
}
|
|
2063
2077
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2064
2078
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2065
2079
|
const channel = this.channels[channelNumber];
|
|
2066
2080
|
const state = channel.state;
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
chorusEffect.input.gain
|
|
2072
|
-
.cancelScheduledValues(scheduleTime)
|
|
2073
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2074
|
-
}
|
|
2075
|
-
else {
|
|
2076
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2077
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2078
|
-
return false;
|
|
2079
|
-
if (note.chorusEffectsSend)
|
|
2080
|
-
note.chorusEffectsSend.disconnect();
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
else {
|
|
2085
|
-
if (0 < chorusSendLevel) {
|
|
2086
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2087
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2088
|
-
});
|
|
2089
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2090
|
-
chorusEffect.input.gain
|
|
2091
|
-
.cancelScheduledValues(scheduleTime)
|
|
2092
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2081
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2082
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2083
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2084
|
+
});
|
|
2095
2085
|
}
|
|
2096
2086
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
2097
2087
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -2227,7 +2217,7 @@ class MidyGM2 {
|
|
|
2227
2217
|
const entries = Object.entries(defaultControllerState);
|
|
2228
2218
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2229
2219
|
if (128 <= type) {
|
|
2230
|
-
this.
|
|
2220
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2231
2221
|
}
|
|
2232
2222
|
else {
|
|
2233
2223
|
state[key] = defaultValue;
|
|
@@ -2259,7 +2249,7 @@ class MidyGM2 {
|
|
|
2259
2249
|
const key = keys[i];
|
|
2260
2250
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2261
2251
|
if (128 <= type) {
|
|
2262
|
-
this.
|
|
2252
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2263
2253
|
}
|
|
2264
2254
|
else {
|
|
2265
2255
|
state[key] = defaultValue;
|
|
@@ -2373,9 +2363,9 @@ class MidyGM2 {
|
|
|
2373
2363
|
case 9:
|
|
2374
2364
|
switch (data[3]) {
|
|
2375
2365
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2376
|
-
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2366
|
+
return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
|
|
2377
2367
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2378
|
-
return this.handleControlChangeSysEx(data);
|
|
2368
|
+
return this.handleControlChangeSysEx(data, scheduleTime);
|
|
2379
2369
|
default:
|
|
2380
2370
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2381
2371
|
}
|
|
@@ -2694,29 +2684,31 @@ class MidyGM2 {
|
|
|
2694
2684
|
: 0;
|
|
2695
2685
|
return channelPressure / 127;
|
|
2696
2686
|
}
|
|
2697
|
-
|
|
2687
|
+
setEffects(channel, note, table, scheduleTime) {
|
|
2698
2688
|
if (0 <= table[0])
|
|
2699
|
-
this.updateDetune(channel, note);
|
|
2689
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2700
2690
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2701
|
-
if (0 <= table[1])
|
|
2702
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2703
|
-
|
|
2704
|
-
|
|
2691
|
+
if (0 <= table[1]) {
|
|
2692
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2693
|
+
}
|
|
2694
|
+
if (0 <= table[2]) {
|
|
2695
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2696
|
+
}
|
|
2705
2697
|
}
|
|
2706
2698
|
else {
|
|
2707
2699
|
if (0 <= table[1])
|
|
2708
|
-
this.setFilterEnvelope(channel, note);
|
|
2700
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2709
2701
|
if (0 <= table[2])
|
|
2710
|
-
this.setVolumeEnvelope(channel, note);
|
|
2702
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2711
2703
|
}
|
|
2712
2704
|
if (0 <= table[3])
|
|
2713
|
-
this.setModLfoToPitch(channel, note);
|
|
2705
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2714
2706
|
if (0 <= table[4])
|
|
2715
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2707
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2716
2708
|
if (0 <= table[5])
|
|
2717
|
-
this.setModLfoToVolume(channel, note);
|
|
2709
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2718
2710
|
}
|
|
2719
|
-
handlePressureSysEx(data, tableName) {
|
|
2711
|
+
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2720
2712
|
const channelNumber = data[4];
|
|
2721
2713
|
const channel = this.channels[channelNumber];
|
|
2722
2714
|
if (channel.isDrum)
|
|
@@ -2727,34 +2719,40 @@ class MidyGM2 {
|
|
|
2727
2719
|
const rr = data[i + 1];
|
|
2728
2720
|
table[pp] = rr;
|
|
2729
2721
|
}
|
|
2722
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2723
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2724
|
+
});
|
|
2730
2725
|
}
|
|
2731
2726
|
initControlTable() {
|
|
2732
2727
|
const ccCount = 128;
|
|
2733
2728
|
const slotSize = 6;
|
|
2734
2729
|
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2735
2730
|
}
|
|
2736
|
-
|
|
2731
|
+
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2737
2732
|
const slotSize = 6;
|
|
2738
2733
|
const offset = controllerType * slotSize;
|
|
2739
2734
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2740
|
-
this.processScheduledNotes(channel,
|
|
2741
|
-
this.
|
|
2735
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2736
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2742
2737
|
});
|
|
2743
2738
|
}
|
|
2744
|
-
handleControlChangeSysEx(data) {
|
|
2739
|
+
handleControlChangeSysEx(data, scheduleTime) {
|
|
2745
2740
|
const channelNumber = data[4];
|
|
2746
2741
|
const channel = this.channels[channelNumber];
|
|
2747
2742
|
if (channel.isDrum)
|
|
2748
2743
|
return;
|
|
2744
|
+
const slotSize = 6;
|
|
2749
2745
|
const controllerType = data[5];
|
|
2750
|
-
const
|
|
2751
|
-
|
|
2746
|
+
const offset = controllerType * slotSize;
|
|
2747
|
+
const table = channel.controlTable;
|
|
2748
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2752
2749
|
const pp = data[i];
|
|
2753
2750
|
const rr = data[i + 1];
|
|
2754
|
-
table[pp] = rr;
|
|
2751
|
+
table[offset + pp] = rr;
|
|
2755
2752
|
}
|
|
2753
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
2756
2754
|
}
|
|
2757
|
-
|
|
2755
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2758
2756
|
const index = keyNumber * 128 + controllerType;
|
|
2759
2757
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2760
2758
|
return controlValue;
|
|
@@ -2766,13 +2764,20 @@ class MidyGM2 {
|
|
|
2766
2764
|
return;
|
|
2767
2765
|
const keyNumber = data[5];
|
|
2768
2766
|
const table = channel.keyBasedInstrumentControlTable;
|
|
2769
|
-
for (let i = 6; i < data.length
|
|
2767
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2770
2768
|
const controllerType = data[i];
|
|
2771
2769
|
const value = data[i + 1];
|
|
2772
2770
|
const index = keyNumber * 128 + controllerType;
|
|
2773
2771
|
table[index] = value;
|
|
2772
|
+
switch (controllerType) {
|
|
2773
|
+
case 7:
|
|
2774
|
+
case 10:
|
|
2775
|
+
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2776
|
+
break;
|
|
2777
|
+
default: // TODO
|
|
2778
|
+
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
2779
|
+
}
|
|
2774
2780
|
}
|
|
2775
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2776
2781
|
}
|
|
2777
2782
|
handleSysEx(data, scheduleTime) {
|
|
2778
2783
|
switch (data[0]) {
|
|
@@ -2810,6 +2815,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2810
2815
|
configurable: true,
|
|
2811
2816
|
writable: true,
|
|
2812
2817
|
value: {
|
|
2818
|
+
scheduleIndex: 0,
|
|
2813
2819
|
detune: 0,
|
|
2814
2820
|
programNumber: 0,
|
|
2815
2821
|
bank: 121 * 128,
|