@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/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,
|
|
@@ -344,15 +362,15 @@ class MidyGM2 {
|
|
|
344
362
|
});
|
|
345
363
|
this.audioContext = audioContext;
|
|
346
364
|
this.options = { ...this.defaultOptions, ...options };
|
|
347
|
-
this.
|
|
365
|
+
this.masterVolume = new GainNode(audioContext);
|
|
348
366
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
349
367
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
350
368
|
this.channels = this.createChannels(audioContext);
|
|
351
369
|
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
352
370
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
353
|
-
this.chorusEffect.output.connect(this.
|
|
354
|
-
this.reverbEffect.output.connect(this.
|
|
355
|
-
this.
|
|
371
|
+
this.chorusEffect.output.connect(this.masterVolume);
|
|
372
|
+
this.reverbEffect.output.connect(this.masterVolume);
|
|
373
|
+
this.masterVolume.connect(audioContext.destination);
|
|
356
374
|
this.GM2SystemOn();
|
|
357
375
|
}
|
|
358
376
|
initSoundFontTable() {
|
|
@@ -398,7 +416,7 @@ class MidyGM2 {
|
|
|
398
416
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
399
417
|
gainL.connect(merger, 0, 0);
|
|
400
418
|
gainR.connect(merger, 0, 1);
|
|
401
|
-
merger.connect(this.
|
|
419
|
+
merger.connect(this.masterVolume);
|
|
402
420
|
return {
|
|
403
421
|
gainL,
|
|
404
422
|
gainR,
|
|
@@ -410,6 +428,7 @@ class MidyGM2 {
|
|
|
410
428
|
return {
|
|
411
429
|
...this.constructor.channelSettings,
|
|
412
430
|
state: new ControllerState(),
|
|
431
|
+
controlTable: this.initControlTable(),
|
|
413
432
|
...this.setChannelAudioNodes(audioContext),
|
|
414
433
|
scheduledNotes: new Map(),
|
|
415
434
|
sostenutoNotes: new Map(),
|
|
@@ -910,15 +929,16 @@ class MidyGM2 {
|
|
|
910
929
|
centToHz(cent) {
|
|
911
930
|
return 8.176 * this.centToRate(cent);
|
|
912
931
|
}
|
|
913
|
-
|
|
932
|
+
calcChannelDetune(channel) {
|
|
914
933
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
915
934
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
916
|
-
const
|
|
917
|
-
const tuning = masterTuning + channelTuning + scaleOctaveTuning;
|
|
935
|
+
const tuning = masterTuning + channelTuning;
|
|
918
936
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
919
937
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
920
938
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
921
|
-
|
|
939
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
940
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
941
|
+
return tuning + pitch + pressure;
|
|
922
942
|
}
|
|
923
943
|
calcNoteDetune(channel, note) {
|
|
924
944
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -938,28 +958,37 @@ class MidyGM2 {
|
|
|
938
958
|
}
|
|
939
959
|
});
|
|
940
960
|
}
|
|
961
|
+
getPortamentoTime(channel) {
|
|
962
|
+
const factor = 5 * Math.log(10) / 127;
|
|
963
|
+
const time = channel.state.portamentoTime;
|
|
964
|
+
return Math.log(time) / factor;
|
|
965
|
+
}
|
|
941
966
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
942
967
|
const now = this.audioContext.currentTime;
|
|
943
968
|
const { voiceParams, startTime } = note;
|
|
944
969
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
945
970
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
946
971
|
const volDelay = startTime + voiceParams.volDelay;
|
|
947
|
-
const portamentoTime = volDelay + channel
|
|
948
|
-
note.
|
|
972
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
973
|
+
note.volumeEnvelopeNode.gain
|
|
949
974
|
.cancelScheduledValues(now)
|
|
950
975
|
.setValueAtTime(0, volDelay)
|
|
951
976
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
952
977
|
}
|
|
953
|
-
setVolumeEnvelope(note) {
|
|
978
|
+
setVolumeEnvelope(channel, note) {
|
|
954
979
|
const now = this.audioContext.currentTime;
|
|
980
|
+
const state = channel.state;
|
|
955
981
|
const { voiceParams, startTime } = note;
|
|
956
|
-
const
|
|
982
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
983
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
984
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
985
|
+
pressure;
|
|
957
986
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
958
987
|
const volDelay = startTime + voiceParams.volDelay;
|
|
959
988
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
960
989
|
const volHold = volAttack + voiceParams.volHold;
|
|
961
990
|
const volDecay = volHold + voiceParams.volDecay;
|
|
962
|
-
note.
|
|
991
|
+
note.volumeEnvelopeNode.gain
|
|
963
992
|
.cancelScheduledValues(now)
|
|
964
993
|
.setValueAtTime(0, startTime)
|
|
965
994
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -1001,14 +1030,16 @@ class MidyGM2 {
|
|
|
1001
1030
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1002
1031
|
const softPedalFactor = 1 -
|
|
1003
1032
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1033
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1034
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1035
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1036
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1006
1037
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1007
1038
|
const sustainFreq = baseFreq +
|
|
1008
1039
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1009
1040
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1010
1041
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1011
|
-
const portamentoTime = startTime + channel
|
|
1042
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1012
1043
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1013
1044
|
note.filterNode.frequency
|
|
1014
1045
|
.cancelScheduledValues(now)
|
|
@@ -1053,14 +1084,14 @@ class MidyGM2 {
|
|
|
1053
1084
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1054
1085
|
this.setModLfoToPitch(channel, note);
|
|
1055
1086
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1056
|
-
this.setModLfoToVolume(note);
|
|
1087
|
+
this.setModLfoToVolume(channel, note);
|
|
1057
1088
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1058
1089
|
note.modulationLFO.connect(note.filterDepth);
|
|
1059
1090
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1060
1091
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1061
1092
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1062
1093
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1063
|
-
note.volumeDepth.connect(note.
|
|
1094
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1064
1095
|
}
|
|
1065
1096
|
startVibrato(channel, note, startTime) {
|
|
1066
1097
|
const { voiceParams } = note;
|
|
@@ -1082,6 +1113,9 @@ class MidyGM2 {
|
|
|
1082
1113
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1083
1114
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1084
1115
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1116
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1117
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1118
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1085
1119
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1086
1120
|
type: "lowpass",
|
|
1087
1121
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
@@ -1108,7 +1142,10 @@ class MidyGM2 {
|
|
|
1108
1142
|
channel.currentBufferSource = note.bufferSource;
|
|
1109
1143
|
}
|
|
1110
1144
|
note.bufferSource.connect(note.filterNode);
|
|
1111
|
-
note.filterNode.connect(note.
|
|
1145
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1146
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1147
|
+
note.volumeNode.connect(note.gainL);
|
|
1148
|
+
note.volumeNode.connect(note.gainR);
|
|
1112
1149
|
if (0 < channel.chorusSendLevel) {
|
|
1113
1150
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1114
1151
|
}
|
|
@@ -1139,8 +1176,8 @@ class MidyGM2 {
|
|
|
1139
1176
|
if (!voice)
|
|
1140
1177
|
return;
|
|
1141
1178
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1142
|
-
note.
|
|
1143
|
-
note.
|
|
1179
|
+
note.gainL.connect(channel.gainL);
|
|
1180
|
+
note.gainR.connect(channel.gainR);
|
|
1144
1181
|
if (channel.state.sostenutoPedal) {
|
|
1145
1182
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1146
1183
|
}
|
|
@@ -1171,7 +1208,7 @@ class MidyGM2 {
|
|
|
1171
1208
|
}
|
|
1172
1209
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1173
1210
|
const note = scheduledNotes[index];
|
|
1174
|
-
note.
|
|
1211
|
+
note.volumeEnvelopeNode.gain
|
|
1175
1212
|
.cancelScheduledValues(endTime)
|
|
1176
1213
|
.linearRampToValueAtTime(0, stopTime);
|
|
1177
1214
|
note.ending = true;
|
|
@@ -1182,8 +1219,11 @@ class MidyGM2 {
|
|
|
1182
1219
|
note.bufferSource.onended = () => {
|
|
1183
1220
|
scheduledNotes[index] = null;
|
|
1184
1221
|
note.bufferSource.disconnect();
|
|
1185
|
-
note.volumeNode.disconnect();
|
|
1186
1222
|
note.filterNode.disconnect();
|
|
1223
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1224
|
+
note.volumeNode.disconnect();
|
|
1225
|
+
note.gainL.disconnect();
|
|
1226
|
+
note.gainR.disconnect();
|
|
1187
1227
|
if (note.modulationDepth) {
|
|
1188
1228
|
note.volumeDepth.disconnect();
|
|
1189
1229
|
note.modulationDepth.disconnect();
|
|
@@ -1232,7 +1272,7 @@ class MidyGM2 {
|
|
|
1232
1272
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1233
1273
|
}
|
|
1234
1274
|
else {
|
|
1235
|
-
const portamentoTime = endTime +
|
|
1275
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1236
1276
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1237
1277
|
const baseRate = note.voiceParams.playbackRate;
|
|
1238
1278
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1302,22 +1342,48 @@ class MidyGM2 {
|
|
|
1302
1342
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1303
1343
|
channel.program = program;
|
|
1304
1344
|
}
|
|
1305
|
-
handleChannelPressure(channelNumber,
|
|
1306
|
-
const now = this.audioContext.currentTime;
|
|
1345
|
+
handleChannelPressure(channelNumber, value) {
|
|
1307
1346
|
const channel = this.channels[channelNumber];
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (channel.
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1347
|
+
const prev = channel.state.channelPressure;
|
|
1348
|
+
const next = value / 127;
|
|
1349
|
+
channel.state.channelPressure = next;
|
|
1350
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1351
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1352
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1353
|
+
}
|
|
1354
|
+
const table = channel.pressureTable;
|
|
1355
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1356
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1357
|
+
const note = noteList[i];
|
|
1358
|
+
if (!note)
|
|
1359
|
+
continue;
|
|
1360
|
+
this.applyDestinationSettings(channel, note, table);
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1319
1363
|
// this.applyVoiceParams(channel, 13);
|
|
1320
1364
|
}
|
|
1365
|
+
setChannelPressure(channel, note) {
|
|
1366
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1367
|
+
this.updateDetune(channel);
|
|
1368
|
+
}
|
|
1369
|
+
if (!note.portamento) {
|
|
1370
|
+
if (channel.pressureTable[1] !== 64) {
|
|
1371
|
+
this.setFilterEnvelope(channel, note);
|
|
1372
|
+
}
|
|
1373
|
+
if (channel.pressureTable[2] !== 64) {
|
|
1374
|
+
this.setVolumeEnvelope(channel, note);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1378
|
+
this.setModLfoToPitch(channel, note);
|
|
1379
|
+
}
|
|
1380
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1381
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1382
|
+
}
|
|
1383
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1384
|
+
this.setModLfoToVolume(channel, note);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1321
1387
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1322
1388
|
const pitchBend = msb * 128 + lsb;
|
|
1323
1389
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1334,13 +1400,15 @@ class MidyGM2 {
|
|
|
1334
1400
|
}
|
|
1335
1401
|
setModLfoToPitch(channel, note) {
|
|
1336
1402
|
const now = this.audioContext.currentTime;
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1403
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1404
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1405
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1406
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1339
1407
|
channel.state.modulationDepth;
|
|
1340
|
-
const
|
|
1408
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1341
1409
|
note.modulationDepth.gain
|
|
1342
1410
|
.cancelScheduledValues(now)
|
|
1343
|
-
.setValueAtTime(modulationDepth
|
|
1411
|
+
.setValueAtTime(modulationDepth, now);
|
|
1344
1412
|
}
|
|
1345
1413
|
setVibLfoToPitch(channel, note) {
|
|
1346
1414
|
const now = this.audioContext.currentTime;
|
|
@@ -1352,69 +1420,75 @@ class MidyGM2 {
|
|
|
1352
1420
|
.cancelScheduledValues(now)
|
|
1353
1421
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1354
1422
|
}
|
|
1355
|
-
setModLfoToFilterFc(note) {
|
|
1423
|
+
setModLfoToFilterFc(channel, note) {
|
|
1356
1424
|
const now = this.audioContext.currentTime;
|
|
1357
|
-
const
|
|
1425
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1426
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1427
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1358
1428
|
note.filterDepth.gain
|
|
1359
1429
|
.cancelScheduledValues(now)
|
|
1360
1430
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1361
1431
|
}
|
|
1362
|
-
setModLfoToVolume(note) {
|
|
1432
|
+
setModLfoToVolume(channel, note) {
|
|
1363
1433
|
const now = this.audioContext.currentTime;
|
|
1364
1434
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1435
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1436
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1437
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1438
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1367
1439
|
note.volumeDepth.gain
|
|
1368
1440
|
.cancelScheduledValues(now)
|
|
1369
|
-
.setValueAtTime(volumeDepth
|
|
1441
|
+
.setValueAtTime(volumeDepth, now);
|
|
1370
1442
|
}
|
|
1371
|
-
|
|
1443
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1372
1444
|
if (0 < prevValue) {
|
|
1373
|
-
if (0 < note.voiceParams.
|
|
1445
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1374
1446
|
const now = this.audioContext.currentTime;
|
|
1375
|
-
const
|
|
1376
|
-
note.
|
|
1447
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1448
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1449
|
+
note.reverbEffectsSend.gain
|
|
1377
1450
|
.cancelScheduledValues(now)
|
|
1378
1451
|
.setValueAtTime(value, now);
|
|
1379
1452
|
}
|
|
1380
1453
|
else {
|
|
1381
|
-
note.
|
|
1454
|
+
note.reverbEffectsSend.disconnect();
|
|
1382
1455
|
}
|
|
1383
1456
|
}
|
|
1384
1457
|
else {
|
|
1385
|
-
if (0 < note.voiceParams.
|
|
1386
|
-
if (!note.
|
|
1387
|
-
note.
|
|
1388
|
-
gain: note.voiceParams.
|
|
1458
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1459
|
+
if (!note.reverbEffectsSend) {
|
|
1460
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1461
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1389
1462
|
});
|
|
1390
|
-
note.volumeNode.connect(note.
|
|
1463
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1391
1464
|
}
|
|
1392
|
-
note.
|
|
1465
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1393
1466
|
}
|
|
1394
1467
|
}
|
|
1395
1468
|
}
|
|
1396
|
-
|
|
1469
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1397
1470
|
if (0 < prevValue) {
|
|
1398
|
-
if (0 < note.voiceParams.
|
|
1471
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1399
1472
|
const now = this.audioContext.currentTime;
|
|
1400
|
-
const
|
|
1401
|
-
note.
|
|
1473
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1474
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1475
|
+
note.chorusEffectsSend.gain
|
|
1402
1476
|
.cancelScheduledValues(now)
|
|
1403
1477
|
.setValueAtTime(value, now);
|
|
1404
1478
|
}
|
|
1405
1479
|
else {
|
|
1406
|
-
note.
|
|
1480
|
+
note.chorusEffectsSend.disconnect();
|
|
1407
1481
|
}
|
|
1408
1482
|
}
|
|
1409
1483
|
else {
|
|
1410
|
-
if (0 < note.voiceParams.
|
|
1411
|
-
if (!note.
|
|
1412
|
-
note.
|
|
1413
|
-
gain: note.voiceParams.
|
|
1484
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1485
|
+
if (!note.chorusEffectsSend) {
|
|
1486
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1487
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1414
1488
|
});
|
|
1415
|
-
note.volumeNode.connect(note.
|
|
1489
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1416
1490
|
}
|
|
1417
|
-
note.
|
|
1491
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1418
1492
|
}
|
|
1419
1493
|
}
|
|
1420
1494
|
}
|
|
@@ -1447,30 +1521,32 @@ class MidyGM2 {
|
|
|
1447
1521
|
}
|
|
1448
1522
|
},
|
|
1449
1523
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1450
|
-
if (0 < channel.state.modulationDepth)
|
|
1451
|
-
this.setModLfoToFilterFc(note);
|
|
1524
|
+
if (0 < channel.state.modulationDepth) {
|
|
1525
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1526
|
+
}
|
|
1452
1527
|
},
|
|
1453
|
-
modLfoToVolume: (channel, note) => {
|
|
1454
|
-
if (0 < channel.state.modulationDepth)
|
|
1455
|
-
this.setModLfoToVolume(note);
|
|
1528
|
+
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1529
|
+
if (0 < channel.state.modulationDepth) {
|
|
1530
|
+
this.setModLfoToVolume(channel, note);
|
|
1531
|
+
}
|
|
1456
1532
|
},
|
|
1457
|
-
chorusEffectsSend: (
|
|
1458
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1533
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1534
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1459
1535
|
},
|
|
1460
|
-
reverbEffectsSend: (
|
|
1461
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1536
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1537
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1462
1538
|
},
|
|
1463
1539
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1464
1540
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1465
1541
|
delayVibLFO: (channel, note, prevValue) => {
|
|
1466
1542
|
if (0 < channel.state.vibratoDepth) {
|
|
1467
1543
|
const now = this.audioContext.currentTime;
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1544
|
+
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1545
|
+
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1470
1546
|
if (now < prevStartTime)
|
|
1471
1547
|
return;
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1548
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1549
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1474
1550
|
note.vibratoLFO.stop(now);
|
|
1475
1551
|
note.vibratoLFO.start(startTime);
|
|
1476
1552
|
}
|
|
@@ -1478,9 +1554,10 @@ class MidyGM2 {
|
|
|
1478
1554
|
freqVibLFO: (channel, note, _prevValue) => {
|
|
1479
1555
|
if (0 < channel.state.vibratoDepth) {
|
|
1480
1556
|
const now = this.audioContext.currentTime;
|
|
1557
|
+
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1481
1558
|
note.vibratoLFO.frequency
|
|
1482
1559
|
.cancelScheduledValues(now)
|
|
1483
|
-
.setValueAtTime(
|
|
1560
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate, now);
|
|
1484
1561
|
}
|
|
1485
1562
|
},
|
|
1486
1563
|
};
|
|
@@ -1577,8 +1654,8 @@ class MidyGM2 {
|
|
|
1577
1654
|
if (handler) {
|
|
1578
1655
|
handler.call(this, channelNumber, value);
|
|
1579
1656
|
const channel = this.channels[channelNumber];
|
|
1580
|
-
|
|
1581
|
-
this.
|
|
1657
|
+
this.applyVoiceParams(channel, controller + 128);
|
|
1658
|
+
this.applyControlTable(channel, controllerType);
|
|
1582
1659
|
}
|
|
1583
1660
|
else {
|
|
1584
1661
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1589,13 +1666,14 @@ class MidyGM2 {
|
|
|
1589
1666
|
}
|
|
1590
1667
|
updateModulation(channel) {
|
|
1591
1668
|
const now = this.audioContext.currentTime;
|
|
1669
|
+
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1592
1670
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1593
1671
|
for (let i = 0; i < noteList.length; i++) {
|
|
1594
1672
|
const note = noteList[i];
|
|
1595
1673
|
if (!note)
|
|
1596
1674
|
continue;
|
|
1597
1675
|
if (note.modulationDepth) {
|
|
1598
|
-
note.modulationDepth.gain.setValueAtTime(
|
|
1676
|
+
note.modulationDepth.gain.setValueAtTime(depth, now);
|
|
1599
1677
|
}
|
|
1600
1678
|
else {
|
|
1601
1679
|
this.setPitchEnvelope(note);
|
|
@@ -1606,8 +1684,7 @@ class MidyGM2 {
|
|
|
1606
1684
|
}
|
|
1607
1685
|
setModulationDepth(channelNumber, modulation) {
|
|
1608
1686
|
const channel = this.channels[channelNumber];
|
|
1609
|
-
channel.state.modulationDepth =
|
|
1610
|
-
channel.modulationDepthRange;
|
|
1687
|
+
channel.state.modulationDepth = modulation / 127;
|
|
1611
1688
|
this.updateModulation(channel);
|
|
1612
1689
|
}
|
|
1613
1690
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
@@ -1615,10 +1692,27 @@ class MidyGM2 {
|
|
|
1615
1692
|
const factor = 5 * Math.log(10) / 127;
|
|
1616
1693
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1617
1694
|
}
|
|
1695
|
+
setKeyBasedVolume(channel) {
|
|
1696
|
+
const now = this.audioContext.currentTime;
|
|
1697
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1698
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1699
|
+
const note = noteList[i];
|
|
1700
|
+
if (!note)
|
|
1701
|
+
continue;
|
|
1702
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1703
|
+
if (keyBasedValue === 0)
|
|
1704
|
+
continue;
|
|
1705
|
+
note.volumeNode.gain
|
|
1706
|
+
.cancelScheduledValues(now)
|
|
1707
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1618
1711
|
setVolume(channelNumber, volume) {
|
|
1619
1712
|
const channel = this.channels[channelNumber];
|
|
1620
1713
|
channel.state.volume = volume / 127;
|
|
1621
1714
|
this.updateChannelVolume(channel);
|
|
1715
|
+
this.setKeyBasedVolume(channel);
|
|
1622
1716
|
}
|
|
1623
1717
|
panToGain(pan) {
|
|
1624
1718
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1627,10 +1721,31 @@ class MidyGM2 {
|
|
|
1627
1721
|
gainRight: Math.sin(theta),
|
|
1628
1722
|
};
|
|
1629
1723
|
}
|
|
1724
|
+
setKeyBasedPan(channel) {
|
|
1725
|
+
const now = this.audioContext.currentTime;
|
|
1726
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1727
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1728
|
+
const note = noteList[i];
|
|
1729
|
+
if (!note)
|
|
1730
|
+
continue;
|
|
1731
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1732
|
+
if (keyBasedValue === 0)
|
|
1733
|
+
continue;
|
|
1734
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1735
|
+
note.gainL.gain
|
|
1736
|
+
.cancelScheduledValues(now)
|
|
1737
|
+
.setValueAtTime(gainLeft, now);
|
|
1738
|
+
note.gainR.gain
|
|
1739
|
+
.cancelScheduledValues(now)
|
|
1740
|
+
.setValueAtTime(gainRight, now);
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1630
1744
|
setPan(channelNumber, pan) {
|
|
1631
1745
|
const channel = this.channels[channelNumber];
|
|
1632
1746
|
channel.state.pan = pan / 127;
|
|
1633
1747
|
this.updateChannelVolume(channel);
|
|
1748
|
+
this.setKeyBasedPan(channel);
|
|
1634
1749
|
}
|
|
1635
1750
|
setExpression(channelNumber, expression) {
|
|
1636
1751
|
const channel = this.channels[channelNumber];
|
|
@@ -1665,6 +1780,22 @@ class MidyGM2 {
|
|
|
1665
1780
|
setPortamento(channelNumber, value) {
|
|
1666
1781
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1667
1782
|
}
|
|
1783
|
+
setSostenutoPedal(channelNumber, value) {
|
|
1784
|
+
const channel = this.channels[channelNumber];
|
|
1785
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1786
|
+
if (64 <= value) {
|
|
1787
|
+
const now = this.audioContext.currentTime;
|
|
1788
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1789
|
+
channel.sostenutoNotes = new Map(activeNotes);
|
|
1790
|
+
}
|
|
1791
|
+
else {
|
|
1792
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
setSoftPedal(channelNumber, softPedal) {
|
|
1796
|
+
const channel = this.channels[channelNumber];
|
|
1797
|
+
channel.state.softPedal = softPedal / 127;
|
|
1798
|
+
}
|
|
1668
1799
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1669
1800
|
const channel = this.channels[channelNumber];
|
|
1670
1801
|
const state = channel.state;
|
|
@@ -1697,7 +1828,7 @@ class MidyGM2 {
|
|
|
1697
1828
|
const note = noteList[i];
|
|
1698
1829
|
if (!note)
|
|
1699
1830
|
continue;
|
|
1700
|
-
this.setReverbEffectsSend(note, 0);
|
|
1831
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1701
1832
|
}
|
|
1702
1833
|
});
|
|
1703
1834
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1738,7 +1869,7 @@ class MidyGM2 {
|
|
|
1738
1869
|
const note = noteList[i];
|
|
1739
1870
|
if (!note)
|
|
1740
1871
|
continue;
|
|
1741
|
-
this.setChorusEffectsSend(note, 0);
|
|
1872
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1742
1873
|
}
|
|
1743
1874
|
});
|
|
1744
1875
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1747,22 +1878,6 @@ class MidyGM2 {
|
|
|
1747
1878
|
}
|
|
1748
1879
|
}
|
|
1749
1880
|
}
|
|
1750
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1751
|
-
const channel = this.channels[channelNumber];
|
|
1752
|
-
channel.state.sostenutoPedal = value / 127;
|
|
1753
|
-
if (64 <= value) {
|
|
1754
|
-
const now = this.audioContext.currentTime;
|
|
1755
|
-
const activeNotes = this.getActiveNotes(channel, now);
|
|
1756
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1757
|
-
}
|
|
1758
|
-
else {
|
|
1759
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1763
|
-
const channel = this.channels[channelNumber];
|
|
1764
|
-
channel.state.softPedal = softPedal / 127;
|
|
1765
|
-
}
|
|
1766
1881
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1767
1882
|
if (maxLSB < channel.dataLSB) {
|
|
1768
1883
|
channel.dataMSB++;
|
|
@@ -1872,7 +1987,6 @@ class MidyGM2 {
|
|
|
1872
1987
|
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1873
1988
|
const channel = this.channels[channelNumber];
|
|
1874
1989
|
channel.modulationDepthRange = modulationDepthRange;
|
|
1875
|
-
channel.modulationDepth = (modulation / 127) * modulationDepthRange;
|
|
1876
1990
|
this.updateModulation(channel);
|
|
1877
1991
|
}
|
|
1878
1992
|
allSoundOff(channelNumber) {
|
|
@@ -1925,7 +2039,7 @@ class MidyGM2 {
|
|
|
1925
2039
|
switch (data[3]) {
|
|
1926
2040
|
case 8:
|
|
1927
2041
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
1928
|
-
return this.
|
|
2042
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
1929
2043
|
default:
|
|
1930
2044
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1931
2045
|
}
|
|
@@ -1978,7 +2092,7 @@ class MidyGM2 {
|
|
|
1978
2092
|
return this.handleMasterFineTuningSysEx(data);
|
|
1979
2093
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1980
2094
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1981
|
-
case 5:
|
|
2095
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
1982
2096
|
return this.handleGlobalParameterControlSysEx(data);
|
|
1983
2097
|
default:
|
|
1984
2098
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -1986,21 +2100,18 @@ class MidyGM2 {
|
|
|
1986
2100
|
break;
|
|
1987
2101
|
case 9:
|
|
1988
2102
|
switch (data[3]) {
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
//
|
|
1992
|
-
|
|
1993
|
-
// // TODO
|
|
1994
|
-
// return this.setControlChange();
|
|
2103
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2104
|
+
return this.handleChannelPressureSysEx(data);
|
|
2105
|
+
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2106
|
+
return this.handleControlChangeSysEx(data);
|
|
1995
2107
|
default:
|
|
1996
2108
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1997
2109
|
}
|
|
1998
2110
|
break;
|
|
1999
2111
|
case 10:
|
|
2000
2112
|
switch (data[3]) {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2113
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2114
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2004
2115
|
default:
|
|
2005
2116
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2006
2117
|
}
|
|
@@ -2019,8 +2130,8 @@ class MidyGM2 {
|
|
|
2019
2130
|
}
|
|
2020
2131
|
else {
|
|
2021
2132
|
const now = this.audioContext.currentTime;
|
|
2022
|
-
this.
|
|
2023
|
-
this.
|
|
2133
|
+
this.masterVolume.gain.cancelScheduledValues(now);
|
|
2134
|
+
this.masterVolume.gain.setValueAtTime(volume * volume, now);
|
|
2024
2135
|
}
|
|
2025
2136
|
}
|
|
2026
2137
|
handleMasterFineTuningSysEx(data) {
|
|
@@ -2045,40 +2156,6 @@ class MidyGM2 {
|
|
|
2045
2156
|
channel.detune += next - prev;
|
|
2046
2157
|
this.updateDetune(channel);
|
|
2047
2158
|
}
|
|
2048
|
-
getChannelBitmap(data) {
|
|
2049
|
-
const bitmap = new Array(16).fill(false);
|
|
2050
|
-
const ff = data[4] & 0b11;
|
|
2051
|
-
const gg = data[5] & 0x7F;
|
|
2052
|
-
const hh = data[6] & 0x7F;
|
|
2053
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2054
|
-
if (hh & (1 << bit))
|
|
2055
|
-
bitmap[bit] = true;
|
|
2056
|
-
}
|
|
2057
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2058
|
-
if (gg & (1 << bit))
|
|
2059
|
-
bitmap[bit + 7] = true;
|
|
2060
|
-
}
|
|
2061
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2062
|
-
if (ff & (1 << bit))
|
|
2063
|
-
bitmap[bit + 14] = true;
|
|
2064
|
-
}
|
|
2065
|
-
return bitmap;
|
|
2066
|
-
}
|
|
2067
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2068
|
-
if (data.length < 18) {
|
|
2069
|
-
console.error("Data length is too short");
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2073
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2074
|
-
if (!channelBitmap[i])
|
|
2075
|
-
continue;
|
|
2076
|
-
for (let j = 0; j < 12; j++) {
|
|
2077
|
-
const value = data[j + 7] - 64; // cent
|
|
2078
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
2159
|
handleGlobalParameterControlSysEx(data) {
|
|
2083
2160
|
if (data[7] === 1) {
|
|
2084
2161
|
switch (data[8]) {
|
|
@@ -2265,6 +2342,122 @@ class MidyGM2 {
|
|
|
2265
2342
|
getChorusSendToReverb(value) {
|
|
2266
2343
|
return value * 0.00787;
|
|
2267
2344
|
}
|
|
2345
|
+
getChannelBitmap(data) {
|
|
2346
|
+
const bitmap = new Array(16).fill(false);
|
|
2347
|
+
const ff = data[4] & 0b11;
|
|
2348
|
+
const gg = data[5] & 0x7F;
|
|
2349
|
+
const hh = data[6] & 0x7F;
|
|
2350
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2351
|
+
if (hh & (1 << bit))
|
|
2352
|
+
bitmap[bit] = true;
|
|
2353
|
+
}
|
|
2354
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2355
|
+
if (gg & (1 << bit))
|
|
2356
|
+
bitmap[bit + 7] = true;
|
|
2357
|
+
}
|
|
2358
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2359
|
+
if (ff & (1 << bit))
|
|
2360
|
+
bitmap[bit + 14] = true;
|
|
2361
|
+
}
|
|
2362
|
+
return bitmap;
|
|
2363
|
+
}
|
|
2364
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2365
|
+
if (data.length < 18) {
|
|
2366
|
+
console.error("Data length is too short");
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2370
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2371
|
+
if (!channelBitmap[i])
|
|
2372
|
+
continue;
|
|
2373
|
+
for (let j = 0; j < 12; j++) {
|
|
2374
|
+
const value = data[j + 7] - 64; // cent
|
|
2375
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
applyDestinationSettings(channel, note, table) {
|
|
2380
|
+
if (table[0] !== 64) {
|
|
2381
|
+
this.updateDetune(channel);
|
|
2382
|
+
}
|
|
2383
|
+
if (!note.portamento) {
|
|
2384
|
+
if (table[1] !== 64) {
|
|
2385
|
+
this.setFilterEnvelope(channel, note);
|
|
2386
|
+
}
|
|
2387
|
+
if (table[2] !== 64) {
|
|
2388
|
+
this.setVolumeEnvelope(channel, note);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (table[3] !== 0) {
|
|
2392
|
+
this.setModLfoToPitch(channel, note);
|
|
2393
|
+
}
|
|
2394
|
+
if (table[4] !== 0) {
|
|
2395
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2396
|
+
}
|
|
2397
|
+
if (table[5] !== 0) {
|
|
2398
|
+
this.setModLfoToVolume(channel, note);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
handleChannelPressureSysEx(data) {
|
|
2402
|
+
const channelNumber = data[4];
|
|
2403
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2404
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2405
|
+
const pp = data[i];
|
|
2406
|
+
const rr = data[i + 1];
|
|
2407
|
+
table[pp] = rr;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
initControlTable() {
|
|
2411
|
+
const channelCount = 128;
|
|
2412
|
+
const slotSize = 6;
|
|
2413
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2414
|
+
const table = new Uint8Array(channelCount * slotSize);
|
|
2415
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
2416
|
+
const offset = ch * slotSize;
|
|
2417
|
+
table.set(defaultValues, offset);
|
|
2418
|
+
}
|
|
2419
|
+
return table;
|
|
2420
|
+
}
|
|
2421
|
+
applyControlTable(channel, controllerType) {
|
|
2422
|
+
const slotSize = 6;
|
|
2423
|
+
const offset = controllerType * slotSize;
|
|
2424
|
+
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2425
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
2426
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
2427
|
+
const note = noteList[i];
|
|
2428
|
+
if (!note)
|
|
2429
|
+
continue;
|
|
2430
|
+
this.applyDestinationSettings(channel, note, table);
|
|
2431
|
+
}
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
handleControlChangeSysEx(data) {
|
|
2435
|
+
const channelNumber = data[4];
|
|
2436
|
+
const controllerType = data[5];
|
|
2437
|
+
const table = this.channels[channelNumber].controlTable[controllerType];
|
|
2438
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2439
|
+
const pp = data[i];
|
|
2440
|
+
const rr = data[i + 1];
|
|
2441
|
+
table[pp] = rr;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2445
|
+
const index = keyNumber * 128 + controllerType;
|
|
2446
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2447
|
+
return (controlValue + 64) / 64;
|
|
2448
|
+
}
|
|
2449
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2450
|
+
const channelNumber = data[4];
|
|
2451
|
+
const keyNumber = data[5];
|
|
2452
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2453
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2454
|
+
const controllerType = data[i];
|
|
2455
|
+
const value = data[i + 1];
|
|
2456
|
+
const index = keyNumber * 128 + controllerType;
|
|
2457
|
+
table[index] = value - 64;
|
|
2458
|
+
}
|
|
2459
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2460
|
+
}
|
|
2268
2461
|
handleExclusiveMessage(data) {
|
|
2269
2462
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2270
2463
|
}
|
|
@@ -2299,6 +2492,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2299
2492
|
currentBufferSource: null,
|
|
2300
2493
|
detune: 0,
|
|
2301
2494
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2495
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2496
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2302
2497
|
program: 0,
|
|
2303
2498
|
bank: 121 * 128,
|
|
2304
2499
|
bankMSB: 121,
|