@marmooo/midy 0.4.1 → 0.4.3
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 +13 -0
- package/esm/midy-GM1.d.ts +10 -8
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +223 -125
- package/esm/midy-GM2.d.ts +10 -8
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +276 -169
- package/esm/midy-GMLite.d.ts +10 -8
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +219 -123
- package/esm/midy.d.ts +12 -8
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +327 -189
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +10 -8
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +223 -125
- package/script/midy-GM2.d.ts +10 -8
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +276 -169
- package/script/midy-GMLite.d.ts +10 -8
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +219 -123
- package/script/midy.d.ts +12 -8
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +327 -189
package/script/midy-GM2.js
CHANGED
|
@@ -17,23 +17,23 @@ class Note {
|
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
-
Object.defineProperty(this, "
|
|
20
|
+
Object.defineProperty(this, "adjustedBaseFreq", {
|
|
21
21
|
enumerable: true,
|
|
22
22
|
configurable: true,
|
|
23
23
|
writable: true,
|
|
24
|
-
value:
|
|
24
|
+
value: 20000
|
|
25
25
|
});
|
|
26
|
-
Object.defineProperty(this, "
|
|
26
|
+
Object.defineProperty(this, "index", {
|
|
27
27
|
enumerable: true,
|
|
28
28
|
configurable: true,
|
|
29
29
|
writable: true,
|
|
30
|
-
value:
|
|
30
|
+
value: -1
|
|
31
31
|
});
|
|
32
|
-
Object.defineProperty(this, "
|
|
32
|
+
Object.defineProperty(this, "ending", {
|
|
33
33
|
enumerable: true,
|
|
34
34
|
configurable: true,
|
|
35
35
|
writable: true,
|
|
36
|
-
value:
|
|
36
|
+
value: false
|
|
37
37
|
});
|
|
38
38
|
Object.defineProperty(this, "bufferSource", {
|
|
39
39
|
enumerable: true,
|
|
@@ -110,6 +110,9 @@ class Note {
|
|
|
110
110
|
this.noteNumber = noteNumber;
|
|
111
111
|
this.velocity = velocity;
|
|
112
112
|
this.startTime = startTime;
|
|
113
|
+
this.ready = new Promise((resolve) => {
|
|
114
|
+
this.resolveReady = resolve;
|
|
115
|
+
});
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
const drumExclusiveClassesByKit = new Array(57);
|
|
@@ -231,8 +234,15 @@ const pitchEnvelopeKeys = [
|
|
|
231
234
|
"playbackRate",
|
|
232
235
|
];
|
|
233
236
|
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
234
|
-
|
|
237
|
+
const defaultPressureValues = new Int8Array([64, 64, 64, 0, 0, 0]);
|
|
238
|
+
function cbToRatio(cb) {
|
|
239
|
+
return Math.pow(10, cb / 200);
|
|
240
|
+
}
|
|
241
|
+
const decayCurve = 1 / (-Math.log(cbToRatio(-1000)));
|
|
242
|
+
const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
|
|
243
|
+
class MidyGM2 extends EventTarget {
|
|
235
244
|
constructor(audioContext) {
|
|
245
|
+
super();
|
|
236
246
|
Object.defineProperty(this, "mode", {
|
|
237
247
|
enumerable: true,
|
|
238
248
|
configurable: true,
|
|
@@ -393,6 +403,26 @@ class MidyGM2 {
|
|
|
393
403
|
writable: true,
|
|
394
404
|
value: false
|
|
395
405
|
});
|
|
406
|
+
Object.defineProperty(this, "totalTimeEventTypes", {
|
|
407
|
+
enumerable: true,
|
|
408
|
+
configurable: true,
|
|
409
|
+
writable: true,
|
|
410
|
+
value: new Set([
|
|
411
|
+
"noteOff",
|
|
412
|
+
])
|
|
413
|
+
});
|
|
414
|
+
Object.defineProperty(this, "tempo", {
|
|
415
|
+
enumerable: true,
|
|
416
|
+
configurable: true,
|
|
417
|
+
writable: true,
|
|
418
|
+
value: 1
|
|
419
|
+
});
|
|
420
|
+
Object.defineProperty(this, "loop", {
|
|
421
|
+
enumerable: true,
|
|
422
|
+
configurable: true,
|
|
423
|
+
writable: true,
|
|
424
|
+
value: false
|
|
425
|
+
});
|
|
396
426
|
Object.defineProperty(this, "playPromise", {
|
|
397
427
|
enumerable: true,
|
|
398
428
|
configurable: true,
|
|
@@ -506,13 +536,13 @@ class MidyGM2 {
|
|
|
506
536
|
this.totalTime = this.calcTotalTime();
|
|
507
537
|
}
|
|
508
538
|
cacheVoiceIds() {
|
|
509
|
-
const timeline = this
|
|
539
|
+
const { channels, timeline, voiceCounter } = this;
|
|
510
540
|
for (let i = 0; i < timeline.length; i++) {
|
|
511
541
|
const event = timeline[i];
|
|
512
542
|
switch (event.type) {
|
|
513
543
|
case "noteOn": {
|
|
514
|
-
const audioBufferId = this.getVoiceId(
|
|
515
|
-
|
|
544
|
+
const audioBufferId = this.getVoiceId(channels[event.channel], event.noteNumber, event.velocity);
|
|
545
|
+
voiceCounter.set(audioBufferId, (voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
516
546
|
break;
|
|
517
547
|
}
|
|
518
548
|
case "controller":
|
|
@@ -527,9 +557,9 @@ class MidyGM2 {
|
|
|
527
557
|
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
528
558
|
}
|
|
529
559
|
}
|
|
530
|
-
for (const [audioBufferId, count] of
|
|
560
|
+
for (const [audioBufferId, count] of voiceCounter) {
|
|
531
561
|
if (count === 1)
|
|
532
|
-
|
|
562
|
+
voiceCounter.delete(audioBufferId);
|
|
533
563
|
}
|
|
534
564
|
this.GM2SystemOn();
|
|
535
565
|
}
|
|
@@ -538,8 +568,12 @@ class MidyGM2 {
|
|
|
538
568
|
const bankTable = this.soundFontTable[programNumber];
|
|
539
569
|
if (!bankTable)
|
|
540
570
|
return;
|
|
541
|
-
|
|
542
|
-
|
|
571
|
+
let bank = channel.isDrum ? 128 : channel.bankLSB;
|
|
572
|
+
if (bankTable[bank] === undefined) {
|
|
573
|
+
if (channel.isDrum)
|
|
574
|
+
return;
|
|
575
|
+
bank = 0;
|
|
576
|
+
}
|
|
543
577
|
const soundFontIndex = bankTable[bank];
|
|
544
578
|
if (soundFontIndex === undefined)
|
|
545
579
|
return;
|
|
@@ -565,7 +599,7 @@ class MidyGM2 {
|
|
|
565
599
|
resetChannelTable(channel) {
|
|
566
600
|
channel.controlTable.fill(-1);
|
|
567
601
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
568
|
-
channel.channelPressureTable.
|
|
602
|
+
channel.channelPressureTable.set(defaultPressureValues);
|
|
569
603
|
channel.keyBasedTable.fill(-1);
|
|
570
604
|
}
|
|
571
605
|
createChannels(audioContext) {
|
|
@@ -581,7 +615,7 @@ class MidyGM2 {
|
|
|
581
615
|
sostenutoNotes: [],
|
|
582
616
|
controlTable: this.initControlTable(),
|
|
583
617
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
584
|
-
channelPressureTable: new Int8Array(
|
|
618
|
+
channelPressureTable: new Int8Array(defaultPressureValues),
|
|
585
619
|
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
586
620
|
keyBasedGainLs: new Array(128),
|
|
587
621
|
keyBasedGainRs: new Array(128),
|
|
@@ -612,19 +646,21 @@ class MidyGM2 {
|
|
|
612
646
|
}
|
|
613
647
|
return bufferSource;
|
|
614
648
|
}
|
|
615
|
-
|
|
649
|
+
scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
616
650
|
const timeOffset = this.resumeTime - this.startTime;
|
|
617
651
|
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
618
652
|
const schedulingOffset = this.startDelay - timeOffset;
|
|
619
653
|
const timeline = this.timeline;
|
|
654
|
+
const inverseTempo = 1 / this.tempo;
|
|
620
655
|
while (queueIndex < timeline.length) {
|
|
621
656
|
const event = timeline[queueIndex];
|
|
622
|
-
|
|
657
|
+
const t = event.startTime * inverseTempo;
|
|
658
|
+
if (lookAheadCheckTime < t)
|
|
623
659
|
break;
|
|
624
|
-
const startTime =
|
|
660
|
+
const startTime = t + schedulingOffset;
|
|
625
661
|
switch (event.type) {
|
|
626
662
|
case "noteOn":
|
|
627
|
-
|
|
663
|
+
this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
628
664
|
break;
|
|
629
665
|
case "noteOff": {
|
|
630
666
|
this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
@@ -650,8 +686,10 @@ class MidyGM2 {
|
|
|
650
686
|
return queueIndex;
|
|
651
687
|
}
|
|
652
688
|
getQueueIndex(second) {
|
|
653
|
-
|
|
654
|
-
|
|
689
|
+
const timeline = this.timeline;
|
|
690
|
+
const inverseTempo = 1 / this.tempo;
|
|
691
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
692
|
+
if (second <= timeline[i].startTime * inverseTempo) {
|
|
655
693
|
return i;
|
|
656
694
|
}
|
|
657
695
|
}
|
|
@@ -662,87 +700,120 @@ class MidyGM2 {
|
|
|
662
700
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
663
701
|
this.voiceCache.clear();
|
|
664
702
|
this.realtimeVoiceCache.clear();
|
|
665
|
-
|
|
666
|
-
|
|
703
|
+
const channels = this.channels;
|
|
704
|
+
for (let i = 0; i < channels.length; i++) {
|
|
705
|
+
channels[i].scheduledNotes = [];
|
|
667
706
|
this.resetChannelStates(i);
|
|
668
707
|
}
|
|
669
708
|
}
|
|
670
709
|
updateStates(queueIndex, nextQueueIndex) {
|
|
710
|
+
const { timeline, resumeTime } = this;
|
|
711
|
+
const inverseTempo = 1 / this.tempo;
|
|
712
|
+
const now = this.audioContext.currentTime;
|
|
671
713
|
if (nextQueueIndex < queueIndex)
|
|
672
714
|
queueIndex = 0;
|
|
673
715
|
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
674
|
-
const event =
|
|
716
|
+
const event = timeline[i];
|
|
675
717
|
switch (event.type) {
|
|
676
718
|
case "controller":
|
|
677
|
-
this.setControlChange(event.channel, event.controllerType, event.value,
|
|
719
|
+
this.setControlChange(event.channel, event.controllerType, event.value, now - resumeTime + event.startTime * inverseTempo);
|
|
678
720
|
break;
|
|
679
721
|
case "programChange":
|
|
680
|
-
this.setProgramChange(event.channel, event.programNumber,
|
|
722
|
+
this.setProgramChange(event.channel, event.programNumber, now - resumeTime + event.startTime * inverseTempo);
|
|
681
723
|
break;
|
|
682
724
|
case "pitchBend":
|
|
683
|
-
this.setPitchBend(event.channel, event.value + 8192,
|
|
725
|
+
this.setPitchBend(event.channel, event.value + 8192, now - resumeTime + event.startTime * inverseTempo);
|
|
684
726
|
break;
|
|
685
727
|
case "sysEx":
|
|
686
|
-
this.handleSysEx(event.data,
|
|
728
|
+
this.handleSysEx(event.data, now - resumeTime + event.startTime * inverseTempo);
|
|
687
729
|
}
|
|
688
730
|
}
|
|
689
731
|
}
|
|
690
732
|
async playNotes() {
|
|
691
|
-
|
|
692
|
-
|
|
733
|
+
const audioContext = this.audioContext;
|
|
734
|
+
if (audioContext.state === "suspended") {
|
|
735
|
+
await audioContext.resume();
|
|
693
736
|
}
|
|
737
|
+
const paused = this.isPaused;
|
|
694
738
|
this.isPlaying = true;
|
|
695
739
|
this.isPaused = false;
|
|
696
|
-
this.startTime =
|
|
740
|
+
this.startTime = audioContext.currentTime;
|
|
741
|
+
if (paused) {
|
|
742
|
+
this.dispatchEvent(new Event("resumed"));
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
this.dispatchEvent(new Event("started"));
|
|
746
|
+
}
|
|
697
747
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
698
|
-
let
|
|
748
|
+
let exitReason;
|
|
699
749
|
this.notePromises = [];
|
|
700
|
-
while (
|
|
701
|
-
const now =
|
|
750
|
+
while (true) {
|
|
751
|
+
const now = audioContext.currentTime;
|
|
702
752
|
if (0 < this.lastActiveSensing &&
|
|
703
753
|
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
704
754
|
await this.stopNotes(0, true, now);
|
|
705
|
-
await
|
|
706
|
-
|
|
755
|
+
await audioContext.suspend();
|
|
756
|
+
exitReason = "aborted";
|
|
707
757
|
break;
|
|
708
758
|
}
|
|
759
|
+
if (this.totalTime < this.currentTime() ||
|
|
760
|
+
this.timeline.length <= queueIndex) {
|
|
761
|
+
await this.stopNotes(0, true, now);
|
|
762
|
+
if (this.loop) {
|
|
763
|
+
this.resetAllStates();
|
|
764
|
+
this.startTime = audioContext.currentTime;
|
|
765
|
+
this.resumeTime = 0;
|
|
766
|
+
queueIndex = 0;
|
|
767
|
+
this.dispatchEvent(new Event("looped"));
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
await audioContext.suspend();
|
|
772
|
+
exitReason = "ended";
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
709
776
|
if (this.isPausing) {
|
|
710
777
|
await this.stopNotes(0, true, now);
|
|
711
|
-
await
|
|
712
|
-
this.
|
|
778
|
+
await audioContext.suspend();
|
|
779
|
+
this.isPausing = false;
|
|
780
|
+
exitReason = "paused";
|
|
713
781
|
break;
|
|
714
782
|
}
|
|
715
783
|
else if (this.isStopping) {
|
|
716
784
|
await this.stopNotes(0, true, now);
|
|
717
|
-
await
|
|
718
|
-
|
|
785
|
+
await audioContext.suspend();
|
|
786
|
+
this.isStopping = false;
|
|
787
|
+
exitReason = "stopped";
|
|
719
788
|
break;
|
|
720
789
|
}
|
|
721
790
|
else if (this.isSeeking) {
|
|
722
|
-
|
|
723
|
-
this.startTime =
|
|
791
|
+
this.stopNotes(0, true, now);
|
|
792
|
+
this.startTime = audioContext.currentTime;
|
|
724
793
|
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
725
794
|
this.updateStates(queueIndex, nextQueueIndex);
|
|
726
795
|
queueIndex = nextQueueIndex;
|
|
727
796
|
this.isSeeking = false;
|
|
797
|
+
this.dispatchEvent(new Event("seeked"));
|
|
728
798
|
continue;
|
|
729
799
|
}
|
|
730
|
-
queueIndex =
|
|
800
|
+
queueIndex = this.scheduleTimelineEvents(now, queueIndex);
|
|
731
801
|
const waitTime = now + this.noteCheckInterval;
|
|
732
802
|
await this.scheduleTask(() => { }, waitTime);
|
|
733
803
|
}
|
|
734
|
-
if (
|
|
735
|
-
const now = this.audioContext.currentTime;
|
|
736
|
-
await this.stopNotes(0, true, now);
|
|
737
|
-
await this.audioContext.suspend();
|
|
738
|
-
finished = true;
|
|
739
|
-
}
|
|
740
|
-
if (finished) {
|
|
741
|
-
this.notePromises = [];
|
|
804
|
+
if (exitReason !== "paused") {
|
|
742
805
|
this.resetAllStates();
|
|
743
806
|
this.lastActiveSensing = 0;
|
|
744
807
|
}
|
|
745
808
|
this.isPlaying = false;
|
|
809
|
+
if (exitReason === "paused") {
|
|
810
|
+
this.isPaused = true;
|
|
811
|
+
this.dispatchEvent(new Event("paused"));
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
this.isPaused = false;
|
|
815
|
+
this.dispatchEvent(new Event(exitReason));
|
|
816
|
+
}
|
|
746
817
|
}
|
|
747
818
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
748
819
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -859,11 +930,13 @@ class MidyGM2 {
|
|
|
859
930
|
return Promise.all(promises);
|
|
860
931
|
}
|
|
861
932
|
stopNotes(velocity, force, scheduleTime) {
|
|
862
|
-
const
|
|
863
|
-
for (let i = 0; i <
|
|
864
|
-
|
|
933
|
+
const channels = this.channels;
|
|
934
|
+
for (let i = 0; i < channels.length; i++) {
|
|
935
|
+
this.stopChannelNotes(i, velocity, force, scheduleTime);
|
|
865
936
|
}
|
|
866
|
-
|
|
937
|
+
const stopPromise = Promise.all(this.notePromises);
|
|
938
|
+
this.notePromises = [];
|
|
939
|
+
return stopPromise;
|
|
867
940
|
}
|
|
868
941
|
async start() {
|
|
869
942
|
if (this.isPlaying || this.isPaused)
|
|
@@ -879,24 +952,20 @@ class MidyGM2 {
|
|
|
879
952
|
return;
|
|
880
953
|
this.isStopping = true;
|
|
881
954
|
await this.playPromise;
|
|
882
|
-
this.isStopping = false;
|
|
883
955
|
}
|
|
884
956
|
async pause() {
|
|
885
957
|
if (!this.isPlaying || this.isPaused)
|
|
886
958
|
return;
|
|
887
959
|
const now = this.audioContext.currentTime;
|
|
888
|
-
this.resumeTime = now
|
|
960
|
+
this.resumeTime = now + this.resumeTime - this.startTime;
|
|
889
961
|
this.isPausing = true;
|
|
890
962
|
await this.playPromise;
|
|
891
|
-
this.isPausing = false;
|
|
892
|
-
this.isPaused = true;
|
|
893
963
|
}
|
|
894
964
|
async resume() {
|
|
895
965
|
if (!this.isPaused)
|
|
896
966
|
return;
|
|
897
967
|
this.playPromise = this.playNotes();
|
|
898
968
|
await this.playPromise;
|
|
899
|
-
this.isPaused = false;
|
|
900
969
|
}
|
|
901
970
|
seekTo(second) {
|
|
902
971
|
this.resumeTime = second;
|
|
@@ -905,11 +974,17 @@ class MidyGM2 {
|
|
|
905
974
|
}
|
|
906
975
|
}
|
|
907
976
|
calcTotalTime() {
|
|
977
|
+
const totalTimeEventTypes = this.totalTimeEventTypes;
|
|
978
|
+
const timeline = this.timeline;
|
|
979
|
+
const inverseTempo = 1 / this.tempo;
|
|
908
980
|
let totalTime = 0;
|
|
909
|
-
for (let i = 0; i <
|
|
910
|
-
const event =
|
|
911
|
-
if (
|
|
912
|
-
|
|
981
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
982
|
+
const event = timeline[i];
|
|
983
|
+
if (!totalTimeEventTypes.has(event.type))
|
|
984
|
+
continue;
|
|
985
|
+
const t = event.startTime * inverseTempo;
|
|
986
|
+
if (totalTime < t)
|
|
987
|
+
totalTime = t;
|
|
913
988
|
}
|
|
914
989
|
return totalTime + this.startDelay;
|
|
915
990
|
}
|
|
@@ -919,19 +994,23 @@ class MidyGM2 {
|
|
|
919
994
|
const now = this.audioContext.currentTime;
|
|
920
995
|
return now + this.resumeTime - this.startTime;
|
|
921
996
|
}
|
|
922
|
-
processScheduledNotes(channel, callback) {
|
|
997
|
+
async processScheduledNotes(channel, callback) {
|
|
923
998
|
const scheduledNotes = channel.scheduledNotes;
|
|
999
|
+
const tasks = [];
|
|
924
1000
|
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
925
1001
|
const note = scheduledNotes[i];
|
|
926
1002
|
if (!note)
|
|
927
1003
|
continue;
|
|
928
1004
|
if (note.ending)
|
|
929
1005
|
continue;
|
|
930
|
-
callback(note);
|
|
1006
|
+
const task = note.ready.then(() => callback(note));
|
|
1007
|
+
tasks.push(task);
|
|
931
1008
|
}
|
|
1009
|
+
await Promise.all(tasks);
|
|
932
1010
|
}
|
|
933
|
-
processActiveNotes(channel, scheduleTime, callback) {
|
|
1011
|
+
async processActiveNotes(channel, scheduleTime, callback) {
|
|
934
1012
|
const scheduledNotes = channel.scheduledNotes;
|
|
1013
|
+
const tasks = [];
|
|
935
1014
|
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
936
1015
|
const note = scheduledNotes[i];
|
|
937
1016
|
if (!note)
|
|
@@ -940,8 +1019,10 @@ class MidyGM2 {
|
|
|
940
1019
|
continue;
|
|
941
1020
|
if (scheduleTime < note.startTime)
|
|
942
1021
|
break;
|
|
943
|
-
callback(note);
|
|
1022
|
+
const task = note.ready.then(() => callback(note));
|
|
1023
|
+
tasks.push(task);
|
|
944
1024
|
}
|
|
1025
|
+
await Promise.all(tasks);
|
|
945
1026
|
}
|
|
946
1027
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
947
1028
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1085,9 +1166,6 @@ class MidyGM2 {
|
|
|
1085
1166
|
feedbackGains,
|
|
1086
1167
|
};
|
|
1087
1168
|
}
|
|
1088
|
-
cbToRatio(cb) {
|
|
1089
|
-
return Math.pow(10, cb / 200);
|
|
1090
|
-
}
|
|
1091
1169
|
rateToCent(rate) {
|
|
1092
1170
|
return 1200 * Math.log2(rate);
|
|
1093
1171
|
}
|
|
@@ -1206,45 +1284,45 @@ class MidyGM2 {
|
|
|
1206
1284
|
}
|
|
1207
1285
|
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1208
1286
|
const { voiceParams, startTime } = note;
|
|
1209
|
-
const attackVolume =
|
|
1287
|
+
const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
|
|
1210
1288
|
(1 + this.getAmplitudeControl(channel));
|
|
1211
1289
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1212
|
-
const
|
|
1213
|
-
const volAttack = volDelay + voiceParams.volAttack;
|
|
1214
|
-
const volHold = volAttack + voiceParams.volHold;
|
|
1290
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1215
1291
|
note.volumeEnvelopeNode.gain
|
|
1216
1292
|
.cancelScheduledValues(scheduleTime)
|
|
1217
|
-
.
|
|
1293
|
+
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1218
1294
|
}
|
|
1219
1295
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1220
1296
|
const { voiceParams, startTime } = note;
|
|
1221
|
-
const attackVolume =
|
|
1297
|
+
const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
|
|
1222
1298
|
(1 + this.getAmplitudeControl(channel));
|
|
1223
1299
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1224
1300
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1225
1301
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
1226
1302
|
const volHold = volAttack + voiceParams.volHold;
|
|
1227
|
-
const
|
|
1303
|
+
const decayDuration = voiceParams.volDecay;
|
|
1228
1304
|
note.volumeEnvelopeNode.gain
|
|
1229
1305
|
.cancelScheduledValues(scheduleTime)
|
|
1230
1306
|
.setValueAtTime(0, startTime)
|
|
1231
|
-
.setValueAtTime(
|
|
1232
|
-
.
|
|
1307
|
+
.setValueAtTime(0, volDelay)
|
|
1308
|
+
.linearRampToValueAtTime(attackVolume, volAttack)
|
|
1233
1309
|
.setValueAtTime(attackVolume, volHold)
|
|
1234
|
-
.
|
|
1310
|
+
.setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
|
|
1235
1311
|
}
|
|
1236
1312
|
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1237
1313
|
const baseRate = note.voiceParams.playbackRate;
|
|
1314
|
+
const portamentoTime = note.startTime +
|
|
1315
|
+
this.getPortamentoTime(channel, note);
|
|
1238
1316
|
note.bufferSource.playbackRate
|
|
1239
1317
|
.cancelScheduledValues(scheduleTime)
|
|
1240
|
-
.
|
|
1318
|
+
.linearRampToValueAtTime(baseRate, portamentoTime);
|
|
1241
1319
|
}
|
|
1242
1320
|
setPitchEnvelope(note, scheduleTime) {
|
|
1243
1321
|
const { voiceParams } = note;
|
|
1244
1322
|
const baseRate = voiceParams.playbackRate;
|
|
1245
1323
|
note.bufferSource.playbackRate
|
|
1246
1324
|
.cancelScheduledValues(scheduleTime)
|
|
1247
|
-
.setValueAtTime(baseRate,
|
|
1325
|
+
.setValueAtTime(baseRate, note.startTime);
|
|
1248
1326
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1249
1327
|
if (modEnvToPitch === 0)
|
|
1250
1328
|
return;
|
|
@@ -1254,12 +1332,12 @@ class MidyGM2 {
|
|
|
1254
1332
|
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1255
1333
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1256
1334
|
const modHold = modAttack + voiceParams.modHold;
|
|
1257
|
-
const
|
|
1335
|
+
const decayDuration = voiceParams.modDecay;
|
|
1258
1336
|
note.bufferSource.playbackRate
|
|
1259
1337
|
.setValueAtTime(baseRate, modDelay)
|
|
1260
|
-
.
|
|
1338
|
+
.linearRampToValueAtTime(peekRate, modAttack)
|
|
1261
1339
|
.setValueAtTime(peekRate, modHold)
|
|
1262
|
-
.
|
|
1340
|
+
.setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
|
|
1263
1341
|
}
|
|
1264
1342
|
clampCutoffFrequency(frequency) {
|
|
1265
1343
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -1268,17 +1346,18 @@ class MidyGM2 {
|
|
|
1268
1346
|
}
|
|
1269
1347
|
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1270
1348
|
const { voiceParams, startTime } = note;
|
|
1271
|
-
const
|
|
1349
|
+
const scale = this.getSoftPedalFactor(channel, note);
|
|
1272
1350
|
const baseCent = voiceParams.initialFilterFc +
|
|
1273
1351
|
this.getFilterCutoffControl(channel);
|
|
1274
|
-
const
|
|
1275
|
-
|
|
1276
|
-
const
|
|
1277
|
-
|
|
1278
|
-
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1279
|
-
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1352
|
+
const sustainCent = baseCent +
|
|
1353
|
+
voiceParams.modEnvToFilterFc * (1 - voiceParams.modSustain);
|
|
1354
|
+
const baseFreq = this.centToHz(baseCent);
|
|
1355
|
+
const sustainFreq = this.centToHz(sustainCent);
|
|
1356
|
+
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq * scale);
|
|
1357
|
+
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq * scale);
|
|
1280
1358
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1281
1359
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1360
|
+
note.adjustedBaseFreq = adjustedSustainFreq;
|
|
1282
1361
|
note.filterNode.frequency
|
|
1283
1362
|
.cancelScheduledValues(scheduleTime)
|
|
1284
1363
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
@@ -1287,40 +1366,44 @@ class MidyGM2 {
|
|
|
1287
1366
|
}
|
|
1288
1367
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1289
1368
|
const { voiceParams, startTime } = note;
|
|
1290
|
-
const
|
|
1369
|
+
const modEnvToFilterFc = voiceParams.modEnvToFilterFc;
|
|
1291
1370
|
const baseCent = voiceParams.initialFilterFc +
|
|
1292
1371
|
this.getFilterCutoffControl(channel);
|
|
1372
|
+
const peekCent = baseCent + modEnvToFilterFc;
|
|
1373
|
+
const sustainCent = baseCent +
|
|
1374
|
+
modEnvToFilterFc * (1 - voiceParams.modSustain);
|
|
1375
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1293
1376
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1294
|
-
const peekFreq = this.centToHz(
|
|
1295
|
-
|
|
1296
|
-
const sustainFreq = baseFreq +
|
|
1297
|
-
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1377
|
+
const peekFreq = this.centToHz(peekCent) * softPedalFactor;
|
|
1378
|
+
const sustainFreq = this.centToHz(sustainCent) * softPedalFactor;
|
|
1298
1379
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1299
1380
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
1300
1381
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1301
1382
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1302
1383
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1303
1384
|
const modHold = modAttack + voiceParams.modHold;
|
|
1304
|
-
const
|
|
1385
|
+
const decayDuration = voiceParams.modDecay;
|
|
1386
|
+
note.adjustedBaseFreq = adjustedBaseFreq;
|
|
1305
1387
|
note.filterNode.frequency
|
|
1306
1388
|
.cancelScheduledValues(scheduleTime)
|
|
1307
1389
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1308
1390
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1309
|
-
.
|
|
1391
|
+
.linearRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1310
1392
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1311
|
-
.
|
|
1393
|
+
.setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
|
|
1312
1394
|
}
|
|
1313
1395
|
startModulation(channel, note, scheduleTime) {
|
|
1396
|
+
const audioContext = this.audioContext;
|
|
1314
1397
|
const { voiceParams } = note;
|
|
1315
|
-
note.modulationLFO = new OscillatorNode(
|
|
1398
|
+
note.modulationLFO = new OscillatorNode(audioContext, {
|
|
1316
1399
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
1317
1400
|
});
|
|
1318
|
-
note.filterDepth = new GainNode(
|
|
1401
|
+
note.filterDepth = new GainNode(audioContext, {
|
|
1319
1402
|
gain: voiceParams.modLfoToFilterFc,
|
|
1320
1403
|
});
|
|
1321
|
-
note.modulationDepth = new GainNode(
|
|
1404
|
+
note.modulationDepth = new GainNode(audioContext);
|
|
1322
1405
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1323
|
-
note.volumeDepth = new GainNode(
|
|
1406
|
+
note.volumeDepth = new GainNode(audioContext);
|
|
1324
1407
|
this.setModLfoToVolume(note, scheduleTime);
|
|
1325
1408
|
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1326
1409
|
note.modulationLFO.connect(note.filterDepth);
|
|
@@ -1373,7 +1456,8 @@ class MidyGM2 {
|
|
|
1373
1456
|
}
|
|
1374
1457
|
}
|
|
1375
1458
|
async setNoteAudioNode(channel, note, realtime) {
|
|
1376
|
-
const
|
|
1459
|
+
const audioContext = this.audioContext;
|
|
1460
|
+
const now = audioContext.currentTime;
|
|
1377
1461
|
const { noteNumber, velocity, startTime } = note;
|
|
1378
1462
|
const state = channel.state;
|
|
1379
1463
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
@@ -1381,8 +1465,8 @@ class MidyGM2 {
|
|
|
1381
1465
|
note.voiceParams = voiceParams;
|
|
1382
1466
|
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
|
|
1383
1467
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1384
|
-
note.volumeEnvelopeNode = new GainNode(
|
|
1385
|
-
note.filterNode = new BiquadFilterNode(
|
|
1468
|
+
note.volumeEnvelopeNode = new GainNode(audioContext);
|
|
1469
|
+
note.filterNode = new BiquadFilterNode(audioContext, {
|
|
1386
1470
|
type: "lowpass",
|
|
1387
1471
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1388
1472
|
});
|
|
@@ -1495,8 +1579,12 @@ class MidyGM2 {
|
|
|
1495
1579
|
const bankTable = this.soundFontTable[programNumber];
|
|
1496
1580
|
if (!bankTable)
|
|
1497
1581
|
return;
|
|
1498
|
-
|
|
1499
|
-
|
|
1582
|
+
let bank = channel.isDrum ? 128 : channel.bankLSB;
|
|
1583
|
+
if (bankTable[bank] === undefined) {
|
|
1584
|
+
if (channel.isDrum)
|
|
1585
|
+
return;
|
|
1586
|
+
bank = 0;
|
|
1587
|
+
}
|
|
1500
1588
|
const soundFontIndex = bankTable[bank];
|
|
1501
1589
|
if (soundFontIndex === undefined)
|
|
1502
1590
|
return;
|
|
@@ -1506,11 +1594,7 @@ class MidyGM2 {
|
|
|
1506
1594
|
return;
|
|
1507
1595
|
await this.setNoteAudioNode(channel, note, realtime);
|
|
1508
1596
|
this.setNoteRouting(channelNumber, note, startTime);
|
|
1509
|
-
note.
|
|
1510
|
-
const off = note.offEvent;
|
|
1511
|
-
if (off) {
|
|
1512
|
-
this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
|
|
1513
|
-
}
|
|
1597
|
+
note.resolveReady();
|
|
1514
1598
|
}
|
|
1515
1599
|
disconnectNote(note) {
|
|
1516
1600
|
note.bufferSource.disconnect();
|
|
@@ -1534,27 +1618,27 @@ class MidyGM2 {
|
|
|
1534
1618
|
}
|
|
1535
1619
|
releaseNote(channel, note, endTime) {
|
|
1536
1620
|
endTime ??= this.audioContext.currentTime;
|
|
1537
|
-
const
|
|
1621
|
+
const duration = note.voiceParams.volRelease;
|
|
1622
|
+
const volRelease = endTime + duration;
|
|
1538
1623
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1539
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1540
1624
|
note.filterNode.frequency
|
|
1541
1625
|
.cancelScheduledValues(endTime)
|
|
1542
|
-
.linearRampToValueAtTime(
|
|
1626
|
+
.linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
|
|
1543
1627
|
note.volumeEnvelopeNode.gain
|
|
1544
1628
|
.cancelScheduledValues(endTime)
|
|
1545
|
-
.
|
|
1629
|
+
.setTargetAtTime(0, endTime, duration * releaseCurve);
|
|
1546
1630
|
return new Promise((resolve) => {
|
|
1547
1631
|
this.scheduleTask(() => {
|
|
1548
1632
|
const bufferSource = note.bufferSource;
|
|
1549
1633
|
bufferSource.loop = false;
|
|
1550
|
-
bufferSource.stop(
|
|
1634
|
+
bufferSource.stop(volRelease);
|
|
1551
1635
|
this.disconnectNote(note);
|
|
1552
1636
|
channel.scheduledNotes[note.index] = undefined;
|
|
1553
1637
|
resolve();
|
|
1554
|
-
},
|
|
1638
|
+
}, volRelease);
|
|
1555
1639
|
});
|
|
1556
1640
|
}
|
|
1557
|
-
noteOff(channelNumber, noteNumber,
|
|
1641
|
+
noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1558
1642
|
const channel = this.channels[channelNumber];
|
|
1559
1643
|
const state = channel.state;
|
|
1560
1644
|
if (!force) {
|
|
@@ -1573,13 +1657,11 @@ class MidyGM2 {
|
|
|
1573
1657
|
if (index < 0)
|
|
1574
1658
|
return;
|
|
1575
1659
|
const note = channel.scheduledNotes[index];
|
|
1576
|
-
if (note.pending) {
|
|
1577
|
-
note.offEvent = { velocity, startTime: endTime };
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
1660
|
note.ending = true;
|
|
1581
1661
|
this.setNoteIndex(channel, index);
|
|
1582
|
-
const promise =
|
|
1662
|
+
const promise = note.ready.then(() => {
|
|
1663
|
+
return this.releaseNote(channel, note, endTime);
|
|
1664
|
+
});
|
|
1583
1665
|
this.notePromises.push(promise);
|
|
1584
1666
|
return promise;
|
|
1585
1667
|
}
|
|
@@ -1675,6 +1757,8 @@ class MidyGM2 {
|
|
|
1675
1757
|
}
|
|
1676
1758
|
}
|
|
1677
1759
|
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1760
|
+
if (!(0 <= scheduleTime))
|
|
1761
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1678
1762
|
const channel = this.channels[channelNumber];
|
|
1679
1763
|
if (channel.isDrum)
|
|
1680
1764
|
return;
|
|
@@ -1690,7 +1774,7 @@ class MidyGM2 {
|
|
|
1690
1774
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1691
1775
|
this.setEffects(channel, note, table, scheduleTime);
|
|
1692
1776
|
});
|
|
1693
|
-
this.applyVoiceParams(channel, 13);
|
|
1777
|
+
this.applyVoiceParams(channel, 13, scheduleTime);
|
|
1694
1778
|
}
|
|
1695
1779
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1696
1780
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1700,7 +1784,8 @@ class MidyGM2 {
|
|
|
1700
1784
|
const channel = this.channels[channelNumber];
|
|
1701
1785
|
if (channel.isDrum)
|
|
1702
1786
|
return;
|
|
1703
|
-
|
|
1787
|
+
if (!(0 <= scheduleTime))
|
|
1788
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1704
1789
|
const state = channel.state;
|
|
1705
1790
|
const prev = state.pitchWheel * 2 - 1;
|
|
1706
1791
|
const next = (value - 8192) / 8192;
|
|
@@ -1747,7 +1832,7 @@ class MidyGM2 {
|
|
|
1747
1832
|
}
|
|
1748
1833
|
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1749
1834
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1750
|
-
const baseDepth =
|
|
1835
|
+
const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1751
1836
|
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1752
1837
|
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1753
1838
|
note.volumeDepth.gain
|
|
@@ -1994,7 +2079,8 @@ class MidyGM2 {
|
|
|
1994
2079
|
const channel = this.channels[channelNumber];
|
|
1995
2080
|
if (channel.isDrum)
|
|
1996
2081
|
return;
|
|
1997
|
-
|
|
2082
|
+
if (!(0 <= scheduleTime))
|
|
2083
|
+
scheduleTime = this.audioContext.currentTime;
|
|
1998
2084
|
channel.state.modulationDepthMSB = modulation / 127;
|
|
1999
2085
|
this.updateModulation(channel, scheduleTime);
|
|
2000
2086
|
}
|
|
@@ -2017,7 +2103,8 @@ class MidyGM2 {
|
|
|
2017
2103
|
});
|
|
2018
2104
|
}
|
|
2019
2105
|
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
2020
|
-
|
|
2106
|
+
if (!(0 <= scheduleTime))
|
|
2107
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2021
2108
|
const channel = this.channels[channelNumber];
|
|
2022
2109
|
channel.state.portamentoTimeMSB = portamentoTime / 127;
|
|
2023
2110
|
if (channel.isDrum)
|
|
@@ -2025,7 +2112,8 @@ class MidyGM2 {
|
|
|
2025
2112
|
this.updatePortamento(channel, scheduleTime);
|
|
2026
2113
|
}
|
|
2027
2114
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
2028
|
-
|
|
2115
|
+
if (!(0 <= scheduleTime))
|
|
2116
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2029
2117
|
const channel = this.channels[channelNumber];
|
|
2030
2118
|
channel.state.volumeMSB = volume / 127;
|
|
2031
2119
|
if (channel.isDrum) {
|
|
@@ -2045,7 +2133,8 @@ class MidyGM2 {
|
|
|
2045
2133
|
};
|
|
2046
2134
|
}
|
|
2047
2135
|
setPan(channelNumber, pan, scheduleTime) {
|
|
2048
|
-
|
|
2136
|
+
if (!(0 <= scheduleTime))
|
|
2137
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2049
2138
|
const channel = this.channels[channelNumber];
|
|
2050
2139
|
channel.state.panMSB = pan / 127;
|
|
2051
2140
|
if (channel.isDrum) {
|
|
@@ -2058,7 +2147,8 @@ class MidyGM2 {
|
|
|
2058
2147
|
}
|
|
2059
2148
|
}
|
|
2060
2149
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
2061
|
-
|
|
2150
|
+
if (!(0 <= scheduleTime))
|
|
2151
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2062
2152
|
const channel = this.channels[channelNumber];
|
|
2063
2153
|
channel.state.expressionMSB = expression / 127;
|
|
2064
2154
|
this.updateChannelVolume(channel, scheduleTime);
|
|
@@ -2107,7 +2197,8 @@ class MidyGM2 {
|
|
|
2107
2197
|
const channel = this.channels[channelNumber];
|
|
2108
2198
|
if (channel.isDrum)
|
|
2109
2199
|
return;
|
|
2110
|
-
|
|
2200
|
+
if (!(0 <= scheduleTime))
|
|
2201
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2111
2202
|
channel.state.sustainPedal = value / 127;
|
|
2112
2203
|
if (64 <= value) {
|
|
2113
2204
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2125,7 +2216,8 @@ class MidyGM2 {
|
|
|
2125
2216
|
const channel = this.channels[channelNumber];
|
|
2126
2217
|
if (channel.isDrum)
|
|
2127
2218
|
return;
|
|
2128
|
-
|
|
2219
|
+
if (!(0 <= scheduleTime))
|
|
2220
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2129
2221
|
channel.state.portamento = value / 127;
|
|
2130
2222
|
this.updatePortamento(channel, scheduleTime);
|
|
2131
2223
|
}
|
|
@@ -2133,7 +2225,8 @@ class MidyGM2 {
|
|
|
2133
2225
|
const channel = this.channels[channelNumber];
|
|
2134
2226
|
if (channel.isDrum)
|
|
2135
2227
|
return;
|
|
2136
|
-
|
|
2228
|
+
if (!(0 <= scheduleTime))
|
|
2229
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2137
2230
|
channel.state.sostenutoPedal = value / 127;
|
|
2138
2231
|
if (64 <= value) {
|
|
2139
2232
|
const sostenutoNotes = [];
|
|
@@ -2154,7 +2247,8 @@ class MidyGM2 {
|
|
|
2154
2247
|
if (channel.isDrum)
|
|
2155
2248
|
return;
|
|
2156
2249
|
const state = channel.state;
|
|
2157
|
-
|
|
2250
|
+
if (!(0 <= scheduleTime))
|
|
2251
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2158
2252
|
state.softPedal = softPedal / 127;
|
|
2159
2253
|
this.processScheduledNotes(channel, (note) => {
|
|
2160
2254
|
if (this.isPortamento(channel, note)) {
|
|
@@ -2168,7 +2262,8 @@ class MidyGM2 {
|
|
|
2168
2262
|
});
|
|
2169
2263
|
}
|
|
2170
2264
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
2171
|
-
|
|
2265
|
+
if (!(0 <= scheduleTime))
|
|
2266
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2172
2267
|
const channel = this.channels[channelNumber];
|
|
2173
2268
|
const state = channel.state;
|
|
2174
2269
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2177,7 +2272,8 @@ class MidyGM2 {
|
|
|
2177
2272
|
});
|
|
2178
2273
|
}
|
|
2179
2274
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2180
|
-
|
|
2275
|
+
if (!(0 <= scheduleTime))
|
|
2276
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2181
2277
|
const channel = this.channels[channelNumber];
|
|
2182
2278
|
const state = channel.state;
|
|
2183
2279
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2251,7 +2347,8 @@ class MidyGM2 {
|
|
|
2251
2347
|
const channel = this.channels[channelNumber];
|
|
2252
2348
|
if (channel.isDrum)
|
|
2253
2349
|
return;
|
|
2254
|
-
|
|
2350
|
+
if (!(0 <= scheduleTime))
|
|
2351
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2255
2352
|
const state = channel.state;
|
|
2256
2353
|
const prev = state.pitchWheelSensitivity;
|
|
2257
2354
|
const next = value / 12800;
|
|
@@ -2271,7 +2368,8 @@ class MidyGM2 {
|
|
|
2271
2368
|
const channel = this.channels[channelNumber];
|
|
2272
2369
|
if (channel.isDrum)
|
|
2273
2370
|
return;
|
|
2274
|
-
|
|
2371
|
+
if (!(0 <= scheduleTime))
|
|
2372
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2275
2373
|
const prev = channel.fineTuning;
|
|
2276
2374
|
const next = value;
|
|
2277
2375
|
channel.fineTuning = next;
|
|
@@ -2288,7 +2386,8 @@ class MidyGM2 {
|
|
|
2288
2386
|
const channel = this.channels[channelNumber];
|
|
2289
2387
|
if (channel.isDrum)
|
|
2290
2388
|
return;
|
|
2291
|
-
|
|
2389
|
+
if (!(0 <= scheduleTime))
|
|
2390
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2292
2391
|
const prev = channel.coarseTuning;
|
|
2293
2392
|
const next = value;
|
|
2294
2393
|
channel.coarseTuning = next;
|
|
@@ -2305,12 +2404,14 @@ class MidyGM2 {
|
|
|
2305
2404
|
const channel = this.channels[channelNumber];
|
|
2306
2405
|
if (channel.isDrum)
|
|
2307
2406
|
return;
|
|
2308
|
-
|
|
2407
|
+
if (!(0 <= scheduleTime))
|
|
2408
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2309
2409
|
channel.modulationDepthRange = value;
|
|
2310
2410
|
this.updateModulation(channel, scheduleTime);
|
|
2311
2411
|
}
|
|
2312
2412
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2313
|
-
|
|
2413
|
+
if (!(0 <= scheduleTime))
|
|
2414
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2314
2415
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2315
2416
|
}
|
|
2316
2417
|
resetChannelStates(channelNumber) {
|
|
@@ -2369,7 +2470,8 @@ class MidyGM2 {
|
|
|
2369
2470
|
}
|
|
2370
2471
|
}
|
|
2371
2472
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2372
|
-
|
|
2473
|
+
if (!(0 <= scheduleTime))
|
|
2474
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2373
2475
|
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2374
2476
|
}
|
|
2375
2477
|
omniOff(channelNumber, value, scheduleTime) {
|
|
@@ -2418,30 +2520,34 @@ class MidyGM2 {
|
|
|
2418
2520
|
}
|
|
2419
2521
|
}
|
|
2420
2522
|
GM1SystemOn(scheduleTime) {
|
|
2421
|
-
|
|
2523
|
+
const channels = this.channels;
|
|
2524
|
+
if (!(0 <= scheduleTime))
|
|
2525
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2422
2526
|
this.mode = "GM1";
|
|
2423
|
-
for (let i = 0; i <
|
|
2527
|
+
for (let i = 0; i < channels.length; i++) {
|
|
2424
2528
|
this.allSoundOff(i, 0, scheduleTime);
|
|
2425
|
-
const channel =
|
|
2529
|
+
const channel = channels[i];
|
|
2426
2530
|
channel.bankMSB = 0;
|
|
2427
2531
|
channel.bankLSB = 0;
|
|
2428
2532
|
channel.isDrum = false;
|
|
2429
2533
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2534
|
+
channels[9].bankMSB = 1;
|
|
2535
|
+
channels[9].isDrum = true;
|
|
2432
2536
|
}
|
|
2433
2537
|
GM2SystemOn(scheduleTime) {
|
|
2434
|
-
|
|
2538
|
+
const channels = this.channels;
|
|
2539
|
+
if (!(0 <= scheduleTime))
|
|
2540
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2435
2541
|
this.mode = "GM2";
|
|
2436
|
-
for (let i = 0; i <
|
|
2542
|
+
for (let i = 0; i < channels.length; i++) {
|
|
2437
2543
|
this.allSoundOff(i, 0, scheduleTime);
|
|
2438
|
-
const channel =
|
|
2544
|
+
const channel = channels[i];
|
|
2439
2545
|
channel.bankMSB = 121;
|
|
2440
2546
|
channel.bankLSB = 0;
|
|
2441
2547
|
channel.isDrum = false;
|
|
2442
2548
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2549
|
+
channels[9].bankMSB = 120;
|
|
2550
|
+
channels[9].isDrum = true;
|
|
2445
2551
|
}
|
|
2446
2552
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2447
2553
|
switch (data[2]) {
|
|
@@ -2486,7 +2592,8 @@ class MidyGM2 {
|
|
|
2486
2592
|
this.setMasterVolume(volume, scheduleTime);
|
|
2487
2593
|
}
|
|
2488
2594
|
setMasterVolume(value, scheduleTime) {
|
|
2489
|
-
|
|
2595
|
+
if (!(0 <= scheduleTime))
|
|
2596
|
+
scheduleTime = this.audioContext.currentTime;
|
|
2490
2597
|
this.masterVolume.gain
|
|
2491
2598
|
.cancelScheduledValues(scheduleTime)
|
|
2492
2599
|
.setValueAtTime(value * value, scheduleTime);
|
|
@@ -2754,9 +2861,9 @@ class MidyGM2 {
|
|
|
2754
2861
|
getAmplitudeControl(channel) {
|
|
2755
2862
|
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2756
2863
|
const channelPressure = (0 <= channelPressureRaw)
|
|
2757
|
-
?
|
|
2864
|
+
? channel.state.channelPressure * 127 / channelPressureRaw
|
|
2758
2865
|
: 0;
|
|
2759
|
-
return channelPressure
|
|
2866
|
+
return channelPressure;
|
|
2760
2867
|
}
|
|
2761
2868
|
getLFOPitchDepth(channel) {
|
|
2762
2869
|
const channelPressureRaw = channel.channelPressureTable[3];
|
|
@@ -2780,27 +2887,27 @@ class MidyGM2 {
|
|
|
2780
2887
|
return channelPressure / 127;
|
|
2781
2888
|
}
|
|
2782
2889
|
setEffects(channel, note, table, scheduleTime) {
|
|
2783
|
-
if (0
|
|
2890
|
+
if (0 < table[0])
|
|
2784
2891
|
this.updateDetune(channel, note, scheduleTime);
|
|
2785
2892
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2786
|
-
if (0
|
|
2893
|
+
if (0 < table[1]) {
|
|
2787
2894
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2788
2895
|
}
|
|
2789
|
-
if (0
|
|
2896
|
+
if (0 < table[2]) {
|
|
2790
2897
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2791
2898
|
}
|
|
2792
2899
|
}
|
|
2793
2900
|
else {
|
|
2794
|
-
if (0
|
|
2901
|
+
if (0 < table[1])
|
|
2795
2902
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2796
|
-
if (0
|
|
2903
|
+
if (0 < table[2])
|
|
2797
2904
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2798
2905
|
}
|
|
2799
|
-
if (0
|
|
2906
|
+
if (0 < table[3])
|
|
2800
2907
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2801
|
-
if (0
|
|
2908
|
+
if (0 < table[4])
|
|
2802
2909
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2803
|
-
if (0
|
|
2910
|
+
if (0 < table[5])
|
|
2804
2911
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2805
2912
|
}
|
|
2806
2913
|
handlePressureSysEx(data, tableName, scheduleTime) {
|