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