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