@marmooo/midy 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/esm/midy-GM1.d.ts +5 -5
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +32 -29
- package/esm/midy-GM2.d.ts +35 -19
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +350 -155
- package/esm/midy-GMLite.d.ts +5 -5
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +26 -24
- package/esm/midy.d.ts +30 -15
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +312 -139
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +5 -5
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +32 -29
- package/script/midy-GM2.d.ts +35 -19
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +350 -155
- package/script/midy-GMLite.d.ts +5 -5
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +26 -24
- package/script/midy.d.ts +30 -15
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +312 -139
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,
|
|
@@ -341,15 +359,15 @@ export class MidyGM2 {
|
|
|
341
359
|
});
|
|
342
360
|
this.audioContext = audioContext;
|
|
343
361
|
this.options = { ...this.defaultOptions, ...options };
|
|
344
|
-
this.
|
|
362
|
+
this.masterVolume = new GainNode(audioContext);
|
|
345
363
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
346
364
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
347
365
|
this.channels = this.createChannels(audioContext);
|
|
348
366
|
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
349
367
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
350
|
-
this.chorusEffect.output.connect(this.
|
|
351
|
-
this.reverbEffect.output.connect(this.
|
|
352
|
-
this.
|
|
368
|
+
this.chorusEffect.output.connect(this.masterVolume);
|
|
369
|
+
this.reverbEffect.output.connect(this.masterVolume);
|
|
370
|
+
this.masterVolume.connect(audioContext.destination);
|
|
353
371
|
this.GM2SystemOn();
|
|
354
372
|
}
|
|
355
373
|
initSoundFontTable() {
|
|
@@ -395,7 +413,7 @@ export class MidyGM2 {
|
|
|
395
413
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
396
414
|
gainL.connect(merger, 0, 0);
|
|
397
415
|
gainR.connect(merger, 0, 1);
|
|
398
|
-
merger.connect(this.
|
|
416
|
+
merger.connect(this.masterVolume);
|
|
399
417
|
return {
|
|
400
418
|
gainL,
|
|
401
419
|
gainR,
|
|
@@ -407,6 +425,7 @@ export class MidyGM2 {
|
|
|
407
425
|
return {
|
|
408
426
|
...this.constructor.channelSettings,
|
|
409
427
|
state: new ControllerState(),
|
|
428
|
+
controlTable: this.initControlTable(),
|
|
410
429
|
...this.setChannelAudioNodes(audioContext),
|
|
411
430
|
scheduledNotes: new Map(),
|
|
412
431
|
sostenutoNotes: new Map(),
|
|
@@ -907,15 +926,16 @@ export class MidyGM2 {
|
|
|
907
926
|
centToHz(cent) {
|
|
908
927
|
return 8.176 * this.centToRate(cent);
|
|
909
928
|
}
|
|
910
|
-
|
|
929
|
+
calcChannelDetune(channel) {
|
|
911
930
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
912
931
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
913
|
-
const
|
|
914
|
-
const tuning = masterTuning + channelTuning + scaleOctaveTuning;
|
|
932
|
+
const tuning = masterTuning + channelTuning;
|
|
915
933
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
916
934
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
917
935
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
918
|
-
|
|
936
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
937
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
938
|
+
return tuning + pitch + pressure;
|
|
919
939
|
}
|
|
920
940
|
calcNoteDetune(channel, note) {
|
|
921
941
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -935,28 +955,37 @@ export class MidyGM2 {
|
|
|
935
955
|
}
|
|
936
956
|
});
|
|
937
957
|
}
|
|
958
|
+
getPortamentoTime(channel) {
|
|
959
|
+
const factor = 5 * Math.log(10) / 127;
|
|
960
|
+
const time = channel.state.portamentoTime;
|
|
961
|
+
return Math.log(time) / factor;
|
|
962
|
+
}
|
|
938
963
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
939
964
|
const now = this.audioContext.currentTime;
|
|
940
965
|
const { voiceParams, startTime } = note;
|
|
941
966
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
942
967
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
943
968
|
const volDelay = startTime + voiceParams.volDelay;
|
|
944
|
-
const portamentoTime = volDelay + channel
|
|
945
|
-
note.
|
|
969
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
970
|
+
note.volumeEnvelopeNode.gain
|
|
946
971
|
.cancelScheduledValues(now)
|
|
947
972
|
.setValueAtTime(0, volDelay)
|
|
948
973
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
949
974
|
}
|
|
950
|
-
setVolumeEnvelope(note) {
|
|
975
|
+
setVolumeEnvelope(channel, note) {
|
|
951
976
|
const now = this.audioContext.currentTime;
|
|
977
|
+
const state = channel.state;
|
|
952
978
|
const { voiceParams, startTime } = note;
|
|
953
|
-
const
|
|
979
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
980
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
981
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
982
|
+
pressure;
|
|
954
983
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
955
984
|
const volDelay = startTime + voiceParams.volDelay;
|
|
956
985
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
957
986
|
const volHold = volAttack + voiceParams.volHold;
|
|
958
987
|
const volDecay = volHold + voiceParams.volDecay;
|
|
959
|
-
note.
|
|
988
|
+
note.volumeEnvelopeNode.gain
|
|
960
989
|
.cancelScheduledValues(now)
|
|
961
990
|
.setValueAtTime(0, startTime)
|
|
962
991
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -998,14 +1027,16 @@ export class MidyGM2 {
|
|
|
998
1027
|
const { voiceParams, noteNumber, startTime } = note;
|
|
999
1028
|
const softPedalFactor = 1 -
|
|
1000
1029
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1030
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1031
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1032
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1033
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1003
1034
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1004
1035
|
const sustainFreq = baseFreq +
|
|
1005
1036
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1006
1037
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1007
1038
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1008
|
-
const portamentoTime = startTime + channel
|
|
1039
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1009
1040
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1010
1041
|
note.filterNode.frequency
|
|
1011
1042
|
.cancelScheduledValues(now)
|
|
@@ -1050,14 +1081,14 @@ export class MidyGM2 {
|
|
|
1050
1081
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1051
1082
|
this.setModLfoToPitch(channel, note);
|
|
1052
1083
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1053
|
-
this.setModLfoToVolume(note);
|
|
1084
|
+
this.setModLfoToVolume(channel, note);
|
|
1054
1085
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1055
1086
|
note.modulationLFO.connect(note.filterDepth);
|
|
1056
1087
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1057
1088
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1058
1089
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1059
1090
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1060
|
-
note.volumeDepth.connect(note.
|
|
1091
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1061
1092
|
}
|
|
1062
1093
|
startVibrato(channel, note, startTime) {
|
|
1063
1094
|
const { voiceParams } = note;
|
|
@@ -1079,6 +1110,9 @@ export class MidyGM2 {
|
|
|
1079
1110
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1080
1111
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1081
1112
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1113
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1114
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1115
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1082
1116
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1083
1117
|
type: "lowpass",
|
|
1084
1118
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
@@ -1105,7 +1139,10 @@ export class MidyGM2 {
|
|
|
1105
1139
|
channel.currentBufferSource = note.bufferSource;
|
|
1106
1140
|
}
|
|
1107
1141
|
note.bufferSource.connect(note.filterNode);
|
|
1108
|
-
note.filterNode.connect(note.
|
|
1142
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1143
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1144
|
+
note.volumeNode.connect(note.gainL);
|
|
1145
|
+
note.volumeNode.connect(note.gainR);
|
|
1109
1146
|
if (0 < channel.chorusSendLevel) {
|
|
1110
1147
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1111
1148
|
}
|
|
@@ -1136,8 +1173,8 @@ export class MidyGM2 {
|
|
|
1136
1173
|
if (!voice)
|
|
1137
1174
|
return;
|
|
1138
1175
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1139
|
-
note.
|
|
1140
|
-
note.
|
|
1176
|
+
note.gainL.connect(channel.gainL);
|
|
1177
|
+
note.gainR.connect(channel.gainR);
|
|
1141
1178
|
if (channel.state.sostenutoPedal) {
|
|
1142
1179
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1143
1180
|
}
|
|
@@ -1168,7 +1205,7 @@ export class MidyGM2 {
|
|
|
1168
1205
|
}
|
|
1169
1206
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1170
1207
|
const note = scheduledNotes[index];
|
|
1171
|
-
note.
|
|
1208
|
+
note.volumeEnvelopeNode.gain
|
|
1172
1209
|
.cancelScheduledValues(endTime)
|
|
1173
1210
|
.linearRampToValueAtTime(0, stopTime);
|
|
1174
1211
|
note.ending = true;
|
|
@@ -1179,8 +1216,11 @@ export class MidyGM2 {
|
|
|
1179
1216
|
note.bufferSource.onended = () => {
|
|
1180
1217
|
scheduledNotes[index] = null;
|
|
1181
1218
|
note.bufferSource.disconnect();
|
|
1182
|
-
note.volumeNode.disconnect();
|
|
1183
1219
|
note.filterNode.disconnect();
|
|
1220
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1221
|
+
note.volumeNode.disconnect();
|
|
1222
|
+
note.gainL.disconnect();
|
|
1223
|
+
note.gainR.disconnect();
|
|
1184
1224
|
if (note.modulationDepth) {
|
|
1185
1225
|
note.volumeDepth.disconnect();
|
|
1186
1226
|
note.modulationDepth.disconnect();
|
|
@@ -1229,7 +1269,7 @@ export class MidyGM2 {
|
|
|
1229
1269
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1230
1270
|
}
|
|
1231
1271
|
else {
|
|
1232
|
-
const portamentoTime = endTime +
|
|
1272
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1233
1273
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1234
1274
|
const baseRate = note.voiceParams.playbackRate;
|
|
1235
1275
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1299,22 +1339,48 @@ export class MidyGM2 {
|
|
|
1299
1339
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1300
1340
|
channel.program = program;
|
|
1301
1341
|
}
|
|
1302
|
-
handleChannelPressure(channelNumber,
|
|
1303
|
-
const now = this.audioContext.currentTime;
|
|
1342
|
+
handleChannelPressure(channelNumber, value) {
|
|
1304
1343
|
const channel = this.channels[channelNumber];
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
if (channel.
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1344
|
+
const prev = channel.state.channelPressure;
|
|
1345
|
+
const next = value / 127;
|
|
1346
|
+
channel.state.channelPressure = next;
|
|
1347
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1348
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1349
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1350
|
+
}
|
|
1351
|
+
const table = channel.pressureTable;
|
|
1352
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1353
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1354
|
+
const note = noteList[i];
|
|
1355
|
+
if (!note)
|
|
1356
|
+
continue;
|
|
1357
|
+
this.applyDestinationSettings(channel, note, table);
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1316
1360
|
// this.applyVoiceParams(channel, 13);
|
|
1317
1361
|
}
|
|
1362
|
+
setChannelPressure(channel, note) {
|
|
1363
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1364
|
+
this.updateDetune(channel);
|
|
1365
|
+
}
|
|
1366
|
+
if (!note.portamento) {
|
|
1367
|
+
if (channel.pressureTable[1] !== 64) {
|
|
1368
|
+
this.setFilterEnvelope(channel, note);
|
|
1369
|
+
}
|
|
1370
|
+
if (channel.pressureTable[2] !== 64) {
|
|
1371
|
+
this.setVolumeEnvelope(channel, note);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1375
|
+
this.setModLfoToPitch(channel, note);
|
|
1376
|
+
}
|
|
1377
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1378
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1379
|
+
}
|
|
1380
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1381
|
+
this.setModLfoToVolume(channel, note);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1318
1384
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1319
1385
|
const pitchBend = msb * 128 + lsb;
|
|
1320
1386
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1331,13 +1397,15 @@ export class MidyGM2 {
|
|
|
1331
1397
|
}
|
|
1332
1398
|
setModLfoToPitch(channel, note) {
|
|
1333
1399
|
const now = this.audioContext.currentTime;
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1400
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1401
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1402
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1403
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1336
1404
|
channel.state.modulationDepth;
|
|
1337
|
-
const
|
|
1405
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1338
1406
|
note.modulationDepth.gain
|
|
1339
1407
|
.cancelScheduledValues(now)
|
|
1340
|
-
.setValueAtTime(modulationDepth
|
|
1408
|
+
.setValueAtTime(modulationDepth, now);
|
|
1341
1409
|
}
|
|
1342
1410
|
setVibLfoToPitch(channel, note) {
|
|
1343
1411
|
const now = this.audioContext.currentTime;
|
|
@@ -1349,69 +1417,75 @@ export class MidyGM2 {
|
|
|
1349
1417
|
.cancelScheduledValues(now)
|
|
1350
1418
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1351
1419
|
}
|
|
1352
|
-
setModLfoToFilterFc(note) {
|
|
1420
|
+
setModLfoToFilterFc(channel, note) {
|
|
1353
1421
|
const now = this.audioContext.currentTime;
|
|
1354
|
-
const
|
|
1422
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1423
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1424
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1355
1425
|
note.filterDepth.gain
|
|
1356
1426
|
.cancelScheduledValues(now)
|
|
1357
1427
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1358
1428
|
}
|
|
1359
|
-
setModLfoToVolume(note) {
|
|
1429
|
+
setModLfoToVolume(channel, note) {
|
|
1360
1430
|
const now = this.audioContext.currentTime;
|
|
1361
1431
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1432
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1433
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1434
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1435
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1364
1436
|
note.volumeDepth.gain
|
|
1365
1437
|
.cancelScheduledValues(now)
|
|
1366
|
-
.setValueAtTime(volumeDepth
|
|
1438
|
+
.setValueAtTime(volumeDepth, now);
|
|
1367
1439
|
}
|
|
1368
|
-
|
|
1440
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1369
1441
|
if (0 < prevValue) {
|
|
1370
|
-
if (0 < note.voiceParams.
|
|
1442
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1371
1443
|
const now = this.audioContext.currentTime;
|
|
1372
|
-
const
|
|
1373
|
-
note.
|
|
1444
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1445
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1446
|
+
note.reverbEffectsSend.gain
|
|
1374
1447
|
.cancelScheduledValues(now)
|
|
1375
1448
|
.setValueAtTime(value, now);
|
|
1376
1449
|
}
|
|
1377
1450
|
else {
|
|
1378
|
-
note.
|
|
1451
|
+
note.reverbEffectsSend.disconnect();
|
|
1379
1452
|
}
|
|
1380
1453
|
}
|
|
1381
1454
|
else {
|
|
1382
|
-
if (0 < note.voiceParams.
|
|
1383
|
-
if (!note.
|
|
1384
|
-
note.
|
|
1385
|
-
gain: note.voiceParams.
|
|
1455
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1456
|
+
if (!note.reverbEffectsSend) {
|
|
1457
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1458
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1386
1459
|
});
|
|
1387
|
-
note.volumeNode.connect(note.
|
|
1460
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1388
1461
|
}
|
|
1389
|
-
note.
|
|
1462
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1390
1463
|
}
|
|
1391
1464
|
}
|
|
1392
1465
|
}
|
|
1393
|
-
|
|
1466
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1394
1467
|
if (0 < prevValue) {
|
|
1395
|
-
if (0 < note.voiceParams.
|
|
1468
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1396
1469
|
const now = this.audioContext.currentTime;
|
|
1397
|
-
const
|
|
1398
|
-
note.
|
|
1470
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1471
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1472
|
+
note.chorusEffectsSend.gain
|
|
1399
1473
|
.cancelScheduledValues(now)
|
|
1400
1474
|
.setValueAtTime(value, now);
|
|
1401
1475
|
}
|
|
1402
1476
|
else {
|
|
1403
|
-
note.
|
|
1477
|
+
note.chorusEffectsSend.disconnect();
|
|
1404
1478
|
}
|
|
1405
1479
|
}
|
|
1406
1480
|
else {
|
|
1407
|
-
if (0 < note.voiceParams.
|
|
1408
|
-
if (!note.
|
|
1409
|
-
note.
|
|
1410
|
-
gain: note.voiceParams.
|
|
1481
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1482
|
+
if (!note.chorusEffectsSend) {
|
|
1483
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1484
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1411
1485
|
});
|
|
1412
|
-
note.volumeNode.connect(note.
|
|
1486
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1413
1487
|
}
|
|
1414
|
-
note.
|
|
1488
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1415
1489
|
}
|
|
1416
1490
|
}
|
|
1417
1491
|
}
|
|
@@ -1444,30 +1518,32 @@ export class MidyGM2 {
|
|
|
1444
1518
|
}
|
|
1445
1519
|
},
|
|
1446
1520
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1447
|
-
if (0 < channel.state.modulationDepth)
|
|
1448
|
-
this.setModLfoToFilterFc(note);
|
|
1521
|
+
if (0 < channel.state.modulationDepth) {
|
|
1522
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1523
|
+
}
|
|
1449
1524
|
},
|
|
1450
|
-
modLfoToVolume: (channel, note) => {
|
|
1451
|
-
if (0 < channel.state.modulationDepth)
|
|
1452
|
-
this.setModLfoToVolume(note);
|
|
1525
|
+
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1526
|
+
if (0 < channel.state.modulationDepth) {
|
|
1527
|
+
this.setModLfoToVolume(channel, note);
|
|
1528
|
+
}
|
|
1453
1529
|
},
|
|
1454
|
-
chorusEffectsSend: (
|
|
1455
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1530
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1531
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1456
1532
|
},
|
|
1457
|
-
reverbEffectsSend: (
|
|
1458
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1533
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1534
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1459
1535
|
},
|
|
1460
1536
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1461
1537
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1462
1538
|
delayVibLFO: (channel, note, prevValue) => {
|
|
1463
1539
|
if (0 < channel.state.vibratoDepth) {
|
|
1464
1540
|
const now = this.audioContext.currentTime;
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1541
|
+
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1542
|
+
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1467
1543
|
if (now < prevStartTime)
|
|
1468
1544
|
return;
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1545
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1546
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1471
1547
|
note.vibratoLFO.stop(now);
|
|
1472
1548
|
note.vibratoLFO.start(startTime);
|
|
1473
1549
|
}
|
|
@@ -1475,9 +1551,10 @@ export class MidyGM2 {
|
|
|
1475
1551
|
freqVibLFO: (channel, note, _prevValue) => {
|
|
1476
1552
|
if (0 < channel.state.vibratoDepth) {
|
|
1477
1553
|
const now = this.audioContext.currentTime;
|
|
1554
|
+
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1478
1555
|
note.vibratoLFO.frequency
|
|
1479
1556
|
.cancelScheduledValues(now)
|
|
1480
|
-
.setValueAtTime(
|
|
1557
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate, now);
|
|
1481
1558
|
}
|
|
1482
1559
|
},
|
|
1483
1560
|
};
|
|
@@ -1574,8 +1651,8 @@ export class MidyGM2 {
|
|
|
1574
1651
|
if (handler) {
|
|
1575
1652
|
handler.call(this, channelNumber, value);
|
|
1576
1653
|
const channel = this.channels[channelNumber];
|
|
1577
|
-
|
|
1578
|
-
this.
|
|
1654
|
+
this.applyVoiceParams(channel, controller + 128);
|
|
1655
|
+
this.applyControlTable(channel, controllerType);
|
|
1579
1656
|
}
|
|
1580
1657
|
else {
|
|
1581
1658
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1586,13 +1663,14 @@ export class MidyGM2 {
|
|
|
1586
1663
|
}
|
|
1587
1664
|
updateModulation(channel) {
|
|
1588
1665
|
const now = this.audioContext.currentTime;
|
|
1666
|
+
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1589
1667
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1590
1668
|
for (let i = 0; i < noteList.length; i++) {
|
|
1591
1669
|
const note = noteList[i];
|
|
1592
1670
|
if (!note)
|
|
1593
1671
|
continue;
|
|
1594
1672
|
if (note.modulationDepth) {
|
|
1595
|
-
note.modulationDepth.gain.setValueAtTime(
|
|
1673
|
+
note.modulationDepth.gain.setValueAtTime(depth, now);
|
|
1596
1674
|
}
|
|
1597
1675
|
else {
|
|
1598
1676
|
this.setPitchEnvelope(note);
|
|
@@ -1603,8 +1681,7 @@ export class MidyGM2 {
|
|
|
1603
1681
|
}
|
|
1604
1682
|
setModulationDepth(channelNumber, modulation) {
|
|
1605
1683
|
const channel = this.channels[channelNumber];
|
|
1606
|
-
channel.state.modulationDepth =
|
|
1607
|
-
channel.modulationDepthRange;
|
|
1684
|
+
channel.state.modulationDepth = modulation / 127;
|
|
1608
1685
|
this.updateModulation(channel);
|
|
1609
1686
|
}
|
|
1610
1687
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
@@ -1612,10 +1689,27 @@ export class MidyGM2 {
|
|
|
1612
1689
|
const factor = 5 * Math.log(10) / 127;
|
|
1613
1690
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1614
1691
|
}
|
|
1692
|
+
setKeyBasedVolume(channel) {
|
|
1693
|
+
const now = this.audioContext.currentTime;
|
|
1694
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1695
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1696
|
+
const note = noteList[i];
|
|
1697
|
+
if (!note)
|
|
1698
|
+
continue;
|
|
1699
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1700
|
+
if (keyBasedValue === 0)
|
|
1701
|
+
continue;
|
|
1702
|
+
note.volumeNode.gain
|
|
1703
|
+
.cancelScheduledValues(now)
|
|
1704
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1615
1708
|
setVolume(channelNumber, volume) {
|
|
1616
1709
|
const channel = this.channels[channelNumber];
|
|
1617
1710
|
channel.state.volume = volume / 127;
|
|
1618
1711
|
this.updateChannelVolume(channel);
|
|
1712
|
+
this.setKeyBasedVolume(channel);
|
|
1619
1713
|
}
|
|
1620
1714
|
panToGain(pan) {
|
|
1621
1715
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1624,10 +1718,31 @@ export class MidyGM2 {
|
|
|
1624
1718
|
gainRight: Math.sin(theta),
|
|
1625
1719
|
};
|
|
1626
1720
|
}
|
|
1721
|
+
setKeyBasedPan(channel) {
|
|
1722
|
+
const now = this.audioContext.currentTime;
|
|
1723
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1724
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1725
|
+
const note = noteList[i];
|
|
1726
|
+
if (!note)
|
|
1727
|
+
continue;
|
|
1728
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1729
|
+
if (keyBasedValue === 0)
|
|
1730
|
+
continue;
|
|
1731
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1732
|
+
note.gainL.gain
|
|
1733
|
+
.cancelScheduledValues(now)
|
|
1734
|
+
.setValueAtTime(gainLeft, now);
|
|
1735
|
+
note.gainR.gain
|
|
1736
|
+
.cancelScheduledValues(now)
|
|
1737
|
+
.setValueAtTime(gainRight, now);
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1627
1741
|
setPan(channelNumber, pan) {
|
|
1628
1742
|
const channel = this.channels[channelNumber];
|
|
1629
1743
|
channel.state.pan = pan / 127;
|
|
1630
1744
|
this.updateChannelVolume(channel);
|
|
1745
|
+
this.setKeyBasedPan(channel);
|
|
1631
1746
|
}
|
|
1632
1747
|
setExpression(channelNumber, expression) {
|
|
1633
1748
|
const channel = this.channels[channelNumber];
|
|
@@ -1662,6 +1777,22 @@ export class MidyGM2 {
|
|
|
1662
1777
|
setPortamento(channelNumber, value) {
|
|
1663
1778
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1664
1779
|
}
|
|
1780
|
+
setSostenutoPedal(channelNumber, value) {
|
|
1781
|
+
const channel = this.channels[channelNumber];
|
|
1782
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1783
|
+
if (64 <= value) {
|
|
1784
|
+
const now = this.audioContext.currentTime;
|
|
1785
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1786
|
+
channel.sostenutoNotes = new Map(activeNotes);
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
setSoftPedal(channelNumber, softPedal) {
|
|
1793
|
+
const channel = this.channels[channelNumber];
|
|
1794
|
+
channel.state.softPedal = softPedal / 127;
|
|
1795
|
+
}
|
|
1665
1796
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1666
1797
|
const channel = this.channels[channelNumber];
|
|
1667
1798
|
const state = channel.state;
|
|
@@ -1694,7 +1825,7 @@ export class MidyGM2 {
|
|
|
1694
1825
|
const note = noteList[i];
|
|
1695
1826
|
if (!note)
|
|
1696
1827
|
continue;
|
|
1697
|
-
this.setReverbEffectsSend(note, 0);
|
|
1828
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1698
1829
|
}
|
|
1699
1830
|
});
|
|
1700
1831
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1735,7 +1866,7 @@ export class MidyGM2 {
|
|
|
1735
1866
|
const note = noteList[i];
|
|
1736
1867
|
if (!note)
|
|
1737
1868
|
continue;
|
|
1738
|
-
this.setChorusEffectsSend(note, 0);
|
|
1869
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1739
1870
|
}
|
|
1740
1871
|
});
|
|
1741
1872
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1744,22 +1875,6 @@ export class MidyGM2 {
|
|
|
1744
1875
|
}
|
|
1745
1876
|
}
|
|
1746
1877
|
}
|
|
1747
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1748
|
-
const channel = this.channels[channelNumber];
|
|
1749
|
-
channel.state.sostenutoPedal = value / 127;
|
|
1750
|
-
if (64 <= value) {
|
|
1751
|
-
const now = this.audioContext.currentTime;
|
|
1752
|
-
const activeNotes = this.getActiveNotes(channel, now);
|
|
1753
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1754
|
-
}
|
|
1755
|
-
else {
|
|
1756
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1760
|
-
const channel = this.channels[channelNumber];
|
|
1761
|
-
channel.state.softPedal = softPedal / 127;
|
|
1762
|
-
}
|
|
1763
1878
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1764
1879
|
if (maxLSB < channel.dataLSB) {
|
|
1765
1880
|
channel.dataMSB++;
|
|
@@ -1869,7 +1984,6 @@ export class MidyGM2 {
|
|
|
1869
1984
|
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1870
1985
|
const channel = this.channels[channelNumber];
|
|
1871
1986
|
channel.modulationDepthRange = modulationDepthRange;
|
|
1872
|
-
channel.modulationDepth = (modulation / 127) * modulationDepthRange;
|
|
1873
1987
|
this.updateModulation(channel);
|
|
1874
1988
|
}
|
|
1875
1989
|
allSoundOff(channelNumber) {
|
|
@@ -1922,7 +2036,7 @@ export class MidyGM2 {
|
|
|
1922
2036
|
switch (data[3]) {
|
|
1923
2037
|
case 8:
|
|
1924
2038
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
1925
|
-
return this.
|
|
2039
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
1926
2040
|
default:
|
|
1927
2041
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1928
2042
|
}
|
|
@@ -1975,7 +2089,7 @@ export class MidyGM2 {
|
|
|
1975
2089
|
return this.handleMasterFineTuningSysEx(data);
|
|
1976
2090
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1977
2091
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1978
|
-
case 5:
|
|
2092
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
1979
2093
|
return this.handleGlobalParameterControlSysEx(data);
|
|
1980
2094
|
default:
|
|
1981
2095
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -1983,21 +2097,18 @@ export class MidyGM2 {
|
|
|
1983
2097
|
break;
|
|
1984
2098
|
case 9:
|
|
1985
2099
|
switch (data[3]) {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
//
|
|
1989
|
-
|
|
1990
|
-
// // TODO
|
|
1991
|
-
// return this.setControlChange();
|
|
2100
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2101
|
+
return this.handleChannelPressureSysEx(data);
|
|
2102
|
+
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2103
|
+
return this.handleControlChangeSysEx(data);
|
|
1992
2104
|
default:
|
|
1993
2105
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1994
2106
|
}
|
|
1995
2107
|
break;
|
|
1996
2108
|
case 10:
|
|
1997
2109
|
switch (data[3]) {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2110
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2111
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2001
2112
|
default:
|
|
2002
2113
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2003
2114
|
}
|
|
@@ -2016,8 +2127,8 @@ export class MidyGM2 {
|
|
|
2016
2127
|
}
|
|
2017
2128
|
else {
|
|
2018
2129
|
const now = this.audioContext.currentTime;
|
|
2019
|
-
this.
|
|
2020
|
-
this.
|
|
2130
|
+
this.masterVolume.gain.cancelScheduledValues(now);
|
|
2131
|
+
this.masterVolume.gain.setValueAtTime(volume * volume, now);
|
|
2021
2132
|
}
|
|
2022
2133
|
}
|
|
2023
2134
|
handleMasterFineTuningSysEx(data) {
|
|
@@ -2042,40 +2153,6 @@ export class MidyGM2 {
|
|
|
2042
2153
|
channel.detune += next - prev;
|
|
2043
2154
|
this.updateDetune(channel);
|
|
2044
2155
|
}
|
|
2045
|
-
getChannelBitmap(data) {
|
|
2046
|
-
const bitmap = new Array(16).fill(false);
|
|
2047
|
-
const ff = data[4] & 0b11;
|
|
2048
|
-
const gg = data[5] & 0x7F;
|
|
2049
|
-
const hh = data[6] & 0x7F;
|
|
2050
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2051
|
-
if (hh & (1 << bit))
|
|
2052
|
-
bitmap[bit] = true;
|
|
2053
|
-
}
|
|
2054
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2055
|
-
if (gg & (1 << bit))
|
|
2056
|
-
bitmap[bit + 7] = true;
|
|
2057
|
-
}
|
|
2058
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2059
|
-
if (ff & (1 << bit))
|
|
2060
|
-
bitmap[bit + 14] = true;
|
|
2061
|
-
}
|
|
2062
|
-
return bitmap;
|
|
2063
|
-
}
|
|
2064
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2065
|
-
if (data.length < 18) {
|
|
2066
|
-
console.error("Data length is too short");
|
|
2067
|
-
return;
|
|
2068
|
-
}
|
|
2069
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2070
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2071
|
-
if (!channelBitmap[i])
|
|
2072
|
-
continue;
|
|
2073
|
-
for (let j = 0; j < 12; j++) {
|
|
2074
|
-
const value = data[j + 7] - 64; // cent
|
|
2075
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
2156
|
handleGlobalParameterControlSysEx(data) {
|
|
2080
2157
|
if (data[7] === 1) {
|
|
2081
2158
|
switch (data[8]) {
|
|
@@ -2262,6 +2339,122 @@ export class MidyGM2 {
|
|
|
2262
2339
|
getChorusSendToReverb(value) {
|
|
2263
2340
|
return value * 0.00787;
|
|
2264
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
|
+
applyDestinationSettings(channel, note, table) {
|
|
2377
|
+
if (table[0] !== 64) {
|
|
2378
|
+
this.updateDetune(channel);
|
|
2379
|
+
}
|
|
2380
|
+
if (!note.portamento) {
|
|
2381
|
+
if (table[1] !== 64) {
|
|
2382
|
+
this.setFilterEnvelope(channel, note);
|
|
2383
|
+
}
|
|
2384
|
+
if (table[2] !== 64) {
|
|
2385
|
+
this.setVolumeEnvelope(channel, note);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (table[3] !== 0) {
|
|
2389
|
+
this.setModLfoToPitch(channel, note);
|
|
2390
|
+
}
|
|
2391
|
+
if (table[4] !== 0) {
|
|
2392
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2393
|
+
}
|
|
2394
|
+
if (table[5] !== 0) {
|
|
2395
|
+
this.setModLfoToVolume(channel, note);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
handleChannelPressureSysEx(data) {
|
|
2399
|
+
const channelNumber = data[4];
|
|
2400
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2401
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2402
|
+
const pp = data[i];
|
|
2403
|
+
const rr = data[i + 1];
|
|
2404
|
+
table[pp] = rr;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
initControlTable() {
|
|
2408
|
+
const channelCount = 128;
|
|
2409
|
+
const slotSize = 6;
|
|
2410
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2411
|
+
const table = new Uint8Array(channelCount * slotSize);
|
|
2412
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
2413
|
+
const offset = ch * slotSize;
|
|
2414
|
+
table.set(defaultValues, offset);
|
|
2415
|
+
}
|
|
2416
|
+
return table;
|
|
2417
|
+
}
|
|
2418
|
+
applyControlTable(channel, controllerType) {
|
|
2419
|
+
const slotSize = 6;
|
|
2420
|
+
const offset = controllerType * slotSize;
|
|
2421
|
+
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2422
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
2423
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
2424
|
+
const note = noteList[i];
|
|
2425
|
+
if (!note)
|
|
2426
|
+
continue;
|
|
2427
|
+
this.applyDestinationSettings(channel, note, table);
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
handleControlChangeSysEx(data) {
|
|
2432
|
+
const channelNumber = data[4];
|
|
2433
|
+
const controllerType = data[5];
|
|
2434
|
+
const table = this.channels[channelNumber].controlTable[controllerType];
|
|
2435
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2436
|
+
const pp = data[i];
|
|
2437
|
+
const rr = data[i + 1];
|
|
2438
|
+
table[pp] = rr;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2442
|
+
const index = keyNumber * 128 + controllerType;
|
|
2443
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2444
|
+
return (controlValue + 64) / 64;
|
|
2445
|
+
}
|
|
2446
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2447
|
+
const channelNumber = data[4];
|
|
2448
|
+
const keyNumber = data[5];
|
|
2449
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2450
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2451
|
+
const controllerType = data[i];
|
|
2452
|
+
const value = data[i + 1];
|
|
2453
|
+
const index = keyNumber * 128 + controllerType;
|
|
2454
|
+
table[index] = value - 64;
|
|
2455
|
+
}
|
|
2456
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2457
|
+
}
|
|
2265
2458
|
handleExclusiveMessage(data) {
|
|
2266
2459
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2267
2460
|
}
|
|
@@ -2295,6 +2488,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2295
2488
|
currentBufferSource: null,
|
|
2296
2489
|
detune: 0,
|
|
2297
2490
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2491
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2492
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2298
2493
|
program: 0,
|
|
2299
2494
|
bank: 121 * 128,
|
|
2300
2495
|
bankMSB: 121,
|