@marmooo/midy 0.2.0 → 0.2.2
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/LICENSE +201 -0
- package/README.md +118 -0
- package/esm/midy-GM1.d.ts +12 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +85 -78
- package/esm/midy-GM2.d.ts +40 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +377 -194
- package/esm/midy-GMLite.d.ts +9 -7
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +64 -61
- package/esm/midy.d.ts +39 -21
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +444 -252
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +12 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +85 -78
- package/script/midy-GM2.d.ts +40 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +377 -194
- package/script/midy-GMLite.d.ts +9 -7
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +64 -61
- package/script/midy.d.ts +39 -21
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +444 -252
package/script/midy-GM2.js
CHANGED
|
@@ -17,12 +17,30 @@ class Note {
|
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
20
26
|
Object.defineProperty(this, "volumeNode", {
|
|
21
27
|
enumerable: true,
|
|
22
28
|
configurable: true,
|
|
23
29
|
writable: true,
|
|
24
30
|
value: void 0
|
|
25
31
|
});
|
|
32
|
+
Object.defineProperty(this, "gainL", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "gainR", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
26
44
|
Object.defineProperty(this, "volumeDepth", {
|
|
27
45
|
enumerable: true,
|
|
28
46
|
configurable: true,
|
|
@@ -901,16 +919,48 @@ class MidyGM2 {
|
|
|
901
919
|
cbToRatio(cb) {
|
|
902
920
|
return Math.pow(10, cb / 200);
|
|
903
921
|
}
|
|
922
|
+
rateToCent(rate) {
|
|
923
|
+
return 1200 * Math.log2(rate);
|
|
924
|
+
}
|
|
925
|
+
centToRate(cent) {
|
|
926
|
+
return Math.pow(2, cent / 1200);
|
|
927
|
+
}
|
|
904
928
|
centToHz(cent) {
|
|
905
|
-
return 8.176 *
|
|
929
|
+
return 8.176 * this.centToRate(cent);
|
|
906
930
|
}
|
|
907
|
-
|
|
931
|
+
calcChannelDetune(channel) {
|
|
908
932
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
909
933
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
934
|
+
const tuning = masterTuning + channelTuning;
|
|
910
935
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
911
|
-
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity *
|
|
936
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
912
937
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
913
|
-
|
|
938
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
939
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
940
|
+
return tuning + pitch + pressure;
|
|
941
|
+
}
|
|
942
|
+
calcNoteDetune(channel, note) {
|
|
943
|
+
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
944
|
+
}
|
|
945
|
+
updateDetune(channel) {
|
|
946
|
+
const now = this.audioContext.currentTime;
|
|
947
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
948
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
949
|
+
const note = noteList[i];
|
|
950
|
+
if (!note)
|
|
951
|
+
continue;
|
|
952
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
953
|
+
const detune = channel.detune + noteDetune;
|
|
954
|
+
note.bufferSource.detune
|
|
955
|
+
.cancelScheduledValues(now)
|
|
956
|
+
.setValueAtTime(detune, now);
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
getPortamentoTime(channel) {
|
|
961
|
+
const factor = 5 * Math.log(10) / 127;
|
|
962
|
+
const time = channel.state.portamentoTime;
|
|
963
|
+
return Math.log(time) / factor;
|
|
914
964
|
}
|
|
915
965
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
916
966
|
const now = this.audioContext.currentTime;
|
|
@@ -918,22 +968,26 @@ class MidyGM2 {
|
|
|
918
968
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
919
969
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
920
970
|
const volDelay = startTime + voiceParams.volDelay;
|
|
921
|
-
const portamentoTime = volDelay + channel
|
|
922
|
-
note.
|
|
971
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
972
|
+
note.volumeEnvelopeNode.gain
|
|
923
973
|
.cancelScheduledValues(now)
|
|
924
974
|
.setValueAtTime(0, volDelay)
|
|
925
975
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
926
976
|
}
|
|
927
|
-
setVolumeEnvelope(note) {
|
|
977
|
+
setVolumeEnvelope(channel, note) {
|
|
928
978
|
const now = this.audioContext.currentTime;
|
|
979
|
+
const state = channel.state;
|
|
929
980
|
const { voiceParams, startTime } = note;
|
|
930
|
-
const
|
|
981
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
982
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
983
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
984
|
+
pressure;
|
|
931
985
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
932
986
|
const volDelay = startTime + voiceParams.volDelay;
|
|
933
987
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
934
988
|
const volHold = volAttack + voiceParams.volHold;
|
|
935
989
|
const volDecay = volHold + voiceParams.volDecay;
|
|
936
|
-
note.
|
|
990
|
+
note.volumeEnvelopeNode.gain
|
|
937
991
|
.cancelScheduledValues(now)
|
|
938
992
|
.setValueAtTime(0, startTime)
|
|
939
993
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -941,32 +995,28 @@ class MidyGM2 {
|
|
|
941
995
|
.setValueAtTime(attackVolume, volHold)
|
|
942
996
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
943
997
|
}
|
|
944
|
-
|
|
998
|
+
setPitchEnvelope(note) {
|
|
945
999
|
const now = this.audioContext.currentTime;
|
|
1000
|
+
const { voiceParams } = note;
|
|
1001
|
+
const baseRate = voiceParams.playbackRate;
|
|
946
1002
|
note.bufferSource.playbackRate
|
|
947
1003
|
.cancelScheduledValues(now)
|
|
948
|
-
.setValueAtTime(
|
|
949
|
-
|
|
950
|
-
setPitch(channel, note) {
|
|
951
|
-
const now = this.audioContext.currentTime;
|
|
952
|
-
const { startTime } = note;
|
|
953
|
-
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
954
|
-
note.bufferSource.detune
|
|
955
|
-
.cancelScheduledValues(now)
|
|
956
|
-
.setValueAtTime(basePitch, startTime);
|
|
957
|
-
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
1004
|
+
.setValueAtTime(baseRate, now);
|
|
1005
|
+
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
958
1006
|
if (modEnvToPitch === 0)
|
|
959
1007
|
return;
|
|
1008
|
+
const basePitch = this.rateToCent(baseRate);
|
|
960
1009
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1010
|
+
const peekRate = this.centToRate(peekPitch);
|
|
961
1011
|
const modDelay = startTime + voiceParams.modDelay;
|
|
962
1012
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
963
1013
|
const modHold = modAttack + voiceParams.modHold;
|
|
964
1014
|
const modDecay = modHold + voiceParams.modDecay;
|
|
965
|
-
note.bufferSource.
|
|
966
|
-
.setValueAtTime(
|
|
967
|
-
.exponentialRampToValueAtTime(
|
|
968
|
-
.setValueAtTime(
|
|
969
|
-
.linearRampToValueAtTime(
|
|
1015
|
+
note.bufferSource.playbackRate
|
|
1016
|
+
.setValueAtTime(baseRate, modDelay)
|
|
1017
|
+
.exponentialRampToValueAtTime(peekRate, modAttack)
|
|
1018
|
+
.setValueAtTime(peekRate, modHold)
|
|
1019
|
+
.linearRampToValueAtTime(baseRate, modDecay);
|
|
970
1020
|
}
|
|
971
1021
|
clampCutoffFrequency(frequency) {
|
|
972
1022
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -979,14 +1029,16 @@ class MidyGM2 {
|
|
|
979
1029
|
const { voiceParams, noteNumber, startTime } = note;
|
|
980
1030
|
const softPedalFactor = 1 -
|
|
981
1031
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
982
|
-
const
|
|
983
|
-
|
|
1032
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1033
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1034
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1035
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
984
1036
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
985
1037
|
const sustainFreq = baseFreq +
|
|
986
1038
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
987
1039
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
988
1040
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
989
|
-
const portamentoTime = startTime + channel
|
|
1041
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
990
1042
|
const modDelay = startTime + voiceParams.modDelay;
|
|
991
1043
|
note.filterNode.frequency
|
|
992
1044
|
.cancelScheduledValues(now)
|
|
@@ -1031,14 +1083,14 @@ class MidyGM2 {
|
|
|
1031
1083
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1032
1084
|
this.setModLfoToPitch(channel, note);
|
|
1033
1085
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1034
|
-
this.setModLfoToVolume(note);
|
|
1086
|
+
this.setModLfoToVolume(channel, note);
|
|
1035
1087
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1036
1088
|
note.modulationLFO.connect(note.filterDepth);
|
|
1037
1089
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1038
1090
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1039
1091
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1040
1092
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1041
|
-
note.volumeDepth.connect(note.
|
|
1093
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1042
1094
|
}
|
|
1043
1095
|
startVibrato(channel, note, startTime) {
|
|
1044
1096
|
const { voiceParams } = note;
|
|
@@ -1060,6 +1112,9 @@ class MidyGM2 {
|
|
|
1060
1112
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1061
1113
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1062
1114
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1115
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1116
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1117
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1063
1118
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1064
1119
|
type: "lowpass",
|
|
1065
1120
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
@@ -1077,9 +1132,8 @@ class MidyGM2 {
|
|
|
1077
1132
|
if (0 < state.vibratoDepth) {
|
|
1078
1133
|
this.startVibrato(channel, note, startTime);
|
|
1079
1134
|
}
|
|
1080
|
-
this.
|
|
1135
|
+
this.setPitchEnvelope(note);
|
|
1081
1136
|
if (0 < state.modulationDepth) {
|
|
1082
|
-
this.setPitch(channel, note);
|
|
1083
1137
|
this.startModulation(channel, note, startTime);
|
|
1084
1138
|
}
|
|
1085
1139
|
if (this.mono && channel.currentBufferSource) {
|
|
@@ -1087,7 +1141,10 @@ class MidyGM2 {
|
|
|
1087
1141
|
channel.currentBufferSource = note.bufferSource;
|
|
1088
1142
|
}
|
|
1089
1143
|
note.bufferSource.connect(note.filterNode);
|
|
1090
|
-
note.filterNode.connect(note.
|
|
1144
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1145
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1146
|
+
note.volumeNode.connect(note.gainL);
|
|
1147
|
+
note.volumeNode.connect(note.gainR);
|
|
1091
1148
|
if (0 < channel.chorusSendLevel) {
|
|
1092
1149
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1093
1150
|
}
|
|
@@ -1118,8 +1175,8 @@ class MidyGM2 {
|
|
|
1118
1175
|
if (!voice)
|
|
1119
1176
|
return;
|
|
1120
1177
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1121
|
-
note.
|
|
1122
|
-
note.
|
|
1178
|
+
note.gainL.connect(channel.gainL);
|
|
1179
|
+
note.gainR.connect(channel.gainR);
|
|
1123
1180
|
if (channel.state.sostenutoPedal) {
|
|
1124
1181
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1125
1182
|
}
|
|
@@ -1150,7 +1207,7 @@ class MidyGM2 {
|
|
|
1150
1207
|
}
|
|
1151
1208
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1152
1209
|
const note = scheduledNotes[index];
|
|
1153
|
-
note.
|
|
1210
|
+
note.volumeEnvelopeNode.gain
|
|
1154
1211
|
.cancelScheduledValues(endTime)
|
|
1155
1212
|
.linearRampToValueAtTime(0, stopTime);
|
|
1156
1213
|
note.ending = true;
|
|
@@ -1161,8 +1218,11 @@ class MidyGM2 {
|
|
|
1161
1218
|
note.bufferSource.onended = () => {
|
|
1162
1219
|
scheduledNotes[index] = null;
|
|
1163
1220
|
note.bufferSource.disconnect();
|
|
1164
|
-
note.volumeNode.disconnect();
|
|
1165
1221
|
note.filterNode.disconnect();
|
|
1222
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1223
|
+
note.volumeNode.disconnect();
|
|
1224
|
+
note.gainL.disconnect();
|
|
1225
|
+
note.gainR.disconnect();
|
|
1166
1226
|
if (note.modulationDepth) {
|
|
1167
1227
|
note.volumeDepth.disconnect();
|
|
1168
1228
|
note.modulationDepth.disconnect();
|
|
@@ -1211,12 +1271,13 @@ class MidyGM2 {
|
|
|
1211
1271
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1212
1272
|
}
|
|
1213
1273
|
else {
|
|
1214
|
-
const portamentoTime = endTime +
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1274
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1275
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1276
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1277
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1278
|
+
note.bufferSource.playbackRate
|
|
1218
1279
|
.cancelScheduledValues(endTime)
|
|
1219
|
-
.linearRampToValueAtTime(
|
|
1280
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1220
1281
|
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1221
1282
|
}
|
|
1222
1283
|
}
|
|
@@ -1280,22 +1341,45 @@ class MidyGM2 {
|
|
|
1280
1341
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1281
1342
|
channel.program = program;
|
|
1282
1343
|
}
|
|
1283
|
-
handleChannelPressure(channelNumber,
|
|
1284
|
-
const now = this.audioContext.currentTime;
|
|
1344
|
+
handleChannelPressure(channelNumber, value) {
|
|
1285
1345
|
const channel = this.channels[channelNumber];
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (channel.
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
activeNote.volumeNode.gain
|
|
1293
|
-
.cancelScheduledValues(now)
|
|
1294
|
-
.setValueAtTime(gain * pressure, now);
|
|
1295
|
-
});
|
|
1346
|
+
const prev = channel.state.channelPressure;
|
|
1347
|
+
const next = value / 127;
|
|
1348
|
+
channel.state.channelPressure = next;
|
|
1349
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1350
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1351
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1296
1352
|
}
|
|
1353
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1354
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1355
|
+
const note = noteList[i];
|
|
1356
|
+
if (!note)
|
|
1357
|
+
continue;
|
|
1358
|
+
this.setChannelPressure(channel, note);
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1297
1361
|
// this.applyVoiceParams(channel, 13);
|
|
1298
1362
|
}
|
|
1363
|
+
setChannelPressure(channel, note) {
|
|
1364
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1365
|
+
this.updateDetune(channel);
|
|
1366
|
+
}
|
|
1367
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1368
|
+
this.setFilterEnvelope(channel, note);
|
|
1369
|
+
}
|
|
1370
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1371
|
+
this.setVolumeEnvelope(channel, note);
|
|
1372
|
+
}
|
|
1373
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1374
|
+
this.setModLfoToPitch(channel, note);
|
|
1375
|
+
}
|
|
1376
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1377
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1378
|
+
}
|
|
1379
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1380
|
+
this.setModLfoToVolume(channel, note);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1299
1383
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1300
1384
|
const pitchBend = msb * 128 + lsb;
|
|
1301
1385
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1303,98 +1387,107 @@ class MidyGM2 {
|
|
|
1303
1387
|
setPitchBend(channelNumber, value) {
|
|
1304
1388
|
const channel = this.channels[channelNumber];
|
|
1305
1389
|
const state = channel.state;
|
|
1390
|
+
const prev = state.pitchWheel * 2 - 1;
|
|
1391
|
+
const next = (value - 8192) / 8192;
|
|
1306
1392
|
state.pitchWheel = value / 16383;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
this.updateDetune(channel, detuneChange);
|
|
1393
|
+
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1394
|
+
this.updateDetune(channel);
|
|
1310
1395
|
this.applyVoiceParams(channel, 14);
|
|
1311
1396
|
}
|
|
1312
1397
|
setModLfoToPitch(channel, note) {
|
|
1313
1398
|
const now = this.audioContext.currentTime;
|
|
1314
|
-
const
|
|
1315
|
-
const
|
|
1399
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1400
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1401
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1402
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1316
1403
|
channel.state.modulationDepth;
|
|
1317
|
-
const
|
|
1404
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1318
1405
|
note.modulationDepth.gain
|
|
1319
1406
|
.cancelScheduledValues(now)
|
|
1320
|
-
.setValueAtTime(modulationDepth
|
|
1407
|
+
.setValueAtTime(modulationDepth, now);
|
|
1408
|
+
}
|
|
1409
|
+
setVibLfoToPitch(channel, note) {
|
|
1410
|
+
const now = this.audioContext.currentTime;
|
|
1411
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1412
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1413
|
+
2;
|
|
1414
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1415
|
+
note.vibratoDepth.gain
|
|
1416
|
+
.cancelScheduledValues(now)
|
|
1417
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1418
|
+
}
|
|
1419
|
+
setModLfoToFilterFc(channel, note) {
|
|
1420
|
+
const now = this.audioContext.currentTime;
|
|
1421
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1422
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1423
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1424
|
+
note.filterDepth.gain
|
|
1425
|
+
.cancelScheduledValues(now)
|
|
1426
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1321
1427
|
}
|
|
1322
|
-
setModLfoToVolume(note) {
|
|
1428
|
+
setModLfoToVolume(channel, note) {
|
|
1323
1429
|
const now = this.audioContext.currentTime;
|
|
1324
1430
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
1431
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1432
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1433
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1434
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1327
1435
|
note.volumeDepth.gain
|
|
1328
1436
|
.cancelScheduledValues(now)
|
|
1329
|
-
.setValueAtTime(volumeDepth
|
|
1437
|
+
.setValueAtTime(volumeDepth, now);
|
|
1330
1438
|
}
|
|
1331
|
-
|
|
1439
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1332
1440
|
if (0 < prevValue) {
|
|
1333
|
-
if (0 < note.voiceParams.
|
|
1441
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1334
1442
|
const now = this.audioContext.currentTime;
|
|
1335
|
-
const
|
|
1336
|
-
note.
|
|
1443
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1444
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1445
|
+
note.reverbEffectsSend.gain
|
|
1337
1446
|
.cancelScheduledValues(now)
|
|
1338
1447
|
.setValueAtTime(value, now);
|
|
1339
1448
|
}
|
|
1340
1449
|
else {
|
|
1341
|
-
note.
|
|
1450
|
+
note.reverbEffectsSend.disconnect();
|
|
1342
1451
|
}
|
|
1343
1452
|
}
|
|
1344
1453
|
else {
|
|
1345
|
-
if (0 < note.voiceParams.
|
|
1346
|
-
if (!note.
|
|
1347
|
-
note.
|
|
1348
|
-
gain: note.voiceParams.
|
|
1454
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1455
|
+
if (!note.reverbEffectsSend) {
|
|
1456
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1457
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1349
1458
|
});
|
|
1350
|
-
note.volumeNode.connect(note.
|
|
1459
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1351
1460
|
}
|
|
1352
|
-
note.
|
|
1461
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1353
1462
|
}
|
|
1354
1463
|
}
|
|
1355
1464
|
}
|
|
1356
|
-
|
|
1465
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1357
1466
|
if (0 < prevValue) {
|
|
1358
|
-
if (0 < note.voiceParams.
|
|
1467
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1359
1468
|
const now = this.audioContext.currentTime;
|
|
1360
|
-
const
|
|
1361
|
-
note.
|
|
1469
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1470
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1471
|
+
note.chorusEffectsSend.gain
|
|
1362
1472
|
.cancelScheduledValues(now)
|
|
1363
1473
|
.setValueAtTime(value, now);
|
|
1364
1474
|
}
|
|
1365
1475
|
else {
|
|
1366
|
-
note.
|
|
1476
|
+
note.chorusEffectsSend.disconnect();
|
|
1367
1477
|
}
|
|
1368
1478
|
}
|
|
1369
1479
|
else {
|
|
1370
|
-
if (0 < note.voiceParams.
|
|
1371
|
-
if (!note.
|
|
1372
|
-
note.
|
|
1373
|
-
gain: note.voiceParams.
|
|
1480
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1481
|
+
if (!note.chorusEffectsSend) {
|
|
1482
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1483
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1374
1484
|
});
|
|
1375
|
-
note.volumeNode.connect(note.
|
|
1485
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1376
1486
|
}
|
|
1377
|
-
note.
|
|
1487
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1378
1488
|
}
|
|
1379
1489
|
}
|
|
1380
1490
|
}
|
|
1381
|
-
setVibLfoToPitch(channel, note) {
|
|
1382
|
-
const now = this.audioContext.currentTime;
|
|
1383
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1384
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1385
|
-
2;
|
|
1386
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1387
|
-
note.vibratoDepth.gain
|
|
1388
|
-
.cancelScheduledValues(now)
|
|
1389
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1390
|
-
}
|
|
1391
|
-
setModLfoToFilterFc(note) {
|
|
1392
|
-
const now = this.audioContext.currentTime;
|
|
1393
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1394
|
-
note.filterDepth.gain
|
|
1395
|
-
.cancelScheduledValues(now)
|
|
1396
|
-
.setValueAtTime(modLfoToFilterFc, now);
|
|
1397
|
-
}
|
|
1398
1491
|
setDelayModLFO(note) {
|
|
1399
1492
|
const now = this.audioContext.currentTime;
|
|
1400
1493
|
const startTime = note.startTime;
|
|
@@ -1424,18 +1517,20 @@ class MidyGM2 {
|
|
|
1424
1517
|
}
|
|
1425
1518
|
},
|
|
1426
1519
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1427
|
-
if (0 < channel.state.modulationDepth)
|
|
1428
|
-
this.setModLfoToFilterFc(note);
|
|
1520
|
+
if (0 < channel.state.modulationDepth) {
|
|
1521
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1522
|
+
}
|
|
1429
1523
|
},
|
|
1430
1524
|
modLfoToVolume: (channel, note) => {
|
|
1431
|
-
if (0 < channel.state.modulationDepth)
|
|
1432
|
-
this.setModLfoToVolume(note);
|
|
1525
|
+
if (0 < channel.state.modulationDepth) {
|
|
1526
|
+
this.setModLfoToVolume(channel, note);
|
|
1527
|
+
}
|
|
1433
1528
|
},
|
|
1434
|
-
chorusEffectsSend: (
|
|
1435
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1529
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1530
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1436
1531
|
},
|
|
1437
|
-
reverbEffectsSend: (
|
|
1438
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1532
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1533
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1439
1534
|
},
|
|
1440
1535
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1441
1536
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1503,7 +1598,7 @@ class MidyGM2 {
|
|
|
1503
1598
|
else {
|
|
1504
1599
|
this.setFilterEnvelope(channel, note);
|
|
1505
1600
|
}
|
|
1506
|
-
this.
|
|
1601
|
+
this.setPitchEnvelope(note);
|
|
1507
1602
|
}
|
|
1508
1603
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1509
1604
|
if (appliedVolumeEnvelope)
|
|
@@ -1575,7 +1670,7 @@ class MidyGM2 {
|
|
|
1575
1670
|
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1576
1671
|
}
|
|
1577
1672
|
else {
|
|
1578
|
-
this.
|
|
1673
|
+
this.setPitchEnvelope(note);
|
|
1579
1674
|
this.startModulation(channel, note, now);
|
|
1580
1675
|
}
|
|
1581
1676
|
}
|
|
@@ -1592,10 +1687,27 @@ class MidyGM2 {
|
|
|
1592
1687
|
const factor = 5 * Math.log(10) / 127;
|
|
1593
1688
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1594
1689
|
}
|
|
1690
|
+
setKeyBasedVolume(channel) {
|
|
1691
|
+
const now = this.audioContext.currentTime;
|
|
1692
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1693
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1694
|
+
const note = noteList[i];
|
|
1695
|
+
if (!note)
|
|
1696
|
+
continue;
|
|
1697
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1698
|
+
if (keyBasedValue === 0)
|
|
1699
|
+
continue;
|
|
1700
|
+
note.volumeNode.gain
|
|
1701
|
+
.cancelScheduledValues(now)
|
|
1702
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1595
1706
|
setVolume(channelNumber, volume) {
|
|
1596
1707
|
const channel = this.channels[channelNumber];
|
|
1597
1708
|
channel.state.volume = volume / 127;
|
|
1598
1709
|
this.updateChannelVolume(channel);
|
|
1710
|
+
this.setKeyBasedVolume(channel);
|
|
1599
1711
|
}
|
|
1600
1712
|
panToGain(pan) {
|
|
1601
1713
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1604,10 +1716,31 @@ class MidyGM2 {
|
|
|
1604
1716
|
gainRight: Math.sin(theta),
|
|
1605
1717
|
};
|
|
1606
1718
|
}
|
|
1719
|
+
setKeyBasedPan(channel) {
|
|
1720
|
+
const now = this.audioContext.currentTime;
|
|
1721
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1722
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1723
|
+
const note = noteList[i];
|
|
1724
|
+
if (!note)
|
|
1725
|
+
continue;
|
|
1726
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1727
|
+
if (keyBasedValue === 0)
|
|
1728
|
+
continue;
|
|
1729
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1730
|
+
note.gainL.gain
|
|
1731
|
+
.cancelScheduledValues(now)
|
|
1732
|
+
.setValueAtTime(gainLeft, now);
|
|
1733
|
+
note.gainR.gain
|
|
1734
|
+
.cancelScheduledValues(now)
|
|
1735
|
+
.setValueAtTime(gainRight, now);
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1607
1739
|
setPan(channelNumber, pan) {
|
|
1608
1740
|
const channel = this.channels[channelNumber];
|
|
1609
1741
|
channel.state.pan = pan / 127;
|
|
1610
1742
|
this.updateChannelVolume(channel);
|
|
1743
|
+
this.setKeyBasedPan(channel);
|
|
1611
1744
|
}
|
|
1612
1745
|
setExpression(channelNumber, expression) {
|
|
1613
1746
|
const channel = this.channels[channelNumber];
|
|
@@ -1642,6 +1775,22 @@ class MidyGM2 {
|
|
|
1642
1775
|
setPortamento(channelNumber, value) {
|
|
1643
1776
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1644
1777
|
}
|
|
1778
|
+
setSostenutoPedal(channelNumber, value) {
|
|
1779
|
+
const channel = this.channels[channelNumber];
|
|
1780
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1781
|
+
if (64 <= value) {
|
|
1782
|
+
const now = this.audioContext.currentTime;
|
|
1783
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1784
|
+
channel.sostenutoNotes = new Map(activeNotes);
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
setSoftPedal(channelNumber, softPedal) {
|
|
1791
|
+
const channel = this.channels[channelNumber];
|
|
1792
|
+
channel.state.softPedal = softPedal / 127;
|
|
1793
|
+
}
|
|
1645
1794
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1646
1795
|
const channel = this.channels[channelNumber];
|
|
1647
1796
|
const state = channel.state;
|
|
@@ -1674,7 +1823,7 @@ class MidyGM2 {
|
|
|
1674
1823
|
const note = noteList[i];
|
|
1675
1824
|
if (!note)
|
|
1676
1825
|
continue;
|
|
1677
|
-
this.setReverbEffectsSend(note, 0);
|
|
1826
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1678
1827
|
}
|
|
1679
1828
|
});
|
|
1680
1829
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1715,7 +1864,7 @@ class MidyGM2 {
|
|
|
1715
1864
|
const note = noteList[i];
|
|
1716
1865
|
if (!note)
|
|
1717
1866
|
continue;
|
|
1718
|
-
this.setChorusEffectsSend(note, 0);
|
|
1867
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1719
1868
|
}
|
|
1720
1869
|
});
|
|
1721
1870
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1724,22 +1873,6 @@ class MidyGM2 {
|
|
|
1724
1873
|
}
|
|
1725
1874
|
}
|
|
1726
1875
|
}
|
|
1727
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1728
|
-
const channel = this.channels[channelNumber];
|
|
1729
|
-
channel.state.sostenutoPedal = value / 127;
|
|
1730
|
-
if (64 <= value) {
|
|
1731
|
-
const now = this.audioContext.currentTime;
|
|
1732
|
-
const activeNotes = this.getActiveNotes(channel, now);
|
|
1733
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1734
|
-
}
|
|
1735
|
-
else {
|
|
1736
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1740
|
-
const channel = this.channels[channelNumber];
|
|
1741
|
-
channel.state.softPedal = softPedal / 127;
|
|
1742
|
-
}
|
|
1743
1876
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1744
1877
|
if (maxLSB < channel.dataLSB) {
|
|
1745
1878
|
channel.dataMSB++;
|
|
@@ -1796,59 +1929,49 @@ class MidyGM2 {
|
|
|
1796
1929
|
this.channels[channelNumber].dataMSB = value;
|
|
1797
1930
|
this.handleRPN(channelNumber);
|
|
1798
1931
|
}
|
|
1799
|
-
updateDetune(channel, detune) {
|
|
1800
|
-
const now = this.audioContext.currentTime;
|
|
1801
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1802
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1803
|
-
const note = noteList[i];
|
|
1804
|
-
if (!note)
|
|
1805
|
-
continue;
|
|
1806
|
-
const { bufferSource } = note;
|
|
1807
|
-
bufferSource.detune
|
|
1808
|
-
.cancelScheduledValues(now)
|
|
1809
|
-
.setValueAtTime(detune, now);
|
|
1810
|
-
}
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
1932
|
handlePitchBendRangeRPN(channelNumber) {
|
|
1814
1933
|
const channel = this.channels[channelNumber];
|
|
1815
1934
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1816
1935
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1817
1936
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1818
1937
|
}
|
|
1819
|
-
setPitchBendRange(channelNumber,
|
|
1938
|
+
setPitchBendRange(channelNumber, value) {
|
|
1820
1939
|
const channel = this.channels[channelNumber];
|
|
1821
1940
|
const state = channel.state;
|
|
1822
|
-
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1941
|
+
const prev = state.pitchWheelSensitivity;
|
|
1942
|
+
const next = value / 128;
|
|
1943
|
+
state.pitchWheelSensitivity = next;
|
|
1944
|
+
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1945
|
+
this.updateDetune(channel);
|
|
1825
1946
|
this.applyVoiceParams(channel, 16);
|
|
1826
1947
|
}
|
|
1827
1948
|
handleFineTuningRPN(channelNumber) {
|
|
1828
1949
|
const channel = this.channels[channelNumber];
|
|
1829
1950
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1830
|
-
const fineTuning =
|
|
1951
|
+
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1831
1952
|
this.setFineTuning(channelNumber, fineTuning);
|
|
1832
1953
|
}
|
|
1833
|
-
setFineTuning(channelNumber,
|
|
1954
|
+
setFineTuning(channelNumber, value) {
|
|
1834
1955
|
const channel = this.channels[channelNumber];
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1956
|
+
const prev = channel.fineTuning;
|
|
1957
|
+
const next = (value - 8192) / 8.192; // cent
|
|
1958
|
+
channel.fineTuning = next;
|
|
1959
|
+
channel.detune += next - prev;
|
|
1960
|
+
this.updateDetune(channel);
|
|
1839
1961
|
}
|
|
1840
1962
|
handleCoarseTuningRPN(channelNumber) {
|
|
1841
1963
|
const channel = this.channels[channelNumber];
|
|
1842
1964
|
this.limitDataMSB(channel, 0, 127);
|
|
1843
|
-
const coarseTuning = channel.dataMSB
|
|
1844
|
-
this.
|
|
1965
|
+
const coarseTuning = channel.dataMSB;
|
|
1966
|
+
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1845
1967
|
}
|
|
1846
|
-
setCoarseTuning(channelNumber,
|
|
1968
|
+
setCoarseTuning(channelNumber, value) {
|
|
1847
1969
|
const channel = this.channels[channelNumber];
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1970
|
+
const prev = channel.coarseTuning;
|
|
1971
|
+
const next = (value - 64) * 100; // cent
|
|
1972
|
+
channel.coarseTuning = next;
|
|
1973
|
+
channel.detune += next - prev;
|
|
1974
|
+
this.updateDetune(channel);
|
|
1852
1975
|
}
|
|
1853
1976
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1854
1977
|
const channel = this.channels[channelNumber];
|
|
@@ -1908,6 +2031,15 @@ class MidyGM2 {
|
|
|
1908
2031
|
}
|
|
1909
2032
|
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1910
2033
|
switch (data[2]) {
|
|
2034
|
+
case 8:
|
|
2035
|
+
switch (data[3]) {
|
|
2036
|
+
case 8:
|
|
2037
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2038
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2039
|
+
default:
|
|
2040
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2041
|
+
}
|
|
2042
|
+
break;
|
|
1911
2043
|
case 9:
|
|
1912
2044
|
switch (data[3]) {
|
|
1913
2045
|
case 1:
|
|
@@ -1956,26 +2088,16 @@ class MidyGM2 {
|
|
|
1956
2088
|
return this.handleMasterFineTuningSysEx(data);
|
|
1957
2089
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1958
2090
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1959
|
-
case 5:
|
|
2091
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
1960
2092
|
return this.handleGlobalParameterControlSysEx(data);
|
|
1961
2093
|
default:
|
|
1962
2094
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1963
2095
|
}
|
|
1964
2096
|
break;
|
|
1965
|
-
case 8:
|
|
1966
|
-
switch (data[3]) {
|
|
1967
|
-
// case 8:
|
|
1968
|
-
// // TODO
|
|
1969
|
-
// return this.handleScaleOctaveTuning1ByteFormat();
|
|
1970
|
-
default:
|
|
1971
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1972
|
-
}
|
|
1973
|
-
break;
|
|
1974
2097
|
case 9:
|
|
1975
2098
|
switch (data[3]) {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
// return this.setChannelPressure();
|
|
2099
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2100
|
+
return this.handleChannelPressureSysEx(data);
|
|
1979
2101
|
// case 3:
|
|
1980
2102
|
// // TODO
|
|
1981
2103
|
// return this.setControlChange();
|
|
@@ -1985,9 +2107,8 @@ class MidyGM2 {
|
|
|
1985
2107
|
break;
|
|
1986
2108
|
case 10:
|
|
1987
2109
|
switch (data[3]) {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2110
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2111
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
1991
2112
|
default:
|
|
1992
2113
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1993
2114
|
}
|
|
@@ -2011,28 +2132,26 @@ class MidyGM2 {
|
|
|
2011
2132
|
}
|
|
2012
2133
|
}
|
|
2013
2134
|
handleMasterFineTuningSysEx(data) {
|
|
2014
|
-
const fineTuning =
|
|
2135
|
+
const fineTuning = data[5] * 128 + data[4];
|
|
2015
2136
|
this.setMasterFineTuning(fineTuning);
|
|
2016
2137
|
}
|
|
2017
|
-
setMasterFineTuning(
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
}
|
|
2138
|
+
setMasterFineTuning(value) {
|
|
2139
|
+
const prev = this.masterFineTuning;
|
|
2140
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2141
|
+
this.masterFineTuning = next;
|
|
2142
|
+
channel.detune += next - prev;
|
|
2143
|
+
this.updateDetune(channel);
|
|
2024
2144
|
}
|
|
2025
2145
|
handleMasterCoarseTuningSysEx(data) {
|
|
2026
2146
|
const coarseTuning = data[4];
|
|
2027
2147
|
this.setMasterCoarseTuning(coarseTuning);
|
|
2028
2148
|
}
|
|
2029
|
-
setMasterCoarseTuning(
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
}
|
|
2149
|
+
setMasterCoarseTuning(value) {
|
|
2150
|
+
const prev = this.masterCoarseTuning;
|
|
2151
|
+
const next = (value - 64) * 100; // cent
|
|
2152
|
+
this.masterCoarseTuning = next;
|
|
2153
|
+
channel.detune += next - prev;
|
|
2154
|
+
this.updateDetune(channel);
|
|
2036
2155
|
}
|
|
2037
2156
|
handleGlobalParameterControlSysEx(data) {
|
|
2038
2157
|
if (data[7] === 1) {
|
|
@@ -2220,6 +2339,66 @@ class MidyGM2 {
|
|
|
2220
2339
|
getChorusSendToReverb(value) {
|
|
2221
2340
|
return value * 0.00787;
|
|
2222
2341
|
}
|
|
2342
|
+
getChannelBitmap(data) {
|
|
2343
|
+
const bitmap = new Array(16).fill(false);
|
|
2344
|
+
const ff = data[4] & 0b11;
|
|
2345
|
+
const gg = data[5] & 0x7F;
|
|
2346
|
+
const hh = data[6] & 0x7F;
|
|
2347
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2348
|
+
if (hh & (1 << bit))
|
|
2349
|
+
bitmap[bit] = true;
|
|
2350
|
+
}
|
|
2351
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2352
|
+
if (gg & (1 << bit))
|
|
2353
|
+
bitmap[bit + 7] = true;
|
|
2354
|
+
}
|
|
2355
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2356
|
+
if (ff & (1 << bit))
|
|
2357
|
+
bitmap[bit + 14] = true;
|
|
2358
|
+
}
|
|
2359
|
+
return bitmap;
|
|
2360
|
+
}
|
|
2361
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2362
|
+
if (data.length < 18) {
|
|
2363
|
+
console.error("Data length is too short");
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2367
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2368
|
+
if (!channelBitmap[i])
|
|
2369
|
+
continue;
|
|
2370
|
+
for (let j = 0; j < 12; j++) {
|
|
2371
|
+
const value = data[j + 7] - 64; // cent
|
|
2372
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
handleChannelPressureSysEx(data) {
|
|
2377
|
+
const channelNumber = data[4];
|
|
2378
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2379
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2380
|
+
const pp = data[i];
|
|
2381
|
+
const rr = data[i + 1];
|
|
2382
|
+
table[pp] = rr;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2386
|
+
const index = keyNumber * 128 + controllerType;
|
|
2387
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2388
|
+
return (controlValue + 64) / 64;
|
|
2389
|
+
}
|
|
2390
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2391
|
+
const channelNumber = data[4];
|
|
2392
|
+
const keyNumber = data[5];
|
|
2393
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2394
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2395
|
+
const controllerType = data[i];
|
|
2396
|
+
const value = data[i + 1];
|
|
2397
|
+
const index = keyNumber * 128 + controllerType;
|
|
2398
|
+
table[index] = value - 64;
|
|
2399
|
+
}
|
|
2400
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2401
|
+
}
|
|
2223
2402
|
handleExclusiveMessage(data) {
|
|
2224
2403
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2225
2404
|
}
|
|
@@ -2252,6 +2431,10 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2252
2431
|
writable: true,
|
|
2253
2432
|
value: {
|
|
2254
2433
|
currentBufferSource: null,
|
|
2434
|
+
detune: 0,
|
|
2435
|
+
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2436
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2437
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2255
2438
|
program: 0,
|
|
2256
2439
|
bank: 121 * 128,
|
|
2257
2440
|
bankMSB: 121,
|