@marmooo/midy 0.2.5 → 0.2.7
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 +17 -12
- package/esm/midy-GM1.d.ts +85 -87
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +188 -237
- package/esm/midy-GM2.d.ts +129 -126
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +374 -390
- package/esm/midy-GMLite.d.ts +85 -84
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +191 -190
- package/esm/midy.d.ts +153 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +420 -442
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +85 -87
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +188 -237
- package/script/midy-GM2.d.ts +129 -126
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +374 -390
- package/script/midy-GMLite.d.ts +85 -84
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +191 -190
- package/script/midy.d.ts +153 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +420 -442
package/esm/midy-GM2.js
CHANGED
|
@@ -66,31 +66,37 @@ class Note {
|
|
|
66
66
|
writable: true,
|
|
67
67
|
value: void 0
|
|
68
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
69
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
70
76
|
enumerable: true,
|
|
71
77
|
configurable: true,
|
|
72
78
|
writable: true,
|
|
73
79
|
value: void 0
|
|
74
80
|
});
|
|
75
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
76
82
|
enumerable: true,
|
|
77
83
|
configurable: true,
|
|
78
84
|
writable: true,
|
|
79
85
|
value: void 0
|
|
80
86
|
});
|
|
81
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "volumeNode", {
|
|
82
88
|
enumerable: true,
|
|
83
89
|
configurable: true,
|
|
84
90
|
writable: true,
|
|
85
91
|
value: void 0
|
|
86
92
|
});
|
|
87
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "gainL", {
|
|
88
94
|
enumerable: true,
|
|
89
95
|
configurable: true,
|
|
90
96
|
writable: true,
|
|
91
97
|
value: void 0
|
|
92
98
|
});
|
|
93
|
-
Object.defineProperty(this, "
|
|
99
|
+
Object.defineProperty(this, "gainR", {
|
|
94
100
|
enumerable: true,
|
|
95
101
|
configurable: true,
|
|
96
102
|
writable: true,
|
|
@@ -492,6 +498,9 @@ export class MidyGM2 {
|
|
|
492
498
|
...this.setChannelAudioNodes(audioContext),
|
|
493
499
|
scheduledNotes: new SparseMap(128),
|
|
494
500
|
sostenutoNotes: new SparseMap(128),
|
|
501
|
+
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
502
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
503
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
495
504
|
};
|
|
496
505
|
});
|
|
497
506
|
return channels;
|
|
@@ -558,10 +567,11 @@ export class MidyGM2 {
|
|
|
558
567
|
const event = this.timeline[queueIndex];
|
|
559
568
|
if (event.startTime > t + this.lookAhead)
|
|
560
569
|
break;
|
|
570
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
561
571
|
switch (event.type) {
|
|
562
572
|
case "noteOn":
|
|
563
573
|
if (event.velocity !== 0) {
|
|
564
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
574
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
565
575
|
break;
|
|
566
576
|
}
|
|
567
577
|
/* falls through */
|
|
@@ -569,26 +579,27 @@ export class MidyGM2 {
|
|
|
569
579
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
570
580
|
if (portamentoTarget)
|
|
571
581
|
portamentoTarget.portamento = true;
|
|
572
|
-
const notePromise = this.
|
|
582
|
+
const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
583
|
+
portamentoTarget?.noteNumber);
|
|
573
584
|
if (notePromise) {
|
|
574
585
|
this.notePromises.push(notePromise);
|
|
575
586
|
}
|
|
576
587
|
break;
|
|
577
588
|
}
|
|
578
589
|
case "controller":
|
|
579
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
590
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
580
591
|
break;
|
|
581
592
|
case "programChange":
|
|
582
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
593
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
583
594
|
break;
|
|
584
595
|
case "channelAftertouch":
|
|
585
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
596
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
586
597
|
break;
|
|
587
598
|
case "pitchBend":
|
|
588
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
599
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
589
600
|
break;
|
|
590
601
|
case "sysEx":
|
|
591
|
-
this.handleSysEx(event.data);
|
|
602
|
+
this.handleSysEx(event.data, startTime);
|
|
592
603
|
}
|
|
593
604
|
queueIndex++;
|
|
594
605
|
}
|
|
@@ -619,10 +630,11 @@ export class MidyGM2 {
|
|
|
619
630
|
resolve();
|
|
620
631
|
return;
|
|
621
632
|
}
|
|
622
|
-
const
|
|
633
|
+
const now = this.audioContext.currentTime;
|
|
634
|
+
const t = now + offset;
|
|
623
635
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
624
636
|
if (this.isPausing) {
|
|
625
|
-
await this.stopNotes(0, true);
|
|
637
|
+
await this.stopNotes(0, true, now);
|
|
626
638
|
this.notePromises = [];
|
|
627
639
|
resolve();
|
|
628
640
|
this.isPausing = false;
|
|
@@ -630,7 +642,7 @@ export class MidyGM2 {
|
|
|
630
642
|
return;
|
|
631
643
|
}
|
|
632
644
|
else if (this.isStopping) {
|
|
633
|
-
await this.stopNotes(0, true);
|
|
645
|
+
await this.stopNotes(0, true, now);
|
|
634
646
|
this.notePromises = [];
|
|
635
647
|
this.exclusiveClassMap.clear();
|
|
636
648
|
this.audioBufferCache.clear();
|
|
@@ -640,7 +652,7 @@ export class MidyGM2 {
|
|
|
640
652
|
return;
|
|
641
653
|
}
|
|
642
654
|
else if (this.isSeeking) {
|
|
643
|
-
this.stopNotes(0, true);
|
|
655
|
+
this.stopNotes(0, true, now);
|
|
644
656
|
this.exclusiveClassMap.clear();
|
|
645
657
|
this.startTime = this.audioContext.currentTime;
|
|
646
658
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -649,7 +661,6 @@ export class MidyGM2 {
|
|
|
649
661
|
await schedulePlayback();
|
|
650
662
|
}
|
|
651
663
|
else {
|
|
652
|
-
const now = this.audioContext.currentTime;
|
|
653
664
|
const waitTime = now + this.noteCheckInterval;
|
|
654
665
|
await this.scheduleTask(() => { }, waitTime);
|
|
655
666
|
await schedulePlayback();
|
|
@@ -769,25 +780,26 @@ export class MidyGM2 {
|
|
|
769
780
|
}
|
|
770
781
|
return { instruments, timeline };
|
|
771
782
|
}
|
|
772
|
-
|
|
773
|
-
const now = this.audioContext.currentTime;
|
|
783
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
774
784
|
const channel = this.channels[channelNumber];
|
|
785
|
+
const promises = [];
|
|
775
786
|
channel.scheduledNotes.forEach((noteList) => {
|
|
776
787
|
for (let i = 0; i < noteList.length; i++) {
|
|
777
788
|
const note = noteList[i];
|
|
778
789
|
if (!note)
|
|
779
790
|
continue;
|
|
780
|
-
const promise = this.
|
|
781
|
-
force);
|
|
791
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
782
792
|
this.notePromises.push(promise);
|
|
793
|
+
promises.push(promise);
|
|
783
794
|
}
|
|
784
795
|
});
|
|
785
796
|
channel.scheduledNotes.clear();
|
|
786
|
-
|
|
797
|
+
return Promise.all(promises);
|
|
787
798
|
}
|
|
788
|
-
stopNotes(velocity, force) {
|
|
799
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
800
|
+
const promises = [];
|
|
789
801
|
for (let i = 0; i < this.channels.length; i++) {
|
|
790
|
-
this.stopChannelNotes(i, velocity, force);
|
|
802
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
791
803
|
}
|
|
792
804
|
return Promise.all(this.notePromises);
|
|
793
805
|
}
|
|
@@ -835,22 +847,34 @@ export class MidyGM2 {
|
|
|
835
847
|
const now = this.audioContext.currentTime;
|
|
836
848
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
837
849
|
}
|
|
838
|
-
|
|
850
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
851
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
852
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
853
|
+
const note = noteList[i];
|
|
854
|
+
if (!note)
|
|
855
|
+
continue;
|
|
856
|
+
if (scheduleTime < note.startTime)
|
|
857
|
+
continue;
|
|
858
|
+
callback(note);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
getActiveNotes(channel, scheduleTime) {
|
|
839
863
|
const activeNotes = new SparseMap(128);
|
|
840
864
|
channel.scheduledNotes.forEach((noteList) => {
|
|
841
|
-
const activeNote = this.getActiveNote(noteList,
|
|
865
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
842
866
|
if (activeNote) {
|
|
843
867
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
844
868
|
}
|
|
845
869
|
});
|
|
846
870
|
return activeNotes;
|
|
847
871
|
}
|
|
848
|
-
getActiveNote(noteList,
|
|
872
|
+
getActiveNote(noteList, scheduleTime) {
|
|
849
873
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
850
874
|
const note = noteList[i];
|
|
851
875
|
if (!note)
|
|
852
876
|
return;
|
|
853
|
-
if (
|
|
877
|
+
if (scheduleTime < note.startTime)
|
|
854
878
|
continue;
|
|
855
879
|
return (note.ending) ? null : note;
|
|
856
880
|
}
|
|
@@ -1010,73 +1034,64 @@ export class MidyGM2 {
|
|
|
1010
1034
|
calcNoteDetune(channel, note) {
|
|
1011
1035
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1012
1036
|
}
|
|
1013
|
-
updateChannelDetune(channel) {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
const note = noteList[i];
|
|
1017
|
-
if (!note)
|
|
1018
|
-
continue;
|
|
1019
|
-
this.updateDetune(channel, note, 0);
|
|
1020
|
-
}
|
|
1037
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1038
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1039
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1021
1040
|
});
|
|
1022
1041
|
}
|
|
1023
|
-
updateDetune(channel, note,
|
|
1024
|
-
const now = this.audioContext.currentTime;
|
|
1042
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1025
1043
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1026
|
-
const detune = channel.detune + noteDetune
|
|
1044
|
+
const detune = channel.detune + noteDetune;
|
|
1027
1045
|
note.bufferSource.detune
|
|
1028
|
-
.cancelScheduledValues(
|
|
1029
|
-
.setValueAtTime(detune,
|
|
1046
|
+
.cancelScheduledValues(scheduleTime)
|
|
1047
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1030
1048
|
}
|
|
1031
1049
|
getPortamentoTime(channel) {
|
|
1032
1050
|
const factor = 5 * Math.log(10) / 127;
|
|
1033
1051
|
const time = channel.state.portamentoTime;
|
|
1034
1052
|
return Math.log(time) / factor;
|
|
1035
1053
|
}
|
|
1036
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1037
|
-
const now = this.audioContext.currentTime;
|
|
1054
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1038
1055
|
const { voiceParams, startTime } = note;
|
|
1039
1056
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1040
1057
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1041
1058
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1042
1059
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1043
1060
|
note.volumeEnvelopeNode.gain
|
|
1044
|
-
.cancelScheduledValues(
|
|
1061
|
+
.cancelScheduledValues(scheduleTime)
|
|
1045
1062
|
.setValueAtTime(0, volDelay)
|
|
1046
1063
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1047
1064
|
}
|
|
1048
|
-
setVolumeEnvelope(note,
|
|
1049
|
-
const now = this.audioContext.currentTime;
|
|
1065
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1050
1066
|
const { voiceParams, startTime } = note;
|
|
1051
1067
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1052
|
-
(1 +
|
|
1068
|
+
(1 + this.getAmplitudeControl(channel));
|
|
1053
1069
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1054
1070
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1055
1071
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
1056
1072
|
const volHold = volAttack + voiceParams.volHold;
|
|
1057
1073
|
const volDecay = volHold + voiceParams.volDecay;
|
|
1058
1074
|
note.volumeEnvelopeNode.gain
|
|
1059
|
-
.cancelScheduledValues(
|
|
1075
|
+
.cancelScheduledValues(scheduleTime)
|
|
1060
1076
|
.setValueAtTime(0, startTime)
|
|
1061
1077
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1062
1078
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
1063
1079
|
.setValueAtTime(attackVolume, volHold)
|
|
1064
1080
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1065
1081
|
}
|
|
1066
|
-
setPitchEnvelope(note) {
|
|
1067
|
-
const now = this.audioContext.currentTime;
|
|
1082
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1068
1083
|
const { voiceParams } = note;
|
|
1069
1084
|
const baseRate = voiceParams.playbackRate;
|
|
1070
1085
|
note.bufferSource.playbackRate
|
|
1071
|
-
.cancelScheduledValues(
|
|
1072
|
-
.setValueAtTime(baseRate,
|
|
1086
|
+
.cancelScheduledValues(scheduleTime)
|
|
1087
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1073
1088
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1074
1089
|
if (modEnvToPitch === 0)
|
|
1075
1090
|
return;
|
|
1076
1091
|
const basePitch = this.rateToCent(baseRate);
|
|
1077
1092
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1078
1093
|
const peekRate = this.centToRate(peekPitch);
|
|
1079
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1094
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1080
1095
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1081
1096
|
const modHold = modAttack + voiceParams.modHold;
|
|
1082
1097
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1091,8 +1106,7 @@ export class MidyGM2 {
|
|
|
1091
1106
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1092
1107
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1093
1108
|
}
|
|
1094
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1095
|
-
const now = this.audioContext.currentTime;
|
|
1109
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1096
1110
|
const state = channel.state;
|
|
1097
1111
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1098
1112
|
const softPedalFactor = 1 -
|
|
@@ -1107,18 +1121,18 @@ export class MidyGM2 {
|
|
|
1107
1121
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1108
1122
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1109
1123
|
note.filterNode.frequency
|
|
1110
|
-
.cancelScheduledValues(
|
|
1124
|
+
.cancelScheduledValues(scheduleTime)
|
|
1111
1125
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1112
1126
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1113
1127
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1114
1128
|
}
|
|
1115
|
-
setFilterEnvelope(channel, note,
|
|
1116
|
-
const now = this.audioContext.currentTime;
|
|
1129
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1117
1130
|
const state = channel.state;
|
|
1118
1131
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1119
1132
|
const softPedalFactor = 1 -
|
|
1120
1133
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1121
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1134
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1135
|
+
this.getFilterCutoffControl(channel);
|
|
1122
1136
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1123
1137
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1124
1138
|
softPedalFactor;
|
|
@@ -1132,14 +1146,14 @@ export class MidyGM2 {
|
|
|
1132
1146
|
const modHold = modAttack + voiceParams.modHold;
|
|
1133
1147
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1134
1148
|
note.filterNode.frequency
|
|
1135
|
-
.cancelScheduledValues(
|
|
1149
|
+
.cancelScheduledValues(scheduleTime)
|
|
1136
1150
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1137
1151
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1138
1152
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1139
1153
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1140
1154
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1141
1155
|
}
|
|
1142
|
-
startModulation(channel, note,
|
|
1156
|
+
startModulation(channel, note, scheduleTime) {
|
|
1143
1157
|
const { voiceParams } = note;
|
|
1144
1158
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1145
1159
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1148,10 +1162,10 @@ export class MidyGM2 {
|
|
|
1148
1162
|
gain: voiceParams.modLfoToFilterFc,
|
|
1149
1163
|
});
|
|
1150
1164
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1151
|
-
this.setModLfoToPitch(channel, note,
|
|
1165
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1152
1166
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1153
|
-
this.setModLfoToVolume(note,
|
|
1154
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1167
|
+
this.setModLfoToVolume(note, scheduleTime);
|
|
1168
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1155
1169
|
note.modulationLFO.connect(note.filterDepth);
|
|
1156
1170
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1157
1171
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1159,15 +1173,15 @@ export class MidyGM2 {
|
|
|
1159
1173
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1160
1174
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1161
1175
|
}
|
|
1162
|
-
startVibrato(channel, note,
|
|
1176
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1163
1177
|
const { voiceParams } = note;
|
|
1164
1178
|
const state = channel.state;
|
|
1165
1179
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1166
1180
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1167
1181
|
});
|
|
1168
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1182
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1169
1183
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1170
|
-
this.setVibLfoToPitch(channel, note);
|
|
1184
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1171
1185
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1172
1186
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1173
1187
|
}
|
|
@@ -1190,6 +1204,7 @@ export class MidyGM2 {
|
|
|
1190
1204
|
}
|
|
1191
1205
|
}
|
|
1192
1206
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1207
|
+
const now = this.audioContext.currentTime;
|
|
1193
1208
|
const state = channel.state;
|
|
1194
1209
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1195
1210
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1206,20 +1221,20 @@ export class MidyGM2 {
|
|
|
1206
1221
|
});
|
|
1207
1222
|
if (portamento) {
|
|
1208
1223
|
note.portamento = true;
|
|
1209
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1210
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1224
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1225
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1211
1226
|
}
|
|
1212
1227
|
else {
|
|
1213
1228
|
note.portamento = false;
|
|
1214
|
-
this.setVolumeEnvelope(note,
|
|
1215
|
-
this.setFilterEnvelope(channel, note,
|
|
1229
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1230
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1216
1231
|
}
|
|
1217
1232
|
if (0 < state.vibratoDepth) {
|
|
1218
|
-
this.startVibrato(channel, note,
|
|
1233
|
+
this.startVibrato(channel, note, now);
|
|
1219
1234
|
}
|
|
1220
|
-
this.setPitchEnvelope(note);
|
|
1235
|
+
this.setPitchEnvelope(note, now);
|
|
1221
1236
|
if (0 < state.modulationDepth) {
|
|
1222
|
-
this.startModulation(channel, note,
|
|
1237
|
+
this.startModulation(channel, note, now);
|
|
1223
1238
|
}
|
|
1224
1239
|
if (this.mono && channel.currentBufferSource) {
|
|
1225
1240
|
channel.currentBufferSource.stop(startTime);
|
|
@@ -1231,10 +1246,10 @@ export class MidyGM2 {
|
|
|
1231
1246
|
note.volumeNode.connect(note.gainL);
|
|
1232
1247
|
note.volumeNode.connect(note.gainR);
|
|
1233
1248
|
if (0 < channel.chorusSendLevel) {
|
|
1234
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1249
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1235
1250
|
}
|
|
1236
1251
|
if (0 < channel.reverbSendLevel) {
|
|
1237
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1252
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1238
1253
|
}
|
|
1239
1254
|
note.bufferSource.start(startTime);
|
|
1240
1255
|
return note;
|
|
@@ -1271,9 +1286,9 @@ export class MidyGM2 {
|
|
|
1271
1286
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1272
1287
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1273
1288
|
if (!prevNote.ending) {
|
|
1274
|
-
this.
|
|
1275
|
-
startTime,
|
|
1276
|
-
|
|
1289
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1290
|
+
startTime, true, // force
|
|
1291
|
+
undefined);
|
|
1277
1292
|
}
|
|
1278
1293
|
}
|
|
1279
1294
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1286,9 +1301,9 @@ export class MidyGM2 {
|
|
|
1286
1301
|
scheduledNotes.set(noteNumber, [note]);
|
|
1287
1302
|
}
|
|
1288
1303
|
}
|
|
1289
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1290
|
-
|
|
1291
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
1304
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1305
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1306
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1292
1307
|
}
|
|
1293
1308
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1294
1309
|
const note = scheduledNotes[index];
|
|
@@ -1328,7 +1343,7 @@ export class MidyGM2 {
|
|
|
1328
1343
|
note.bufferSource.stop(stopTime);
|
|
1329
1344
|
});
|
|
1330
1345
|
}
|
|
1331
|
-
|
|
1346
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1332
1347
|
const channel = this.channels[channelNumber];
|
|
1333
1348
|
const state = channel.state;
|
|
1334
1349
|
if (!force) {
|
|
@@ -1367,24 +1382,19 @@ export class MidyGM2 {
|
|
|
1367
1382
|
}
|
|
1368
1383
|
}
|
|
1369
1384
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
return this.
|
|
1385
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1386
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1387
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
|
|
1388
|
+
undefined);
|
|
1373
1389
|
}
|
|
1374
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1390
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1375
1391
|
const velocity = halfVelocity * 2;
|
|
1376
1392
|
const channel = this.channels[channelNumber];
|
|
1377
1393
|
const promises = [];
|
|
1378
|
-
channel
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (!note)
|
|
1383
|
-
continue;
|
|
1384
|
-
const { noteNumber } = note;
|
|
1385
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1386
|
-
promises.push(promise);
|
|
1387
|
-
}
|
|
1394
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1395
|
+
const { noteNumber } = note;
|
|
1396
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1397
|
+
promises.push(promise);
|
|
1388
1398
|
});
|
|
1389
1399
|
return promises;
|
|
1390
1400
|
}
|
|
@@ -1395,39 +1405,38 @@ export class MidyGM2 {
|
|
|
1395
1405
|
channel.state.sostenutoPedal = 0;
|
|
1396
1406
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1397
1407
|
const { noteNumber } = activeNote;
|
|
1398
|
-
const promise = this.
|
|
1408
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1399
1409
|
promises.push(promise);
|
|
1400
1410
|
});
|
|
1401
1411
|
channel.sostenutoNotes.clear();
|
|
1402
1412
|
return promises;
|
|
1403
1413
|
}
|
|
1404
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1414
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1405
1415
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
1406
1416
|
const messageType = statusByte & 0xF0;
|
|
1407
1417
|
switch (messageType) {
|
|
1408
1418
|
case 0x80:
|
|
1409
|
-
return this.
|
|
1419
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1410
1420
|
case 0x90:
|
|
1411
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1421
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1412
1422
|
case 0xB0:
|
|
1413
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1423
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1414
1424
|
case 0xC0:
|
|
1415
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1425
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1416
1426
|
case 0xD0:
|
|
1417
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1427
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1418
1428
|
case 0xE0:
|
|
1419
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1429
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1420
1430
|
default:
|
|
1421
1431
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1422
1432
|
}
|
|
1423
1433
|
}
|
|
1424
|
-
handleProgramChange(channelNumber, program) {
|
|
1434
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1425
1435
|
const channel = this.channels[channelNumber];
|
|
1426
1436
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1427
1437
|
channel.program = program;
|
|
1428
1438
|
}
|
|
1429
|
-
handleChannelPressure(channelNumber, value) {
|
|
1430
|
-
const now = this.audioContext.currentTime;
|
|
1439
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1431
1440
|
const channel = this.channels[channelNumber];
|
|
1432
1441
|
const prev = channel.state.channelPressure;
|
|
1433
1442
|
const next = value / 127;
|
|
@@ -1437,69 +1446,68 @@ export class MidyGM2 {
|
|
|
1437
1446
|
channel.detune += pressureDepth * (next - prev);
|
|
1438
1447
|
}
|
|
1439
1448
|
const table = channel.channelPressureTable;
|
|
1440
|
-
this.getActiveNotes(channel,
|
|
1441
|
-
this.
|
|
1449
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1450
|
+
this.setControllerParameters(channel, note, table);
|
|
1442
1451
|
});
|
|
1443
1452
|
// this.applyVoiceParams(channel, 13);
|
|
1444
1453
|
}
|
|
1445
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1454
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1446
1455
|
const pitchBend = msb * 128 + lsb;
|
|
1447
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1456
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1448
1457
|
}
|
|
1449
|
-
setPitchBend(channelNumber, value) {
|
|
1458
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1459
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1450
1460
|
const channel = this.channels[channelNumber];
|
|
1451
1461
|
const state = channel.state;
|
|
1452
1462
|
const prev = state.pitchWheel * 2 - 1;
|
|
1453
1463
|
const next = (value - 8192) / 8192;
|
|
1454
1464
|
state.pitchWheel = value / 16383;
|
|
1455
1465
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1456
|
-
this.updateChannelDetune(channel);
|
|
1457
|
-
this.applyVoiceParams(channel, 14);
|
|
1466
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1467
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1458
1468
|
}
|
|
1459
|
-
setModLfoToPitch(channel, note,
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1469
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1470
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1471
|
+
this.getLFOPitchDepth(channel);
|
|
1462
1472
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1463
1473
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1464
1474
|
note.modulationDepth.gain
|
|
1465
|
-
.cancelScheduledValues(
|
|
1466
|
-
.setValueAtTime(modulationDepth,
|
|
1475
|
+
.cancelScheduledValues(scheduleTime)
|
|
1476
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1467
1477
|
}
|
|
1468
|
-
setVibLfoToPitch(channel, note) {
|
|
1469
|
-
const now = this.audioContext.currentTime;
|
|
1478
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1470
1479
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1471
1480
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1472
1481
|
2;
|
|
1473
1482
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1474
1483
|
note.vibratoDepth.gain
|
|
1475
|
-
.cancelScheduledValues(
|
|
1476
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1484
|
+
.cancelScheduledValues(scheduleTime)
|
|
1485
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1477
1486
|
}
|
|
1478
|
-
setModLfoToFilterFc(note,
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1487
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1488
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1489
|
+
this.getLFOFilterDepth(channel);
|
|
1481
1490
|
note.filterDepth.gain
|
|
1482
|
-
.cancelScheduledValues(
|
|
1483
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1491
|
+
.cancelScheduledValues(scheduleTime)
|
|
1492
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1484
1493
|
}
|
|
1485
|
-
setModLfoToVolume(note,
|
|
1486
|
-
const now = this.audioContext.currentTime;
|
|
1494
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1487
1495
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1488
1496
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1489
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1497
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1498
|
+
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1490
1499
|
note.volumeDepth.gain
|
|
1491
|
-
.cancelScheduledValues(
|
|
1492
|
-
.setValueAtTime(volumeDepth,
|
|
1500
|
+
.cancelScheduledValues(scheduleTime)
|
|
1501
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1493
1502
|
}
|
|
1494
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1503
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1495
1504
|
if (0 < prevValue) {
|
|
1496
1505
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1497
|
-
const now = this.audioContext.currentTime;
|
|
1498
1506
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1499
1507
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1500
1508
|
note.reverbEffectsSend.gain
|
|
1501
|
-
.cancelScheduledValues(
|
|
1502
|
-
.setValueAtTime(value,
|
|
1509
|
+
.cancelScheduledValues(scheduleTime)
|
|
1510
|
+
.setValueAtTime(value, scheduleTime);
|
|
1503
1511
|
}
|
|
1504
1512
|
else {
|
|
1505
1513
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1517,15 +1525,14 @@ export class MidyGM2 {
|
|
|
1517
1525
|
}
|
|
1518
1526
|
}
|
|
1519
1527
|
}
|
|
1520
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1528
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1521
1529
|
if (0 < prevValue) {
|
|
1522
1530
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1523
|
-
const now = this.audioContext.currentTime;
|
|
1524
1531
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1525
1532
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1526
1533
|
note.chorusEffectsSend.gain
|
|
1527
|
-
.cancelScheduledValues(
|
|
1528
|
-
.setValueAtTime(value,
|
|
1534
|
+
.cancelScheduledValues(scheduleTime)
|
|
1535
|
+
.setValueAtTime(value, scheduleTime);
|
|
1529
1536
|
}
|
|
1530
1537
|
else {
|
|
1531
1538
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1543,75 +1550,71 @@ export class MidyGM2 {
|
|
|
1543
1550
|
}
|
|
1544
1551
|
}
|
|
1545
1552
|
}
|
|
1546
|
-
setDelayModLFO(note) {
|
|
1547
|
-
const now = this.audioContext.currentTime;
|
|
1553
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1548
1554
|
const startTime = note.startTime;
|
|
1549
|
-
if (startTime <
|
|
1555
|
+
if (startTime < scheduleTime)
|
|
1550
1556
|
return;
|
|
1551
|
-
note.modulationLFO.stop(
|
|
1557
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1552
1558
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1553
1559
|
note.modulationLFO.connect(note.filterDepth);
|
|
1554
1560
|
}
|
|
1555
|
-
setFreqModLFO(note) {
|
|
1556
|
-
const now = this.audioContext.currentTime;
|
|
1561
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1557
1562
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1558
1563
|
note.modulationLFO.frequency
|
|
1559
|
-
.cancelScheduledValues(
|
|
1560
|
-
.setValueAtTime(freqModLFO,
|
|
1564
|
+
.cancelScheduledValues(scheduleTime)
|
|
1565
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1561
1566
|
}
|
|
1562
|
-
setFreqVibLFO(channel, note) {
|
|
1563
|
-
const now = this.audioContext.currentTime;
|
|
1567
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1564
1568
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1565
1569
|
note.vibratoLFO.frequency
|
|
1566
|
-
.cancelScheduledValues(
|
|
1567
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1570
|
+
.cancelScheduledValues(scheduleTime)
|
|
1571
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1568
1572
|
}
|
|
1569
1573
|
createVoiceParamsHandlers() {
|
|
1570
1574
|
return {
|
|
1571
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1575
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1572
1576
|
if (0 < channel.state.modulationDepth) {
|
|
1573
|
-
this.setModLfoToPitch(channel, note,
|
|
1577
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1574
1578
|
}
|
|
1575
1579
|
},
|
|
1576
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1580
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1577
1581
|
if (0 < channel.state.vibratoDepth) {
|
|
1578
|
-
this.setVibLfoToPitch(channel, note);
|
|
1582
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1579
1583
|
}
|
|
1580
1584
|
},
|
|
1581
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1585
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1582
1586
|
if (0 < channel.state.modulationDepth) {
|
|
1583
|
-
this.setModLfoToFilterFc(note,
|
|
1587
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1584
1588
|
}
|
|
1585
1589
|
},
|
|
1586
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1590
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1587
1591
|
if (0 < channel.state.modulationDepth) {
|
|
1588
|
-
this.setModLfoToVolume(note,
|
|
1592
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1589
1593
|
}
|
|
1590
1594
|
},
|
|
1591
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1592
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1595
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1596
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1593
1597
|
},
|
|
1594
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1595
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1598
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1599
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1596
1600
|
},
|
|
1597
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1598
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1599
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1601
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1602
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1603
|
+
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1600
1604
|
if (0 < channel.state.vibratoDepth) {
|
|
1601
|
-
const now = this.audioContext.currentTime;
|
|
1602
1605
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1603
1606
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1604
|
-
if (
|
|
1607
|
+
if (scheduleTime < prevStartTime)
|
|
1605
1608
|
return;
|
|
1606
1609
|
const value = note.voiceParams.delayVibLFO;
|
|
1607
1610
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1608
|
-
note.vibratoLFO.stop(
|
|
1611
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1609
1612
|
note.vibratoLFO.start(startTime);
|
|
1610
1613
|
}
|
|
1611
1614
|
},
|
|
1612
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1615
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1613
1616
|
if (0 < channel.state.vibratoDepth) {
|
|
1614
|
-
this.setFreqVibLFO(channel, note);
|
|
1617
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1615
1618
|
}
|
|
1616
1619
|
},
|
|
1617
1620
|
};
|
|
@@ -1623,7 +1626,7 @@ export class MidyGM2 {
|
|
|
1623
1626
|
state[3] = noteNumber / 127;
|
|
1624
1627
|
return state;
|
|
1625
1628
|
}
|
|
1626
|
-
applyVoiceParams(channel, controllerType) {
|
|
1629
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1627
1630
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1628
1631
|
for (let i = 0; i < noteList.length; i++) {
|
|
1629
1632
|
const note = noteList[i];
|
|
@@ -1639,7 +1642,7 @@ export class MidyGM2 {
|
|
|
1639
1642
|
continue;
|
|
1640
1643
|
note.voiceParams[key] = value;
|
|
1641
1644
|
if (key in this.voiceParamsHandlers) {
|
|
1642
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1645
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1643
1646
|
}
|
|
1644
1647
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1645
1648
|
if (appliedFilterEnvelope)
|
|
@@ -1652,12 +1655,12 @@ export class MidyGM2 {
|
|
|
1652
1655
|
noteVoiceParams[key] = voiceParams[key];
|
|
1653
1656
|
}
|
|
1654
1657
|
if (note.portamento) {
|
|
1655
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1658
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1656
1659
|
}
|
|
1657
1660
|
else {
|
|
1658
|
-
this.setFilterEnvelope(channel, note,
|
|
1661
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1659
1662
|
}
|
|
1660
|
-
this.setPitchEnvelope(note);
|
|
1663
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1661
1664
|
}
|
|
1662
1665
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1663
1666
|
if (appliedVolumeEnvelope)
|
|
@@ -1669,7 +1672,7 @@ export class MidyGM2 {
|
|
|
1669
1672
|
if (key in voiceParams)
|
|
1670
1673
|
noteVoiceParams[key] = voiceParams[key];
|
|
1671
1674
|
}
|
|
1672
|
-
this.setVolumeEnvelope(note,
|
|
1675
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1673
1676
|
}
|
|
1674
1677
|
}
|
|
1675
1678
|
}
|
|
@@ -1703,12 +1706,12 @@ export class MidyGM2 {
|
|
|
1703
1706
|
127: this.polyOn,
|
|
1704
1707
|
};
|
|
1705
1708
|
}
|
|
1706
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1709
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1707
1710
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1708
1711
|
if (handler) {
|
|
1709
|
-
handler.call(this, channelNumber, value);
|
|
1712
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1710
1713
|
const channel = this.channels[channelNumber];
|
|
1711
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1714
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1712
1715
|
this.applyControlTable(channel, controllerType);
|
|
1713
1716
|
}
|
|
1714
1717
|
else {
|
|
@@ -1718,55 +1721,45 @@ export class MidyGM2 {
|
|
|
1718
1721
|
setBankMSB(channelNumber, msb) {
|
|
1719
1722
|
this.channels[channelNumber].bankMSB = msb;
|
|
1720
1723
|
}
|
|
1721
|
-
updateModulation(channel) {
|
|
1722
|
-
|
|
1724
|
+
updateModulation(channel, scheduleTime) {
|
|
1725
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1723
1726
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1732
|
-
else {
|
|
1733
|
-
this.setPitchEnvelope(note);
|
|
1734
|
-
this.startModulation(channel, note, now);
|
|
1735
|
-
}
|
|
1727
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1728
|
+
if (note.modulationDepth) {
|
|
1729
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1733
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1736
1734
|
}
|
|
1737
1735
|
});
|
|
1738
1736
|
}
|
|
1739
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1737
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1740
1738
|
const channel = this.channels[channelNumber];
|
|
1741
1739
|
channel.state.modulationDepth = modulation / 127;
|
|
1742
|
-
this.updateModulation(channel);
|
|
1740
|
+
this.updateModulation(channel, scheduleTime);
|
|
1743
1741
|
}
|
|
1744
1742
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1745
1743
|
const channel = this.channels[channelNumber];
|
|
1746
1744
|
const factor = 5 * Math.log(10) / 127;
|
|
1747
1745
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1748
1746
|
}
|
|
1749
|
-
setKeyBasedVolume(channel) {
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
if (!note)
|
|
1755
|
-
continue;
|
|
1756
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1757
|
-
if (keyBasedValue === 0)
|
|
1758
|
-
continue;
|
|
1747
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1748
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1749
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1750
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1751
|
+
if (keyBasedValue !== 0) {
|
|
1759
1752
|
note.volumeNode.gain
|
|
1760
|
-
.cancelScheduledValues(
|
|
1761
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1753
|
+
.cancelScheduledValues(scheduleTime)
|
|
1754
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1762
1755
|
}
|
|
1763
1756
|
});
|
|
1764
1757
|
}
|
|
1765
|
-
setVolume(channelNumber, volume) {
|
|
1758
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1766
1759
|
const channel = this.channels[channelNumber];
|
|
1767
1760
|
channel.state.volume = volume / 127;
|
|
1768
|
-
this.updateChannelVolume(channel);
|
|
1769
|
-
this.setKeyBasedVolume(channel);
|
|
1761
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1762
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1770
1763
|
}
|
|
1771
1764
|
panToGain(pan) {
|
|
1772
1765
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1775,90 +1768,84 @@ export class MidyGM2 {
|
|
|
1775
1768
|
gainRight: Math.sin(theta),
|
|
1776
1769
|
};
|
|
1777
1770
|
}
|
|
1778
|
-
setKeyBasedPan(channel) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
if (!note)
|
|
1784
|
-
continue;
|
|
1785
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1786
|
-
if (keyBasedValue === 0)
|
|
1787
|
-
continue;
|
|
1771
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1772
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1773
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1774
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1775
|
+
if (keyBasedValue !== 0) {
|
|
1788
1776
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1789
1777
|
note.gainL.gain
|
|
1790
|
-
.cancelScheduledValues(
|
|
1791
|
-
.setValueAtTime(gainLeft,
|
|
1778
|
+
.cancelScheduledValues(scheduleTime)
|
|
1779
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1792
1780
|
note.gainR.gain
|
|
1793
|
-
.cancelScheduledValues(
|
|
1794
|
-
.setValueAtTime(gainRight,
|
|
1781
|
+
.cancelScheduledValues(scheduleTime)
|
|
1782
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1795
1783
|
}
|
|
1796
1784
|
});
|
|
1797
1785
|
}
|
|
1798
|
-
setPan(channelNumber, pan) {
|
|
1786
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1799
1787
|
const channel = this.channels[channelNumber];
|
|
1800
1788
|
channel.state.pan = pan / 127;
|
|
1801
|
-
this.updateChannelVolume(channel);
|
|
1802
|
-
this.setKeyBasedPan(channel);
|
|
1789
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1790
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1803
1791
|
}
|
|
1804
|
-
setExpression(channelNumber, expression) {
|
|
1792
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1805
1793
|
const channel = this.channels[channelNumber];
|
|
1806
1794
|
channel.state.expression = expression / 127;
|
|
1807
|
-
this.updateChannelVolume(channel);
|
|
1795
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1808
1796
|
}
|
|
1809
1797
|
setBankLSB(channelNumber, lsb) {
|
|
1810
1798
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1811
1799
|
}
|
|
1812
|
-
dataEntryLSB(channelNumber, value) {
|
|
1800
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1813
1801
|
this.channels[channelNumber].dataLSB = value;
|
|
1814
|
-
this.handleRPN(channelNumber);
|
|
1802
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1815
1803
|
}
|
|
1816
|
-
updateChannelVolume(channel) {
|
|
1817
|
-
const now = this.audioContext.currentTime;
|
|
1804
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1818
1805
|
const state = channel.state;
|
|
1819
1806
|
const volume = state.volume * state.expression;
|
|
1820
1807
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1821
1808
|
channel.gainL.gain
|
|
1822
|
-
.cancelScheduledValues(
|
|
1823
|
-
.setValueAtTime(volume * gainLeft,
|
|
1809
|
+
.cancelScheduledValues(scheduleTime)
|
|
1810
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1824
1811
|
channel.gainR.gain
|
|
1825
|
-
.cancelScheduledValues(
|
|
1826
|
-
.setValueAtTime(volume * gainRight,
|
|
1812
|
+
.cancelScheduledValues(scheduleTime)
|
|
1813
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1827
1814
|
}
|
|
1828
|
-
setSustainPedal(channelNumber, value) {
|
|
1815
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1816
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1829
1817
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1830
1818
|
if (value < 64) {
|
|
1831
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1819
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1832
1820
|
}
|
|
1833
1821
|
}
|
|
1834
1822
|
setPortamento(channelNumber, value) {
|
|
1835
1823
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1836
1824
|
}
|
|
1837
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1825
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1838
1826
|
const channel = this.channels[channelNumber];
|
|
1839
1827
|
channel.state.sostenutoPedal = value / 127;
|
|
1840
1828
|
if (64 <= value) {
|
|
1841
|
-
|
|
1842
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1829
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1843
1830
|
}
|
|
1844
1831
|
else {
|
|
1845
1832
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
1846
1833
|
}
|
|
1847
1834
|
}
|
|
1848
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1835
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1849
1836
|
const channel = this.channels[channelNumber];
|
|
1850
1837
|
channel.state.softPedal = softPedal / 127;
|
|
1851
1838
|
}
|
|
1852
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1839
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1853
1840
|
const channel = this.channels[channelNumber];
|
|
1854
1841
|
const state = channel.state;
|
|
1855
1842
|
const reverbEffect = this.reverbEffect;
|
|
1856
1843
|
if (0 < state.reverbSendLevel) {
|
|
1857
1844
|
if (0 < reverbSendLevel) {
|
|
1858
|
-
const now = this.audioContext.currentTime;
|
|
1859
1845
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1860
|
-
reverbEffect.input.gain
|
|
1861
|
-
|
|
1846
|
+
reverbEffect.input.gain
|
|
1847
|
+
.cancelScheduledValues(scheduleTime)
|
|
1848
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1862
1849
|
}
|
|
1863
1850
|
else {
|
|
1864
1851
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1875,31 +1862,31 @@ export class MidyGM2 {
|
|
|
1875
1862
|
}
|
|
1876
1863
|
else {
|
|
1877
1864
|
if (0 < reverbSendLevel) {
|
|
1878
|
-
const now = this.audioContext.currentTime;
|
|
1879
1865
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1880
1866
|
for (let i = 0; i < noteList.length; i++) {
|
|
1881
1867
|
const note = noteList[i];
|
|
1882
1868
|
if (!note)
|
|
1883
1869
|
continue;
|
|
1884
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1870
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
1885
1871
|
}
|
|
1886
1872
|
});
|
|
1887
1873
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1888
|
-
reverbEffect.input.gain
|
|
1889
|
-
|
|
1874
|
+
reverbEffect.input.gain
|
|
1875
|
+
.cancelScheduledValues(scheduleTime)
|
|
1876
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
1890
1877
|
}
|
|
1891
1878
|
}
|
|
1892
1879
|
}
|
|
1893
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1880
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
1894
1881
|
const channel = this.channels[channelNumber];
|
|
1895
1882
|
const state = channel.state;
|
|
1896
1883
|
const chorusEffect = this.chorusEffect;
|
|
1897
1884
|
if (0 < state.chorusSendLevel) {
|
|
1898
1885
|
if (0 < chorusSendLevel) {
|
|
1899
|
-
const now = this.audioContext.currentTime;
|
|
1900
1886
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1901
|
-
chorusEffect.input.gain
|
|
1902
|
-
|
|
1887
|
+
chorusEffect.input.gain
|
|
1888
|
+
.cancelScheduledValues(scheduleTime)
|
|
1889
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1903
1890
|
}
|
|
1904
1891
|
else {
|
|
1905
1892
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1916,18 +1903,18 @@ export class MidyGM2 {
|
|
|
1916
1903
|
}
|
|
1917
1904
|
else {
|
|
1918
1905
|
if (0 < chorusSendLevel) {
|
|
1919
|
-
const now = this.audioContext.currentTime;
|
|
1920
1906
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1921
1907
|
for (let i = 0; i < noteList.length; i++) {
|
|
1922
1908
|
const note = noteList[i];
|
|
1923
1909
|
if (!note)
|
|
1924
1910
|
continue;
|
|
1925
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1911
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
1926
1912
|
}
|
|
1927
1913
|
});
|
|
1928
1914
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1929
|
-
chorusEffect.input.gain
|
|
1930
|
-
|
|
1915
|
+
chorusEffect.input.gain
|
|
1916
|
+
.cancelScheduledValues(scheduleTime)
|
|
1917
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
1931
1918
|
}
|
|
1932
1919
|
}
|
|
1933
1920
|
}
|
|
@@ -1957,12 +1944,12 @@ export class MidyGM2 {
|
|
|
1957
1944
|
channel.dataMSB = minMSB;
|
|
1958
1945
|
}
|
|
1959
1946
|
}
|
|
1960
|
-
handleRPN(channelNumber) {
|
|
1947
|
+
handleRPN(channelNumber, scheduleTime) {
|
|
1961
1948
|
const channel = this.channels[channelNumber];
|
|
1962
1949
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1963
1950
|
switch (rpn) {
|
|
1964
1951
|
case 0:
|
|
1965
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
1952
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
1966
1953
|
break;
|
|
1967
1954
|
case 1:
|
|
1968
1955
|
this.handleFineTuningRPN(channelNumber);
|
|
@@ -1983,25 +1970,26 @@ export class MidyGM2 {
|
|
|
1983
1970
|
setRPNLSB(channelNumber, value) {
|
|
1984
1971
|
this.channels[channelNumber].rpnLSB = value;
|
|
1985
1972
|
}
|
|
1986
|
-
dataEntryMSB(channelNumber, value) {
|
|
1973
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
1987
1974
|
this.channels[channelNumber].dataMSB = value;
|
|
1988
|
-
this.handleRPN(channelNumber);
|
|
1975
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1989
1976
|
}
|
|
1990
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
1977
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
1991
1978
|
const channel = this.channels[channelNumber];
|
|
1992
1979
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1993
1980
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1994
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1981
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1995
1982
|
}
|
|
1996
|
-
setPitchBendRange(channelNumber, value) {
|
|
1983
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1984
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1997
1985
|
const channel = this.channels[channelNumber];
|
|
1998
1986
|
const state = channel.state;
|
|
1999
1987
|
const prev = state.pitchWheelSensitivity;
|
|
2000
1988
|
const next = value / 128;
|
|
2001
1989
|
state.pitchWheelSensitivity = next;
|
|
2002
1990
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2003
|
-
this.updateChannelDetune(channel);
|
|
2004
|
-
this.applyVoiceParams(channel, 16);
|
|
1991
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1992
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2005
1993
|
}
|
|
2006
1994
|
handleFineTuningRPN(channelNumber) {
|
|
2007
1995
|
const channel = this.channels[channelNumber];
|
|
@@ -2042,8 +2030,9 @@ export class MidyGM2 {
|
|
|
2042
2030
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2043
2031
|
this.updateModulation(channel);
|
|
2044
2032
|
}
|
|
2045
|
-
allSoundOff(channelNumber) {
|
|
2046
|
-
|
|
2033
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2034
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2035
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2047
2036
|
}
|
|
2048
2037
|
resetAllControllers(channelNumber) {
|
|
2049
2038
|
const stateTypes = [
|
|
@@ -2071,8 +2060,9 @@ export class MidyGM2 {
|
|
|
2071
2060
|
channel[type] = this.constructor.channelSettings[type];
|
|
2072
2061
|
}
|
|
2073
2062
|
}
|
|
2074
|
-
allNotesOff(channelNumber) {
|
|
2075
|
-
|
|
2063
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2064
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2065
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2076
2066
|
}
|
|
2077
2067
|
omniOff() {
|
|
2078
2068
|
this.omni = false;
|
|
@@ -2086,13 +2076,13 @@ export class MidyGM2 {
|
|
|
2086
2076
|
polyOn() {
|
|
2087
2077
|
this.mono = false;
|
|
2088
2078
|
}
|
|
2089
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2079
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2090
2080
|
switch (data[2]) {
|
|
2091
2081
|
case 8:
|
|
2092
2082
|
switch (data[3]) {
|
|
2093
2083
|
case 8:
|
|
2094
2084
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2095
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2085
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2096
2086
|
default:
|
|
2097
2087
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2098
2088
|
}
|
|
@@ -2135,18 +2125,18 @@ export class MidyGM2 {
|
|
|
2135
2125
|
this.channels[9].bankMSB = 120;
|
|
2136
2126
|
this.channels[9].bank = 120 * 128;
|
|
2137
2127
|
}
|
|
2138
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2128
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2139
2129
|
switch (data[2]) {
|
|
2140
2130
|
case 4:
|
|
2141
2131
|
switch (data[3]) {
|
|
2142
2132
|
case 1:
|
|
2143
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2133
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2144
2134
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2145
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2135
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2146
2136
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2147
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2137
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2148
2138
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2149
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2139
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2150
2140
|
default:
|
|
2151
2141
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2152
2142
|
}
|
|
@@ -2164,7 +2154,7 @@ export class MidyGM2 {
|
|
|
2164
2154
|
case 10:
|
|
2165
2155
|
switch (data[3]) {
|
|
2166
2156
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2167
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2157
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2168
2158
|
default:
|
|
2169
2159
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2170
2160
|
}
|
|
@@ -2173,49 +2163,50 @@ export class MidyGM2 {
|
|
|
2173
2163
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2174
2164
|
}
|
|
2175
2165
|
}
|
|
2176
|
-
handleMasterVolumeSysEx(data) {
|
|
2166
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2177
2167
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2178
|
-
this.setMasterVolume(volume);
|
|
2168
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2179
2169
|
}
|
|
2180
|
-
setMasterVolume(volume) {
|
|
2170
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2171
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2181
2172
|
if (volume < 0 && 1 < volume) {
|
|
2182
2173
|
console.error("Master Volume is out of range");
|
|
2183
2174
|
}
|
|
2184
2175
|
else {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2176
|
+
this.masterVolume.gain
|
|
2177
|
+
.cancelScheduledValues(scheduleTime)
|
|
2178
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2188
2179
|
}
|
|
2189
2180
|
}
|
|
2190
|
-
handleMasterFineTuningSysEx(data) {
|
|
2181
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2191
2182
|
const fineTuning = data[5] * 128 + data[4];
|
|
2192
|
-
this.setMasterFineTuning(fineTuning);
|
|
2183
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2193
2184
|
}
|
|
2194
|
-
setMasterFineTuning(value) {
|
|
2185
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2195
2186
|
const prev = this.masterFineTuning;
|
|
2196
2187
|
const next = (value - 8192) / 8.192; // cent
|
|
2197
2188
|
this.masterFineTuning = next;
|
|
2198
2189
|
channel.detune += next - prev;
|
|
2199
|
-
this.updateChannelDetune(channel);
|
|
2190
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2200
2191
|
}
|
|
2201
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2192
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2202
2193
|
const coarseTuning = data[4];
|
|
2203
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2194
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2204
2195
|
}
|
|
2205
|
-
setMasterCoarseTuning(value) {
|
|
2196
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2206
2197
|
const prev = this.masterCoarseTuning;
|
|
2207
2198
|
const next = (value - 64) * 100; // cent
|
|
2208
2199
|
this.masterCoarseTuning = next;
|
|
2209
2200
|
channel.detune += next - prev;
|
|
2210
|
-
this.updateChannelDetune(channel);
|
|
2201
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2211
2202
|
}
|
|
2212
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2203
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2213
2204
|
if (data[7] === 1) {
|
|
2214
2205
|
switch (data[8]) {
|
|
2215
2206
|
case 1:
|
|
2216
2207
|
return this.handleReverbParameterSysEx(data);
|
|
2217
2208
|
case 2:
|
|
2218
|
-
return this.handleChorusParameterSysEx(data);
|
|
2209
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2219
2210
|
default:
|
|
2220
2211
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2221
2212
|
}
|
|
@@ -2294,88 +2285,84 @@ export class MidyGM2 {
|
|
|
2294
2285
|
calcDelay(rt60, feedback) {
|
|
2295
2286
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2296
2287
|
}
|
|
2297
|
-
handleChorusParameterSysEx(data) {
|
|
2288
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2298
2289
|
switch (data[9]) {
|
|
2299
2290
|
case 0:
|
|
2300
|
-
return this.setChorusType(data[10]);
|
|
2291
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2301
2292
|
case 1:
|
|
2302
|
-
return this.setChorusModRate(data[10]);
|
|
2293
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2303
2294
|
case 2:
|
|
2304
|
-
return this.setChorusModDepth(data[10]);
|
|
2295
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2305
2296
|
case 3:
|
|
2306
|
-
return this.setChorusFeedback(data[10]);
|
|
2297
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2307
2298
|
case 4:
|
|
2308
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2299
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2309
2300
|
}
|
|
2310
2301
|
}
|
|
2311
|
-
setChorusType(type) {
|
|
2302
|
+
setChorusType(type, scheduleTime) {
|
|
2312
2303
|
switch (type) {
|
|
2313
2304
|
case 0:
|
|
2314
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2305
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2315
2306
|
case 1:
|
|
2316
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2307
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2317
2308
|
case 2:
|
|
2318
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2309
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2319
2310
|
case 3:
|
|
2320
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2311
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2321
2312
|
case 4:
|
|
2322
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2313
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2323
2314
|
case 5:
|
|
2324
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2315
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2325
2316
|
default:
|
|
2326
2317
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2327
2318
|
}
|
|
2328
2319
|
}
|
|
2329
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2330
|
-
this.setChorusModRate(modRate);
|
|
2331
|
-
this.setChorusModDepth(modDepth);
|
|
2332
|
-
this.setChorusFeedback(feedback);
|
|
2333
|
-
this.setChorusSendToReverb(sendToReverb);
|
|
2320
|
+
setChorusParameter(modRate, modDepth, feedback, sendToReverb, scheduleTime) {
|
|
2321
|
+
this.setChorusModRate(modRate, scheduleTime);
|
|
2322
|
+
this.setChorusModDepth(modDepth, scheduleTime);
|
|
2323
|
+
this.setChorusFeedback(feedback, scheduleTime);
|
|
2324
|
+
this.setChorusSendToReverb(sendToReverb, scheduleTime);
|
|
2334
2325
|
}
|
|
2335
|
-
setChorusModRate(value) {
|
|
2336
|
-
const now = this.audioContext.currentTime;
|
|
2326
|
+
setChorusModRate(value, scheduleTime) {
|
|
2337
2327
|
const modRate = this.getChorusModRate(value);
|
|
2338
2328
|
this.chorus.modRate = modRate;
|
|
2339
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2329
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2340
2330
|
}
|
|
2341
2331
|
getChorusModRate(value) {
|
|
2342
2332
|
return value * 0.122; // Hz
|
|
2343
2333
|
}
|
|
2344
|
-
setChorusModDepth(value) {
|
|
2345
|
-
const now = this.audioContext.currentTime;
|
|
2334
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2346
2335
|
const modDepth = this.getChorusModDepth(value);
|
|
2347
2336
|
this.chorus.modDepth = modDepth;
|
|
2348
2337
|
this.chorusEffect.lfoGain.gain
|
|
2349
|
-
.cancelScheduledValues(
|
|
2350
|
-
.setValueAtTime(modDepth / 2,
|
|
2338
|
+
.cancelScheduledValues(scheduleTime)
|
|
2339
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2351
2340
|
}
|
|
2352
2341
|
getChorusModDepth(value) {
|
|
2353
2342
|
return (value + 1) / 3200; // second
|
|
2354
2343
|
}
|
|
2355
|
-
setChorusFeedback(value) {
|
|
2356
|
-
const now = this.audioContext.currentTime;
|
|
2344
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2357
2345
|
const feedback = this.getChorusFeedback(value);
|
|
2358
2346
|
this.chorus.feedback = feedback;
|
|
2359
2347
|
const chorusEffect = this.chorusEffect;
|
|
2360
2348
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2361
2349
|
chorusEffect.feedbackGains[i].gain
|
|
2362
|
-
.cancelScheduledValues(
|
|
2363
|
-
.setValueAtTime(feedback,
|
|
2350
|
+
.cancelScheduledValues(scheduleTime)
|
|
2351
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2364
2352
|
}
|
|
2365
2353
|
}
|
|
2366
2354
|
getChorusFeedback(value) {
|
|
2367
2355
|
return value * 0.00763;
|
|
2368
2356
|
}
|
|
2369
|
-
setChorusSendToReverb(value) {
|
|
2357
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2370
2358
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2371
2359
|
const sendGain = this.chorusEffect.sendGain;
|
|
2372
2360
|
if (0 < this.chorus.sendToReverb) {
|
|
2373
2361
|
this.chorus.sendToReverb = sendToReverb;
|
|
2374
2362
|
if (0 < sendToReverb) {
|
|
2375
|
-
const now = this.audioContext.currentTime;
|
|
2376
2363
|
sendGain.gain
|
|
2377
|
-
.cancelScheduledValues(
|
|
2378
|
-
.setValueAtTime(sendToReverb,
|
|
2364
|
+
.cancelScheduledValues(scheduleTime)
|
|
2365
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2379
2366
|
}
|
|
2380
2367
|
else {
|
|
2381
2368
|
sendGain.disconnect();
|
|
@@ -2384,11 +2371,10 @@ export class MidyGM2 {
|
|
|
2384
2371
|
else {
|
|
2385
2372
|
this.chorus.sendToReverb = sendToReverb;
|
|
2386
2373
|
if (0 < sendToReverb) {
|
|
2387
|
-
const now = this.audioContext.currentTime;
|
|
2388
2374
|
sendGain.connect(this.reverbEffect.input);
|
|
2389
2375
|
sendGain.gain
|
|
2390
|
-
.cancelScheduledValues(
|
|
2391
|
-
.setValueAtTime(sendToReverb,
|
|
2376
|
+
.cancelScheduledValues(scheduleTime)
|
|
2377
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2392
2378
|
}
|
|
2393
2379
|
}
|
|
2394
2380
|
}
|
|
@@ -2414,7 +2400,7 @@ export class MidyGM2 {
|
|
|
2414
2400
|
}
|
|
2415
2401
|
return bitmap;
|
|
2416
2402
|
}
|
|
2417
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2403
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2418
2404
|
if (data.length < 19) {
|
|
2419
2405
|
console.error("Data length is too short");
|
|
2420
2406
|
return;
|
|
@@ -2429,47 +2415,51 @@ export class MidyGM2 {
|
|
|
2429
2415
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2430
2416
|
}
|
|
2431
2417
|
if (realtime)
|
|
2432
|
-
this.updateChannelDetune(channel);
|
|
2418
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2433
2419
|
}
|
|
2434
2420
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
if (!note.portamento) {
|
|
2440
|
-
if (table[1] !== 64) {
|
|
2441
|
-
const channelPressure = channel.channelPressureTable[1] *
|
|
2442
|
-
channel.state.channelPressure;
|
|
2443
|
-
const pressure = (channelPressure - 64) * 15;
|
|
2444
|
-
this.setFilterEnvelope(channel, note, pressure);
|
|
2445
|
-
}
|
|
2446
|
-
if (table[2] !== 64) {
|
|
2447
|
-
const channelPressure = channel.channelPressureTable[2] *
|
|
2448
|
-
channel.state.channelPressure;
|
|
2449
|
-
const pressure = channelPressure / 64;
|
|
2450
|
-
this.setVolumeEnvelope(note, pressure);
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
if (table[3] !== 0) {
|
|
2454
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2455
|
-
channel.state.channelPressure;
|
|
2456
|
-
const pressure = channelPressure / 127 * 600;
|
|
2457
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2458
|
-
}
|
|
2459
|
-
if (table[4] !== 0) {
|
|
2460
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2461
|
-
channel.state.channelPressure;
|
|
2462
|
-
const pressure = channelPressure / 127 * 2400;
|
|
2463
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2464
|
-
}
|
|
2465
|
-
if (table[5] !== 0) {
|
|
2466
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2467
|
-
channel.state.channelPressure;
|
|
2468
|
-
const pressure = channelPressure / 127;
|
|
2469
|
-
this.setModLfoToVolume(note, pressure);
|
|
2470
|
-
}
|
|
2421
|
+
getFilterCutoffControl(channel) {
|
|
2422
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2423
|
+
channel.state.channelPressure;
|
|
2424
|
+
return channelPressure * 15;
|
|
2471
2425
|
}
|
|
2472
|
-
|
|
2426
|
+
getAmplitudeControl(channel) {
|
|
2427
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2428
|
+
channel.state.channelPressure;
|
|
2429
|
+
return channelPressure / 64;
|
|
2430
|
+
}
|
|
2431
|
+
getLFOPitchDepth(channel) {
|
|
2432
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2433
|
+
channel.state.channelPressure;
|
|
2434
|
+
return channelPressure / 127 * 600;
|
|
2435
|
+
}
|
|
2436
|
+
getLFOFilterDepth(channel) {
|
|
2437
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2438
|
+
channel.state.channelPressure;
|
|
2439
|
+
return channelPressure / 127 * 2400;
|
|
2440
|
+
}
|
|
2441
|
+
getLFOAmplitudeDepth(channel) {
|
|
2442
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2443
|
+
channel.state.channelPressure;
|
|
2444
|
+
return channelPressure / 127;
|
|
2445
|
+
}
|
|
2446
|
+
setControllerParameters(channel, note, table) {
|
|
2447
|
+
if (table[0] !== 64)
|
|
2448
|
+
this.updateDetune(channel, note);
|
|
2449
|
+
if (!note.portamento) {
|
|
2450
|
+
if (table[1] !== 64)
|
|
2451
|
+
this.setFilterEnvelope(channel, note);
|
|
2452
|
+
if (table[2] !== 64)
|
|
2453
|
+
this.setVolumeEnvelope(channel, note);
|
|
2454
|
+
}
|
|
2455
|
+
if (table[3] !== 0)
|
|
2456
|
+
this.setModLfoToPitch(channel, note);
|
|
2457
|
+
if (table[4] !== 0)
|
|
2458
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2459
|
+
if (table[5] !== 0)
|
|
2460
|
+
this.setModLfoToVolume(channel, note);
|
|
2461
|
+
}
|
|
2462
|
+
handlePressureSysEx(data, tableName) {
|
|
2473
2463
|
const channelNumber = data[4];
|
|
2474
2464
|
const table = this.channels[channelNumber][tableName];
|
|
2475
2465
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2498,7 +2488,7 @@ export class MidyGM2 {
|
|
|
2498
2488
|
const note = noteList[i];
|
|
2499
2489
|
if (!note)
|
|
2500
2490
|
continue;
|
|
2501
|
-
this.
|
|
2491
|
+
this.setControllerParameters(channel, note, table);
|
|
2502
2492
|
}
|
|
2503
2493
|
});
|
|
2504
2494
|
}
|
|
@@ -2517,7 +2507,7 @@ export class MidyGM2 {
|
|
|
2517
2507
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2518
2508
|
return (controlValue + 64) / 64;
|
|
2519
2509
|
}
|
|
2520
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2510
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2521
2511
|
const channelNumber = data[4];
|
|
2522
2512
|
const keyNumber = data[5];
|
|
2523
2513
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2527,30 +2517,27 @@ export class MidyGM2 {
|
|
|
2527
2517
|
const index = keyNumber * 128 + controllerType;
|
|
2528
2518
|
table[index] = value - 64;
|
|
2529
2519
|
}
|
|
2530
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2531
|
-
}
|
|
2532
|
-
handleExclusiveMessage(data) {
|
|
2533
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2520
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2534
2521
|
}
|
|
2535
|
-
handleSysEx(data) {
|
|
2522
|
+
handleSysEx(data, scheduleTime) {
|
|
2536
2523
|
switch (data[0]) {
|
|
2537
2524
|
case 126:
|
|
2538
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2525
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2539
2526
|
case 127:
|
|
2540
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2527
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2541
2528
|
default:
|
|
2542
|
-
|
|
2529
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2543
2530
|
}
|
|
2544
2531
|
}
|
|
2545
|
-
scheduleTask(callback,
|
|
2532
|
+
scheduleTask(callback, scheduleTime) {
|
|
2546
2533
|
return new Promise((resolve) => {
|
|
2547
2534
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2548
2535
|
bufferSource.onended = () => {
|
|
2549
2536
|
callback();
|
|
2550
2537
|
resolve();
|
|
2551
2538
|
};
|
|
2552
|
-
bufferSource.start(
|
|
2553
|
-
bufferSource.stop(
|
|
2539
|
+
bufferSource.start(scheduleTime);
|
|
2540
|
+
bufferSource.stop(scheduleTime);
|
|
2554
2541
|
});
|
|
2555
2542
|
}
|
|
2556
2543
|
}
|
|
@@ -2561,9 +2548,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2561
2548
|
value: {
|
|
2562
2549
|
currentBufferSource: null,
|
|
2563
2550
|
detune: 0,
|
|
2564
|
-
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
2565
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2566
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2567
2551
|
program: 0,
|
|
2568
2552
|
bank: 121 * 128,
|
|
2569
2553
|
bankMSB: 121,
|