@marmooo/midy 0.3.7 → 0.4.0
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 +31 -11
- package/esm/midy-GM1.d.ts +15 -30
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +161 -104
- package/esm/midy-GM2.d.ts +19 -36
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +245 -205
- package/esm/midy-GMLite.d.ts +14 -30
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +163 -107
- package/esm/midy.d.ts +19 -37
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +306 -277
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +15 -30
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +161 -104
- package/script/midy-GM2.d.ts +19 -36
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +245 -205
- package/script/midy-GMLite.d.ts +14 -30
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +163 -107
- package/script/midy.d.ts +19 -37
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +306 -277
package/script/midy.js
CHANGED
|
@@ -4,7 +4,19 @@ exports.Midy = void 0;
|
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime
|
|
7
|
+
constructor(noteNumber, velocity, startTime) {
|
|
8
|
+
Object.defineProperty(this, "voice", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "voiceParams", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
8
20
|
Object.defineProperty(this, "index", {
|
|
9
21
|
enumerable: true,
|
|
10
22
|
configurable: true,
|
|
@@ -17,6 +29,12 @@ class Note {
|
|
|
17
29
|
writable: true,
|
|
18
30
|
value: false
|
|
19
31
|
});
|
|
32
|
+
Object.defineProperty(this, "pending", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: true
|
|
37
|
+
});
|
|
20
38
|
Object.defineProperty(this, "bufferSource", {
|
|
21
39
|
enumerable: true,
|
|
22
40
|
configurable: true,
|
|
@@ -98,8 +116,6 @@ class Note {
|
|
|
98
116
|
this.noteNumber = noteNumber;
|
|
99
117
|
this.velocity = velocity;
|
|
100
118
|
this.startTime = startTime;
|
|
101
|
-
this.voice = voice;
|
|
102
|
-
this.voiceParams = voiceParams;
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
121
|
const drumExclusiveClassesByKit = new Array(57);
|
|
@@ -290,6 +306,18 @@ class Midy {
|
|
|
290
306
|
writable: true,
|
|
291
307
|
value: 0
|
|
292
308
|
});
|
|
309
|
+
Object.defineProperty(this, "lastActiveSensing", {
|
|
310
|
+
enumerable: true,
|
|
311
|
+
configurable: true,
|
|
312
|
+
writable: true,
|
|
313
|
+
value: 0
|
|
314
|
+
});
|
|
315
|
+
Object.defineProperty(this, "activeSensingThreshold", {
|
|
316
|
+
enumerable: true,
|
|
317
|
+
configurable: true,
|
|
318
|
+
writable: true,
|
|
319
|
+
value: 0.3
|
|
320
|
+
});
|
|
293
321
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
294
322
|
enumerable: true,
|
|
295
323
|
configurable: true,
|
|
@@ -330,7 +358,7 @@ class Midy {
|
|
|
330
358
|
enumerable: true,
|
|
331
359
|
configurable: true,
|
|
332
360
|
writable: true,
|
|
333
|
-
value:
|
|
361
|
+
value: Array.from({ length: 128 }, () => [])
|
|
334
362
|
});
|
|
335
363
|
Object.defineProperty(this, "voiceCounter", {
|
|
336
364
|
enumerable: true,
|
|
@@ -344,6 +372,12 @@ class Midy {
|
|
|
344
372
|
writable: true,
|
|
345
373
|
value: new Map()
|
|
346
374
|
});
|
|
375
|
+
Object.defineProperty(this, "realtimeVoiceCache", {
|
|
376
|
+
enumerable: true,
|
|
377
|
+
configurable: true,
|
|
378
|
+
writable: true,
|
|
379
|
+
value: new Map()
|
|
380
|
+
});
|
|
347
381
|
Object.defineProperty(this, "isPlaying", {
|
|
348
382
|
enumerable: true,
|
|
349
383
|
configurable: true,
|
|
@@ -417,8 +451,10 @@ class Midy {
|
|
|
417
451
|
length: 1,
|
|
418
452
|
sampleRate: audioContext.sampleRate,
|
|
419
453
|
});
|
|
454
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
420
455
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
421
456
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
457
|
+
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
422
458
|
this.channels = this.createChannels(audioContext);
|
|
423
459
|
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
424
460
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -428,21 +464,14 @@ class Midy {
|
|
|
428
464
|
this.scheduler.connect(audioContext.destination);
|
|
429
465
|
this.GM2SystemOn();
|
|
430
466
|
}
|
|
431
|
-
initSoundFontTable() {
|
|
432
|
-
const table = new Array(128);
|
|
433
|
-
for (let i = 0; i < 128; i++) {
|
|
434
|
-
table[i] = new Map();
|
|
435
|
-
}
|
|
436
|
-
return table;
|
|
437
|
-
}
|
|
438
467
|
addSoundFont(soundFont) {
|
|
439
468
|
const index = this.soundFonts.length;
|
|
440
469
|
this.soundFonts.push(soundFont);
|
|
441
470
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
471
|
+
const soundFontTable = this.soundFontTable;
|
|
442
472
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
banks.set(presetHeader.bank, index);
|
|
473
|
+
const { preset, bank } = presetHeaders[i];
|
|
474
|
+
soundFontTable[preset][bank] = index;
|
|
446
475
|
}
|
|
447
476
|
}
|
|
448
477
|
async toUint8Array(input) {
|
|
@@ -520,13 +549,17 @@ class Midy {
|
|
|
520
549
|
this.GM2SystemOn();
|
|
521
550
|
}
|
|
522
551
|
getVoiceId(channel, noteNumber, velocity) {
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
|
|
552
|
+
const programNumber = channel.programNumber;
|
|
553
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
554
|
+
if (!bankTable)
|
|
555
|
+
return;
|
|
556
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
557
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
558
|
+
const soundFontIndex = bankTable[bank];
|
|
526
559
|
if (soundFontIndex === undefined)
|
|
527
560
|
return;
|
|
528
561
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
529
|
-
const voice = soundFont.getVoice(
|
|
562
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
530
563
|
const { instrument, sampleID } = voice.generators;
|
|
531
564
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
532
565
|
}
|
|
@@ -597,19 +630,22 @@ class Midy {
|
|
|
597
630
|
}
|
|
598
631
|
return bufferSource;
|
|
599
632
|
}
|
|
600
|
-
async scheduleTimelineEvents(
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
633
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
634
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
635
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
636
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
637
|
+
const timeline = this.timeline;
|
|
638
|
+
while (queueIndex < timeline.length) {
|
|
639
|
+
const event = timeline[queueIndex];
|
|
640
|
+
if (lookAheadCheckTime < event.startTime)
|
|
604
641
|
break;
|
|
605
|
-
const
|
|
606
|
-
const startTime = event.startTime + delay;
|
|
642
|
+
const startTime = event.startTime + schedulingOffset;
|
|
607
643
|
switch (event.type) {
|
|
608
644
|
case "noteOn":
|
|
609
|
-
await this.
|
|
645
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
610
646
|
break;
|
|
611
647
|
case "noteOff": {
|
|
612
|
-
const notePromise = this.
|
|
648
|
+
const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
613
649
|
if (notePromise)
|
|
614
650
|
this.notePromises.push(notePromise);
|
|
615
651
|
break;
|
|
@@ -648,6 +684,7 @@ class Midy {
|
|
|
648
684
|
this.exclusiveClassNotes.fill(undefined);
|
|
649
685
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
650
686
|
this.voiceCache.clear();
|
|
687
|
+
this.realtimeVoiceCache.clear();
|
|
651
688
|
for (let i = 0; i < this.channels.length; i++) {
|
|
652
689
|
this.channels[i].scheduledNotes = [];
|
|
653
690
|
this.resetChannelStates(i);
|
|
@@ -681,13 +718,17 @@ class Midy {
|
|
|
681
718
|
this.isPaused = false;
|
|
682
719
|
this.startTime = this.audioContext.currentTime;
|
|
683
720
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
684
|
-
let resumeTime = this.resumeTime - this.startTime;
|
|
685
721
|
let finished = false;
|
|
686
722
|
this.notePromises = [];
|
|
687
723
|
while (queueIndex < this.timeline.length) {
|
|
688
724
|
const now = this.audioContext.currentTime;
|
|
689
|
-
|
|
690
|
-
|
|
725
|
+
if (0 < this.lastActiveSensing &&
|
|
726
|
+
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
727
|
+
await this.stopNotes(0, true, now);
|
|
728
|
+
await this.audioContext.suspend();
|
|
729
|
+
finished = true;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
691
732
|
if (this.isPausing) {
|
|
692
733
|
await this.stopNotes(0, true, now);
|
|
693
734
|
await this.audioContext.suspend();
|
|
@@ -706,16 +747,17 @@ class Midy {
|
|
|
706
747
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
707
748
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
708
749
|
queueIndex = nextQueueIndex;
|
|
709
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
710
750
|
this.isSeeking = false;
|
|
711
751
|
continue;
|
|
712
752
|
}
|
|
753
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
713
754
|
const waitTime = now + this.noteCheckInterval;
|
|
714
755
|
await this.scheduleTask(() => { }, waitTime);
|
|
715
756
|
}
|
|
716
757
|
if (finished) {
|
|
717
758
|
this.notePromises = [];
|
|
718
759
|
this.resetAllStates();
|
|
760
|
+
this.lastActiveSensing = 0;
|
|
719
761
|
}
|
|
720
762
|
this.isPlaying = false;
|
|
721
763
|
}
|
|
@@ -725,17 +767,17 @@ class Midy {
|
|
|
725
767
|
secondToTicks(second, secondsPerBeat) {
|
|
726
768
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
727
769
|
}
|
|
770
|
+
getSoundFontId(channel) {
|
|
771
|
+
const programNumber = channel.programNumber;
|
|
772
|
+
const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
|
|
773
|
+
const bank = bankNumber.toString().padStart(3, "0");
|
|
774
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
775
|
+
return `${bank}:${program}`;
|
|
776
|
+
}
|
|
728
777
|
extractMidiData(midi) {
|
|
729
778
|
const instruments = new Set();
|
|
730
779
|
const timeline = [];
|
|
731
|
-
const
|
|
732
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
733
|
-
tmpChannels[i] = {
|
|
734
|
-
programNumber: -1,
|
|
735
|
-
bankMSB: this.channels[i].bankMSB,
|
|
736
|
-
bankLSB: this.channels[i].bankLSB,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
780
|
+
const channels = this.channels;
|
|
739
781
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
740
782
|
const track = midi.tracks[i];
|
|
741
783
|
let currentTicks = 0;
|
|
@@ -745,48 +787,40 @@ class Midy {
|
|
|
745
787
|
event.ticks = currentTicks;
|
|
746
788
|
switch (event.type) {
|
|
747
789
|
case "noteOn": {
|
|
748
|
-
const channel =
|
|
749
|
-
|
|
750
|
-
channel.programNumber = event.programNumber;
|
|
751
|
-
switch (channel.bankMSB) {
|
|
752
|
-
case 120:
|
|
753
|
-
instruments.add(`128:0`);
|
|
754
|
-
break;
|
|
755
|
-
case 121:
|
|
756
|
-
instruments.add(`${channel.bankLSB}:0`);
|
|
757
|
-
break;
|
|
758
|
-
default: {
|
|
759
|
-
const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
|
|
760
|
-
instruments.add(`${bankNumber}:0`);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
channel.programNumber = 0;
|
|
764
|
-
}
|
|
790
|
+
const channel = channels[event.channel];
|
|
791
|
+
instruments.add(this.getSoundFontId(channel));
|
|
765
792
|
break;
|
|
766
793
|
}
|
|
767
794
|
case "controller":
|
|
768
795
|
switch (event.controllerType) {
|
|
769
796
|
case 0:
|
|
770
|
-
|
|
797
|
+
this.setBankMSB(event.channel, event.value);
|
|
771
798
|
break;
|
|
772
799
|
case 32:
|
|
773
|
-
|
|
800
|
+
this.setBankLSB(event.channel, event.value);
|
|
774
801
|
break;
|
|
775
802
|
}
|
|
776
803
|
break;
|
|
777
804
|
case "programChange": {
|
|
778
|
-
const channel =
|
|
779
|
-
channel
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
805
|
+
const channel = channels[event.channel];
|
|
806
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
807
|
+
instruments.add(this.getSoundFontId(channel));
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case "sysEx": {
|
|
811
|
+
const data = event.data;
|
|
812
|
+
if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
|
|
813
|
+
switch (data[3]) {
|
|
814
|
+
case 1:
|
|
815
|
+
this.GM1SystemOn(scheduleTime);
|
|
816
|
+
break;
|
|
817
|
+
case 2: // GM System Off
|
|
818
|
+
break;
|
|
819
|
+
case 3:
|
|
820
|
+
this.GM2SystemOn(scheduleTime);
|
|
821
|
+
break;
|
|
822
|
+
default:
|
|
823
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
790
824
|
}
|
|
791
825
|
}
|
|
792
826
|
}
|
|
@@ -825,7 +859,7 @@ class Midy {
|
|
|
825
859
|
const channel = this.channels[channelNumber];
|
|
826
860
|
const promises = [];
|
|
827
861
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
828
|
-
const promise = this.
|
|
862
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
829
863
|
this.notePromises.push(promise);
|
|
830
864
|
promises.push(promise);
|
|
831
865
|
});
|
|
@@ -835,7 +869,7 @@ class Midy {
|
|
|
835
869
|
const channel = this.channels[channelNumber];
|
|
836
870
|
const promises = [];
|
|
837
871
|
this.processScheduledNotes(channel, (note) => {
|
|
838
|
-
const promise = this.
|
|
872
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
839
873
|
this.notePromises.push(promise);
|
|
840
874
|
promises.push(promise);
|
|
841
875
|
});
|
|
@@ -868,7 +902,7 @@ class Midy {
|
|
|
868
902
|
if (!this.isPlaying || this.isPaused)
|
|
869
903
|
return;
|
|
870
904
|
const now = this.audioContext.currentTime;
|
|
871
|
-
this.resumeTime
|
|
905
|
+
this.resumeTime = now - this.startTime - this.startDelay;
|
|
872
906
|
this.isPausing = true;
|
|
873
907
|
await this.playPromise;
|
|
874
908
|
this.isPausing = false;
|
|
@@ -894,11 +928,13 @@ class Midy {
|
|
|
894
928
|
if (totalTime < event.startTime)
|
|
895
929
|
totalTime = event.startTime;
|
|
896
930
|
}
|
|
897
|
-
return totalTime;
|
|
931
|
+
return totalTime + this.startDelay;
|
|
898
932
|
}
|
|
899
933
|
currentTime() {
|
|
934
|
+
if (!this.isPlaying)
|
|
935
|
+
return this.resumeTime;
|
|
900
936
|
const now = this.audioContext.currentTime;
|
|
901
|
-
return
|
|
937
|
+
return now + this.resumeTime - this.startTime;
|
|
902
938
|
}
|
|
903
939
|
processScheduledNotes(channel, callback) {
|
|
904
940
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -1110,7 +1146,7 @@ class Midy {
|
|
|
1110
1146
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1111
1147
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1112
1148
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1113
|
-
if (
|
|
1149
|
+
if (this.isPortamento(channel, note)) {
|
|
1114
1150
|
const startTime = note.startTime;
|
|
1115
1151
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1116
1152
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
@@ -1330,31 +1366,42 @@ class Midy {
|
|
|
1330
1366
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1331
1367
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1332
1368
|
}
|
|
1333
|
-
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1369
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
1334
1370
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
this.voiceCache.delete(audioBufferId);
|
|
1340
|
-
}
|
|
1341
|
-
return cache.audioBuffer;
|
|
1342
|
-
}
|
|
1343
|
-
else {
|
|
1344
|
-
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1371
|
+
if (realtime) {
|
|
1372
|
+
const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
|
|
1373
|
+
if (cachedAudioBuffer)
|
|
1374
|
+
return cachedAudioBuffer;
|
|
1345
1375
|
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1346
|
-
|
|
1347
|
-
this.voiceCache.set(audioBufferId, cache);
|
|
1376
|
+
this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
|
|
1348
1377
|
return audioBuffer;
|
|
1349
1378
|
}
|
|
1379
|
+
else {
|
|
1380
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1381
|
+
if (cache) {
|
|
1382
|
+
cache.counter += 1;
|
|
1383
|
+
if (cache.maxCount <= cache.counter) {
|
|
1384
|
+
this.voiceCache.delete(audioBufferId);
|
|
1385
|
+
}
|
|
1386
|
+
return cache.audioBuffer;
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1390
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1391
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1392
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1393
|
+
return audioBuffer;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1350
1396
|
}
|
|
1351
|
-
async
|
|
1397
|
+
async setNoteAudioNode(channel, note, realtime) {
|
|
1352
1398
|
const now = this.audioContext.currentTime;
|
|
1399
|
+
const { noteNumber, velocity, startTime } = note;
|
|
1353
1400
|
const state = channel.state;
|
|
1354
1401
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1355
|
-
const voiceParams = voice.getAllParams(controllerState);
|
|
1356
|
-
|
|
1357
|
-
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1402
|
+
const voiceParams = note.voice.getAllParams(controllerState);
|
|
1403
|
+
note.voiceParams = voiceParams;
|
|
1404
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1358
1405
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1359
1406
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1360
1407
|
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
@@ -1366,7 +1413,7 @@ class Midy {
|
|
|
1366
1413
|
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1367
1414
|
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1368
1415
|
}
|
|
1369
|
-
if (
|
|
1416
|
+
if (!channel.isDrum && this.isPortamento(channel, note)) {
|
|
1370
1417
|
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1371
1418
|
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1372
1419
|
this.setPortamentoPitchEnvelope(note, now);
|
|
@@ -1394,22 +1441,6 @@ class Midy {
|
|
|
1394
1441
|
note.bufferSource.start(startTime);
|
|
1395
1442
|
return note;
|
|
1396
1443
|
}
|
|
1397
|
-
calcBank(channel) {
|
|
1398
|
-
switch (this.mode) {
|
|
1399
|
-
case "GM1":
|
|
1400
|
-
if (channel.isDrum)
|
|
1401
|
-
return 128;
|
|
1402
|
-
return 0;
|
|
1403
|
-
case "GM2":
|
|
1404
|
-
if (channel.bankMSB === 121)
|
|
1405
|
-
return 0;
|
|
1406
|
-
if (channel.isDrum)
|
|
1407
|
-
return 128;
|
|
1408
|
-
return channel.bank;
|
|
1409
|
-
default:
|
|
1410
|
-
return channel.bank;
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
1444
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1414
1445
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1415
1446
|
if (exclusiveClass === 0)
|
|
@@ -1418,7 +1449,7 @@ class Midy {
|
|
|
1418
1449
|
if (prev) {
|
|
1419
1450
|
const [prevNote, prevChannelNumber] = prev;
|
|
1420
1451
|
if (prevNote && !prevNote.ending) {
|
|
1421
|
-
this.
|
|
1452
|
+
this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1422
1453
|
startTime, true);
|
|
1423
1454
|
}
|
|
1424
1455
|
}
|
|
@@ -1438,23 +1469,14 @@ class Midy {
|
|
|
1438
1469
|
channelNumber;
|
|
1439
1470
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1440
1471
|
if (prevNote && !prevNote.ending) {
|
|
1441
|
-
this.
|
|
1472
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1442
1473
|
startTime, true);
|
|
1443
1474
|
}
|
|
1444
1475
|
this.drumExclusiveClassNotes[index] = note;
|
|
1445
1476
|
}
|
|
1446
|
-
|
|
1477
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
1447
1478
|
const channel = this.channels[channelNumber];
|
|
1448
|
-
const
|
|
1449
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1450
|
-
.get(bankNumber);
|
|
1451
|
-
if (soundFontIndex === undefined)
|
|
1452
|
-
return;
|
|
1453
|
-
const soundFont = this.soundFonts[soundFontIndex];
|
|
1454
|
-
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1455
|
-
if (!voice)
|
|
1456
|
-
return;
|
|
1457
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1479
|
+
const { noteNumber, volumeEnvelopeNode } = note;
|
|
1458
1480
|
if (channel.isDrum) {
|
|
1459
1481
|
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1460
1482
|
let gainL = keyBasedGainLs[noteNumber];
|
|
@@ -1464,25 +1486,48 @@ class Midy {
|
|
|
1464
1486
|
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1465
1487
|
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1466
1488
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1489
|
+
volumeEnvelopeNode.connect(gainL);
|
|
1490
|
+
volumeEnvelopeNode.connect(gainR);
|
|
1469
1491
|
}
|
|
1470
1492
|
else {
|
|
1471
|
-
|
|
1472
|
-
|
|
1493
|
+
volumeEnvelopeNode.connect(channel.gainL);
|
|
1494
|
+
volumeEnvelopeNode.connect(channel.gainR);
|
|
1473
1495
|
}
|
|
1474
1496
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1475
1497
|
channel.sustainNotes.push(note);
|
|
1476
1498
|
}
|
|
1477
1499
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1478
1500
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1501
|
+
}
|
|
1502
|
+
async noteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1503
|
+
const channel = this.channels[channelNumber];
|
|
1504
|
+
const realtime = startTime === undefined;
|
|
1505
|
+
if (realtime)
|
|
1506
|
+
startTime = this.audioContext.currentTime;
|
|
1507
|
+
const note = new Note(noteNumber, velocity, startTime);
|
|
1479
1508
|
const scheduledNotes = channel.scheduledNotes;
|
|
1480
1509
|
note.index = scheduledNotes.length;
|
|
1481
1510
|
scheduledNotes.push(note);
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1511
|
+
const programNumber = channel.programNumber;
|
|
1512
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
1513
|
+
if (!bankTable)
|
|
1514
|
+
return;
|
|
1515
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1516
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1517
|
+
const soundFontIndex = bankTable[bank];
|
|
1518
|
+
if (soundFontIndex === undefined)
|
|
1519
|
+
return;
|
|
1520
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
1521
|
+
note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1522
|
+
if (!note.voice)
|
|
1523
|
+
return;
|
|
1524
|
+
await this.setNoteAudioNode(channel, note, realtime);
|
|
1525
|
+
this.setNoteRouting(channelNumber, note, startTime);
|
|
1526
|
+
note.pending = false;
|
|
1527
|
+
const off = note.offEvent;
|
|
1528
|
+
if (off) {
|
|
1529
|
+
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
1530
|
+
}
|
|
1486
1531
|
}
|
|
1487
1532
|
disconnectNote(note) {
|
|
1488
1533
|
note.bufferSource.disconnect();
|
|
@@ -1505,6 +1550,7 @@ class Midy {
|
|
|
1505
1550
|
}
|
|
1506
1551
|
}
|
|
1507
1552
|
releaseNote(channel, note, endTime) {
|
|
1553
|
+
endTime ??= this.audioContext.currentTime;
|
|
1508
1554
|
const releaseTime = this.getRelativeKeyBasedValue(channel, note, 72) * 2;
|
|
1509
1555
|
const volRelease = endTime + note.voiceParams.volRelease * releaseTime;
|
|
1510
1556
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
@@ -1526,7 +1572,7 @@ class Midy {
|
|
|
1526
1572
|
}, stopTime);
|
|
1527
1573
|
});
|
|
1528
1574
|
}
|
|
1529
|
-
|
|
1575
|
+
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
1530
1576
|
const channel = this.channels[channelNumber];
|
|
1531
1577
|
const state = channel.state;
|
|
1532
1578
|
if (!force) {
|
|
@@ -1545,6 +1591,10 @@ class Midy {
|
|
|
1545
1591
|
if (index < 0)
|
|
1546
1592
|
return;
|
|
1547
1593
|
const note = channel.scheduledNotes[index];
|
|
1594
|
+
if (note.pending) {
|
|
1595
|
+
note.offEvent = { velocity, startTime: endTime };
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1548
1598
|
note.ending = true;
|
|
1549
1599
|
this.setNoteIndex(channel, index);
|
|
1550
1600
|
this.releaseNote(channel, note, endTime);
|
|
@@ -1575,16 +1625,12 @@ class Midy {
|
|
|
1575
1625
|
}
|
|
1576
1626
|
return -1;
|
|
1577
1627
|
}
|
|
1578
|
-
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1579
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1580
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1581
|
-
}
|
|
1582
1628
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1583
1629
|
const velocity = halfVelocity * 2;
|
|
1584
1630
|
const channel = this.channels[channelNumber];
|
|
1585
1631
|
const promises = [];
|
|
1586
1632
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1587
|
-
const promise = this.
|
|
1633
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1588
1634
|
promises.push(promise);
|
|
1589
1635
|
}
|
|
1590
1636
|
channel.sustainNotes = [];
|
|
@@ -1598,33 +1644,48 @@ class Midy {
|
|
|
1598
1644
|
channel.state.sostenutoPedal = 0;
|
|
1599
1645
|
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1600
1646
|
const note = sostenutoNotes[i];
|
|
1601
|
-
const promise = this.
|
|
1647
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1602
1648
|
promises.push(promise);
|
|
1603
1649
|
}
|
|
1604
1650
|
channel.sostenutoNotes = [];
|
|
1605
1651
|
return promises;
|
|
1606
1652
|
}
|
|
1607
|
-
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1653
|
+
createMessageHandlers() {
|
|
1654
|
+
const handlers = new Array(256);
|
|
1655
|
+
// Channel Message
|
|
1656
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1657
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1658
|
+
handlers[0xA0] = (data, scheduleTime) => this.setPolyphonicKeyPressure(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1659
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1660
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1661
|
+
handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
|
|
1662
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1663
|
+
// System Common Message
|
|
1664
|
+
// handlers[0xF1] = (_data, _scheduleTime) => {}; // MTC Quarter Frame
|
|
1665
|
+
// handlers[0xF2] = (_data, _scheduleTime) => {}; // Song Position Pointer
|
|
1666
|
+
// handlers[0xF3] = (_data, _scheduleTime) => {}; // Song Select
|
|
1667
|
+
// handlers[0xF6] = (_data, _scheduleTime) => {}; // Tune Request
|
|
1668
|
+
// handlers[0xF7] = (_data, _scheduleTime) => {}; // End of Exclusive (EOX)
|
|
1669
|
+
// System Real Time Message
|
|
1670
|
+
// handlers[0xF8] = (_data, _scheduleTime) => {}; // Timing Clock
|
|
1671
|
+
// handlers[0xFA] = (_data, _scheduleTime) => {}; // Start
|
|
1672
|
+
// handlers[0xFB] = (_data, _scheduleTime) => {}; // Continue
|
|
1673
|
+
// handlers[0xFC] = (_data, _scheduleTime) => {}; // Stop
|
|
1674
|
+
handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
|
|
1675
|
+
// handlers[0xFF] = (_data, _scheduleTime) => {}; // Reset
|
|
1676
|
+
return handlers;
|
|
1677
|
+
}
|
|
1678
|
+
handleMessage(data, scheduleTime) {
|
|
1679
|
+
const status = data[0];
|
|
1680
|
+
if (status === 0xF0) {
|
|
1681
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1627
1682
|
}
|
|
1683
|
+
const handler = this.messageHandlers[status];
|
|
1684
|
+
if (handler)
|
|
1685
|
+
handler(data, scheduleTime);
|
|
1686
|
+
}
|
|
1687
|
+
activeSensing() {
|
|
1688
|
+
this.lastActiveSensing = performance.now();
|
|
1628
1689
|
}
|
|
1629
1690
|
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1630
1691
|
const channel = this.channels[channelNumber];
|
|
@@ -1639,19 +1700,18 @@ class Midy {
|
|
|
1639
1700
|
}
|
|
1640
1701
|
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1641
1702
|
const channel = this.channels[channelNumber];
|
|
1642
|
-
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1643
1703
|
channel.programNumber = programNumber;
|
|
1644
1704
|
if (this.mode === "GM2") {
|
|
1645
1705
|
switch (channel.bankMSB) {
|
|
1646
1706
|
case 120:
|
|
1647
1707
|
channel.isDrum = true;
|
|
1708
|
+
channel.keyBasedTable.fill(-1);
|
|
1648
1709
|
break;
|
|
1649
1710
|
case 121:
|
|
1650
1711
|
channel.isDrum = false;
|
|
1651
1712
|
break;
|
|
1652
1713
|
}
|
|
1653
1714
|
}
|
|
1654
|
-
channel.keyBasedTable.fill(-1);
|
|
1655
1715
|
}
|
|
1656
1716
|
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1657
1717
|
const channel = this.channels[channelNumber];
|
|
@@ -1988,22 +2048,20 @@ class Midy {
|
|
|
1988
2048
|
this.updateModulation(channel, scheduleTime);
|
|
1989
2049
|
}
|
|
1990
2050
|
updatePortamento(channel, scheduleTime) {
|
|
2051
|
+
if (channel.isDrum)
|
|
2052
|
+
return;
|
|
1991
2053
|
this.processScheduledNotes(channel, (note) => {
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1998
|
-
}
|
|
2054
|
+
if (this.isPortamento(channel, note)) {
|
|
2055
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2056
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2057
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
2058
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1999
2059
|
}
|
|
2000
2060
|
else {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
2006
|
-
}
|
|
2061
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2062
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2063
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
2064
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2007
2065
|
}
|
|
2008
2066
|
});
|
|
2009
2067
|
}
|
|
@@ -2109,6 +2167,9 @@ class Midy {
|
|
|
2109
2167
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
2110
2168
|
}
|
|
2111
2169
|
}
|
|
2170
|
+
isPortamento(channel, note) {
|
|
2171
|
+
return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
|
|
2172
|
+
}
|
|
2112
2173
|
setPortamento(channelNumber, value, scheduleTime) {
|
|
2113
2174
|
const channel = this.channels[channelNumber];
|
|
2114
2175
|
if (channel.isDrum)
|
|
@@ -2145,7 +2206,7 @@ class Midy {
|
|
|
2145
2206
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2146
2207
|
state.softPedal = softPedal / 127;
|
|
2147
2208
|
this.processScheduledNotes(channel, (note) => {
|
|
2148
|
-
if (
|
|
2209
|
+
if (this.isPortamento(channel, note)) {
|
|
2149
2210
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2150
2211
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2151
2212
|
}
|
|
@@ -2203,7 +2264,7 @@ class Midy {
|
|
|
2203
2264
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2204
2265
|
state.brightness = brightness / 127;
|
|
2205
2266
|
this.processScheduledNotes(channel, (note) => {
|
|
2206
|
-
if (
|
|
2267
|
+
if (this.isPortamento(channel, note)) {
|
|
2207
2268
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2208
2269
|
}
|
|
2209
2270
|
else {
|
|
@@ -2539,11 +2600,9 @@ class Midy {
|
|
|
2539
2600
|
const channel = this.channels[i];
|
|
2540
2601
|
channel.bankMSB = 0;
|
|
2541
2602
|
channel.bankLSB = 0;
|
|
2542
|
-
channel.bank = 0;
|
|
2543
2603
|
channel.isDrum = false;
|
|
2544
2604
|
}
|
|
2545
2605
|
this.channels[9].bankMSB = 1;
|
|
2546
|
-
this.channels[9].bank = 128;
|
|
2547
2606
|
this.channels[9].isDrum = true;
|
|
2548
2607
|
}
|
|
2549
2608
|
GM2SystemOn(scheduleTime) {
|
|
@@ -2554,11 +2613,9 @@ class Midy {
|
|
|
2554
2613
|
const channel = this.channels[i];
|
|
2555
2614
|
channel.bankMSB = 121;
|
|
2556
2615
|
channel.bankLSB = 0;
|
|
2557
|
-
channel.bank = 121 * 128;
|
|
2558
2616
|
channel.isDrum = false;
|
|
2559
2617
|
}
|
|
2560
2618
|
this.channels[9].bankMSB = 120;
|
|
2561
|
-
this.channels[9].bank = 120 * 128;
|
|
2562
2619
|
this.channels[9].isDrum = true;
|
|
2563
2620
|
}
|
|
2564
2621
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -2616,16 +2673,11 @@ class Midy {
|
|
|
2616
2673
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2617
2674
|
this.setMasterVolume(volume, scheduleTime);
|
|
2618
2675
|
}
|
|
2619
|
-
setMasterVolume(
|
|
2676
|
+
setMasterVolume(value, scheduleTime) {
|
|
2620
2677
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
else {
|
|
2625
|
-
this.masterVolume.gain
|
|
2626
|
-
.cancelScheduledValues(scheduleTime)
|
|
2627
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
2628
|
-
}
|
|
2678
|
+
this.masterVolume.gain
|
|
2679
|
+
.cancelScheduledValues(scheduleTime)
|
|
2680
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
2629
2681
|
}
|
|
2630
2682
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2631
2683
|
const value = (data[5] * 128 + data[4]) / 16383;
|
|
@@ -3040,6 +3092,70 @@ class Midy {
|
|
|
3040
3092
|
const controlValue = channel.keyBasedTable[index];
|
|
3041
3093
|
return controlValue;
|
|
3042
3094
|
}
|
|
3095
|
+
createKeyBasedControllerHandlers() {
|
|
3096
|
+
const handlers = new Array(128);
|
|
3097
|
+
handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3098
|
+
handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3099
|
+
handlers[71] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3100
|
+
if (note.noteNumber === keyNumber) {
|
|
3101
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
3102
|
+
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
3103
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
handlers[73] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3107
|
+
if (note.noteNumber === keyNumber) {
|
|
3108
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3109
|
+
}
|
|
3110
|
+
});
|
|
3111
|
+
handlers[74] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3112
|
+
if (note.noteNumber === keyNumber) {
|
|
3113
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
handlers[75] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3117
|
+
if (note.noteNumber === keyNumber) {
|
|
3118
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
3121
|
+
handlers[76] = (channel, keyNumber, scheduleTime) => {
|
|
3122
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3123
|
+
return;
|
|
3124
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3125
|
+
if (note.noteNumber === keyNumber) {
|
|
3126
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
3127
|
+
}
|
|
3128
|
+
});
|
|
3129
|
+
};
|
|
3130
|
+
handlers[77] = (channel, keyNumber, scheduleTime) => {
|
|
3131
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3132
|
+
return;
|
|
3133
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3134
|
+
if (note.noteNumber === keyNumber) {
|
|
3135
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
3136
|
+
}
|
|
3137
|
+
});
|
|
3138
|
+
};
|
|
3139
|
+
handlers[78] = (channel, keyNumber) => {
|
|
3140
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3141
|
+
return;
|
|
3142
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3143
|
+
if (note.noteNumber === keyNumber)
|
|
3144
|
+
this.setDelayVibLFO(channel, note);
|
|
3145
|
+
});
|
|
3146
|
+
};
|
|
3147
|
+
handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3148
|
+
if (note.noteNumber === keyNumber) {
|
|
3149
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3153
|
+
if (note.noteNumber === keyNumber) {
|
|
3154
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
3155
|
+
}
|
|
3156
|
+
});
|
|
3157
|
+
return handlers;
|
|
3158
|
+
}
|
|
3043
3159
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
3044
3160
|
const channelNumber = data[4];
|
|
3045
3161
|
const channel = this.channels[channelNumber];
|
|
@@ -3052,95 +3168,9 @@ class Midy {
|
|
|
3052
3168
|
const value = data[i + 1];
|
|
3053
3169
|
const index = keyNumber * 128 + controllerType;
|
|
3054
3170
|
table[index] = value;
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3059
|
-
break;
|
|
3060
|
-
case 71:
|
|
3061
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3062
|
-
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
3063
|
-
if (note.noteNumber === keyNumber) {
|
|
3064
|
-
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
3065
|
-
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
3066
|
-
}
|
|
3067
|
-
});
|
|
3068
|
-
break;
|
|
3069
|
-
case 73:
|
|
3070
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3071
|
-
if (note.noteNumber === keyNumber) {
|
|
3072
|
-
if (0.5 <= channel.state.portamento &&
|
|
3073
|
-
0 <= note.portamentoNoteNumber) {
|
|
3074
|
-
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
3075
|
-
}
|
|
3076
|
-
else {
|
|
3077
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
});
|
|
3081
|
-
break;
|
|
3082
|
-
case 74:
|
|
3083
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3084
|
-
if (note.noteNumber === keyNumber) {
|
|
3085
|
-
if (0.5 <= channel.state.portamento &&
|
|
3086
|
-
0 <= note.portamentoNoteNumber) {
|
|
3087
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
3088
|
-
}
|
|
3089
|
-
else {
|
|
3090
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
});
|
|
3094
|
-
break;
|
|
3095
|
-
case 75:
|
|
3096
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3097
|
-
if (note.noteNumber === keyNumber) {
|
|
3098
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3099
|
-
}
|
|
3100
|
-
});
|
|
3101
|
-
break;
|
|
3102
|
-
case 76:
|
|
3103
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3104
|
-
break;
|
|
3105
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3106
|
-
if (note.noteNumber === keyNumber) {
|
|
3107
|
-
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
3108
|
-
}
|
|
3109
|
-
});
|
|
3110
|
-
break;
|
|
3111
|
-
case 77:
|
|
3112
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3113
|
-
break;
|
|
3114
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3115
|
-
if (note.noteNumber === keyNumber) {
|
|
3116
|
-
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
3117
|
-
}
|
|
3118
|
-
});
|
|
3119
|
-
break;
|
|
3120
|
-
case 78:
|
|
3121
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3122
|
-
break;
|
|
3123
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3124
|
-
if (note.noteNumber === keyNumber) {
|
|
3125
|
-
this.setDelayVibLFO(channel, note);
|
|
3126
|
-
}
|
|
3127
|
-
});
|
|
3128
|
-
break;
|
|
3129
|
-
case 91:
|
|
3130
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3131
|
-
if (note.noteNumber === keyNumber) {
|
|
3132
|
-
this.setReverbSend(channel, note, scheduleTime);
|
|
3133
|
-
}
|
|
3134
|
-
});
|
|
3135
|
-
break;
|
|
3136
|
-
case 93:
|
|
3137
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3138
|
-
if (note.noteNumber === keyNumber) {
|
|
3139
|
-
this.setChorusSend(channel, note, scheduleTime);
|
|
3140
|
-
}
|
|
3141
|
-
});
|
|
3142
|
-
break;
|
|
3143
|
-
}
|
|
3171
|
+
const handler = this.keyBasedControllerHandlers[controllerType];
|
|
3172
|
+
if (handler)
|
|
3173
|
+
handler(channel, keyNumber, scheduleTime);
|
|
3144
3174
|
}
|
|
3145
3175
|
}
|
|
3146
3176
|
handleSysEx(data, scheduleTime) {
|
|
@@ -3182,7 +3212,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3182
3212
|
scheduleIndex: 0,
|
|
3183
3213
|
detune: 0,
|
|
3184
3214
|
programNumber: 0,
|
|
3185
|
-
bank: 121 * 128,
|
|
3186
3215
|
bankMSB: 121,
|
|
3187
3216
|
bankLSB: 0,
|
|
3188
3217
|
dataMSB: 0,
|