@marmooo/midy 0.3.6 → 0.3.8
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 +38 -20
- package/esm/midy-GM1.d.ts +36 -30
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +184 -142
- package/esm/midy-GM2.d.ts +43 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +324 -279
- package/esm/midy-GMLite.d.ts +35 -30
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +178 -139
- package/esm/midy.d.ts +45 -34
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +414 -302
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +36 -30
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +184 -142
- package/script/midy-GM2.d.ts +43 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +324 -279
- package/script/midy-GMLite.d.ts +35 -30
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +178 -139
- package/script/midy.d.ts +45 -34
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +414 -302
package/script/midy.js
CHANGED
|
@@ -243,13 +243,13 @@ class Midy {
|
|
|
243
243
|
configurable: true,
|
|
244
244
|
writable: true,
|
|
245
245
|
value: 0
|
|
246
|
-
}); //
|
|
246
|
+
}); // cent
|
|
247
247
|
Object.defineProperty(this, "masterCoarseTuning", {
|
|
248
248
|
enumerable: true,
|
|
249
249
|
configurable: true,
|
|
250
250
|
writable: true,
|
|
251
251
|
value: 0
|
|
252
|
-
}); //
|
|
252
|
+
}); // cent
|
|
253
253
|
Object.defineProperty(this, "reverb", {
|
|
254
254
|
enumerable: true,
|
|
255
255
|
configurable: true,
|
|
@@ -290,6 +290,18 @@ class Midy {
|
|
|
290
290
|
writable: true,
|
|
291
291
|
value: 0
|
|
292
292
|
});
|
|
293
|
+
Object.defineProperty(this, "lastActiveSensing", {
|
|
294
|
+
enumerable: true,
|
|
295
|
+
configurable: true,
|
|
296
|
+
writable: true,
|
|
297
|
+
value: 0
|
|
298
|
+
});
|
|
299
|
+
Object.defineProperty(this, "activeSensingThreshold", {
|
|
300
|
+
enumerable: true,
|
|
301
|
+
configurable: true,
|
|
302
|
+
writable: true,
|
|
303
|
+
value: 0.3
|
|
304
|
+
});
|
|
293
305
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
294
306
|
enumerable: true,
|
|
295
307
|
configurable: true,
|
|
@@ -330,7 +342,7 @@ class Midy {
|
|
|
330
342
|
enumerable: true,
|
|
331
343
|
configurable: true,
|
|
332
344
|
writable: true,
|
|
333
|
-
value:
|
|
345
|
+
value: Array.from({ length: 128 }, () => [])
|
|
334
346
|
});
|
|
335
347
|
Object.defineProperty(this, "voiceCounter", {
|
|
336
348
|
enumerable: true,
|
|
@@ -374,6 +386,12 @@ class Midy {
|
|
|
374
386
|
writable: true,
|
|
375
387
|
value: false
|
|
376
388
|
});
|
|
389
|
+
Object.defineProperty(this, "playPromise", {
|
|
390
|
+
enumerable: true,
|
|
391
|
+
configurable: true,
|
|
392
|
+
writable: true,
|
|
393
|
+
value: void 0
|
|
394
|
+
});
|
|
377
395
|
Object.defineProperty(this, "timeline", {
|
|
378
396
|
enumerable: true,
|
|
379
397
|
configurable: true,
|
|
@@ -411,8 +429,10 @@ class Midy {
|
|
|
411
429
|
length: 1,
|
|
412
430
|
sampleRate: audioContext.sampleRate,
|
|
413
431
|
});
|
|
432
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
414
433
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
415
434
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
435
|
+
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
416
436
|
this.channels = this.createChannels(audioContext);
|
|
417
437
|
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
418
438
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -422,21 +442,14 @@ class Midy {
|
|
|
422
442
|
this.scheduler.connect(audioContext.destination);
|
|
423
443
|
this.GM2SystemOn();
|
|
424
444
|
}
|
|
425
|
-
initSoundFontTable() {
|
|
426
|
-
const table = new Array(128);
|
|
427
|
-
for (let i = 0; i < 128; i++) {
|
|
428
|
-
table[i] = new Map();
|
|
429
|
-
}
|
|
430
|
-
return table;
|
|
431
|
-
}
|
|
432
445
|
addSoundFont(soundFont) {
|
|
433
446
|
const index = this.soundFonts.length;
|
|
434
447
|
this.soundFonts.push(soundFont);
|
|
435
448
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
449
|
+
const soundFontTable = this.soundFontTable;
|
|
436
450
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
banks.set(presetHeader.bank, index);
|
|
451
|
+
const { preset, bank } = presetHeaders[i];
|
|
452
|
+
soundFontTable[preset][bank] = index;
|
|
440
453
|
}
|
|
441
454
|
}
|
|
442
455
|
async toUint8Array(input) {
|
|
@@ -514,13 +527,17 @@ class Midy {
|
|
|
514
527
|
this.GM2SystemOn();
|
|
515
528
|
}
|
|
516
529
|
getVoiceId(channel, noteNumber, velocity) {
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
530
|
+
const programNumber = channel.programNumber;
|
|
531
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
532
|
+
if (!bankTable)
|
|
533
|
+
return;
|
|
534
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
535
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
536
|
+
const soundFontIndex = bankTable[bank];
|
|
520
537
|
if (soundFontIndex === undefined)
|
|
521
538
|
return;
|
|
522
539
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
523
|
-
const voice = soundFont.getVoice(
|
|
540
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
524
541
|
const { instrument, sampleID } = voice.generators;
|
|
525
542
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
526
543
|
}
|
|
@@ -543,7 +560,7 @@ class Midy {
|
|
|
543
560
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
544
561
|
channel.channelPressureTable.fill(-1);
|
|
545
562
|
channel.polyphonicKeyPressureTable.fill(-1);
|
|
546
|
-
channel.
|
|
563
|
+
channel.keyBasedTable.fill(-1);
|
|
547
564
|
}
|
|
548
565
|
createChannels(audioContext) {
|
|
549
566
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
@@ -560,7 +577,7 @@ class Midy {
|
|
|
560
577
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
561
578
|
channelPressureTable: new Int8Array(6).fill(-1),
|
|
562
579
|
polyphonicKeyPressureTable: new Int8Array(6).fill(-1),
|
|
563
|
-
|
|
580
|
+
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
564
581
|
keyBasedGainLs: new Array(128),
|
|
565
582
|
keyBasedGainRs: new Array(128),
|
|
566
583
|
};
|
|
@@ -591,13 +608,16 @@ class Midy {
|
|
|
591
608
|
}
|
|
592
609
|
return bufferSource;
|
|
593
610
|
}
|
|
594
|
-
async scheduleTimelineEvents(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
612
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
613
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
614
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
615
|
+
const timeline = this.timeline;
|
|
616
|
+
while (queueIndex < timeline.length) {
|
|
617
|
+
const event = timeline[queueIndex];
|
|
618
|
+
if (lookAheadCheckTime < event.startTime)
|
|
598
619
|
break;
|
|
599
|
-
const
|
|
600
|
-
const startTime = event.startTime + delay;
|
|
620
|
+
const startTime = event.startTime + schedulingOffset;
|
|
601
621
|
switch (event.type) {
|
|
602
622
|
case "noteOn":
|
|
603
623
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
@@ -638,72 +658,85 @@ class Midy {
|
|
|
638
658
|
}
|
|
639
659
|
return 0;
|
|
640
660
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
661
|
+
resetAllStates() {
|
|
662
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
663
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
664
|
+
this.voiceCache.clear();
|
|
665
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
666
|
+
this.channels[i].scheduledNotes = [];
|
|
667
|
+
this.resetChannelStates(i);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
updateStates(queueIndex, nextQueueIndex) {
|
|
671
|
+
if (nextQueueIndex < queueIndex)
|
|
672
|
+
queueIndex = 0;
|
|
673
|
+
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
674
|
+
const event = this.timeline[i];
|
|
675
|
+
switch (event.type) {
|
|
676
|
+
case "controller":
|
|
677
|
+
this.setControlChange(event.channel, event.controllerType, event.value, 0);
|
|
678
|
+
break;
|
|
679
|
+
case "programChange":
|
|
680
|
+
this.setProgramChange(event.channel, event.programNumber, 0);
|
|
681
|
+
break;
|
|
682
|
+
case "pitchBend":
|
|
683
|
+
this.setPitchBend(event.channel, event.value + 8192, 0);
|
|
684
|
+
break;
|
|
685
|
+
case "sysEx":
|
|
686
|
+
this.handleSysEx(event.data, 0);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async playNotes() {
|
|
691
|
+
if (this.audioContext.state === "suspended") {
|
|
692
|
+
await this.audioContext.resume();
|
|
693
|
+
}
|
|
694
|
+
this.isPlaying = true;
|
|
695
|
+
this.isPaused = false;
|
|
696
|
+
this.startTime = this.audioContext.currentTime;
|
|
697
|
+
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
698
|
+
let finished = false;
|
|
699
|
+
this.notePromises = [];
|
|
700
|
+
while (queueIndex < this.timeline.length) {
|
|
701
|
+
const now = this.audioContext.currentTime;
|
|
702
|
+
if (0 < this.lastActiveSensing &&
|
|
703
|
+
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
704
|
+
await this.stopNotes(0, true, now);
|
|
705
|
+
await this.audioContext.suspend();
|
|
706
|
+
finished = true;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
710
|
+
if (this.isPausing) {
|
|
711
|
+
await this.stopNotes(0, true, now);
|
|
712
|
+
await this.audioContext.suspend();
|
|
713
|
+
this.notePromises = [];
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
else if (this.isStopping) {
|
|
717
|
+
await this.stopNotes(0, true, now);
|
|
718
|
+
await this.audioContext.suspend();
|
|
719
|
+
finished = true;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
else if (this.isSeeking) {
|
|
723
|
+
await this.stopNotes(0, true, now);
|
|
724
|
+
this.startTime = this.audioContext.currentTime;
|
|
725
|
+
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
726
|
+
this.updateStates(queueIndex, nextQueueIndex);
|
|
727
|
+
queueIndex = nextQueueIndex;
|
|
728
|
+
this.isSeeking = false;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
const waitTime = now + this.noteCheckInterval;
|
|
732
|
+
await this.scheduleTask(() => { }, waitTime);
|
|
733
|
+
}
|
|
734
|
+
if (finished) {
|
|
648
735
|
this.notePromises = [];
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
654
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
655
|
-
this.voiceCache.clear();
|
|
656
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
657
|
-
this.channels[i].scheduledNotes = [];
|
|
658
|
-
this.resetAllStates(i);
|
|
659
|
-
}
|
|
660
|
-
resolve();
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
const now = this.audioContext.currentTime;
|
|
664
|
-
const t = now + resumeTime;
|
|
665
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
666
|
-
if (this.isPausing) {
|
|
667
|
-
await this.stopNotes(0, true, now);
|
|
668
|
-
this.notePromises = [];
|
|
669
|
-
this.isPausing = false;
|
|
670
|
-
this.isPaused = true;
|
|
671
|
-
resolve();
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
else if (this.isStopping) {
|
|
675
|
-
await this.stopNotes(0, true, now);
|
|
676
|
-
this.notePromises = [];
|
|
677
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
678
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
679
|
-
this.voiceCache.clear();
|
|
680
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
681
|
-
this.channels[i].scheduledNotes = [];
|
|
682
|
-
this.resetAllStates(i);
|
|
683
|
-
}
|
|
684
|
-
this.isStopping = false;
|
|
685
|
-
this.isPaused = false;
|
|
686
|
-
resolve();
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
else if (this.isSeeking) {
|
|
690
|
-
this.stopNotes(0, true, now);
|
|
691
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
692
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
693
|
-
this.startTime = this.audioContext.currentTime;
|
|
694
|
-
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
695
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
696
|
-
this.isSeeking = false;
|
|
697
|
-
await schedulePlayback();
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
const waitTime = now + this.noteCheckInterval;
|
|
701
|
-
await this.scheduleTask(() => { }, waitTime);
|
|
702
|
-
await schedulePlayback();
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
schedulePlayback();
|
|
706
|
-
});
|
|
736
|
+
this.resetAllStates();
|
|
737
|
+
this.lastActiveSensing = 0;
|
|
738
|
+
}
|
|
739
|
+
this.isPlaying = false;
|
|
707
740
|
}
|
|
708
741
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
709
742
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -711,17 +744,17 @@ class Midy {
|
|
|
711
744
|
secondToTicks(second, secondsPerBeat) {
|
|
712
745
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
713
746
|
}
|
|
747
|
+
getSoundFontId(channel) {
|
|
748
|
+
const programNumber = channel.programNumber;
|
|
749
|
+
const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
|
|
750
|
+
const bank = bankNumber.toString().padStart(3, "0");
|
|
751
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
752
|
+
return `${bank}:${program}`;
|
|
753
|
+
}
|
|
714
754
|
extractMidiData(midi) {
|
|
715
755
|
const instruments = new Set();
|
|
716
756
|
const timeline = [];
|
|
717
|
-
const
|
|
718
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
719
|
-
tmpChannels[i] = {
|
|
720
|
-
programNumber: -1,
|
|
721
|
-
bankMSB: this.channels[i].bankMSB,
|
|
722
|
-
bankLSB: this.channels[i].bankLSB,
|
|
723
|
-
};
|
|
724
|
-
}
|
|
757
|
+
const channels = this.channels;
|
|
725
758
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
726
759
|
const track = midi.tracks[i];
|
|
727
760
|
let currentTicks = 0;
|
|
@@ -731,48 +764,40 @@ class Midy {
|
|
|
731
764
|
event.ticks = currentTicks;
|
|
732
765
|
switch (event.type) {
|
|
733
766
|
case "noteOn": {
|
|
734
|
-
const channel =
|
|
735
|
-
|
|
736
|
-
channel.programNumber = event.programNumber;
|
|
737
|
-
switch (channel.bankMSB) {
|
|
738
|
-
case 120:
|
|
739
|
-
instruments.add(`128:0`);
|
|
740
|
-
break;
|
|
741
|
-
case 121:
|
|
742
|
-
instruments.add(`${channel.bankLSB}:0`);
|
|
743
|
-
break;
|
|
744
|
-
default: {
|
|
745
|
-
const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
|
|
746
|
-
instruments.add(`${bankNumber}:0`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
channel.programNumber = 0;
|
|
750
|
-
}
|
|
767
|
+
const channel = channels[event.channel];
|
|
768
|
+
instruments.add(this.getSoundFontId(channel));
|
|
751
769
|
break;
|
|
752
770
|
}
|
|
753
771
|
case "controller":
|
|
754
772
|
switch (event.controllerType) {
|
|
755
773
|
case 0:
|
|
756
|
-
|
|
774
|
+
this.setBankMSB(event.channel, event.value);
|
|
757
775
|
break;
|
|
758
776
|
case 32:
|
|
759
|
-
|
|
777
|
+
this.setBankLSB(event.channel, event.value);
|
|
760
778
|
break;
|
|
761
779
|
}
|
|
762
780
|
break;
|
|
763
781
|
case "programChange": {
|
|
764
|
-
const channel =
|
|
765
|
-
channel
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
782
|
+
const channel = channels[event.channel];
|
|
783
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
784
|
+
instruments.add(this.getSoundFontId(channel));
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
case "sysEx": {
|
|
788
|
+
const data = event.data;
|
|
789
|
+
if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
|
|
790
|
+
switch (data[3]) {
|
|
791
|
+
case 1:
|
|
792
|
+
this.GM1SystemOn(scheduleTime);
|
|
793
|
+
break;
|
|
794
|
+
case 2: // GM System Off
|
|
795
|
+
break;
|
|
796
|
+
case 3:
|
|
797
|
+
this.GM2SystemOn(scheduleTime);
|
|
798
|
+
break;
|
|
799
|
+
default:
|
|
800
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
776
801
|
}
|
|
777
802
|
}
|
|
778
803
|
}
|
|
@@ -840,26 +865,32 @@ class Midy {
|
|
|
840
865
|
this.resumeTime = 0;
|
|
841
866
|
if (this.voiceCounter.size === 0)
|
|
842
867
|
this.cacheVoiceIds();
|
|
843
|
-
|
|
844
|
-
this.
|
|
868
|
+
this.playPromise = this.playNotes();
|
|
869
|
+
await this.playPromise;
|
|
845
870
|
}
|
|
846
|
-
stop() {
|
|
871
|
+
async stop() {
|
|
847
872
|
if (!this.isPlaying)
|
|
848
873
|
return;
|
|
849
874
|
this.isStopping = true;
|
|
875
|
+
await this.playPromise;
|
|
876
|
+
this.isStopping = false;
|
|
850
877
|
}
|
|
851
|
-
pause() {
|
|
878
|
+
async pause() {
|
|
852
879
|
if (!this.isPlaying || this.isPaused)
|
|
853
880
|
return;
|
|
854
881
|
const now = this.audioContext.currentTime;
|
|
855
882
|
this.resumeTime += now - this.startTime - this.startDelay;
|
|
856
883
|
this.isPausing = true;
|
|
884
|
+
await this.playPromise;
|
|
885
|
+
this.isPausing = false;
|
|
886
|
+
this.isPaused = true;
|
|
857
887
|
}
|
|
858
888
|
async resume() {
|
|
859
889
|
if (!this.isPaused)
|
|
860
890
|
return;
|
|
861
|
-
|
|
862
|
-
this.
|
|
891
|
+
this.playPromise = this.playNotes();
|
|
892
|
+
await this.playPromise;
|
|
893
|
+
this.isPaused = false;
|
|
863
894
|
}
|
|
864
895
|
seekTo(second) {
|
|
865
896
|
this.resumeTime = second;
|
|
@@ -1090,7 +1121,7 @@ class Midy {
|
|
|
1090
1121
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1091
1122
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1092
1123
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1093
|
-
if (
|
|
1124
|
+
if (this.isPortamento(channel, note)) {
|
|
1094
1125
|
const startTime = note.startTime;
|
|
1095
1126
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1096
1127
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
@@ -1167,28 +1198,29 @@ class Midy {
|
|
|
1167
1198
|
return Math.exp(y);
|
|
1168
1199
|
}
|
|
1169
1200
|
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1170
|
-
const state = channel.state;
|
|
1171
1201
|
const { voiceParams, startTime } = note;
|
|
1172
1202
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1173
1203
|
(1 + this.getAmplitudeControl(channel, note));
|
|
1174
1204
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1175
1205
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1176
|
-
const
|
|
1206
|
+
const attackTime = this.getRelativeKeyBasedValue(channel, note, 73) * 2;
|
|
1207
|
+
const volAttack = volDelay + voiceParams.volAttack * attackTime;
|
|
1177
1208
|
const volHold = volAttack + voiceParams.volHold;
|
|
1178
1209
|
note.volumeEnvelopeNode.gain
|
|
1179
1210
|
.cancelScheduledValues(scheduleTime)
|
|
1180
1211
|
.setValueAtTime(sustainVolume, volHold);
|
|
1181
1212
|
}
|
|
1182
1213
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1183
|
-
const state = channel.state;
|
|
1184
1214
|
const { voiceParams, startTime } = note;
|
|
1185
1215
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1186
1216
|
(1 + this.getAmplitudeControl(channel, note));
|
|
1187
1217
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1188
1218
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1189
|
-
const
|
|
1219
|
+
const attackTime = this.getRelativeKeyBasedValue(channel, note, 73) * 2;
|
|
1220
|
+
const volAttack = volDelay + voiceParams.volAttack * attackTime;
|
|
1190
1221
|
const volHold = volAttack + voiceParams.volHold;
|
|
1191
|
-
const
|
|
1222
|
+
const decayTime = this.getRelativeKeyBasedValue(channel, note, 75) * 2;
|
|
1223
|
+
const volDecay = volHold + voiceParams.volDecay * decayTime;
|
|
1192
1224
|
note.volumeEnvelopeNode.gain
|
|
1193
1225
|
.cancelScheduledValues(scheduleTime)
|
|
1194
1226
|
.setValueAtTime(0, startTime)
|
|
@@ -1231,14 +1263,13 @@ class Midy {
|
|
|
1231
1263
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1232
1264
|
}
|
|
1233
1265
|
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1234
|
-
const state = channel.state;
|
|
1235
1266
|
const { voiceParams, startTime } = note;
|
|
1236
1267
|
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1237
1268
|
const baseCent = voiceParams.initialFilterFc +
|
|
1238
1269
|
this.getFilterCutoffControl(channel, note);
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) *
|
|
1270
|
+
const brightness = this.getRelativeKeyBasedValue(channel, note, 74) * 2;
|
|
1271
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor * brightness;
|
|
1272
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * brightness;
|
|
1242
1273
|
const sustainFreq = baseFreq +
|
|
1243
1274
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1244
1275
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
@@ -1252,15 +1283,14 @@ class Midy {
|
|
|
1252
1283
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1253
1284
|
}
|
|
1254
1285
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1255
|
-
const state = channel.state;
|
|
1256
1286
|
const { voiceParams, startTime } = note;
|
|
1257
1287
|
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1258
1288
|
const baseCent = voiceParams.initialFilterFc +
|
|
1259
1289
|
this.getFilterCutoffControl(channel, note);
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1290
|
+
const brightness = this.getRelativeKeyBasedValue(channel, note, 74) * 2;
|
|
1291
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor * brightness;
|
|
1262
1292
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1263
|
-
softPedalFactor *
|
|
1293
|
+
softPedalFactor * brightness;
|
|
1264
1294
|
const sustainFreq = baseFreq +
|
|
1265
1295
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1266
1296
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
@@ -1300,11 +1330,12 @@ class Midy {
|
|
|
1300
1330
|
}
|
|
1301
1331
|
startVibrato(channel, note, scheduleTime) {
|
|
1302
1332
|
const { voiceParams } = note;
|
|
1303
|
-
const
|
|
1333
|
+
const vibratoRate = this.getRelativeKeyBasedValue(channel, note, 76) * 2;
|
|
1334
|
+
const vibratoDelay = this.getRelativeKeyBasedValue(channel, note, 78) * 2;
|
|
1304
1335
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1305
|
-
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1336
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
|
|
1306
1337
|
});
|
|
1307
|
-
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO *
|
|
1338
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
|
|
1308
1339
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1309
1340
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1310
1341
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
@@ -1337,15 +1368,16 @@ class Midy {
|
|
|
1337
1368
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1338
1369
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1339
1370
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1371
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
1340
1372
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1341
1373
|
type: "lowpass",
|
|
1342
|
-
Q: voiceParams.initialFilterQ / 5 *
|
|
1374
|
+
Q: voiceParams.initialFilterQ / 5 * filterResonance, // dB
|
|
1343
1375
|
});
|
|
1344
1376
|
const prevNote = channel.scheduledNotes.at(-1);
|
|
1345
1377
|
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1346
1378
|
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1347
1379
|
}
|
|
1348
|
-
if (
|
|
1380
|
+
if (!channel.isDrum && this.isPortamento(channel, note)) {
|
|
1349
1381
|
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1350
1382
|
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1351
1383
|
this.setPortamentoPitchEnvelope(note, now);
|
|
@@ -1373,22 +1405,6 @@ class Midy {
|
|
|
1373
1405
|
note.bufferSource.start(startTime);
|
|
1374
1406
|
return note;
|
|
1375
1407
|
}
|
|
1376
|
-
calcBank(channel) {
|
|
1377
|
-
switch (this.mode) {
|
|
1378
|
-
case "GM1":
|
|
1379
|
-
if (channel.isDrum)
|
|
1380
|
-
return 128;
|
|
1381
|
-
return 0;
|
|
1382
|
-
case "GM2":
|
|
1383
|
-
if (channel.bankMSB === 121)
|
|
1384
|
-
return 0;
|
|
1385
|
-
if (channel.isDrum)
|
|
1386
|
-
return 128;
|
|
1387
|
-
return channel.bank;
|
|
1388
|
-
default:
|
|
1389
|
-
return channel.bank;
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
1408
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1393
1409
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1394
1410
|
if (exclusiveClass === 0)
|
|
@@ -1424,13 +1440,17 @@ class Midy {
|
|
|
1424
1440
|
}
|
|
1425
1441
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1426
1442
|
const channel = this.channels[channelNumber];
|
|
1427
|
-
const
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1443
|
+
const programNumber = channel.programNumber;
|
|
1444
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
1445
|
+
if (!bankTable)
|
|
1446
|
+
return;
|
|
1447
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1448
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1449
|
+
const soundFontIndex = bankTable[bank];
|
|
1430
1450
|
if (soundFontIndex === undefined)
|
|
1431
1451
|
return;
|
|
1432
1452
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1433
|
-
const voice = soundFont.getVoice(
|
|
1453
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1434
1454
|
if (!voice)
|
|
1435
1455
|
return;
|
|
1436
1456
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
@@ -1484,8 +1504,8 @@ class Midy {
|
|
|
1484
1504
|
}
|
|
1485
1505
|
}
|
|
1486
1506
|
releaseNote(channel, note, endTime) {
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1507
|
+
const releaseTime = this.getRelativeKeyBasedValue(channel, note, 72) * 2;
|
|
1508
|
+
const volRelease = endTime + note.voiceParams.volRelease * releaseTime;
|
|
1489
1509
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1490
1510
|
const stopTime = Math.min(volRelease, modRelease);
|
|
1491
1511
|
note.filterNode.frequency
|
|
@@ -1583,27 +1603,42 @@ class Midy {
|
|
|
1583
1603
|
channel.sostenutoNotes = [];
|
|
1584
1604
|
return promises;
|
|
1585
1605
|
}
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
+
createMessageHandlers() {
|
|
1607
|
+
const handlers = new Array(256);
|
|
1608
|
+
// Channel Message
|
|
1609
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1610
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1611
|
+
handlers[0xA0] = (data, scheduleTime) => this.setPolyphonicKeyPressure(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1612
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1613
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1614
|
+
handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
|
|
1615
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1616
|
+
// System Common Message
|
|
1617
|
+
// handlers[0xF1] = (_data, _scheduleTime) => {}; // MTC Quarter Frame
|
|
1618
|
+
// handlers[0xF2] = (_data, _scheduleTime) => {}; // Song Position Pointer
|
|
1619
|
+
// handlers[0xF3] = (_data, _scheduleTime) => {}; // Song Select
|
|
1620
|
+
// handlers[0xF6] = (_data, _scheduleTime) => {}; // Tune Request
|
|
1621
|
+
// handlers[0xF7] = (_data, _scheduleTime) => {}; // End of Exclusive (EOX)
|
|
1622
|
+
// System Real Time Message
|
|
1623
|
+
// handlers[0xF8] = (_data, _scheduleTime) => {}; // Timing Clock
|
|
1624
|
+
// handlers[0xFA] = (_data, _scheduleTime) => {}; // Start
|
|
1625
|
+
// handlers[0xFB] = (_data, _scheduleTime) => {}; // Continue
|
|
1626
|
+
// handlers[0xFC] = (_data, _scheduleTime) => {}; // Stop
|
|
1627
|
+
handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
|
|
1628
|
+
// handlers[0xFF] = (_data, _scheduleTime) => {}; // Reset
|
|
1629
|
+
return handlers;
|
|
1630
|
+
}
|
|
1631
|
+
handleMessage(data, scheduleTime) {
|
|
1632
|
+
const status = data[0];
|
|
1633
|
+
if (status === 0xF0) {
|
|
1634
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1606
1635
|
}
|
|
1636
|
+
const handler = this.messageHandlers[status];
|
|
1637
|
+
if (handler)
|
|
1638
|
+
handler(data, scheduleTime);
|
|
1639
|
+
}
|
|
1640
|
+
activeSensing() {
|
|
1641
|
+
this.lastActiveSensing = performance.now();
|
|
1607
1642
|
}
|
|
1608
1643
|
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1609
1644
|
const channel = this.channels[channelNumber];
|
|
@@ -1618,12 +1653,12 @@ class Midy {
|
|
|
1618
1653
|
}
|
|
1619
1654
|
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1620
1655
|
const channel = this.channels[channelNumber];
|
|
1621
|
-
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1622
1656
|
channel.programNumber = programNumber;
|
|
1623
1657
|
if (this.mode === "GM2") {
|
|
1624
1658
|
switch (channel.bankMSB) {
|
|
1625
1659
|
case 120:
|
|
1626
1660
|
channel.isDrum = true;
|
|
1661
|
+
channel.keyBasedTable.fill(-1);
|
|
1627
1662
|
break;
|
|
1628
1663
|
case 121:
|
|
1629
1664
|
channel.isDrum = false;
|
|
@@ -1671,23 +1706,29 @@ class Midy {
|
|
|
1671
1706
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1672
1707
|
this.getLFOPitchDepth(channel, note);
|
|
1673
1708
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1674
|
-
const
|
|
1709
|
+
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1675
1710
|
note.modulationDepth.gain
|
|
1676
1711
|
.cancelScheduledValues(scheduleTime)
|
|
1677
|
-
.setValueAtTime(
|
|
1712
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1678
1713
|
}
|
|
1679
1714
|
else {
|
|
1680
1715
|
this.startModulation(channel, note, scheduleTime);
|
|
1681
1716
|
}
|
|
1682
1717
|
}
|
|
1683
1718
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
.
|
|
1690
|
-
.
|
|
1719
|
+
if (note.vibratoDepth) {
|
|
1720
|
+
const vibratoDepth = this.getKeyBasedValue(channel, note.noteNumber, 77) *
|
|
1721
|
+
2;
|
|
1722
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1723
|
+
const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
|
|
1724
|
+
const depth = baseDepth * Math.sign(vibLfoToPitch);
|
|
1725
|
+
note.vibratoDepth.gain
|
|
1726
|
+
.cancelScheduledValues(scheduleTime)
|
|
1727
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1728
|
+
}
|
|
1729
|
+
else {
|
|
1730
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1731
|
+
}
|
|
1691
1732
|
}
|
|
1692
1733
|
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1693
1734
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
@@ -1765,13 +1806,12 @@ class Midy {
|
|
|
1765
1806
|
}
|
|
1766
1807
|
}
|
|
1767
1808
|
}
|
|
1768
|
-
setDelayModLFO(note
|
|
1769
|
-
const startTime = note.startTime;
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
note.modulationLFO.connect(note.filterDepth);
|
|
1809
|
+
setDelayModLFO(note) {
|
|
1810
|
+
const startTime = note.startTime + note.voiceParams.delayModLFO;
|
|
1811
|
+
try {
|
|
1812
|
+
note.modulationLFO.start(startTime);
|
|
1813
|
+
}
|
|
1814
|
+
catch { /* empty */ }
|
|
1775
1815
|
}
|
|
1776
1816
|
setFreqModLFO(note, scheduleTime) {
|
|
1777
1817
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
@@ -1780,54 +1820,65 @@ class Midy {
|
|
|
1780
1820
|
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1781
1821
|
}
|
|
1782
1822
|
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1823
|
+
const vibratoRate = this.getRelativeKeyBasedValue(channel, note, 76) * 2;
|
|
1783
1824
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1784
1825
|
note.vibratoLFO.frequency
|
|
1785
1826
|
.cancelScheduledValues(scheduleTime)
|
|
1786
|
-
.setValueAtTime(freqVibLFO *
|
|
1827
|
+
.setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
|
|
1828
|
+
}
|
|
1829
|
+
setDelayVibLFO(channel, note) {
|
|
1830
|
+
const vibratoDelay = this.getRelativeKeyBasedValue(channel, note, 78) * 2;
|
|
1831
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1832
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1833
|
+
try {
|
|
1834
|
+
note.vibratoLFO.start(startTime);
|
|
1835
|
+
}
|
|
1836
|
+
catch { /* empty */ }
|
|
1787
1837
|
}
|
|
1788
1838
|
createVoiceParamsHandlers() {
|
|
1789
1839
|
return {
|
|
1790
|
-
modLfoToPitch: (channel, note,
|
|
1840
|
+
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1791
1841
|
if (0 < channel.state.modulationDepth) {
|
|
1792
1842
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1793
1843
|
}
|
|
1794
1844
|
},
|
|
1795
|
-
vibLfoToPitch: (channel, note,
|
|
1845
|
+
vibLfoToPitch: (channel, note, scheduleTime) => {
|
|
1796
1846
|
if (0 < channel.state.vibratoDepth) {
|
|
1797
1847
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1798
1848
|
}
|
|
1799
1849
|
},
|
|
1800
|
-
modLfoToFilterFc: (channel, note,
|
|
1850
|
+
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1801
1851
|
if (0 < channel.state.modulationDepth) {
|
|
1802
1852
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1803
1853
|
}
|
|
1804
1854
|
},
|
|
1805
|
-
modLfoToVolume: (channel, note,
|
|
1855
|
+
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1806
1856
|
if (0 < channel.state.modulationDepth) {
|
|
1807
1857
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1808
1858
|
}
|
|
1809
1859
|
},
|
|
1810
|
-
chorusEffectsSend: (channel, note,
|
|
1860
|
+
chorusEffectsSend: (channel, note, scheduleTime) => {
|
|
1811
1861
|
this.setChorusSend(channel, note, scheduleTime);
|
|
1812
1862
|
},
|
|
1813
|
-
reverbEffectsSend: (channel, note,
|
|
1863
|
+
reverbEffectsSend: (channel, note, scheduleTime) => {
|
|
1814
1864
|
this.setReverbSend(channel, note, scheduleTime);
|
|
1815
1865
|
},
|
|
1816
|
-
delayModLFO: (_channel, note,
|
|
1817
|
-
|
|
1818
|
-
|
|
1866
|
+
delayModLFO: (_channel, note, _scheduleTime) => {
|
|
1867
|
+
if (0 < channel.state.modulationDepth) {
|
|
1868
|
+
this.setDelayModLFO(note);
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1872
|
+
if (0 < channel.state.modulationDepth) {
|
|
1873
|
+
this.setFreqModLFO(note, scheduleTime);
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
delayVibLFO: (channel, note, _scheduleTime) => {
|
|
1819
1877
|
if (0 < channel.state.vibratoDepth) {
|
|
1820
|
-
|
|
1821
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1822
|
-
if (scheduleTime < prevStartTime)
|
|
1823
|
-
return;
|
|
1824
|
-
const value = note.voiceParams.delayVibLFO;
|
|
1825
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
1826
|
-
note.vibratoLFO.stop(scheduleTime);
|
|
1827
|
-
note.vibratoLFO.start(startTime);
|
|
1878
|
+
setDelayVibLFO(channel, note);
|
|
1828
1879
|
}
|
|
1829
1880
|
},
|
|
1830
|
-
freqVibLFO: (channel, note,
|
|
1881
|
+
freqVibLFO: (channel, note, scheduleTime) => {
|
|
1831
1882
|
if (0 < channel.state.vibratoDepth) {
|
|
1832
1883
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1833
1884
|
}
|
|
@@ -1856,7 +1907,7 @@ class Midy {
|
|
|
1856
1907
|
continue;
|
|
1857
1908
|
note.voiceParams[key] = value;
|
|
1858
1909
|
if (key in this.voiceParamsHandlers) {
|
|
1859
|
-
this.voiceParamsHandlers[key](channel, note,
|
|
1910
|
+
this.voiceParamsHandlers[key](channel, note, scheduleTime);
|
|
1860
1911
|
}
|
|
1861
1912
|
else {
|
|
1862
1913
|
if (volumeEnvelopeKeySet.has(key))
|
|
@@ -1950,22 +2001,20 @@ class Midy {
|
|
|
1950
2001
|
this.updateModulation(channel, scheduleTime);
|
|
1951
2002
|
}
|
|
1952
2003
|
updatePortamento(channel, scheduleTime) {
|
|
2004
|
+
if (channel.isDrum)
|
|
2005
|
+
return;
|
|
1953
2006
|
this.processScheduledNotes(channel, (note) => {
|
|
1954
|
-
if (
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1960
|
-
}
|
|
2007
|
+
if (this.isPortamento(channel, note)) {
|
|
2008
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2009
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2010
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
2011
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1961
2012
|
}
|
|
1962
2013
|
else {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1968
|
-
}
|
|
2014
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2015
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2016
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
2017
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1969
2018
|
}
|
|
1970
2019
|
});
|
|
1971
2020
|
}
|
|
@@ -2035,13 +2084,13 @@ class Midy {
|
|
|
2035
2084
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2036
2085
|
}
|
|
2037
2086
|
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
2038
|
-
const state = channel.state;
|
|
2039
|
-
const defaultVolume = state.volume * state.expression;
|
|
2040
|
-
const defaultPan = state.pan;
|
|
2041
2087
|
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
2042
|
-
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2043
2088
|
if (!gainL)
|
|
2044
2089
|
return;
|
|
2090
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2091
|
+
const state = channel.state;
|
|
2092
|
+
const defaultVolume = state.volume * state.expression;
|
|
2093
|
+
const defaultPan = state.pan;
|
|
2045
2094
|
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
2046
2095
|
const volume = (0 <= keyBasedVolume)
|
|
2047
2096
|
? defaultVolume * keyBasedVolume / 64
|
|
@@ -2071,6 +2120,9 @@ class Midy {
|
|
|
2071
2120
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
2072
2121
|
}
|
|
2073
2122
|
}
|
|
2123
|
+
isPortamento(channel, note) {
|
|
2124
|
+
return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
|
|
2125
|
+
}
|
|
2074
2126
|
setPortamento(channelNumber, value, scheduleTime) {
|
|
2075
2127
|
const channel = this.channels[channelNumber];
|
|
2076
2128
|
if (channel.isDrum)
|
|
@@ -2107,7 +2159,7 @@ class Midy {
|
|
|
2107
2159
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2108
2160
|
state.softPedal = softPedal / 127;
|
|
2109
2161
|
this.processScheduledNotes(channel, (note) => {
|
|
2110
|
-
if (
|
|
2162
|
+
if (this.isPortamento(channel, note)) {
|
|
2111
2163
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2112
2164
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2113
2165
|
}
|
|
@@ -2117,18 +2169,27 @@ class Midy {
|
|
|
2117
2169
|
}
|
|
2118
2170
|
});
|
|
2119
2171
|
}
|
|
2120
|
-
setFilterResonance(channelNumber,
|
|
2172
|
+
setFilterResonance(channelNumber, ccValue, scheduleTime) {
|
|
2121
2173
|
const channel = this.channels[channelNumber];
|
|
2122
2174
|
if (channel.isDrum)
|
|
2123
2175
|
return;
|
|
2124
2176
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2125
2177
|
const state = channel.state;
|
|
2126
|
-
state.filterResonance =
|
|
2178
|
+
state.filterResonance = ccValue / 127;
|
|
2179
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
2127
2180
|
this.processScheduledNotes(channel, (note) => {
|
|
2128
|
-
const Q = note.voiceParams.initialFilterQ / 5 *
|
|
2181
|
+
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
2129
2182
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2130
2183
|
});
|
|
2131
2184
|
}
|
|
2185
|
+
getRelativeKeyBasedValue(channel, note, controllerType) {
|
|
2186
|
+
const ccState = channel.state.array[128 + controllerType];
|
|
2187
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, controllerType);
|
|
2188
|
+
if (keyBasedValue < 0)
|
|
2189
|
+
return ccState;
|
|
2190
|
+
const keyValue = ccState + keyBasedValue / 127 - 0.5;
|
|
2191
|
+
return keyValue < 0 ? keyValue : 0;
|
|
2192
|
+
}
|
|
2132
2193
|
setReleaseTime(channelNumber, releaseTime, scheduleTime) {
|
|
2133
2194
|
const channel = this.channels[channelNumber];
|
|
2134
2195
|
if (channel.isDrum)
|
|
@@ -2143,9 +2204,9 @@ class Midy {
|
|
|
2143
2204
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2144
2205
|
channel.state.attackTime = attackTime / 127;
|
|
2145
2206
|
this.processScheduledNotes(channel, (note) => {
|
|
2146
|
-
if (note.startTime
|
|
2147
|
-
|
|
2148
|
-
|
|
2207
|
+
if (scheduleTime < note.startTime) {
|
|
2208
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2209
|
+
}
|
|
2149
2210
|
});
|
|
2150
2211
|
}
|
|
2151
2212
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
@@ -2156,7 +2217,7 @@ class Midy {
|
|
|
2156
2217
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2157
2218
|
state.brightness = brightness / 127;
|
|
2158
2219
|
this.processScheduledNotes(channel, (note) => {
|
|
2159
|
-
if (
|
|
2220
|
+
if (this.isPortamento(channel, note)) {
|
|
2160
2221
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2161
2222
|
}
|
|
2162
2223
|
else {
|
|
@@ -2306,8 +2367,8 @@ class Midy {
|
|
|
2306
2367
|
}
|
|
2307
2368
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2308
2369
|
const channel = this.channels[channelNumber];
|
|
2309
|
-
this.limitData(channel, 0, 127, 0,
|
|
2310
|
-
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2370
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
2371
|
+
const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2311
2372
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2312
2373
|
}
|
|
2313
2374
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
@@ -2317,7 +2378,7 @@ class Midy {
|
|
|
2317
2378
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2318
2379
|
const state = channel.state;
|
|
2319
2380
|
const prev = state.pitchWheelSensitivity;
|
|
2320
|
-
const next = value /
|
|
2381
|
+
const next = value / 12800;
|
|
2321
2382
|
state.pitchWheelSensitivity = next;
|
|
2322
2383
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2323
2384
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2326,7 +2387,8 @@ class Midy {
|
|
|
2326
2387
|
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
2327
2388
|
const channel = this.channels[channelNumber];
|
|
2328
2389
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2329
|
-
const
|
|
2390
|
+
const value = channel.dataMSB * 128 + channel.dataLSB;
|
|
2391
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2330
2392
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2331
2393
|
}
|
|
2332
2394
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2335,7 +2397,7 @@ class Midy {
|
|
|
2335
2397
|
return;
|
|
2336
2398
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2337
2399
|
const prev = channel.fineTuning;
|
|
2338
|
-
const next =
|
|
2400
|
+
const next = value;
|
|
2339
2401
|
channel.fineTuning = next;
|
|
2340
2402
|
channel.detune += next - prev;
|
|
2341
2403
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2343,7 +2405,7 @@ class Midy {
|
|
|
2343
2405
|
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2344
2406
|
const channel = this.channels[channelNumber];
|
|
2345
2407
|
this.limitDataMSB(channel, 0, 127);
|
|
2346
|
-
const coarseTuning = channel.dataMSB;
|
|
2408
|
+
const coarseTuning = (channel.dataMSB - 64) * 100;
|
|
2347
2409
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2348
2410
|
}
|
|
2349
2411
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2352,7 +2414,7 @@ class Midy {
|
|
|
2352
2414
|
return;
|
|
2353
2415
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2354
2416
|
const prev = channel.coarseTuning;
|
|
2355
|
-
const next =
|
|
2417
|
+
const next = value;
|
|
2356
2418
|
channel.coarseTuning = next;
|
|
2357
2419
|
channel.detune += next - prev;
|
|
2358
2420
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2360,22 +2422,22 @@ class Midy {
|
|
|
2360
2422
|
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2361
2423
|
const channel = this.channels[channelNumber];
|
|
2362
2424
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2363
|
-
const
|
|
2364
|
-
this.setModulationDepthRange(channelNumber,
|
|
2425
|
+
const value = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2426
|
+
this.setModulationDepthRange(channelNumber, value, scheduleTime);
|
|
2365
2427
|
}
|
|
2366
|
-
setModulationDepthRange(channelNumber,
|
|
2428
|
+
setModulationDepthRange(channelNumber, value, scheduleTime) {
|
|
2367
2429
|
const channel = this.channels[channelNumber];
|
|
2368
2430
|
if (channel.isDrum)
|
|
2369
2431
|
return;
|
|
2370
2432
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2371
|
-
channel.modulationDepthRange =
|
|
2433
|
+
channel.modulationDepthRange = value;
|
|
2372
2434
|
this.updateModulation(channel, scheduleTime);
|
|
2373
2435
|
}
|
|
2374
2436
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2375
2437
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2376
2438
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2377
2439
|
}
|
|
2378
|
-
|
|
2440
|
+
resetChannelStates(channelNumber) {
|
|
2379
2441
|
const scheduleTime = this.audioContext.currentTime;
|
|
2380
2442
|
const channel = this.channels[channelNumber];
|
|
2381
2443
|
const state = channel.state;
|
|
@@ -2393,8 +2455,8 @@ class Midy {
|
|
|
2393
2455
|
}
|
|
2394
2456
|
this.resetChannelTable(channel);
|
|
2395
2457
|
this.mode = "GM2";
|
|
2396
|
-
this.masterFineTuning = 0; //
|
|
2397
|
-
this.masterCoarseTuning = 0; //
|
|
2458
|
+
this.masterFineTuning = 0; // cent
|
|
2459
|
+
this.masterCoarseTuning = 0; // cent
|
|
2398
2460
|
}
|
|
2399
2461
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2400
2462
|
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
@@ -2491,11 +2553,9 @@ class Midy {
|
|
|
2491
2553
|
const channel = this.channels[i];
|
|
2492
2554
|
channel.bankMSB = 0;
|
|
2493
2555
|
channel.bankLSB = 0;
|
|
2494
|
-
channel.bank = 0;
|
|
2495
2556
|
channel.isDrum = false;
|
|
2496
2557
|
}
|
|
2497
2558
|
this.channels[9].bankMSB = 1;
|
|
2498
|
-
this.channels[9].bank = 128;
|
|
2499
2559
|
this.channels[9].isDrum = true;
|
|
2500
2560
|
}
|
|
2501
2561
|
GM2SystemOn(scheduleTime) {
|
|
@@ -2506,11 +2566,9 @@ class Midy {
|
|
|
2506
2566
|
const channel = this.channels[i];
|
|
2507
2567
|
channel.bankMSB = 121;
|
|
2508
2568
|
channel.bankLSB = 0;
|
|
2509
|
-
channel.bank = 121 * 128;
|
|
2510
2569
|
channel.isDrum = false;
|
|
2511
2570
|
}
|
|
2512
2571
|
this.channels[9].bankMSB = 120;
|
|
2513
|
-
this.channels[9].bank = 120 * 128;
|
|
2514
2572
|
this.channels[9].isDrum = true;
|
|
2515
2573
|
}
|
|
2516
2574
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -2568,24 +2626,20 @@ class Midy {
|
|
|
2568
2626
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2569
2627
|
this.setMasterVolume(volume, scheduleTime);
|
|
2570
2628
|
}
|
|
2571
|
-
setMasterVolume(
|
|
2629
|
+
setMasterVolume(value, scheduleTime) {
|
|
2572
2630
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
else {
|
|
2577
|
-
this.masterVolume.gain
|
|
2578
|
-
.cancelScheduledValues(scheduleTime)
|
|
2579
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
2580
|
-
}
|
|
2631
|
+
this.masterVolume.gain
|
|
2632
|
+
.cancelScheduledValues(scheduleTime)
|
|
2633
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
2581
2634
|
}
|
|
2582
2635
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2583
|
-
const
|
|
2636
|
+
const value = (data[5] * 128 + data[4]) / 16383;
|
|
2637
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2584
2638
|
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2585
2639
|
}
|
|
2586
2640
|
setMasterFineTuning(value, scheduleTime) {
|
|
2587
2641
|
const prev = this.masterFineTuning;
|
|
2588
|
-
const next =
|
|
2642
|
+
const next = value;
|
|
2589
2643
|
this.masterFineTuning = next;
|
|
2590
2644
|
const detuneChange = next - prev;
|
|
2591
2645
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2597,12 +2651,12 @@ class Midy {
|
|
|
2597
2651
|
}
|
|
2598
2652
|
}
|
|
2599
2653
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2600
|
-
const coarseTuning = data[4];
|
|
2654
|
+
const coarseTuning = (data[4] - 64) * 100;
|
|
2601
2655
|
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2602
2656
|
}
|
|
2603
2657
|
setMasterCoarseTuning(value, scheduleTime) {
|
|
2604
2658
|
const prev = this.masterCoarseTuning;
|
|
2605
|
-
const next =
|
|
2659
|
+
const next = value;
|
|
2606
2660
|
this.masterCoarseTuning = next;
|
|
2607
2661
|
const detuneChange = next - prev;
|
|
2608
2662
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2988,29 +3042,88 @@ class Midy {
|
|
|
2988
3042
|
}
|
|
2989
3043
|
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2990
3044
|
const index = keyNumber * 128 + controllerType;
|
|
2991
|
-
const controlValue = channel.
|
|
3045
|
+
const controlValue = channel.keyBasedTable[index];
|
|
2992
3046
|
return controlValue;
|
|
2993
3047
|
}
|
|
3048
|
+
createKeyBasedControllerHandlers() {
|
|
3049
|
+
const handlers = new Array(128);
|
|
3050
|
+
handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3051
|
+
handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3052
|
+
handlers[71] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3053
|
+
if (note.noteNumber === keyNumber) {
|
|
3054
|
+
const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
|
|
3055
|
+
const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
|
|
3056
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
3057
|
+
}
|
|
3058
|
+
});
|
|
3059
|
+
handlers[73] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3060
|
+
if (note.noteNumber === keyNumber) {
|
|
3061
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3062
|
+
}
|
|
3063
|
+
});
|
|
3064
|
+
handlers[74] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3065
|
+
if (note.noteNumber === keyNumber) {
|
|
3066
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
handlers[75] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3070
|
+
if (note.noteNumber === keyNumber) {
|
|
3071
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
handlers[76] = (channel, keyNumber, scheduleTime) => {
|
|
3075
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3076
|
+
return;
|
|
3077
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3078
|
+
if (note.noteNumber === keyNumber) {
|
|
3079
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
};
|
|
3083
|
+
handlers[77] = (channel, keyNumber, scheduleTime) => {
|
|
3084
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3085
|
+
return;
|
|
3086
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3087
|
+
if (note.noteNumber === keyNumber) {
|
|
3088
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
};
|
|
3092
|
+
handlers[78] = (channel, keyNumber) => {
|
|
3093
|
+
if (channel.state.vibratoDepth <= 0)
|
|
3094
|
+
return;
|
|
3095
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3096
|
+
if (note.noteNumber === keyNumber)
|
|
3097
|
+
this.setDelayVibLFO(channel, note);
|
|
3098
|
+
});
|
|
3099
|
+
};
|
|
3100
|
+
handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3101
|
+
if (note.noteNumber === keyNumber) {
|
|
3102
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
3105
|
+
handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
3106
|
+
if (note.noteNumber === keyNumber) {
|
|
3107
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
return handlers;
|
|
3111
|
+
}
|
|
2994
3112
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2995
3113
|
const channelNumber = data[4];
|
|
2996
3114
|
const channel = this.channels[channelNumber];
|
|
2997
3115
|
if (!channel.isDrum)
|
|
2998
3116
|
return;
|
|
2999
3117
|
const keyNumber = data[5];
|
|
3000
|
-
const table = channel.
|
|
3118
|
+
const table = channel.keyBasedTable;
|
|
3001
3119
|
for (let i = 6; i < data.length; i += 2) {
|
|
3002
3120
|
const controllerType = data[i];
|
|
3003
3121
|
const value = data[i + 1];
|
|
3004
3122
|
const index = keyNumber * 128 + controllerType;
|
|
3005
3123
|
table[index] = value;
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3010
|
-
break;
|
|
3011
|
-
default: // TODO
|
|
3012
|
-
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
3013
|
-
}
|
|
3124
|
+
const handler = this.keyBasedControllerHandlers[controllerType];
|
|
3125
|
+
if (handler)
|
|
3126
|
+
handler(channel, keyNumber, scheduleTime);
|
|
3014
3127
|
}
|
|
3015
3128
|
}
|
|
3016
3129
|
handleSysEx(data, scheduleTime) {
|
|
@@ -3052,7 +3165,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3052
3165
|
scheduleIndex: 0,
|
|
3053
3166
|
detune: 0,
|
|
3054
3167
|
programNumber: 0,
|
|
3055
|
-
bank: 121 * 128,
|
|
3056
3168
|
bankMSB: 121,
|
|
3057
3169
|
bankLSB: 0,
|
|
3058
3170
|
dataMSB: 0,
|
|
@@ -3061,7 +3173,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3061
3173
|
rpnLSB: 127,
|
|
3062
3174
|
mono: false, // CC#124, CC#125
|
|
3063
3175
|
modulationDepthRange: 50, // cent
|
|
3064
|
-
fineTuning: 0, //
|
|
3065
|
-
coarseTuning: 0, //
|
|
3176
|
+
fineTuning: 0, // cent
|
|
3177
|
+
coarseTuning: 0, // cent
|
|
3066
3178
|
}
|
|
3067
3179
|
});
|