@marmooo/midy 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +2 -2
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +15 -15
- package/esm/midy-GM2.d.ts +28 -16
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +268 -132
- package/esm/midy-GMLite.d.ts +2 -2
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +15 -15
- package/esm/midy.d.ts +24 -12
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +252 -116
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +2 -2
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +15 -15
- package/script/midy-GM2.d.ts +28 -16
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +268 -132
- package/script/midy-GMLite.d.ts +2 -2
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +15 -15
- package/script/midy.d.ts +24 -12
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +252 -116
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,
|
|
@@ -920,7 +938,9 @@ export class Midy {
|
|
|
920
938
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
921
939
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
922
940
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
923
|
-
|
|
941
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
942
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
943
|
+
return tuning + pitch + pressure;
|
|
924
944
|
}
|
|
925
945
|
calcNoteDetune(channel, note) {
|
|
926
946
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -940,14 +960,19 @@ export class Midy {
|
|
|
940
960
|
}
|
|
941
961
|
});
|
|
942
962
|
}
|
|
963
|
+
getPortamentoTime(channel) {
|
|
964
|
+
const factor = 5 * Math.log(10) / 127;
|
|
965
|
+
const time = channel.state.portamentoTime;
|
|
966
|
+
return Math.log(time) / factor;
|
|
967
|
+
}
|
|
943
968
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
944
969
|
const now = this.audioContext.currentTime;
|
|
945
970
|
const { voiceParams, startTime } = note;
|
|
946
971
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
947
972
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
948
973
|
const volDelay = startTime + voiceParams.volDelay;
|
|
949
|
-
const portamentoTime = volDelay + channel
|
|
950
|
-
note.
|
|
974
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
975
|
+
note.volumeEnvelopeNode.gain
|
|
951
976
|
.cancelScheduledValues(now)
|
|
952
977
|
.setValueAtTime(0, volDelay)
|
|
953
978
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
@@ -956,13 +981,16 @@ export class Midy {
|
|
|
956
981
|
const now = this.audioContext.currentTime;
|
|
957
982
|
const state = channel.state;
|
|
958
983
|
const { voiceParams, startTime } = note;
|
|
959
|
-
const
|
|
984
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
985
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
986
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
987
|
+
pressure;
|
|
960
988
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
961
989
|
const volDelay = startTime + voiceParams.volDelay;
|
|
962
990
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
963
991
|
const volHold = volAttack + voiceParams.volHold;
|
|
964
992
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
965
|
-
note.
|
|
993
|
+
note.volumeEnvelopeNode.gain
|
|
966
994
|
.cancelScheduledValues(now)
|
|
967
995
|
.setValueAtTime(0, startTime)
|
|
968
996
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -1004,14 +1032,17 @@ export class Midy {
|
|
|
1004
1032
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1005
1033
|
const softPedalFactor = 1 -
|
|
1006
1034
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1035
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1036
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1037
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1038
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1039
|
+
state.brightness * 2;
|
|
1009
1040
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1010
1041
|
const sustainFreq = baseFreq +
|
|
1011
1042
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1012
1043
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1013
1044
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1014
|
-
const portamentoTime = startTime + channel
|
|
1045
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1015
1046
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1016
1047
|
note.filterNode.frequency
|
|
1017
1048
|
.cancelScheduledValues(now)
|
|
@@ -1056,14 +1087,14 @@ export class Midy {
|
|
|
1056
1087
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1057
1088
|
this.setModLfoToPitch(channel, note);
|
|
1058
1089
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1059
|
-
this.setModLfoToVolume(note);
|
|
1090
|
+
this.setModLfoToVolume(channel, note);
|
|
1060
1091
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1061
1092
|
note.modulationLFO.connect(note.filterDepth);
|
|
1062
1093
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1063
1094
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1064
1095
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1065
1096
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1066
|
-
note.volumeDepth.connect(note.
|
|
1097
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1067
1098
|
}
|
|
1068
1099
|
startVibrato(channel, note, startTime) {
|
|
1069
1100
|
const { voiceParams } = note;
|
|
@@ -1085,6 +1116,9 @@ export class Midy {
|
|
|
1085
1116
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1086
1117
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1087
1118
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1119
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1120
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1121
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1088
1122
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1089
1123
|
type: "lowpass",
|
|
1090
1124
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
@@ -1111,7 +1145,10 @@ export class Midy {
|
|
|
1111
1145
|
channel.currentBufferSource = note.bufferSource;
|
|
1112
1146
|
}
|
|
1113
1147
|
note.bufferSource.connect(note.filterNode);
|
|
1114
|
-
note.filterNode.connect(note.
|
|
1148
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1149
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1150
|
+
note.volumeNode.connect(note.gainL);
|
|
1151
|
+
note.volumeNode.connect(note.gainR);
|
|
1115
1152
|
if (0 < channel.chorusSendLevel) {
|
|
1116
1153
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1117
1154
|
}
|
|
@@ -1142,8 +1179,8 @@ export class Midy {
|
|
|
1142
1179
|
if (!voice)
|
|
1143
1180
|
return;
|
|
1144
1181
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1145
|
-
note.
|
|
1146
|
-
note.
|
|
1182
|
+
note.gainL.connect(channel.gainL);
|
|
1183
|
+
note.gainR.connect(channel.gainR);
|
|
1147
1184
|
if (channel.state.sostenutoPedal) {
|
|
1148
1185
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1149
1186
|
}
|
|
@@ -1174,7 +1211,7 @@ export class Midy {
|
|
|
1174
1211
|
}
|
|
1175
1212
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1176
1213
|
const note = scheduledNotes[index];
|
|
1177
|
-
note.
|
|
1214
|
+
note.volumeEnvelopeNode.gain
|
|
1178
1215
|
.cancelScheduledValues(endTime)
|
|
1179
1216
|
.linearRampToValueAtTime(0, stopTime);
|
|
1180
1217
|
note.ending = true;
|
|
@@ -1185,8 +1222,11 @@ export class Midy {
|
|
|
1185
1222
|
note.bufferSource.onended = () => {
|
|
1186
1223
|
scheduledNotes[index] = null;
|
|
1187
1224
|
note.bufferSource.disconnect();
|
|
1188
|
-
note.volumeNode.disconnect();
|
|
1189
1225
|
note.filterNode.disconnect();
|
|
1226
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1227
|
+
note.volumeNode.disconnect();
|
|
1228
|
+
note.gainL.disconnect();
|
|
1229
|
+
note.gainR.disconnect();
|
|
1190
1230
|
if (note.modulationDepth) {
|
|
1191
1231
|
note.volumeDepth.disconnect();
|
|
1192
1232
|
note.modulationDepth.disconnect();
|
|
@@ -1236,7 +1276,7 @@ export class Midy {
|
|
|
1236
1276
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1237
1277
|
}
|
|
1238
1278
|
else {
|
|
1239
|
-
const portamentoTime = endTime +
|
|
1279
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1240
1280
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1241
1281
|
const baseRate = note.voiceParams.playbackRate;
|
|
1242
1282
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1311,7 +1351,7 @@ export class Midy {
|
|
|
1311
1351
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1312
1352
|
if (activeNotes.has(noteNumber)) {
|
|
1313
1353
|
const activeNote = activeNotes.get(noteNumber);
|
|
1314
|
-
const gain = activeNote.
|
|
1354
|
+
const gain = activeNote.gainL.gain.value;
|
|
1315
1355
|
activeNote.volumeNode.gain
|
|
1316
1356
|
.cancelScheduledValues(now)
|
|
1317
1357
|
.setValueAtTime(gain * pressure, now);
|
|
@@ -1324,22 +1364,45 @@ export class Midy {
|
|
|
1324
1364
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1325
1365
|
channel.program = program;
|
|
1326
1366
|
}
|
|
1327
|
-
handleChannelPressure(channelNumber,
|
|
1328
|
-
const now = this.audioContext.currentTime;
|
|
1367
|
+
handleChannelPressure(channelNumber, value) {
|
|
1329
1368
|
const channel = this.channels[channelNumber];
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
if (channel.
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
activeNote.volumeNode.gain
|
|
1337
|
-
.cancelScheduledValues(now)
|
|
1338
|
-
.setValueAtTime(gain * pressure, now);
|
|
1339
|
-
});
|
|
1369
|
+
const prev = channel.state.channelPressure;
|
|
1370
|
+
const next = value / 127;
|
|
1371
|
+
channel.state.channelPressure = next;
|
|
1372
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1373
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1374
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1340
1375
|
}
|
|
1376
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1377
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1378
|
+
const note = noteList[i];
|
|
1379
|
+
if (!note)
|
|
1380
|
+
continue;
|
|
1381
|
+
this.setChannelPressure(channel, note);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1341
1384
|
// this.applyVoiceParams(channel, 13);
|
|
1342
1385
|
}
|
|
1386
|
+
setChannelPressure(channel, note) {
|
|
1387
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1388
|
+
this.updateDetune(channel);
|
|
1389
|
+
}
|
|
1390
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1391
|
+
this.setFilterEnvelope(channel, note);
|
|
1392
|
+
}
|
|
1393
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1394
|
+
this.setVolumeEnvelope(channel, note);
|
|
1395
|
+
}
|
|
1396
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1397
|
+
this.setModLfoToPitch(channel, note);
|
|
1398
|
+
}
|
|
1399
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1400
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1401
|
+
}
|
|
1402
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1403
|
+
this.setModLfoToVolume(channel, note);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1343
1406
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1344
1407
|
const pitchBend = msb * 128 + lsb;
|
|
1345
1408
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1356,13 +1419,15 @@ export class Midy {
|
|
|
1356
1419
|
}
|
|
1357
1420
|
setModLfoToPitch(channel, note) {
|
|
1358
1421
|
const now = this.audioContext.currentTime;
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1422
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1423
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1424
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1425
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1361
1426
|
channel.state.modulationDepth;
|
|
1362
|
-
const
|
|
1427
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1363
1428
|
note.modulationDepth.gain
|
|
1364
1429
|
.cancelScheduledValues(now)
|
|
1365
|
-
.setValueAtTime(modulationDepth
|
|
1430
|
+
.setValueAtTime(modulationDepth, now);
|
|
1366
1431
|
}
|
|
1367
1432
|
setVibLfoToPitch(channel, note) {
|
|
1368
1433
|
const now = this.audioContext.currentTime;
|
|
@@ -1374,69 +1439,75 @@ export class Midy {
|
|
|
1374
1439
|
.cancelScheduledValues(now)
|
|
1375
1440
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1376
1441
|
}
|
|
1377
|
-
setModLfoToFilterFc(note) {
|
|
1442
|
+
setModLfoToFilterFc(channel, note) {
|
|
1378
1443
|
const now = this.audioContext.currentTime;
|
|
1379
|
-
const
|
|
1444
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1445
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1446
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1380
1447
|
note.filterDepth.gain
|
|
1381
1448
|
.cancelScheduledValues(now)
|
|
1382
1449
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1383
1450
|
}
|
|
1384
|
-
setModLfoToVolume(note) {
|
|
1451
|
+
setModLfoToVolume(channel, note) {
|
|
1385
1452
|
const now = this.audioContext.currentTime;
|
|
1386
1453
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1387
|
-
const
|
|
1388
|
-
const
|
|
1454
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1455
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1456
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1457
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1389
1458
|
note.volumeDepth.gain
|
|
1390
1459
|
.cancelScheduledValues(now)
|
|
1391
|
-
.setValueAtTime(volumeDepth
|
|
1460
|
+
.setValueAtTime(volumeDepth, now);
|
|
1392
1461
|
}
|
|
1393
|
-
|
|
1462
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1394
1463
|
if (0 < prevValue) {
|
|
1395
|
-
if (0 < note.voiceParams.
|
|
1464
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1396
1465
|
const now = this.audioContext.currentTime;
|
|
1397
|
-
const
|
|
1398
|
-
note.
|
|
1466
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1467
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1468
|
+
note.reverbEffectsSend.gain
|
|
1399
1469
|
.cancelScheduledValues(now)
|
|
1400
1470
|
.setValueAtTime(value, now);
|
|
1401
1471
|
}
|
|
1402
1472
|
else {
|
|
1403
|
-
note.
|
|
1473
|
+
note.reverbEffectsSend.disconnect();
|
|
1404
1474
|
}
|
|
1405
1475
|
}
|
|
1406
1476
|
else {
|
|
1407
|
-
if (0 < note.voiceParams.
|
|
1408
|
-
if (!note.
|
|
1409
|
-
note.
|
|
1410
|
-
gain: note.voiceParams.
|
|
1477
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1478
|
+
if (!note.reverbEffectsSend) {
|
|
1479
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1480
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1411
1481
|
});
|
|
1412
|
-
note.volumeNode.connect(note.
|
|
1482
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1413
1483
|
}
|
|
1414
|
-
note.
|
|
1484
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1415
1485
|
}
|
|
1416
1486
|
}
|
|
1417
1487
|
}
|
|
1418
|
-
|
|
1488
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1419
1489
|
if (0 < prevValue) {
|
|
1420
|
-
if (0 < note.voiceParams.
|
|
1490
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1421
1491
|
const now = this.audioContext.currentTime;
|
|
1422
|
-
const
|
|
1423
|
-
note.
|
|
1492
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1493
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1494
|
+
note.chorusEffectsSend.gain
|
|
1424
1495
|
.cancelScheduledValues(now)
|
|
1425
1496
|
.setValueAtTime(value, now);
|
|
1426
1497
|
}
|
|
1427
1498
|
else {
|
|
1428
|
-
note.
|
|
1499
|
+
note.chorusEffectsSend.disconnect();
|
|
1429
1500
|
}
|
|
1430
1501
|
}
|
|
1431
1502
|
else {
|
|
1432
|
-
if (0 < note.voiceParams.
|
|
1433
|
-
if (!note.
|
|
1434
|
-
note.
|
|
1435
|
-
gain: note.voiceParams.
|
|
1503
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1504
|
+
if (!note.chorusEffectsSend) {
|
|
1505
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1506
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1436
1507
|
});
|
|
1437
|
-
note.volumeNode.connect(note.
|
|
1508
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1438
1509
|
}
|
|
1439
|
-
note.
|
|
1510
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1440
1511
|
}
|
|
1441
1512
|
}
|
|
1442
1513
|
}
|
|
@@ -1469,18 +1540,20 @@ export class Midy {
|
|
|
1469
1540
|
}
|
|
1470
1541
|
},
|
|
1471
1542
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1472
|
-
if (0 < channel.state.modulationDepth)
|
|
1473
|
-
this.setModLfoToFilterFc(note);
|
|
1543
|
+
if (0 < channel.state.modulationDepth) {
|
|
1544
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1545
|
+
}
|
|
1474
1546
|
},
|
|
1475
1547
|
modLfoToVolume: (channel, note) => {
|
|
1476
|
-
if (0 < channel.state.modulationDepth)
|
|
1477
|
-
this.setModLfoToVolume(note);
|
|
1548
|
+
if (0 < channel.state.modulationDepth) {
|
|
1549
|
+
this.setModLfoToVolume(channel, note);
|
|
1550
|
+
}
|
|
1478
1551
|
},
|
|
1479
|
-
chorusEffectsSend: (
|
|
1480
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1552
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1553
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1481
1554
|
},
|
|
1482
|
-
reverbEffectsSend: (
|
|
1483
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1555
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1556
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1484
1557
|
},
|
|
1485
1558
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1486
1559
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1647,10 +1720,27 @@ export class Midy {
|
|
|
1647
1720
|
const factor = 5 * Math.log(10) / 127;
|
|
1648
1721
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1649
1722
|
}
|
|
1723
|
+
setKeyBasedVolume(channel) {
|
|
1724
|
+
const now = this.audioContext.currentTime;
|
|
1725
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1726
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1727
|
+
const note = noteList[i];
|
|
1728
|
+
if (!note)
|
|
1729
|
+
continue;
|
|
1730
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1731
|
+
if (keyBasedValue === 0)
|
|
1732
|
+
continue;
|
|
1733
|
+
note.volumeNode.gain
|
|
1734
|
+
.cancelScheduledValues(now)
|
|
1735
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1650
1739
|
setVolume(channelNumber, volume) {
|
|
1651
1740
|
const channel = this.channels[channelNumber];
|
|
1652
1741
|
channel.state.volume = volume / 127;
|
|
1653
1742
|
this.updateChannelVolume(channel);
|
|
1743
|
+
this.setKeyBasedVolume(channel);
|
|
1654
1744
|
}
|
|
1655
1745
|
panToGain(pan) {
|
|
1656
1746
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1659,10 +1749,31 @@ export class Midy {
|
|
|
1659
1749
|
gainRight: Math.sin(theta),
|
|
1660
1750
|
};
|
|
1661
1751
|
}
|
|
1752
|
+
setKeyBasedPan(channel) {
|
|
1753
|
+
const now = this.audioContext.currentTime;
|
|
1754
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1755
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1756
|
+
const note = noteList[i];
|
|
1757
|
+
if (!note)
|
|
1758
|
+
continue;
|
|
1759
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1760
|
+
if (keyBasedValue === 0)
|
|
1761
|
+
continue;
|
|
1762
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1763
|
+
note.gainL.gain
|
|
1764
|
+
.cancelScheduledValues(now)
|
|
1765
|
+
.setValueAtTime(gainLeft, now);
|
|
1766
|
+
note.gainR.gain
|
|
1767
|
+
.cancelScheduledValues(now)
|
|
1768
|
+
.setValueAtTime(gainRight, now);
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1662
1772
|
setPan(channelNumber, pan) {
|
|
1663
1773
|
const channel = this.channels[channelNumber];
|
|
1664
1774
|
channel.state.pan = pan / 127;
|
|
1665
1775
|
this.updateChannelVolume(channel);
|
|
1776
|
+
this.setKeyBasedPan(channel);
|
|
1666
1777
|
}
|
|
1667
1778
|
setExpression(channelNumber, expression) {
|
|
1668
1779
|
const channel = this.channels[channelNumber];
|
|
@@ -1824,7 +1935,7 @@ export class Midy {
|
|
|
1824
1935
|
const note = noteList[i];
|
|
1825
1936
|
if (!note)
|
|
1826
1937
|
continue;
|
|
1827
|
-
this.setReverbEffectsSend(note, 0);
|
|
1938
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1828
1939
|
}
|
|
1829
1940
|
});
|
|
1830
1941
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1865,7 +1976,7 @@ export class Midy {
|
|
|
1865
1976
|
const note = noteList[i];
|
|
1866
1977
|
if (!note)
|
|
1867
1978
|
continue;
|
|
1868
|
-
this.setChorusEffectsSend(note, 0);
|
|
1979
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1869
1980
|
}
|
|
1870
1981
|
});
|
|
1871
1982
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2048,7 +2159,7 @@ export class Midy {
|
|
|
2048
2159
|
switch (data[3]) {
|
|
2049
2160
|
case 8:
|
|
2050
2161
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2051
|
-
return this.
|
|
2162
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2052
2163
|
default:
|
|
2053
2164
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2054
2165
|
}
|
|
@@ -2101,7 +2212,7 @@ export class Midy {
|
|
|
2101
2212
|
return this.handleMasterFineTuningSysEx(data);
|
|
2102
2213
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2103
2214
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
2104
|
-
case 5:
|
|
2215
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2105
2216
|
return this.handleGlobalParameterControlSysEx(data);
|
|
2106
2217
|
default:
|
|
2107
2218
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2109,19 +2220,17 @@ export class Midy {
|
|
|
2109
2220
|
break;
|
|
2110
2221
|
case 8:
|
|
2111
2222
|
switch (data[3]) {
|
|
2112
|
-
case 8:
|
|
2113
|
-
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2223
|
+
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2114
2224
|
// TODO: realtime
|
|
2115
|
-
return this.
|
|
2225
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2116
2226
|
default:
|
|
2117
2227
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2118
2228
|
}
|
|
2119
2229
|
break;
|
|
2120
2230
|
case 9:
|
|
2121
2231
|
switch (data[3]) {
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
// return this.setChannelPressure();
|
|
2232
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2233
|
+
return this.handleChannelPressureSysEx(data);
|
|
2125
2234
|
// case 3:
|
|
2126
2235
|
// // TODO
|
|
2127
2236
|
// return this.setControlChange();
|
|
@@ -2131,9 +2240,8 @@ export class Midy {
|
|
|
2131
2240
|
break;
|
|
2132
2241
|
case 10:
|
|
2133
2242
|
switch (data[3]) {
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2243
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2244
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2137
2245
|
default:
|
|
2138
2246
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2139
2247
|
}
|
|
@@ -2178,40 +2286,6 @@ export class Midy {
|
|
|
2178
2286
|
channel.detune += next - prev;
|
|
2179
2287
|
this.updateDetune(channel);
|
|
2180
2288
|
}
|
|
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
2289
|
handleGlobalParameterControlSysEx(data) {
|
|
2216
2290
|
if (data[7] === 1) {
|
|
2217
2291
|
switch (data[8]) {
|
|
@@ -2398,6 +2472,66 @@ export class Midy {
|
|
|
2398
2472
|
getChorusSendToReverb(value) {
|
|
2399
2473
|
return value * 0.00787;
|
|
2400
2474
|
}
|
|
2475
|
+
getChannelBitmap(data) {
|
|
2476
|
+
const bitmap = new Array(16).fill(false);
|
|
2477
|
+
const ff = data[4] & 0b11;
|
|
2478
|
+
const gg = data[5] & 0x7F;
|
|
2479
|
+
const hh = data[6] & 0x7F;
|
|
2480
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2481
|
+
if (hh & (1 << bit))
|
|
2482
|
+
bitmap[bit] = true;
|
|
2483
|
+
}
|
|
2484
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2485
|
+
if (gg & (1 << bit))
|
|
2486
|
+
bitmap[bit + 7] = true;
|
|
2487
|
+
}
|
|
2488
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2489
|
+
if (ff & (1 << bit))
|
|
2490
|
+
bitmap[bit + 14] = true;
|
|
2491
|
+
}
|
|
2492
|
+
return bitmap;
|
|
2493
|
+
}
|
|
2494
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2495
|
+
if (data.length < 18) {
|
|
2496
|
+
console.error("Data length is too short");
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2500
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2501
|
+
if (!channelBitmap[i])
|
|
2502
|
+
continue;
|
|
2503
|
+
for (let j = 0; j < 12; j++) {
|
|
2504
|
+
const value = data[j + 7] - 64; // cent
|
|
2505
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
handleChannelPressureSysEx(data) {
|
|
2510
|
+
const channelNumber = data[4];
|
|
2511
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2512
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2513
|
+
const pp = data[i];
|
|
2514
|
+
const rr = data[i + 1];
|
|
2515
|
+
table[pp] = rr;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2519
|
+
const index = keyNumber * 128 + controllerType;
|
|
2520
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2521
|
+
return (controlValue + 64) / 64;
|
|
2522
|
+
}
|
|
2523
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2524
|
+
const channelNumber = data[4];
|
|
2525
|
+
const keyNumber = data[5];
|
|
2526
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2527
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2528
|
+
const controllerType = data[i];
|
|
2529
|
+
const value = data[i + 1];
|
|
2530
|
+
const index = keyNumber * 128 + controllerType;
|
|
2531
|
+
table[index] = value - 64;
|
|
2532
|
+
}
|
|
2533
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2534
|
+
}
|
|
2401
2535
|
handleExclusiveMessage(data) {
|
|
2402
2536
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2403
2537
|
}
|
|
@@ -2431,6 +2565,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2431
2565
|
currentBufferSource: null,
|
|
2432
2566
|
detune: 0,
|
|
2433
2567
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2568
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2569
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2434
2570
|
program: 0,
|
|
2435
2571
|
bank: 121 * 128,
|
|
2436
2572
|
bankMSB: 121,
|
package/package.json
CHANGED
package/script/midy-GM1.d.ts
CHANGED
|
@@ -132,7 +132,7 @@ export class MidyGM1 {
|
|
|
132
132
|
delayVibLFO: (channel: any, note: any, prevValue: any) => void;
|
|
133
133
|
freqVibLFO: (channel: any, note: any, _prevValue: any) => void;
|
|
134
134
|
};
|
|
135
|
-
getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array
|
|
135
|
+
getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
|
|
136
136
|
applyVoiceParams(channel: any, controllerType: any): void;
|
|
137
137
|
createControlChangeHandlers(): {
|
|
138
138
|
1: (channelNumber: any, modulation: any) => void;
|
|
@@ -189,7 +189,7 @@ declare class Note {
|
|
|
189
189
|
constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
|
|
190
190
|
bufferSource: any;
|
|
191
191
|
filterNode: any;
|
|
192
|
-
|
|
192
|
+
volumeEnvelopeNode: any;
|
|
193
193
|
volumeDepth: any;
|
|
194
194
|
modulationLFO: any;
|
|
195
195
|
modulationDepth: any;
|