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