@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/esm/midy.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
3
|
class Note {
|
|
4
|
-
constructor(noteNumber, velocity, startTime
|
|
4
|
+
constructor(noteNumber, velocity, startTime) {
|
|
5
|
+
Object.defineProperty(this, "voice", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "voiceParams", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
5
17
|
Object.defineProperty(this, "index", {
|
|
6
18
|
enumerable: true,
|
|
7
19
|
configurable: true,
|
|
@@ -14,6 +26,12 @@ class Note {
|
|
|
14
26
|
writable: true,
|
|
15
27
|
value: false
|
|
16
28
|
});
|
|
29
|
+
Object.defineProperty(this, "pending", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: true
|
|
34
|
+
});
|
|
17
35
|
Object.defineProperty(this, "bufferSource", {
|
|
18
36
|
enumerable: true,
|
|
19
37
|
configurable: true,
|
|
@@ -95,8 +113,6 @@ class Note {
|
|
|
95
113
|
this.noteNumber = noteNumber;
|
|
96
114
|
this.velocity = velocity;
|
|
97
115
|
this.startTime = startTime;
|
|
98
|
-
this.voice = voice;
|
|
99
|
-
this.voiceParams = voiceParams;
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
const drumExclusiveClassesByKit = new Array(57);
|
|
@@ -287,6 +303,18 @@ export class Midy {
|
|
|
287
303
|
writable: true,
|
|
288
304
|
value: 0
|
|
289
305
|
});
|
|
306
|
+
Object.defineProperty(this, "lastActiveSensing", {
|
|
307
|
+
enumerable: true,
|
|
308
|
+
configurable: true,
|
|
309
|
+
writable: true,
|
|
310
|
+
value: 0
|
|
311
|
+
});
|
|
312
|
+
Object.defineProperty(this, "activeSensingThreshold", {
|
|
313
|
+
enumerable: true,
|
|
314
|
+
configurable: true,
|
|
315
|
+
writable: true,
|
|
316
|
+
value: 0.3
|
|
317
|
+
});
|
|
290
318
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
291
319
|
enumerable: true,
|
|
292
320
|
configurable: true,
|
|
@@ -327,7 +355,7 @@ export class Midy {
|
|
|
327
355
|
enumerable: true,
|
|
328
356
|
configurable: true,
|
|
329
357
|
writable: true,
|
|
330
|
-
value:
|
|
358
|
+
value: Array.from({ length: 128 }, () => [])
|
|
331
359
|
});
|
|
332
360
|
Object.defineProperty(this, "voiceCounter", {
|
|
333
361
|
enumerable: true,
|
|
@@ -341,6 +369,12 @@ export class Midy {
|
|
|
341
369
|
writable: true,
|
|
342
370
|
value: new Map()
|
|
343
371
|
});
|
|
372
|
+
Object.defineProperty(this, "realtimeVoiceCache", {
|
|
373
|
+
enumerable: true,
|
|
374
|
+
configurable: true,
|
|
375
|
+
writable: true,
|
|
376
|
+
value: new Map()
|
|
377
|
+
});
|
|
344
378
|
Object.defineProperty(this, "isPlaying", {
|
|
345
379
|
enumerable: true,
|
|
346
380
|
configurable: true,
|
|
@@ -414,8 +448,10 @@ export class Midy {
|
|
|
414
448
|
length: 1,
|
|
415
449
|
sampleRate: audioContext.sampleRate,
|
|
416
450
|
});
|
|
451
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
417
452
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
418
453
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
454
|
+
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
419
455
|
this.channels = this.createChannels(audioContext);
|
|
420
456
|
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
421
457
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -425,21 +461,14 @@ export class Midy {
|
|
|
425
461
|
this.scheduler.connect(audioContext.destination);
|
|
426
462
|
this.GM2SystemOn();
|
|
427
463
|
}
|
|
428
|
-
initSoundFontTable() {
|
|
429
|
-
const table = new Array(128);
|
|
430
|
-
for (let i = 0; i < 128; i++) {
|
|
431
|
-
table[i] = new Map();
|
|
432
|
-
}
|
|
433
|
-
return table;
|
|
434
|
-
}
|
|
435
464
|
addSoundFont(soundFont) {
|
|
436
465
|
const index = this.soundFonts.length;
|
|
437
466
|
this.soundFonts.push(soundFont);
|
|
438
467
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
468
|
+
const soundFontTable = this.soundFontTable;
|
|
439
469
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
banks.set(presetHeader.bank, index);
|
|
470
|
+
const { preset, bank } = presetHeaders[i];
|
|
471
|
+
soundFontTable[preset][bank] = index;
|
|
443
472
|
}
|
|
444
473
|
}
|
|
445
474
|
async toUint8Array(input) {
|
|
@@ -517,13 +546,17 @@ export class Midy {
|
|
|
517
546
|
this.GM2SystemOn();
|
|
518
547
|
}
|
|
519
548
|
getVoiceId(channel, noteNumber, velocity) {
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
549
|
+
const programNumber = channel.programNumber;
|
|
550
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
551
|
+
if (!bankTable)
|
|
552
|
+
return;
|
|
553
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
554
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
555
|
+
const soundFontIndex = bankTable[bank];
|
|
523
556
|
if (soundFontIndex === undefined)
|
|
524
557
|
return;
|
|
525
558
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
526
|
-
const voice = soundFont.getVoice(
|
|
559
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
527
560
|
const { instrument, sampleID } = voice.generators;
|
|
528
561
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
529
562
|
}
|
|
@@ -594,19 +627,22 @@ export class Midy {
|
|
|
594
627
|
}
|
|
595
628
|
return bufferSource;
|
|
596
629
|
}
|
|
597
|
-
async scheduleTimelineEvents(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
630
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
631
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
632
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
633
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
634
|
+
const timeline = this.timeline;
|
|
635
|
+
while (queueIndex < timeline.length) {
|
|
636
|
+
const event = timeline[queueIndex];
|
|
637
|
+
if (lookAheadCheckTime < event.startTime)
|
|
601
638
|
break;
|
|
602
|
-
const
|
|
603
|
-
const startTime = event.startTime + delay;
|
|
639
|
+
const startTime = event.startTime + schedulingOffset;
|
|
604
640
|
switch (event.type) {
|
|
605
641
|
case "noteOn":
|
|
606
|
-
await this.
|
|
642
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
607
643
|
break;
|
|
608
644
|
case "noteOff": {
|
|
609
|
-
const notePromise = this.
|
|
645
|
+
const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
610
646
|
if (notePromise)
|
|
611
647
|
this.notePromises.push(notePromise);
|
|
612
648
|
break;
|
|
@@ -645,6 +681,7 @@ export class Midy {
|
|
|
645
681
|
this.exclusiveClassNotes.fill(undefined);
|
|
646
682
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
647
683
|
this.voiceCache.clear();
|
|
684
|
+
this.realtimeVoiceCache.clear();
|
|
648
685
|
for (let i = 0; i < this.channels.length; i++) {
|
|
649
686
|
this.channels[i].scheduledNotes = [];
|
|
650
687
|
this.resetChannelStates(i);
|
|
@@ -678,13 +715,17 @@ export class Midy {
|
|
|
678
715
|
this.isPaused = false;
|
|
679
716
|
this.startTime = this.audioContext.currentTime;
|
|
680
717
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
681
|
-
let resumeTime = this.resumeTime - this.startTime;
|
|
682
718
|
let finished = false;
|
|
683
719
|
this.notePromises = [];
|
|
684
720
|
while (queueIndex < this.timeline.length) {
|
|
685
721
|
const now = this.audioContext.currentTime;
|
|
686
|
-
|
|
687
|
-
|
|
722
|
+
if (0 < this.lastActiveSensing &&
|
|
723
|
+
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
724
|
+
await this.stopNotes(0, true, now);
|
|
725
|
+
await this.audioContext.suspend();
|
|
726
|
+
finished = true;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
688
729
|
if (this.isPausing) {
|
|
689
730
|
await this.stopNotes(0, true, now);
|
|
690
731
|
await this.audioContext.suspend();
|
|
@@ -703,16 +744,17 @@ export class Midy {
|
|
|
703
744
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
704
745
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
705
746
|
queueIndex = nextQueueIndex;
|
|
706
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
707
747
|
this.isSeeking = false;
|
|
708
748
|
continue;
|
|
709
749
|
}
|
|
750
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
710
751
|
const waitTime = now + this.noteCheckInterval;
|
|
711
752
|
await this.scheduleTask(() => { }, waitTime);
|
|
712
753
|
}
|
|
713
754
|
if (finished) {
|
|
714
755
|
this.notePromises = [];
|
|
715
756
|
this.resetAllStates();
|
|
757
|
+
this.lastActiveSensing = 0;
|
|
716
758
|
}
|
|
717
759
|
this.isPlaying = false;
|
|
718
760
|
}
|
|
@@ -722,17 +764,17 @@ export class Midy {
|
|
|
722
764
|
secondToTicks(second, secondsPerBeat) {
|
|
723
765
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
724
766
|
}
|
|
767
|
+
getSoundFontId(channel) {
|
|
768
|
+
const programNumber = channel.programNumber;
|
|
769
|
+
const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
|
|
770
|
+
const bank = bankNumber.toString().padStart(3, "0");
|
|
771
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
772
|
+
return `${bank}:${program}`;
|
|
773
|
+
}
|
|
725
774
|
extractMidiData(midi) {
|
|
726
775
|
const instruments = new Set();
|
|
727
776
|
const timeline = [];
|
|
728
|
-
const
|
|
729
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
730
|
-
tmpChannels[i] = {
|
|
731
|
-
programNumber: -1,
|
|
732
|
-
bankMSB: this.channels[i].bankMSB,
|
|
733
|
-
bankLSB: this.channels[i].bankLSB,
|
|
734
|
-
};
|
|
735
|
-
}
|
|
777
|
+
const channels = this.channels;
|
|
736
778
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
737
779
|
const track = midi.tracks[i];
|
|
738
780
|
let currentTicks = 0;
|
|
@@ -742,48 +784,40 @@ export class Midy {
|
|
|
742
784
|
event.ticks = currentTicks;
|
|
743
785
|
switch (event.type) {
|
|
744
786
|
case "noteOn": {
|
|
745
|
-
const channel =
|
|
746
|
-
|
|
747
|
-
channel.programNumber = event.programNumber;
|
|
748
|
-
switch (channel.bankMSB) {
|
|
749
|
-
case 120:
|
|
750
|
-
instruments.add(`128:0`);
|
|
751
|
-
break;
|
|
752
|
-
case 121:
|
|
753
|
-
instruments.add(`${channel.bankLSB}:0`);
|
|
754
|
-
break;
|
|
755
|
-
default: {
|
|
756
|
-
const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
|
|
757
|
-
instruments.add(`${bankNumber}:0`);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
channel.programNumber = 0;
|
|
761
|
-
}
|
|
787
|
+
const channel = channels[event.channel];
|
|
788
|
+
instruments.add(this.getSoundFontId(channel));
|
|
762
789
|
break;
|
|
763
790
|
}
|
|
764
791
|
case "controller":
|
|
765
792
|
switch (event.controllerType) {
|
|
766
793
|
case 0:
|
|
767
|
-
|
|
794
|
+
this.setBankMSB(event.channel, event.value);
|
|
768
795
|
break;
|
|
769
796
|
case 32:
|
|
770
|
-
|
|
797
|
+
this.setBankLSB(event.channel, event.value);
|
|
771
798
|
break;
|
|
772
799
|
}
|
|
773
800
|
break;
|
|
774
801
|
case "programChange": {
|
|
775
|
-
const channel =
|
|
776
|
-
channel
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
802
|
+
const channel = channels[event.channel];
|
|
803
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
804
|
+
instruments.add(this.getSoundFontId(channel));
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case "sysEx": {
|
|
808
|
+
const data = event.data;
|
|
809
|
+
if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
|
|
810
|
+
switch (data[3]) {
|
|
811
|
+
case 1:
|
|
812
|
+
this.GM1SystemOn(scheduleTime);
|
|
813
|
+
break;
|
|
814
|
+
case 2: // GM System Off
|
|
815
|
+
break;
|
|
816
|
+
case 3:
|
|
817
|
+
this.GM2SystemOn(scheduleTime);
|
|
818
|
+
break;
|
|
819
|
+
default:
|
|
820
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
787
821
|
}
|
|
788
822
|
}
|
|
789
823
|
}
|
|
@@ -822,7 +856,7 @@ export class Midy {
|
|
|
822
856
|
const channel = this.channels[channelNumber];
|
|
823
857
|
const promises = [];
|
|
824
858
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
825
|
-
const promise = this.
|
|
859
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
826
860
|
this.notePromises.push(promise);
|
|
827
861
|
promises.push(promise);
|
|
828
862
|
});
|
|
@@ -832,7 +866,7 @@ export class Midy {
|
|
|
832
866
|
const channel = this.channels[channelNumber];
|
|
833
867
|
const promises = [];
|
|
834
868
|
this.processScheduledNotes(channel, (note) => {
|
|
835
|
-
const promise = this.
|
|
869
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
836
870
|
this.notePromises.push(promise);
|
|
837
871
|
promises.push(promise);
|
|
838
872
|
});
|
|
@@ -865,7 +899,7 @@ export class Midy {
|
|
|
865
899
|
if (!this.isPlaying || this.isPaused)
|
|
866
900
|
return;
|
|
867
901
|
const now = this.audioContext.currentTime;
|
|
868
|
-
this.resumeTime
|
|
902
|
+
this.resumeTime = now - this.startTime - this.startDelay;
|
|
869
903
|
this.isPausing = true;
|
|
870
904
|
await this.playPromise;
|
|
871
905
|
this.isPausing = false;
|
|
@@ -891,11 +925,13 @@ export class Midy {
|
|
|
891
925
|
if (totalTime < event.startTime)
|
|
892
926
|
totalTime = event.startTime;
|
|
893
927
|
}
|
|
894
|
-
return totalTime;
|
|
928
|
+
return totalTime + this.startDelay;
|
|
895
929
|
}
|
|
896
930
|
currentTime() {
|
|
931
|
+
if (!this.isPlaying)
|
|
932
|
+
return this.resumeTime;
|
|
897
933
|
const now = this.audioContext.currentTime;
|
|
898
|
-
return
|
|
934
|
+
return now + this.resumeTime - this.startTime;
|
|
899
935
|
}
|
|
900
936
|
processScheduledNotes(channel, callback) {
|
|
901
937
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -1107,7 +1143,7 @@ export class Midy {
|
|
|
1107
1143
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1108
1144
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1109
1145
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1110
|
-
if (
|
|
1146
|
+
if (this.isPortamento(channel, note)) {
|
|
1111
1147
|
const startTime = note.startTime;
|
|
1112
1148
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1113
1149
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
@@ -1327,31 +1363,42 @@ export class Midy {
|
|
|
1327
1363
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1328
1364
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1329
1365
|
}
|
|
1330
|
-
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1366
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
|
|
1331
1367
|
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
this.voiceCache.delete(audioBufferId);
|
|
1337
|
-
}
|
|
1338
|
-
return cache.audioBuffer;
|
|
1339
|
-
}
|
|
1340
|
-
else {
|
|
1341
|
-
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1368
|
+
if (realtime) {
|
|
1369
|
+
const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
|
|
1370
|
+
if (cachedAudioBuffer)
|
|
1371
|
+
return cachedAudioBuffer;
|
|
1342
1372
|
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1343
|
-
|
|
1344
|
-
this.voiceCache.set(audioBufferId, cache);
|
|
1373
|
+
this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
|
|
1345
1374
|
return audioBuffer;
|
|
1346
1375
|
}
|
|
1376
|
+
else {
|
|
1377
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1378
|
+
if (cache) {
|
|
1379
|
+
cache.counter += 1;
|
|
1380
|
+
if (cache.maxCount <= cache.counter) {
|
|
1381
|
+
this.voiceCache.delete(audioBufferId);
|
|
1382
|
+
}
|
|
1383
|
+
return cache.audioBuffer;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1387
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1388
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1389
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1390
|
+
return audioBuffer;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1347
1393
|
}
|
|
1348
|
-
async
|
|
1394
|
+
async setNoteAudioNode(channel, note, realtime) {
|
|
1349
1395
|
const now = this.audioContext.currentTime;
|
|
1396
|
+
const { noteNumber, velocity, startTime } = note;
|
|
1350
1397
|
const state = channel.state;
|
|
1351
1398
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1352
|
-
const voiceParams = voice.getAllParams(controllerState);
|
|
1353
|
-
|
|
1354
|
-
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1399
|
+
const voiceParams = note.voice.getAllParams(controllerState);
|
|
1400
|
+
note.voiceParams = voiceParams;
|
|
1401
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1355
1402
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1356
1403
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1357
1404
|
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
@@ -1363,7 +1410,7 @@ export class Midy {
|
|
|
1363
1410
|
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1364
1411
|
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1365
1412
|
}
|
|
1366
|
-
if (
|
|
1413
|
+
if (!channel.isDrum && this.isPortamento(channel, note)) {
|
|
1367
1414
|
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1368
1415
|
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1369
1416
|
this.setPortamentoPitchEnvelope(note, now);
|
|
@@ -1391,22 +1438,6 @@ export class Midy {
|
|
|
1391
1438
|
note.bufferSource.start(startTime);
|
|
1392
1439
|
return note;
|
|
1393
1440
|
}
|
|
1394
|
-
calcBank(channel) {
|
|
1395
|
-
switch (this.mode) {
|
|
1396
|
-
case "GM1":
|
|
1397
|
-
if (channel.isDrum)
|
|
1398
|
-
return 128;
|
|
1399
|
-
return 0;
|
|
1400
|
-
case "GM2":
|
|
1401
|
-
if (channel.bankMSB === 121)
|
|
1402
|
-
return 0;
|
|
1403
|
-
if (channel.isDrum)
|
|
1404
|
-
return 128;
|
|
1405
|
-
return channel.bank;
|
|
1406
|
-
default:
|
|
1407
|
-
return channel.bank;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
1441
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1411
1442
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1412
1443
|
if (exclusiveClass === 0)
|
|
@@ -1415,7 +1446,7 @@ export class Midy {
|
|
|
1415
1446
|
if (prev) {
|
|
1416
1447
|
const [prevNote, prevChannelNumber] = prev;
|
|
1417
1448
|
if (prevNote && !prevNote.ending) {
|
|
1418
|
-
this.
|
|
1449
|
+
this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1419
1450
|
startTime, true);
|
|
1420
1451
|
}
|
|
1421
1452
|
}
|
|
@@ -1435,23 +1466,14 @@ export class Midy {
|
|
|
1435
1466
|
channelNumber;
|
|
1436
1467
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1437
1468
|
if (prevNote && !prevNote.ending) {
|
|
1438
|
-
this.
|
|
1469
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1439
1470
|
startTime, true);
|
|
1440
1471
|
}
|
|
1441
1472
|
this.drumExclusiveClassNotes[index] = note;
|
|
1442
1473
|
}
|
|
1443
|
-
|
|
1474
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
1444
1475
|
const channel = this.channels[channelNumber];
|
|
1445
|
-
const
|
|
1446
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1447
|
-
.get(bankNumber);
|
|
1448
|
-
if (soundFontIndex === undefined)
|
|
1449
|
-
return;
|
|
1450
|
-
const soundFont = this.soundFonts[soundFontIndex];
|
|
1451
|
-
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1452
|
-
if (!voice)
|
|
1453
|
-
return;
|
|
1454
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1476
|
+
const { noteNumber, volumeEnvelopeNode } = note;
|
|
1455
1477
|
if (channel.isDrum) {
|
|
1456
1478
|
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1457
1479
|
let gainL = keyBasedGainLs[noteNumber];
|
|
@@ -1461,25 +1483,48 @@ export class Midy {
|
|
|
1461
1483
|
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1462
1484
|
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1463
1485
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1486
|
+
volumeEnvelopeNode.connect(gainL);
|
|
1487
|
+
volumeEnvelopeNode.connect(gainR);
|
|
1466
1488
|
}
|
|
1467
1489
|
else {
|
|
1468
|
-
|
|
1469
|
-
|
|
1490
|
+
volumeEnvelopeNode.connect(channel.gainL);
|
|
1491
|
+
volumeEnvelopeNode.connect(channel.gainR);
|
|
1470
1492
|
}
|
|
1471
1493
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1472
1494
|
channel.sustainNotes.push(note);
|
|
1473
1495
|
}
|
|
1474
1496
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1475
1497
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1498
|
+
}
|
|
1499
|
+
async noteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1500
|
+
const channel = this.channels[channelNumber];
|
|
1501
|
+
const realtime = startTime === undefined;
|
|
1502
|
+
if (realtime)
|
|
1503
|
+
startTime = this.audioContext.currentTime;
|
|
1504
|
+
const note = new Note(noteNumber, velocity, startTime);
|
|
1476
1505
|
const scheduledNotes = channel.scheduledNotes;
|
|
1477
1506
|
note.index = scheduledNotes.length;
|
|
1478
1507
|
scheduledNotes.push(note);
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1508
|
+
const programNumber = channel.programNumber;
|
|
1509
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
1510
|
+
if (!bankTable)
|
|
1511
|
+
return;
|
|
1512
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1513
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1514
|
+
const soundFontIndex = bankTable[bank];
|
|
1515
|
+
if (soundFontIndex === undefined)
|
|
1516
|
+
return;
|
|
1517
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
1518
|
+
note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1519
|
+
if (!note.voice)
|
|
1520
|
+
return;
|
|
1521
|
+
await this.setNoteAudioNode(channel, note, realtime);
|
|
1522
|
+
this.setNoteRouting(channelNumber, note, startTime);
|
|
1523
|
+
note.pending = false;
|
|
1524
|
+
const off = note.offEvent;
|
|
1525
|
+
if (off) {
|
|
1526
|
+
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
1527
|
+
}
|
|
1483
1528
|
}
|
|
1484
1529
|
disconnectNote(note) {
|
|
1485
1530
|
note.bufferSource.disconnect();
|
|
@@ -1502,6 +1547,7 @@ export class Midy {
|
|
|
1502
1547
|
}
|
|
1503
1548
|
}
|
|
1504
1549
|
releaseNote(channel, note, endTime) {
|
|
1550
|
+
endTime ??= this.audioContext.currentTime;
|
|
1505
1551
|
const releaseTime = this.getRelativeKeyBasedValue(channel, note, 72) * 2;
|
|
1506
1552
|
const volRelease = endTime + note.voiceParams.volRelease * releaseTime;
|
|
1507
1553
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
@@ -1523,7 +1569,7 @@ export class Midy {
|
|
|
1523
1569
|
}, stopTime);
|
|
1524
1570
|
});
|
|
1525
1571
|
}
|
|
1526
|
-
|
|
1572
|
+
noteOff(channelNumber, noteNumber, velocity, endTime, force) {
|
|
1527
1573
|
const channel = this.channels[channelNumber];
|
|
1528
1574
|
const state = channel.state;
|
|
1529
1575
|
if (!force) {
|
|
@@ -1542,6 +1588,10 @@ export class Midy {
|
|
|
1542
1588
|
if (index < 0)
|
|
1543
1589
|
return;
|
|
1544
1590
|
const note = channel.scheduledNotes[index];
|
|
1591
|
+
if (note.pending) {
|
|
1592
|
+
note.offEvent = { velocity, startTime: endTime };
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1545
1595
|
note.ending = true;
|
|
1546
1596
|
this.setNoteIndex(channel, index);
|
|
1547
1597
|
this.releaseNote(channel, note, endTime);
|
|
@@ -1572,16 +1622,12 @@ export class Midy {
|
|
|
1572
1622
|
}
|
|
1573
1623
|
return -1;
|
|
1574
1624
|
}
|
|
1575
|
-
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1576
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1577
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1578
|
-
}
|
|
1579
1625
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1580
1626
|
const velocity = halfVelocity * 2;
|
|
1581
1627
|
const channel = this.channels[channelNumber];
|
|
1582
1628
|
const promises = [];
|
|
1583
1629
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1584
|
-
const promise = this.
|
|
1630
|
+
const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1585
1631
|
promises.push(promise);
|
|
1586
1632
|
}
|
|
1587
1633
|
channel.sustainNotes = [];
|
|
@@ -1595,33 +1641,48 @@ export class Midy {
|
|
|
1595
1641
|
channel.state.sostenutoPedal = 0;
|
|
1596
1642
|
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1597
1643
|
const note = sostenutoNotes[i];
|
|
1598
|
-
const promise = this.
|
|
1644
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1599
1645
|
promises.push(promise);
|
|
1600
1646
|
}
|
|
1601
1647
|
channel.sostenutoNotes = [];
|
|
1602
1648
|
return promises;
|
|
1603
1649
|
}
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1650
|
+
createMessageHandlers() {
|
|
1651
|
+
const handlers = new Array(256);
|
|
1652
|
+
// Channel Message
|
|
1653
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1654
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1655
|
+
handlers[0xA0] = (data, scheduleTime) => this.setPolyphonicKeyPressure(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1656
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1657
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1658
|
+
handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
|
|
1659
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1660
|
+
// System Common Message
|
|
1661
|
+
// handlers[0xF1] = (_data, _scheduleTime) => {}; // MTC Quarter Frame
|
|
1662
|
+
// handlers[0xF2] = (_data, _scheduleTime) => {}; // Song Position Pointer
|
|
1663
|
+
// handlers[0xF3] = (_data, _scheduleTime) => {}; // Song Select
|
|
1664
|
+
// handlers[0xF6] = (_data, _scheduleTime) => {}; // Tune Request
|
|
1665
|
+
// handlers[0xF7] = (_data, _scheduleTime) => {}; // End of Exclusive (EOX)
|
|
1666
|
+
// System Real Time Message
|
|
1667
|
+
// handlers[0xF8] = (_data, _scheduleTime) => {}; // Timing Clock
|
|
1668
|
+
// handlers[0xFA] = (_data, _scheduleTime) => {}; // Start
|
|
1669
|
+
// handlers[0xFB] = (_data, _scheduleTime) => {}; // Continue
|
|
1670
|
+
// handlers[0xFC] = (_data, _scheduleTime) => {}; // Stop
|
|
1671
|
+
handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
|
|
1672
|
+
// handlers[0xFF] = (_data, _scheduleTime) => {}; // Reset
|
|
1673
|
+
return handlers;
|
|
1674
|
+
}
|
|
1675
|
+
handleMessage(data, scheduleTime) {
|
|
1676
|
+
const status = data[0];
|
|
1677
|
+
if (status === 0xF0) {
|
|
1678
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1624
1679
|
}
|
|
1680
|
+
const handler = this.messageHandlers[status];
|
|
1681
|
+
if (handler)
|
|
1682
|
+
handler(data, scheduleTime);
|
|
1683
|
+
}
|
|
1684
|
+
activeSensing() {
|
|
1685
|
+
this.lastActiveSensing = performance.now();
|
|
1625
1686
|
}
|
|
1626
1687
|
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1627
1688
|
const channel = this.channels[channelNumber];
|
|
@@ -1636,19 +1697,18 @@ export class Midy {
|
|
|
1636
1697
|
}
|
|
1637
1698
|
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1638
1699
|
const channel = this.channels[channelNumber];
|
|
1639
|
-
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1640
1700
|
channel.programNumber = programNumber;
|
|
1641
1701
|
if (this.mode === "GM2") {
|
|
1642
1702
|
switch (channel.bankMSB) {
|
|
1643
1703
|
case 120:
|
|
1644
1704
|
channel.isDrum = true;
|
|
1705
|
+
channel.keyBasedTable.fill(-1);
|
|
1645
1706
|
break;
|
|
1646
1707
|
case 121:
|
|
1647
1708
|
channel.isDrum = false;
|
|
1648
1709
|
break;
|
|
1649
1710
|
}
|
|
1650
1711
|
}
|
|
1651
|
-
channel.keyBasedTable.fill(-1);
|
|
1652
1712
|
}
|
|
1653
1713
|
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1654
1714
|
const channel = this.channels[channelNumber];
|
|
@@ -1985,22 +2045,20 @@ export class Midy {
|
|
|
1985
2045
|
this.updateModulation(channel, scheduleTime);
|
|
1986
2046
|
}
|
|
1987
2047
|
updatePortamento(channel, scheduleTime) {
|
|
2048
|
+
if (channel.isDrum)
|
|
2049
|
+
return;
|
|
1988
2050
|
this.processScheduledNotes(channel, (note) => {
|
|
1989
|
-
if (
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1995
|
-
}
|
|
2051
|
+
if (this.isPortamento(channel, note)) {
|
|
2052
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2053
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2054
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
2055
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1996
2056
|
}
|
|
1997
2057
|
else {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
2003
|
-
}
|
|
2058
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2059
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2060
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
2061
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2004
2062
|
}
|
|
2005
2063
|
});
|
|
2006
2064
|
}
|
|
@@ -2106,6 +2164,9 @@ export class Midy {
|
|
|
2106
2164
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
2107
2165
|
}
|
|
2108
2166
|
}
|
|
2167
|
+
isPortamento(channel, note) {
|
|
2168
|
+
return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
|
|
2169
|
+
}
|
|
2109
2170
|
setPortamento(channelNumber, value, scheduleTime) {
|
|
2110
2171
|
const channel = this.channels[channelNumber];
|
|
2111
2172
|
if (channel.isDrum)
|
|
@@ -2142,7 +2203,7 @@ export class Midy {
|
|
|
2142
2203
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2143
2204
|
state.softPedal = softPedal / 127;
|
|
2144
2205
|
this.processScheduledNotes(channel, (note) => {
|
|
2145
|
-
if (
|
|
2206
|
+
if (this.isPortamento(channel, note)) {
|
|
2146
2207
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2147
2208
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2148
2209
|
}
|
|
@@ -2200,7 +2261,7 @@ export class Midy {
|
|
|
2200
2261
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2201
2262
|
state.brightness = brightness / 127;
|
|
2202
2263
|
this.processScheduledNotes(channel, (note) => {
|
|
2203
|
-
if (
|
|
2264
|
+
if (this.isPortamento(channel, note)) {
|
|
2204
2265
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2205
2266
|
}
|
|
2206
2267
|
else {
|
|
@@ -2536,11 +2597,9 @@ export class Midy {
|
|
|
2536
2597
|
const channel = this.channels[i];
|
|
2537
2598
|
channel.bankMSB = 0;
|
|
2538
2599
|
channel.bankLSB = 0;
|
|
2539
|
-
channel.bank = 0;
|
|
2540
2600
|
channel.isDrum = false;
|
|
2541
2601
|
}
|
|
2542
2602
|
this.channels[9].bankMSB = 1;
|
|
2543
|
-
this.channels[9].bank = 128;
|
|
2544
2603
|
this.channels[9].isDrum = true;
|
|
2545
2604
|
}
|
|
2546
2605
|
GM2SystemOn(scheduleTime) {
|
|
@@ -2551,11 +2610,9 @@ export class Midy {
|
|
|
2551
2610
|
const channel = this.channels[i];
|
|
2552
2611
|
channel.bankMSB = 121;
|
|
2553
2612
|
channel.bankLSB = 0;
|
|
2554
|
-
channel.bank = 121 * 128;
|
|
2555
2613
|
channel.isDrum = false;
|
|
2556
2614
|
}
|
|
2557
2615
|
this.channels[9].bankMSB = 120;
|
|
2558
|
-
this.channels[9].bank = 120 * 128;
|
|
2559
2616
|
this.channels[9].isDrum = true;
|
|
2560
2617
|
}
|
|
2561
2618
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -2613,16 +2670,11 @@ export class Midy {
|
|
|
2613
2670
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2614
2671
|
this.setMasterVolume(volume, scheduleTime);
|
|
2615
2672
|
}
|
|
2616
|
-
setMasterVolume(
|
|
2673
|
+
setMasterVolume(value, scheduleTime) {
|
|
2617
2674
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
else {
|
|
2622
|
-
this.masterVolume.gain
|
|
2623
|
-
.cancelScheduledValues(scheduleTime)
|
|
2624
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
2625
|
-
}
|
|
2675
|
+
this.masterVolume.gain
|
|
2676
|
+
.cancelScheduledValues(scheduleTime)
|
|
2677
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
2626
2678
|
}
|
|
2627
2679
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2628
2680
|
const value = (data[5] * 128 + data[4]) / 16383;
|
|
@@ -3037,6 +3089,70 @@ export class Midy {
|
|
|
3037
3089
|
const controlValue = channel.keyBasedTable[index];
|
|
3038
3090
|
return controlValue;
|
|
3039
3091
|
}
|
|
3092
|
+
createKeyBasedControllerHandlers() {
|
|
3093
|
+
const handlers = new Array(128);
|
|
3094
|
+
handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3095
|
+
handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3096
|
+
handlers[71] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3097
|
+
if (note.noteNumber === keyNumber) {
|
|
3098
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
3099
|
+
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
3100
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
handlers[73] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3104
|
+
if (note.noteNumber === keyNumber) {
|
|
3105
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3106
|
+
}
|
|
3107
|
+
});
|
|
3108
|
+
handlers[74] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3109
|
+
if (note.noteNumber === keyNumber) {
|
|
3110
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
handlers[75] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3114
|
+
if (note.noteNumber === keyNumber) {
|
|
3115
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
handlers[76] = (channel, keyNumber, scheduleTime) => {
|
|
3119
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3120
|
+
return;
|
|
3121
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3122
|
+
if (note.noteNumber === keyNumber) {
|
|
3123
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
};
|
|
3127
|
+
handlers[77] = (channel, keyNumber, scheduleTime) => {
|
|
3128
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3129
|
+
return;
|
|
3130
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3131
|
+
if (note.noteNumber === keyNumber) {
|
|
3132
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
};
|
|
3136
|
+
handlers[78] = (channel, keyNumber) => {
|
|
3137
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3138
|
+
return;
|
|
3139
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3140
|
+
if (note.noteNumber === keyNumber)
|
|
3141
|
+
this.setDelayVibLFO(channel, note);
|
|
3142
|
+
});
|
|
3143
|
+
};
|
|
3144
|
+
handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3145
|
+
if (note.noteNumber === keyNumber) {
|
|
3146
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3150
|
+
if (note.noteNumber === keyNumber) {
|
|
3151
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
return handlers;
|
|
3155
|
+
}
|
|
3040
3156
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
3041
3157
|
const channelNumber = data[4];
|
|
3042
3158
|
const channel = this.channels[channelNumber];
|
|
@@ -3049,95 +3165,9 @@ export class Midy {
|
|
|
3049
3165
|
const value = data[i + 1];
|
|
3050
3166
|
const index = keyNumber * 128 + controllerType;
|
|
3051
3167
|
table[index] = value;
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3056
|
-
break;
|
|
3057
|
-
case 71:
|
|
3058
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3059
|
-
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
3060
|
-
if (note.noteNumber === keyNumber) {
|
|
3061
|
-
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
3062
|
-
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
3063
|
-
}
|
|
3064
|
-
});
|
|
3065
|
-
break;
|
|
3066
|
-
case 73:
|
|
3067
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3068
|
-
if (note.noteNumber === keyNumber) {
|
|
3069
|
-
if (0.5 <= channel.state.portamento &&
|
|
3070
|
-
0 <= note.portamentoNoteNumber) {
|
|
3071
|
-
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
3072
|
-
}
|
|
3073
|
-
else {
|
|
3074
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
});
|
|
3078
|
-
break;
|
|
3079
|
-
case 74:
|
|
3080
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3081
|
-
if (note.noteNumber === keyNumber) {
|
|
3082
|
-
if (0.5 <= channel.state.portamento &&
|
|
3083
|
-
0 <= note.portamentoNoteNumber) {
|
|
3084
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
3085
|
-
}
|
|
3086
|
-
else {
|
|
3087
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
});
|
|
3091
|
-
break;
|
|
3092
|
-
case 75:
|
|
3093
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3094
|
-
if (note.noteNumber === keyNumber) {
|
|
3095
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3096
|
-
}
|
|
3097
|
-
});
|
|
3098
|
-
break;
|
|
3099
|
-
case 76:
|
|
3100
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3101
|
-
break;
|
|
3102
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3103
|
-
if (note.noteNumber === keyNumber) {
|
|
3104
|
-
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
3105
|
-
}
|
|
3106
|
-
});
|
|
3107
|
-
break;
|
|
3108
|
-
case 77:
|
|
3109
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3110
|
-
break;
|
|
3111
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3112
|
-
if (note.noteNumber === keyNumber) {
|
|
3113
|
-
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
3114
|
-
}
|
|
3115
|
-
});
|
|
3116
|
-
break;
|
|
3117
|
-
case 78:
|
|
3118
|
-
if (channel.state.vibratoDepth <= 0)
|
|
3119
|
-
break;
|
|
3120
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3121
|
-
if (note.noteNumber === keyNumber) {
|
|
3122
|
-
this.setDelayVibLFO(channel, note);
|
|
3123
|
-
}
|
|
3124
|
-
});
|
|
3125
|
-
break;
|
|
3126
|
-
case 91:
|
|
3127
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3128
|
-
if (note.noteNumber === keyNumber) {
|
|
3129
|
-
this.setReverbSend(channel, note, scheduleTime);
|
|
3130
|
-
}
|
|
3131
|
-
});
|
|
3132
|
-
break;
|
|
3133
|
-
case 93:
|
|
3134
|
-
this.processScheduledNotes(channel, (note) => {
|
|
3135
|
-
if (note.noteNumber === keyNumber) {
|
|
3136
|
-
this.setChorusSend(channel, note, scheduleTime);
|
|
3137
|
-
}
|
|
3138
|
-
});
|
|
3139
|
-
break;
|
|
3140
|
-
}
|
|
3168
|
+
const handler = this.keyBasedControllerHandlers[controllerType];
|
|
3169
|
+
if (handler)
|
|
3170
|
+
handler(channel, keyNumber, scheduleTime);
|
|
3141
3171
|
}
|
|
3142
3172
|
}
|
|
3143
3173
|
handleSysEx(data, scheduleTime) {
|
|
@@ -3178,7 +3208,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3178
3208
|
scheduleIndex: 0,
|
|
3179
3209
|
detune: 0,
|
|
3180
3210
|
programNumber: 0,
|
|
3181
|
-
bank: 121 * 128,
|
|
3182
3211
|
bankMSB: 121,
|
|
3183
3212
|
bankLSB: 0,
|
|
3184
3213
|
dataMSB: 0,
|