@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.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 Midy {
|
|
|
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 Midy {
|
|
|
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 Midy {
|
|
|
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(),
|
|
@@ -923,7 +942,9 @@ class Midy {
|
|
|
923
942
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
924
943
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
925
944
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
926
|
-
|
|
945
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
946
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
947
|
+
return tuning + pitch + pressure;
|
|
927
948
|
}
|
|
928
949
|
calcNoteDetune(channel, note) {
|
|
929
950
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -943,14 +964,19 @@ class Midy {
|
|
|
943
964
|
}
|
|
944
965
|
});
|
|
945
966
|
}
|
|
967
|
+
getPortamentoTime(channel) {
|
|
968
|
+
const factor = 5 * Math.log(10) / 127;
|
|
969
|
+
const time = channel.state.portamentoTime;
|
|
970
|
+
return Math.log(time) / factor;
|
|
971
|
+
}
|
|
946
972
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
947
973
|
const now = this.audioContext.currentTime;
|
|
948
974
|
const { voiceParams, startTime } = note;
|
|
949
975
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
950
976
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
951
977
|
const volDelay = startTime + voiceParams.volDelay;
|
|
952
|
-
const portamentoTime = volDelay + channel
|
|
953
|
-
note.
|
|
978
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
979
|
+
note.volumeEnvelopeNode.gain
|
|
954
980
|
.cancelScheduledValues(now)
|
|
955
981
|
.setValueAtTime(0, volDelay)
|
|
956
982
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
@@ -959,13 +985,16 @@ class Midy {
|
|
|
959
985
|
const now = this.audioContext.currentTime;
|
|
960
986
|
const state = channel.state;
|
|
961
987
|
const { voiceParams, startTime } = note;
|
|
962
|
-
const
|
|
988
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
989
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
990
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
991
|
+
pressure;
|
|
963
992
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
964
993
|
const volDelay = startTime + voiceParams.volDelay;
|
|
965
994
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
966
995
|
const volHold = volAttack + voiceParams.volHold;
|
|
967
996
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
968
|
-
note.
|
|
997
|
+
note.volumeEnvelopeNode.gain
|
|
969
998
|
.cancelScheduledValues(now)
|
|
970
999
|
.setValueAtTime(0, startTime)
|
|
971
1000
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -1007,14 +1036,17 @@ class Midy {
|
|
|
1007
1036
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1008
1037
|
const softPedalFactor = 1 -
|
|
1009
1038
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1039
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1040
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1041
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1042
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1043
|
+
state.brightness * 2;
|
|
1012
1044
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1013
1045
|
const sustainFreq = baseFreq +
|
|
1014
1046
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1015
1047
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1016
1048
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1017
|
-
const portamentoTime = startTime + channel
|
|
1049
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1018
1050
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1019
1051
|
note.filterNode.frequency
|
|
1020
1052
|
.cancelScheduledValues(now)
|
|
@@ -1059,14 +1091,14 @@ class Midy {
|
|
|
1059
1091
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1060
1092
|
this.setModLfoToPitch(channel, note);
|
|
1061
1093
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1062
|
-
this.setModLfoToVolume(note);
|
|
1094
|
+
this.setModLfoToVolume(channel, note);
|
|
1063
1095
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1064
1096
|
note.modulationLFO.connect(note.filterDepth);
|
|
1065
1097
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1066
1098
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1067
1099
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1068
1100
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1069
|
-
note.volumeDepth.connect(note.
|
|
1101
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1070
1102
|
}
|
|
1071
1103
|
startVibrato(channel, note, startTime) {
|
|
1072
1104
|
const { voiceParams } = note;
|
|
@@ -1088,6 +1120,9 @@ class Midy {
|
|
|
1088
1120
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1089
1121
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1090
1122
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1123
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1124
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1125
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1091
1126
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1092
1127
|
type: "lowpass",
|
|
1093
1128
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
@@ -1114,7 +1149,10 @@ class Midy {
|
|
|
1114
1149
|
channel.currentBufferSource = note.bufferSource;
|
|
1115
1150
|
}
|
|
1116
1151
|
note.bufferSource.connect(note.filterNode);
|
|
1117
|
-
note.filterNode.connect(note.
|
|
1152
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1153
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1154
|
+
note.volumeNode.connect(note.gainL);
|
|
1155
|
+
note.volumeNode.connect(note.gainR);
|
|
1118
1156
|
if (0 < channel.chorusSendLevel) {
|
|
1119
1157
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1120
1158
|
}
|
|
@@ -1145,8 +1183,8 @@ class Midy {
|
|
|
1145
1183
|
if (!voice)
|
|
1146
1184
|
return;
|
|
1147
1185
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1148
|
-
note.
|
|
1149
|
-
note.
|
|
1186
|
+
note.gainL.connect(channel.gainL);
|
|
1187
|
+
note.gainR.connect(channel.gainR);
|
|
1150
1188
|
if (channel.state.sostenutoPedal) {
|
|
1151
1189
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1152
1190
|
}
|
|
@@ -1177,7 +1215,7 @@ class Midy {
|
|
|
1177
1215
|
}
|
|
1178
1216
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1179
1217
|
const note = scheduledNotes[index];
|
|
1180
|
-
note.
|
|
1218
|
+
note.volumeEnvelopeNode.gain
|
|
1181
1219
|
.cancelScheduledValues(endTime)
|
|
1182
1220
|
.linearRampToValueAtTime(0, stopTime);
|
|
1183
1221
|
note.ending = true;
|
|
@@ -1188,8 +1226,11 @@ class Midy {
|
|
|
1188
1226
|
note.bufferSource.onended = () => {
|
|
1189
1227
|
scheduledNotes[index] = null;
|
|
1190
1228
|
note.bufferSource.disconnect();
|
|
1191
|
-
note.volumeNode.disconnect();
|
|
1192
1229
|
note.filterNode.disconnect();
|
|
1230
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1231
|
+
note.volumeNode.disconnect();
|
|
1232
|
+
note.gainL.disconnect();
|
|
1233
|
+
note.gainR.disconnect();
|
|
1193
1234
|
if (note.modulationDepth) {
|
|
1194
1235
|
note.volumeDepth.disconnect();
|
|
1195
1236
|
note.modulationDepth.disconnect();
|
|
@@ -1239,7 +1280,7 @@ class Midy {
|
|
|
1239
1280
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1240
1281
|
}
|
|
1241
1282
|
else {
|
|
1242
|
-
const portamentoTime = endTime +
|
|
1283
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1243
1284
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1244
1285
|
const baseRate = note.voiceParams.playbackRate;
|
|
1245
1286
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1314,7 +1355,7 @@ class Midy {
|
|
|
1314
1355
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1315
1356
|
if (activeNotes.has(noteNumber)) {
|
|
1316
1357
|
const activeNote = activeNotes.get(noteNumber);
|
|
1317
|
-
const gain = activeNote.
|
|
1358
|
+
const gain = activeNote.gainL.gain.value;
|
|
1318
1359
|
activeNote.volumeNode.gain
|
|
1319
1360
|
.cancelScheduledValues(now)
|
|
1320
1361
|
.setValueAtTime(gain * pressure, now);
|
|
@@ -1327,20 +1368,24 @@ class Midy {
|
|
|
1327
1368
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1328
1369
|
channel.program = program;
|
|
1329
1370
|
}
|
|
1330
|
-
handleChannelPressure(channelNumber,
|
|
1331
|
-
const now = this.audioContext.currentTime;
|
|
1371
|
+
handleChannelPressure(channelNumber, value) {
|
|
1332
1372
|
const channel = this.channels[channelNumber];
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (channel.
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1373
|
+
const prev = channel.state.channelPressure;
|
|
1374
|
+
const next = value / 127;
|
|
1375
|
+
channel.state.channelPressure = next;
|
|
1376
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1377
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1378
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1379
|
+
}
|
|
1380
|
+
const table = channel.pressureTable;
|
|
1381
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1382
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1383
|
+
const note = noteList[i];
|
|
1384
|
+
if (!note)
|
|
1385
|
+
continue;
|
|
1386
|
+
this.applyDestinationSettings(channel, note, table);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1344
1389
|
// this.applyVoiceParams(channel, 13);
|
|
1345
1390
|
}
|
|
1346
1391
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
@@ -1359,13 +1404,15 @@ class Midy {
|
|
|
1359
1404
|
}
|
|
1360
1405
|
setModLfoToPitch(channel, note) {
|
|
1361
1406
|
const now = this.audioContext.currentTime;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1407
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1408
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1409
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1410
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1364
1411
|
channel.state.modulationDepth;
|
|
1365
|
-
const
|
|
1412
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1366
1413
|
note.modulationDepth.gain
|
|
1367
1414
|
.cancelScheduledValues(now)
|
|
1368
|
-
.setValueAtTime(modulationDepth
|
|
1415
|
+
.setValueAtTime(modulationDepth, now);
|
|
1369
1416
|
}
|
|
1370
1417
|
setVibLfoToPitch(channel, note) {
|
|
1371
1418
|
const now = this.audioContext.currentTime;
|
|
@@ -1377,69 +1424,75 @@ class Midy {
|
|
|
1377
1424
|
.cancelScheduledValues(now)
|
|
1378
1425
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1379
1426
|
}
|
|
1380
|
-
setModLfoToFilterFc(note) {
|
|
1427
|
+
setModLfoToFilterFc(channel, note) {
|
|
1381
1428
|
const now = this.audioContext.currentTime;
|
|
1382
|
-
const
|
|
1429
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1430
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1431
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1383
1432
|
note.filterDepth.gain
|
|
1384
1433
|
.cancelScheduledValues(now)
|
|
1385
1434
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1386
1435
|
}
|
|
1387
|
-
setModLfoToVolume(note) {
|
|
1436
|
+
setModLfoToVolume(channel, note) {
|
|
1388
1437
|
const now = this.audioContext.currentTime;
|
|
1389
1438
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1390
|
-
const
|
|
1391
|
-
const
|
|
1439
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1440
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1441
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1442
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1392
1443
|
note.volumeDepth.gain
|
|
1393
1444
|
.cancelScheduledValues(now)
|
|
1394
|
-
.setValueAtTime(volumeDepth
|
|
1445
|
+
.setValueAtTime(volumeDepth, now);
|
|
1395
1446
|
}
|
|
1396
|
-
|
|
1447
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1397
1448
|
if (0 < prevValue) {
|
|
1398
|
-
if (0 < note.voiceParams.
|
|
1449
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1399
1450
|
const now = this.audioContext.currentTime;
|
|
1400
|
-
const
|
|
1401
|
-
note.
|
|
1451
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1452
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1453
|
+
note.reverbEffectsSend.gain
|
|
1402
1454
|
.cancelScheduledValues(now)
|
|
1403
1455
|
.setValueAtTime(value, now);
|
|
1404
1456
|
}
|
|
1405
1457
|
else {
|
|
1406
|
-
note.
|
|
1458
|
+
note.reverbEffectsSend.disconnect();
|
|
1407
1459
|
}
|
|
1408
1460
|
}
|
|
1409
1461
|
else {
|
|
1410
|
-
if (0 < note.voiceParams.
|
|
1411
|
-
if (!note.
|
|
1412
|
-
note.
|
|
1413
|
-
gain: note.voiceParams.
|
|
1462
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1463
|
+
if (!note.reverbEffectsSend) {
|
|
1464
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1465
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1414
1466
|
});
|
|
1415
|
-
note.volumeNode.connect(note.
|
|
1467
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1416
1468
|
}
|
|
1417
|
-
note.
|
|
1469
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1418
1470
|
}
|
|
1419
1471
|
}
|
|
1420
1472
|
}
|
|
1421
|
-
|
|
1473
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1422
1474
|
if (0 < prevValue) {
|
|
1423
|
-
if (0 < note.voiceParams.
|
|
1475
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1424
1476
|
const now = this.audioContext.currentTime;
|
|
1425
|
-
const
|
|
1426
|
-
note.
|
|
1477
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1478
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1479
|
+
note.chorusEffectsSend.gain
|
|
1427
1480
|
.cancelScheduledValues(now)
|
|
1428
1481
|
.setValueAtTime(value, now);
|
|
1429
1482
|
}
|
|
1430
1483
|
else {
|
|
1431
|
-
note.
|
|
1484
|
+
note.chorusEffectsSend.disconnect();
|
|
1432
1485
|
}
|
|
1433
1486
|
}
|
|
1434
1487
|
else {
|
|
1435
|
-
if (0 < note.voiceParams.
|
|
1436
|
-
if (!note.
|
|
1437
|
-
note.
|
|
1438
|
-
gain: note.voiceParams.
|
|
1488
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1489
|
+
if (!note.chorusEffectsSend) {
|
|
1490
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1491
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1439
1492
|
});
|
|
1440
|
-
note.volumeNode.connect(note.
|
|
1493
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1441
1494
|
}
|
|
1442
|
-
note.
|
|
1495
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1443
1496
|
}
|
|
1444
1497
|
}
|
|
1445
1498
|
}
|
|
@@ -1472,30 +1525,32 @@ class Midy {
|
|
|
1472
1525
|
}
|
|
1473
1526
|
},
|
|
1474
1527
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1475
|
-
if (0 < channel.state.modulationDepth)
|
|
1476
|
-
this.setModLfoToFilterFc(note);
|
|
1528
|
+
if (0 < channel.state.modulationDepth) {
|
|
1529
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1530
|
+
}
|
|
1477
1531
|
},
|
|
1478
|
-
modLfoToVolume: (channel, note) => {
|
|
1479
|
-
if (0 < channel.state.modulationDepth)
|
|
1480
|
-
this.setModLfoToVolume(note);
|
|
1532
|
+
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1533
|
+
if (0 < channel.state.modulationDepth) {
|
|
1534
|
+
this.setModLfoToVolume(channel, note);
|
|
1535
|
+
}
|
|
1481
1536
|
},
|
|
1482
|
-
chorusEffectsSend: (
|
|
1483
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1537
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1538
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1484
1539
|
},
|
|
1485
|
-
reverbEffectsSend: (
|
|
1486
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1540
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1541
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1487
1542
|
},
|
|
1488
1543
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1489
1544
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1490
1545
|
delayVibLFO: (channel, note, prevValue) => {
|
|
1491
1546
|
if (0 < channel.state.vibratoDepth) {
|
|
1492
1547
|
const now = this.audioContext.currentTime;
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1548
|
+
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1549
|
+
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1495
1550
|
if (now < prevStartTime)
|
|
1496
1551
|
return;
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1552
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1553
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1499
1554
|
note.vibratoLFO.stop(now);
|
|
1500
1555
|
note.vibratoLFO.start(startTime);
|
|
1501
1556
|
}
|
|
@@ -1503,9 +1558,10 @@ class Midy {
|
|
|
1503
1558
|
freqVibLFO: (channel, note, _prevValue) => {
|
|
1504
1559
|
if (0 < channel.state.vibratoDepth) {
|
|
1505
1560
|
const now = this.audioContext.currentTime;
|
|
1561
|
+
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1506
1562
|
note.vibratoLFO.frequency
|
|
1507
1563
|
.cancelScheduledValues(now)
|
|
1508
|
-
.setValueAtTime(
|
|
1564
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate, now);
|
|
1509
1565
|
}
|
|
1510
1566
|
},
|
|
1511
1567
|
};
|
|
@@ -1612,8 +1668,8 @@ class Midy {
|
|
|
1612
1668
|
if (handler) {
|
|
1613
1669
|
handler.call(this, channelNumber, value);
|
|
1614
1670
|
const channel = this.channels[channelNumber];
|
|
1615
|
-
|
|
1616
|
-
this.
|
|
1671
|
+
this.applyVoiceParams(channel, controllerType + 128);
|
|
1672
|
+
this.applyControlTable(channel, controllerType);
|
|
1617
1673
|
}
|
|
1618
1674
|
else {
|
|
1619
1675
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1624,13 +1680,14 @@ class Midy {
|
|
|
1624
1680
|
}
|
|
1625
1681
|
updateModulation(channel) {
|
|
1626
1682
|
const now = this.audioContext.currentTime;
|
|
1683
|
+
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1627
1684
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1628
1685
|
for (let i = 0; i < noteList.length; i++) {
|
|
1629
1686
|
const note = noteList[i];
|
|
1630
1687
|
if (!note)
|
|
1631
1688
|
continue;
|
|
1632
1689
|
if (note.modulationDepth) {
|
|
1633
|
-
note.modulationDepth.gain.setValueAtTime(
|
|
1690
|
+
note.modulationDepth.gain.setValueAtTime(depth, now);
|
|
1634
1691
|
}
|
|
1635
1692
|
else {
|
|
1636
1693
|
this.setPitchEnvelope(note);
|
|
@@ -1641,8 +1698,7 @@ class Midy {
|
|
|
1641
1698
|
}
|
|
1642
1699
|
setModulationDepth(channelNumber, modulation) {
|
|
1643
1700
|
const channel = this.channels[channelNumber];
|
|
1644
|
-
channel.state.modulationDepth =
|
|
1645
|
-
channel.modulationDepthRange;
|
|
1701
|
+
channel.state.modulationDepth = modulation / 127;
|
|
1646
1702
|
this.updateModulation(channel);
|
|
1647
1703
|
}
|
|
1648
1704
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
@@ -1650,10 +1706,27 @@ class Midy {
|
|
|
1650
1706
|
const factor = 5 * Math.log(10) / 127;
|
|
1651
1707
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1652
1708
|
}
|
|
1709
|
+
setKeyBasedVolume(channel) {
|
|
1710
|
+
const now = this.audioContext.currentTime;
|
|
1711
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1712
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1713
|
+
const note = noteList[i];
|
|
1714
|
+
if (!note)
|
|
1715
|
+
continue;
|
|
1716
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1717
|
+
if (keyBasedValue === 0)
|
|
1718
|
+
continue;
|
|
1719
|
+
note.volumeNode.gain
|
|
1720
|
+
.cancelScheduledValues(now)
|
|
1721
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1653
1725
|
setVolume(channelNumber, volume) {
|
|
1654
1726
|
const channel = this.channels[channelNumber];
|
|
1655
1727
|
channel.state.volume = volume / 127;
|
|
1656
1728
|
this.updateChannelVolume(channel);
|
|
1729
|
+
this.setKeyBasedVolume(channel);
|
|
1657
1730
|
}
|
|
1658
1731
|
panToGain(pan) {
|
|
1659
1732
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1662,10 +1735,31 @@ class Midy {
|
|
|
1662
1735
|
gainRight: Math.sin(theta),
|
|
1663
1736
|
};
|
|
1664
1737
|
}
|
|
1738
|
+
setKeyBasedPan(channel) {
|
|
1739
|
+
const now = this.audioContext.currentTime;
|
|
1740
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1741
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1742
|
+
const note = noteList[i];
|
|
1743
|
+
if (!note)
|
|
1744
|
+
continue;
|
|
1745
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1746
|
+
if (keyBasedValue === 0)
|
|
1747
|
+
continue;
|
|
1748
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1749
|
+
note.gainL.gain
|
|
1750
|
+
.cancelScheduledValues(now)
|
|
1751
|
+
.setValueAtTime(gainLeft, now);
|
|
1752
|
+
note.gainR.gain
|
|
1753
|
+
.cancelScheduledValues(now)
|
|
1754
|
+
.setValueAtTime(gainRight, now);
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1665
1758
|
setPan(channelNumber, pan) {
|
|
1666
1759
|
const channel = this.channels[channelNumber];
|
|
1667
1760
|
channel.state.pan = pan / 127;
|
|
1668
1761
|
this.updateChannelVolume(channel);
|
|
1762
|
+
this.setKeyBasedPan(channel);
|
|
1669
1763
|
}
|
|
1670
1764
|
setExpression(channelNumber, expression) {
|
|
1671
1765
|
const channel = this.channels[channelNumber];
|
|
@@ -1827,7 +1921,7 @@ class Midy {
|
|
|
1827
1921
|
const note = noteList[i];
|
|
1828
1922
|
if (!note)
|
|
1829
1923
|
continue;
|
|
1830
|
-
this.setReverbEffectsSend(note, 0);
|
|
1924
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1831
1925
|
}
|
|
1832
1926
|
});
|
|
1833
1927
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1868,7 +1962,7 @@ class Midy {
|
|
|
1868
1962
|
const note = noteList[i];
|
|
1869
1963
|
if (!note)
|
|
1870
1964
|
continue;
|
|
1871
|
-
this.setChorusEffectsSend(note, 0);
|
|
1965
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1872
1966
|
}
|
|
1873
1967
|
});
|
|
1874
1968
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1998,7 +2092,6 @@ class Midy {
|
|
|
1998
2092
|
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1999
2093
|
const channel = this.channels[channelNumber];
|
|
2000
2094
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2001
|
-
channel.modulationDepth = (modulation / 127) * modulationDepthRange;
|
|
2002
2095
|
this.updateModulation(channel);
|
|
2003
2096
|
}
|
|
2004
2097
|
allSoundOff(channelNumber) {
|
|
@@ -2051,7 +2144,7 @@ class Midy {
|
|
|
2051
2144
|
switch (data[3]) {
|
|
2052
2145
|
case 8:
|
|
2053
2146
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2054
|
-
return this.
|
|
2147
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2055
2148
|
default:
|
|
2056
2149
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2057
2150
|
}
|
|
@@ -2104,7 +2197,7 @@ class Midy {
|
|
|
2104
2197
|
return this.handleMasterFineTuningSysEx(data);
|
|
2105
2198
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2106
2199
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
2107
|
-
case 5:
|
|
2200
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2108
2201
|
return this.handleGlobalParameterControlSysEx(data);
|
|
2109
2202
|
default:
|
|
2110
2203
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2112,31 +2205,27 @@ class Midy {
|
|
|
2112
2205
|
break;
|
|
2113
2206
|
case 8:
|
|
2114
2207
|
switch (data[3]) {
|
|
2115
|
-
case 8:
|
|
2116
|
-
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2208
|
+
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2117
2209
|
// TODO: realtime
|
|
2118
|
-
return this.
|
|
2210
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2119
2211
|
default:
|
|
2120
2212
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2121
2213
|
}
|
|
2122
2214
|
break;
|
|
2123
2215
|
case 9:
|
|
2124
2216
|
switch (data[3]) {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
//
|
|
2128
|
-
|
|
2129
|
-
// // TODO
|
|
2130
|
-
// return this.setControlChange();
|
|
2217
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2218
|
+
return this.handleChannelPressureSysEx(data);
|
|
2219
|
+
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2220
|
+
return this.handleControlChangeSysEx(data);
|
|
2131
2221
|
default:
|
|
2132
2222
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2133
2223
|
}
|
|
2134
2224
|
break;
|
|
2135
2225
|
case 10:
|
|
2136
2226
|
switch (data[3]) {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2227
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2228
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2140
2229
|
default:
|
|
2141
2230
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2142
2231
|
}
|
|
@@ -2155,8 +2244,8 @@ class Midy {
|
|
|
2155
2244
|
}
|
|
2156
2245
|
else {
|
|
2157
2246
|
const now = this.audioContext.currentTime;
|
|
2158
|
-
this.
|
|
2159
|
-
this.
|
|
2247
|
+
this.masterVolume.gain.cancelScheduledValues(now);
|
|
2248
|
+
this.masterVolume.gain.setValueAtTime(volume * volume, now);
|
|
2160
2249
|
}
|
|
2161
2250
|
}
|
|
2162
2251
|
handleMasterFineTuningSysEx(data) {
|
|
@@ -2181,40 +2270,6 @@ class Midy {
|
|
|
2181
2270
|
channel.detune += next - prev;
|
|
2182
2271
|
this.updateDetune(channel);
|
|
2183
2272
|
}
|
|
2184
|
-
getChannelBitmap(data) {
|
|
2185
|
-
const bitmap = new Array(16).fill(false);
|
|
2186
|
-
const ff = data[4] & 0b11;
|
|
2187
|
-
const gg = data[5] & 0x7F;
|
|
2188
|
-
const hh = data[6] & 0x7F;
|
|
2189
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2190
|
-
if (hh & (1 << bit))
|
|
2191
|
-
bitmap[bit] = true;
|
|
2192
|
-
}
|
|
2193
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2194
|
-
if (gg & (1 << bit))
|
|
2195
|
-
bitmap[bit + 7] = true;
|
|
2196
|
-
}
|
|
2197
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2198
|
-
if (ff & (1 << bit))
|
|
2199
|
-
bitmap[bit + 14] = true;
|
|
2200
|
-
}
|
|
2201
|
-
return bitmap;
|
|
2202
|
-
}
|
|
2203
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2204
|
-
if (data.length < 18) {
|
|
2205
|
-
console.error("Data length is too short");
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2209
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2210
|
-
if (!channelBitmap[i])
|
|
2211
|
-
continue;
|
|
2212
|
-
for (let j = 0; j < 12; j++) {
|
|
2213
|
-
const value = data[j + 7] - 64; // cent
|
|
2214
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
2273
|
handleGlobalParameterControlSysEx(data) {
|
|
2219
2274
|
if (data[7] === 1) {
|
|
2220
2275
|
switch (data[8]) {
|
|
@@ -2401,6 +2456,122 @@ class Midy {
|
|
|
2401
2456
|
getChorusSendToReverb(value) {
|
|
2402
2457
|
return value * 0.00787;
|
|
2403
2458
|
}
|
|
2459
|
+
getChannelBitmap(data) {
|
|
2460
|
+
const bitmap = new Array(16).fill(false);
|
|
2461
|
+
const ff = data[4] & 0b11;
|
|
2462
|
+
const gg = data[5] & 0x7F;
|
|
2463
|
+
const hh = data[6] & 0x7F;
|
|
2464
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2465
|
+
if (hh & (1 << bit))
|
|
2466
|
+
bitmap[bit] = true;
|
|
2467
|
+
}
|
|
2468
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2469
|
+
if (gg & (1 << bit))
|
|
2470
|
+
bitmap[bit + 7] = true;
|
|
2471
|
+
}
|
|
2472
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2473
|
+
if (ff & (1 << bit))
|
|
2474
|
+
bitmap[bit + 14] = true;
|
|
2475
|
+
}
|
|
2476
|
+
return bitmap;
|
|
2477
|
+
}
|
|
2478
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2479
|
+
if (data.length < 18) {
|
|
2480
|
+
console.error("Data length is too short");
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2484
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2485
|
+
if (!channelBitmap[i])
|
|
2486
|
+
continue;
|
|
2487
|
+
for (let j = 0; j < 12; j++) {
|
|
2488
|
+
const value = data[j + 7] - 64; // cent
|
|
2489
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
applyDestinationSettings(channel, note, table) {
|
|
2494
|
+
if (table[0] !== 64) {
|
|
2495
|
+
this.updateDetune(channel);
|
|
2496
|
+
}
|
|
2497
|
+
if (!note.portamento) {
|
|
2498
|
+
if (table[1] !== 64) {
|
|
2499
|
+
this.setFilterEnvelope(channel, note);
|
|
2500
|
+
}
|
|
2501
|
+
if (table[2] !== 64) {
|
|
2502
|
+
this.setVolumeEnvelope(channel, note);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
if (table[3] !== 0) {
|
|
2506
|
+
this.setModLfoToPitch(channel, note);
|
|
2507
|
+
}
|
|
2508
|
+
if (table[4] !== 0) {
|
|
2509
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2510
|
+
}
|
|
2511
|
+
if (table[5] !== 0) {
|
|
2512
|
+
this.setModLfoToVolume(channel, note);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
handleChannelPressureSysEx(data) {
|
|
2516
|
+
const channelNumber = data[4];
|
|
2517
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2518
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2519
|
+
const pp = data[i];
|
|
2520
|
+
const rr = data[i + 1];
|
|
2521
|
+
table[pp] = rr;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
initControlTable() {
|
|
2525
|
+
const channelCount = 128;
|
|
2526
|
+
const slotSize = 6;
|
|
2527
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2528
|
+
const table = new Uint8Array(channelCount * slotSize);
|
|
2529
|
+
for (let ch = 0; ch < channelCount; ch++) {
|
|
2530
|
+
const offset = ch * slotSize;
|
|
2531
|
+
table.set(defaultValues, offset);
|
|
2532
|
+
}
|
|
2533
|
+
return table;
|
|
2534
|
+
}
|
|
2535
|
+
applyControlTable(channel, controllerType) {
|
|
2536
|
+
const slotSize = 6;
|
|
2537
|
+
const offset = controllerType * slotSize;
|
|
2538
|
+
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2539
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
2540
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
2541
|
+
const note = noteList[i];
|
|
2542
|
+
if (!note)
|
|
2543
|
+
continue;
|
|
2544
|
+
this.applyDestinationSettings(channel, note, table);
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
handleControlChangeSysEx(data) {
|
|
2549
|
+
const channelNumber = data[4];
|
|
2550
|
+
const controllerType = data[5];
|
|
2551
|
+
const table = this.channels[channelNumber].controlTable[controllerType];
|
|
2552
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2553
|
+
const pp = data[i];
|
|
2554
|
+
const rr = data[i + 1];
|
|
2555
|
+
table[pp] = rr;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2559
|
+
const index = keyNumber * 128 + controllerType;
|
|
2560
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2561
|
+
return (controlValue + 64) / 64;
|
|
2562
|
+
}
|
|
2563
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2564
|
+
const channelNumber = data[4];
|
|
2565
|
+
const keyNumber = data[5];
|
|
2566
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2567
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2568
|
+
const controllerType = data[i];
|
|
2569
|
+
const value = data[i + 1];
|
|
2570
|
+
const index = keyNumber * 128 + controllerType;
|
|
2571
|
+
table[index] = value - 64;
|
|
2572
|
+
}
|
|
2573
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2574
|
+
}
|
|
2404
2575
|
handleExclusiveMessage(data) {
|
|
2405
2576
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2406
2577
|
}
|
|
@@ -2435,6 +2606,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2435
2606
|
currentBufferSource: null,
|
|
2436
2607
|
detune: 0,
|
|
2437
2608
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2609
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2610
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2438
2611
|
program: 0,
|
|
2439
2612
|
bank: 121 * 128,
|
|
2440
2613
|
bankMSB: 121,
|