@marmooo/midy 0.2.0 → 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/LICENSE +201 -0
- package/README.md +118 -0
- package/esm/midy-GM1.d.ts +12 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +85 -78
- package/esm/midy-GM2.d.ts +40 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +377 -194
- package/esm/midy-GMLite.d.ts +9 -7
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +64 -61
- package/esm/midy.d.ts +39 -21
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +444 -252
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +12 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +85 -78
- package/script/midy-GM2.d.ts +40 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +377 -194
- package/script/midy-GMLite.d.ts +9 -7
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +64 -61
- package/script/midy.d.ts +39 -21
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +444 -252
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,
|
|
@@ -907,16 +925,48 @@ class Midy {
|
|
|
907
925
|
cbToRatio(cb) {
|
|
908
926
|
return Math.pow(10, cb / 200);
|
|
909
927
|
}
|
|
928
|
+
rateToCent(rate) {
|
|
929
|
+
return 1200 * Math.log2(rate);
|
|
930
|
+
}
|
|
931
|
+
centToRate(cent) {
|
|
932
|
+
return Math.pow(2, cent / 1200);
|
|
933
|
+
}
|
|
910
934
|
centToHz(cent) {
|
|
911
|
-
return 8.176 *
|
|
935
|
+
return 8.176 * this.centToRate(cent);
|
|
912
936
|
}
|
|
913
|
-
|
|
937
|
+
calcChannelDetune(channel) {
|
|
914
938
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
915
939
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
940
|
+
const tuning = masterTuning + channelTuning;
|
|
916
941
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
917
|
-
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity *
|
|
942
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
918
943
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
919
|
-
|
|
944
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
945
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
946
|
+
return tuning + pitch + pressure;
|
|
947
|
+
}
|
|
948
|
+
calcNoteDetune(channel, note) {
|
|
949
|
+
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
950
|
+
}
|
|
951
|
+
updateDetune(channel) {
|
|
952
|
+
const now = this.audioContext.currentTime;
|
|
953
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
954
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
955
|
+
const note = noteList[i];
|
|
956
|
+
if (!note)
|
|
957
|
+
continue;
|
|
958
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
959
|
+
const detune = channel.detune + noteDetune;
|
|
960
|
+
note.bufferSource.detune
|
|
961
|
+
.cancelScheduledValues(now)
|
|
962
|
+
.setValueAtTime(detune, now);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
getPortamentoTime(channel) {
|
|
967
|
+
const factor = 5 * Math.log(10) / 127;
|
|
968
|
+
const time = channel.state.portamentoTime;
|
|
969
|
+
return Math.log(time) / factor;
|
|
920
970
|
}
|
|
921
971
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
922
972
|
const now = this.audioContext.currentTime;
|
|
@@ -924,8 +974,8 @@ class Midy {
|
|
|
924
974
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
925
975
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
926
976
|
const volDelay = startTime + voiceParams.volDelay;
|
|
927
|
-
const portamentoTime = volDelay + channel
|
|
928
|
-
note.
|
|
977
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
978
|
+
note.volumeEnvelopeNode.gain
|
|
929
979
|
.cancelScheduledValues(now)
|
|
930
980
|
.setValueAtTime(0, volDelay)
|
|
931
981
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
@@ -934,13 +984,16 @@ class Midy {
|
|
|
934
984
|
const now = this.audioContext.currentTime;
|
|
935
985
|
const state = channel.state;
|
|
936
986
|
const { voiceParams, startTime } = note;
|
|
937
|
-
const
|
|
987
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
988
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
989
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
990
|
+
pressure;
|
|
938
991
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
939
992
|
const volDelay = startTime + voiceParams.volDelay;
|
|
940
993
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
941
994
|
const volHold = volAttack + voiceParams.volHold;
|
|
942
995
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
943
|
-
note.
|
|
996
|
+
note.volumeEnvelopeNode.gain
|
|
944
997
|
.cancelScheduledValues(now)
|
|
945
998
|
.setValueAtTime(0, startTime)
|
|
946
999
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -948,32 +1001,28 @@ class Midy {
|
|
|
948
1001
|
.setValueAtTime(attackVolume, volHold)
|
|
949
1002
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
950
1003
|
}
|
|
951
|
-
|
|
1004
|
+
setPitchEnvelope(note) {
|
|
952
1005
|
const now = this.audioContext.currentTime;
|
|
1006
|
+
const { voiceParams } = note;
|
|
1007
|
+
const baseRate = voiceParams.playbackRate;
|
|
953
1008
|
note.bufferSource.playbackRate
|
|
954
1009
|
.cancelScheduledValues(now)
|
|
955
|
-
.setValueAtTime(
|
|
956
|
-
|
|
957
|
-
setPitch(channel, note) {
|
|
958
|
-
const now = this.audioContext.currentTime;
|
|
959
|
-
const { startTime } = note;
|
|
960
|
-
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
961
|
-
note.bufferSource.detune
|
|
962
|
-
.cancelScheduledValues(now)
|
|
963
|
-
.setValueAtTime(basePitch, startTime);
|
|
964
|
-
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
1010
|
+
.setValueAtTime(baseRate, now);
|
|
1011
|
+
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
965
1012
|
if (modEnvToPitch === 0)
|
|
966
1013
|
return;
|
|
1014
|
+
const basePitch = this.rateToCent(baseRate);
|
|
967
1015
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1016
|
+
const peekRate = this.centToRate(peekPitch);
|
|
968
1017
|
const modDelay = startTime + voiceParams.modDelay;
|
|
969
1018
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
970
1019
|
const modHold = modAttack + voiceParams.modHold;
|
|
971
1020
|
const modDecay = modHold + voiceParams.modDecay;
|
|
972
|
-
note.bufferSource.
|
|
973
|
-
.setValueAtTime(
|
|
974
|
-
.exponentialRampToValueAtTime(
|
|
975
|
-
.setValueAtTime(
|
|
976
|
-
.linearRampToValueAtTime(
|
|
1021
|
+
note.bufferSource.playbackRate
|
|
1022
|
+
.setValueAtTime(baseRate, modDelay)
|
|
1023
|
+
.exponentialRampToValueAtTime(peekRate, modAttack)
|
|
1024
|
+
.setValueAtTime(peekRate, modHold)
|
|
1025
|
+
.linearRampToValueAtTime(baseRate, modDecay);
|
|
977
1026
|
}
|
|
978
1027
|
clampCutoffFrequency(frequency) {
|
|
979
1028
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -986,14 +1035,17 @@ class Midy {
|
|
|
986
1035
|
const { voiceParams, noteNumber, startTime } = note;
|
|
987
1036
|
const softPedalFactor = 1 -
|
|
988
1037
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
989
|
-
const
|
|
990
|
-
|
|
1038
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1039
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1040
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1041
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1042
|
+
state.brightness * 2;
|
|
991
1043
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
992
1044
|
const sustainFreq = baseFreq +
|
|
993
1045
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
994
1046
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
995
1047
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
996
|
-
const portamentoTime = startTime + channel
|
|
1048
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
997
1049
|
const modDelay = startTime + voiceParams.modDelay;
|
|
998
1050
|
note.filterNode.frequency
|
|
999
1051
|
.cancelScheduledValues(now)
|
|
@@ -1038,14 +1090,14 @@ class Midy {
|
|
|
1038
1090
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1039
1091
|
this.setModLfoToPitch(channel, note);
|
|
1040
1092
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1041
|
-
this.setModLfoToVolume(note);
|
|
1093
|
+
this.setModLfoToVolume(channel, note);
|
|
1042
1094
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1043
1095
|
note.modulationLFO.connect(note.filterDepth);
|
|
1044
1096
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1045
1097
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1046
1098
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1047
1099
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1048
|
-
note.volumeDepth.connect(note.
|
|
1100
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1049
1101
|
}
|
|
1050
1102
|
startVibrato(channel, note, startTime) {
|
|
1051
1103
|
const { voiceParams } = note;
|
|
@@ -1067,6 +1119,9 @@ class Midy {
|
|
|
1067
1119
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1068
1120
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1069
1121
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1122
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1123
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1124
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1070
1125
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1071
1126
|
type: "lowpass",
|
|
1072
1127
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
@@ -1084,9 +1139,8 @@ class Midy {
|
|
|
1084
1139
|
if (0 < state.vibratoDepth) {
|
|
1085
1140
|
this.startVibrato(channel, note, startTime);
|
|
1086
1141
|
}
|
|
1087
|
-
this.
|
|
1142
|
+
this.setPitchEnvelope(note);
|
|
1088
1143
|
if (0 < state.modulationDepth) {
|
|
1089
|
-
this.setPitch(channel, note);
|
|
1090
1144
|
this.startModulation(channel, note, startTime);
|
|
1091
1145
|
}
|
|
1092
1146
|
if (this.mono && channel.currentBufferSource) {
|
|
@@ -1094,7 +1148,10 @@ class Midy {
|
|
|
1094
1148
|
channel.currentBufferSource = note.bufferSource;
|
|
1095
1149
|
}
|
|
1096
1150
|
note.bufferSource.connect(note.filterNode);
|
|
1097
|
-
note.filterNode.connect(note.
|
|
1151
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1152
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1153
|
+
note.volumeNode.connect(note.gainL);
|
|
1154
|
+
note.volumeNode.connect(note.gainR);
|
|
1098
1155
|
if (0 < channel.chorusSendLevel) {
|
|
1099
1156
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1100
1157
|
}
|
|
@@ -1125,8 +1182,8 @@ class Midy {
|
|
|
1125
1182
|
if (!voice)
|
|
1126
1183
|
return;
|
|
1127
1184
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1128
|
-
note.
|
|
1129
|
-
note.
|
|
1185
|
+
note.gainL.connect(channel.gainL);
|
|
1186
|
+
note.gainR.connect(channel.gainR);
|
|
1130
1187
|
if (channel.state.sostenutoPedal) {
|
|
1131
1188
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1132
1189
|
}
|
|
@@ -1157,7 +1214,7 @@ class Midy {
|
|
|
1157
1214
|
}
|
|
1158
1215
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1159
1216
|
const note = scheduledNotes[index];
|
|
1160
|
-
note.
|
|
1217
|
+
note.volumeEnvelopeNode.gain
|
|
1161
1218
|
.cancelScheduledValues(endTime)
|
|
1162
1219
|
.linearRampToValueAtTime(0, stopTime);
|
|
1163
1220
|
note.ending = true;
|
|
@@ -1168,8 +1225,11 @@ class Midy {
|
|
|
1168
1225
|
note.bufferSource.onended = () => {
|
|
1169
1226
|
scheduledNotes[index] = null;
|
|
1170
1227
|
note.bufferSource.disconnect();
|
|
1171
|
-
note.volumeNode.disconnect();
|
|
1172
1228
|
note.filterNode.disconnect();
|
|
1229
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1230
|
+
note.volumeNode.disconnect();
|
|
1231
|
+
note.gainL.disconnect();
|
|
1232
|
+
note.gainR.disconnect();
|
|
1173
1233
|
if (note.modulationDepth) {
|
|
1174
1234
|
note.volumeDepth.disconnect();
|
|
1175
1235
|
note.modulationDepth.disconnect();
|
|
@@ -1219,12 +1279,13 @@ class Midy {
|
|
|
1219
1279
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1220
1280
|
}
|
|
1221
1281
|
else {
|
|
1222
|
-
const portamentoTime = endTime +
|
|
1223
|
-
const
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1282
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1283
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1284
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1285
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1286
|
+
note.bufferSource.playbackRate
|
|
1226
1287
|
.cancelScheduledValues(endTime)
|
|
1227
|
-
.linearRampToValueAtTime(
|
|
1288
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1228
1289
|
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1229
1290
|
}
|
|
1230
1291
|
}
|
|
@@ -1293,7 +1354,7 @@ class Midy {
|
|
|
1293
1354
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1294
1355
|
if (activeNotes.has(noteNumber)) {
|
|
1295
1356
|
const activeNote = activeNotes.get(noteNumber);
|
|
1296
|
-
const gain = activeNote.
|
|
1357
|
+
const gain = activeNote.gainL.gain.value;
|
|
1297
1358
|
activeNote.volumeNode.gain
|
|
1298
1359
|
.cancelScheduledValues(now)
|
|
1299
1360
|
.setValueAtTime(gain * pressure, now);
|
|
@@ -1306,22 +1367,45 @@ class Midy {
|
|
|
1306
1367
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1307
1368
|
channel.program = program;
|
|
1308
1369
|
}
|
|
1309
|
-
handleChannelPressure(channelNumber,
|
|
1310
|
-
const now = this.audioContext.currentTime;
|
|
1370
|
+
handleChannelPressure(channelNumber, value) {
|
|
1311
1371
|
const channel = this.channels[channelNumber];
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
if (channel.
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
activeNote.volumeNode.gain
|
|
1319
|
-
.cancelScheduledValues(now)
|
|
1320
|
-
.setValueAtTime(gain * pressure, now);
|
|
1321
|
-
});
|
|
1372
|
+
const prev = channel.state.channelPressure;
|
|
1373
|
+
const next = value / 127;
|
|
1374
|
+
channel.state.channelPressure = next;
|
|
1375
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1376
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1377
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1322
1378
|
}
|
|
1379
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1380
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1381
|
+
const note = noteList[i];
|
|
1382
|
+
if (!note)
|
|
1383
|
+
continue;
|
|
1384
|
+
this.setChannelPressure(channel, note);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1323
1387
|
// this.applyVoiceParams(channel, 13);
|
|
1324
1388
|
}
|
|
1389
|
+
setChannelPressure(channel, note) {
|
|
1390
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1391
|
+
this.updateDetune(channel);
|
|
1392
|
+
}
|
|
1393
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1394
|
+
this.setFilterEnvelope(channel, note);
|
|
1395
|
+
}
|
|
1396
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1397
|
+
this.setVolumeEnvelope(channel, note);
|
|
1398
|
+
}
|
|
1399
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1400
|
+
this.setModLfoToPitch(channel, note);
|
|
1401
|
+
}
|
|
1402
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1403
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1404
|
+
}
|
|
1405
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1406
|
+
this.setModLfoToVolume(channel, note);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1325
1409
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1326
1410
|
const pitchBend = msb * 128 + lsb;
|
|
1327
1411
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1329,98 +1413,107 @@ class Midy {
|
|
|
1329
1413
|
setPitchBend(channelNumber, value) {
|
|
1330
1414
|
const channel = this.channels[channelNumber];
|
|
1331
1415
|
const state = channel.state;
|
|
1416
|
+
const prev = state.pitchWheel * 2 - 1;
|
|
1417
|
+
const next = (value - 8192) / 8192;
|
|
1332
1418
|
state.pitchWheel = value / 16383;
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
this.updateDetune(channel, detuneChange);
|
|
1419
|
+
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1420
|
+
this.updateDetune(channel);
|
|
1336
1421
|
this.applyVoiceParams(channel, 14);
|
|
1337
1422
|
}
|
|
1338
1423
|
setModLfoToPitch(channel, note) {
|
|
1339
1424
|
const now = this.audioContext.currentTime;
|
|
1340
|
-
const
|
|
1341
|
-
const
|
|
1425
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1426
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1427
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1428
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1342
1429
|
channel.state.modulationDepth;
|
|
1343
|
-
const
|
|
1430
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1344
1431
|
note.modulationDepth.gain
|
|
1345
1432
|
.cancelScheduledValues(now)
|
|
1346
|
-
.setValueAtTime(modulationDepth
|
|
1433
|
+
.setValueAtTime(modulationDepth, now);
|
|
1347
1434
|
}
|
|
1348
|
-
|
|
1435
|
+
setVibLfoToPitch(channel, note) {
|
|
1436
|
+
const now = this.audioContext.currentTime;
|
|
1437
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1438
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1439
|
+
2;
|
|
1440
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1441
|
+
note.vibratoDepth.gain
|
|
1442
|
+
.cancelScheduledValues(now)
|
|
1443
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1444
|
+
}
|
|
1445
|
+
setModLfoToFilterFc(channel, note) {
|
|
1446
|
+
const now = this.audioContext.currentTime;
|
|
1447
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1448
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1449
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1450
|
+
note.filterDepth.gain
|
|
1451
|
+
.cancelScheduledValues(now)
|
|
1452
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1453
|
+
}
|
|
1454
|
+
setModLfoToVolume(channel, note) {
|
|
1349
1455
|
const now = this.audioContext.currentTime;
|
|
1350
1456
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1351
|
-
const
|
|
1352
|
-
const
|
|
1457
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1458
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1459
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1460
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1353
1461
|
note.volumeDepth.gain
|
|
1354
1462
|
.cancelScheduledValues(now)
|
|
1355
|
-
.setValueAtTime(volumeDepth
|
|
1463
|
+
.setValueAtTime(volumeDepth, now);
|
|
1356
1464
|
}
|
|
1357
|
-
|
|
1465
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1358
1466
|
if (0 < prevValue) {
|
|
1359
|
-
if (0 < note.voiceParams.
|
|
1467
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1360
1468
|
const now = this.audioContext.currentTime;
|
|
1361
|
-
const
|
|
1362
|
-
note.
|
|
1469
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1470
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1471
|
+
note.reverbEffectsSend.gain
|
|
1363
1472
|
.cancelScheduledValues(now)
|
|
1364
1473
|
.setValueAtTime(value, now);
|
|
1365
1474
|
}
|
|
1366
1475
|
else {
|
|
1367
|
-
note.
|
|
1476
|
+
note.reverbEffectsSend.disconnect();
|
|
1368
1477
|
}
|
|
1369
1478
|
}
|
|
1370
1479
|
else {
|
|
1371
|
-
if (0 < note.voiceParams.
|
|
1372
|
-
if (!note.
|
|
1373
|
-
note.
|
|
1374
|
-
gain: note.voiceParams.
|
|
1480
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1481
|
+
if (!note.reverbEffectsSend) {
|
|
1482
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1483
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1375
1484
|
});
|
|
1376
|
-
note.volumeNode.connect(note.
|
|
1485
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1377
1486
|
}
|
|
1378
|
-
note.
|
|
1487
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1379
1488
|
}
|
|
1380
1489
|
}
|
|
1381
1490
|
}
|
|
1382
|
-
|
|
1491
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1383
1492
|
if (0 < prevValue) {
|
|
1384
|
-
if (0 < note.voiceParams.
|
|
1493
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1385
1494
|
const now = this.audioContext.currentTime;
|
|
1386
|
-
const
|
|
1387
|
-
note.
|
|
1495
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1496
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1497
|
+
note.chorusEffectsSend.gain
|
|
1388
1498
|
.cancelScheduledValues(now)
|
|
1389
1499
|
.setValueAtTime(value, now);
|
|
1390
1500
|
}
|
|
1391
1501
|
else {
|
|
1392
|
-
note.
|
|
1502
|
+
note.chorusEffectsSend.disconnect();
|
|
1393
1503
|
}
|
|
1394
1504
|
}
|
|
1395
1505
|
else {
|
|
1396
|
-
if (0 < note.voiceParams.
|
|
1397
|
-
if (!note.
|
|
1398
|
-
note.
|
|
1399
|
-
gain: note.voiceParams.
|
|
1506
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1507
|
+
if (!note.chorusEffectsSend) {
|
|
1508
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1509
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1400
1510
|
});
|
|
1401
|
-
note.volumeNode.connect(note.
|
|
1511
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1402
1512
|
}
|
|
1403
|
-
note.
|
|
1513
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1404
1514
|
}
|
|
1405
1515
|
}
|
|
1406
1516
|
}
|
|
1407
|
-
setVibLfoToPitch(channel, note) {
|
|
1408
|
-
const now = this.audioContext.currentTime;
|
|
1409
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1410
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1411
|
-
2;
|
|
1412
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1413
|
-
note.vibratoDepth.gain
|
|
1414
|
-
.cancelScheduledValues(now)
|
|
1415
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1416
|
-
}
|
|
1417
|
-
setModLfoToFilterFc(note) {
|
|
1418
|
-
const now = this.audioContext.currentTime;
|
|
1419
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1420
|
-
note.filterDepth.gain
|
|
1421
|
-
.cancelScheduledValues(now)
|
|
1422
|
-
.setValueAtTime(modLfoToFilterFc, now);
|
|
1423
|
-
}
|
|
1424
1517
|
setDelayModLFO(note) {
|
|
1425
1518
|
const now = this.audioContext.currentTime;
|
|
1426
1519
|
const startTime = note.startTime;
|
|
@@ -1450,18 +1543,20 @@ class Midy {
|
|
|
1450
1543
|
}
|
|
1451
1544
|
},
|
|
1452
1545
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1453
|
-
if (0 < channel.state.modulationDepth)
|
|
1454
|
-
this.setModLfoToFilterFc(note);
|
|
1546
|
+
if (0 < channel.state.modulationDepth) {
|
|
1547
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1548
|
+
}
|
|
1455
1549
|
},
|
|
1456
1550
|
modLfoToVolume: (channel, note) => {
|
|
1457
|
-
if (0 < channel.state.modulationDepth)
|
|
1458
|
-
this.setModLfoToVolume(note);
|
|
1551
|
+
if (0 < channel.state.modulationDepth) {
|
|
1552
|
+
this.setModLfoToVolume(channel, note);
|
|
1553
|
+
}
|
|
1459
1554
|
},
|
|
1460
|
-
chorusEffectsSend: (
|
|
1461
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1555
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1556
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1462
1557
|
},
|
|
1463
|
-
reverbEffectsSend: (
|
|
1464
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1558
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1559
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1465
1560
|
},
|
|
1466
1561
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1467
1562
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1529,7 +1624,7 @@ class Midy {
|
|
|
1529
1624
|
else {
|
|
1530
1625
|
this.setFilterEnvelope(channel, note);
|
|
1531
1626
|
}
|
|
1532
|
-
this.
|
|
1627
|
+
this.setPitchEnvelope(note);
|
|
1533
1628
|
}
|
|
1534
1629
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1535
1630
|
if (appliedVolumeEnvelope)
|
|
@@ -1611,7 +1706,7 @@ class Midy {
|
|
|
1611
1706
|
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1612
1707
|
}
|
|
1613
1708
|
else {
|
|
1614
|
-
this.
|
|
1709
|
+
this.setPitchEnvelope(note);
|
|
1615
1710
|
this.startModulation(channel, note, now);
|
|
1616
1711
|
}
|
|
1617
1712
|
}
|
|
@@ -1628,10 +1723,27 @@ class Midy {
|
|
|
1628
1723
|
const factor = 5 * Math.log(10) / 127;
|
|
1629
1724
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1630
1725
|
}
|
|
1726
|
+
setKeyBasedVolume(channel) {
|
|
1727
|
+
const now = this.audioContext.currentTime;
|
|
1728
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1729
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1730
|
+
const note = noteList[i];
|
|
1731
|
+
if (!note)
|
|
1732
|
+
continue;
|
|
1733
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1734
|
+
if (keyBasedValue === 0)
|
|
1735
|
+
continue;
|
|
1736
|
+
note.volumeNode.gain
|
|
1737
|
+
.cancelScheduledValues(now)
|
|
1738
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1631
1742
|
setVolume(channelNumber, volume) {
|
|
1632
1743
|
const channel = this.channels[channelNumber];
|
|
1633
1744
|
channel.state.volume = volume / 127;
|
|
1634
1745
|
this.updateChannelVolume(channel);
|
|
1746
|
+
this.setKeyBasedVolume(channel);
|
|
1635
1747
|
}
|
|
1636
1748
|
panToGain(pan) {
|
|
1637
1749
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1640,10 +1752,31 @@ class Midy {
|
|
|
1640
1752
|
gainRight: Math.sin(theta),
|
|
1641
1753
|
};
|
|
1642
1754
|
}
|
|
1755
|
+
setKeyBasedPan(channel) {
|
|
1756
|
+
const now = this.audioContext.currentTime;
|
|
1757
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1758
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1759
|
+
const note = noteList[i];
|
|
1760
|
+
if (!note)
|
|
1761
|
+
continue;
|
|
1762
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1763
|
+
if (keyBasedValue === 0)
|
|
1764
|
+
continue;
|
|
1765
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1766
|
+
note.gainL.gain
|
|
1767
|
+
.cancelScheduledValues(now)
|
|
1768
|
+
.setValueAtTime(gainLeft, now);
|
|
1769
|
+
note.gainR.gain
|
|
1770
|
+
.cancelScheduledValues(now)
|
|
1771
|
+
.setValueAtTime(gainRight, now);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1643
1775
|
setPan(channelNumber, pan) {
|
|
1644
1776
|
const channel = this.channels[channelNumber];
|
|
1645
1777
|
channel.state.pan = pan / 127;
|
|
1646
1778
|
this.updateChannelVolume(channel);
|
|
1779
|
+
this.setKeyBasedPan(channel);
|
|
1647
1780
|
}
|
|
1648
1781
|
setExpression(channelNumber, expression) {
|
|
1649
1782
|
const channel = this.channels[channelNumber];
|
|
@@ -1678,88 +1811,6 @@ class Midy {
|
|
|
1678
1811
|
setPortamento(channelNumber, value) {
|
|
1679
1812
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1680
1813
|
}
|
|
1681
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1682
|
-
const channel = this.channels[channelNumber];
|
|
1683
|
-
const state = channel.state;
|
|
1684
|
-
const reverbEffect = this.reverbEffect;
|
|
1685
|
-
if (0 < state.reverbSendLevel) {
|
|
1686
|
-
if (0 < reverbSendLevel) {
|
|
1687
|
-
const now = this.audioContext.currentTime;
|
|
1688
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1689
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1690
|
-
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1691
|
-
}
|
|
1692
|
-
else {
|
|
1693
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1694
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1695
|
-
const note = noteList[i];
|
|
1696
|
-
if (!note)
|
|
1697
|
-
continue;
|
|
1698
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1699
|
-
continue;
|
|
1700
|
-
note.reverbEffectsSend.disconnect();
|
|
1701
|
-
}
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
else {
|
|
1706
|
-
if (0 < reverbSendLevel) {
|
|
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
|
-
this.setReverbEffectsSend(note, 0);
|
|
1714
|
-
}
|
|
1715
|
-
});
|
|
1716
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1717
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1718
|
-
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1723
|
-
const channel = this.channels[channelNumber];
|
|
1724
|
-
const state = channel.state;
|
|
1725
|
-
const chorusEffect = this.chorusEffect;
|
|
1726
|
-
if (0 < state.chorusSendLevel) {
|
|
1727
|
-
if (0 < chorusSendLevel) {
|
|
1728
|
-
const now = this.audioContext.currentTime;
|
|
1729
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1730
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1731
|
-
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1732
|
-
}
|
|
1733
|
-
else {
|
|
1734
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1735
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1736
|
-
const note = noteList[i];
|
|
1737
|
-
if (!note)
|
|
1738
|
-
continue;
|
|
1739
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1740
|
-
continue;
|
|
1741
|
-
note.chorusEffectsSend.disconnect();
|
|
1742
|
-
}
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
else {
|
|
1747
|
-
if (0 < chorusSendLevel) {
|
|
1748
|
-
const now = this.audioContext.currentTime;
|
|
1749
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1750
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1751
|
-
const note = noteList[i];
|
|
1752
|
-
if (!note)
|
|
1753
|
-
continue;
|
|
1754
|
-
this.setChorusEffectsSend(note, 0);
|
|
1755
|
-
}
|
|
1756
|
-
});
|
|
1757
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1758
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1759
|
-
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
1814
|
setSostenutoPedal(channelNumber, value) {
|
|
1764
1815
|
const channel = this.channels[channelNumber];
|
|
1765
1816
|
channel.state.sostenutoPedal = value / 127;
|
|
@@ -1855,6 +1906,88 @@ class Midy {
|
|
|
1855
1906
|
const channel = this.channels[channelNumber];
|
|
1856
1907
|
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1857
1908
|
}
|
|
1909
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1910
|
+
const channel = this.channels[channelNumber];
|
|
1911
|
+
const state = channel.state;
|
|
1912
|
+
const reverbEffect = this.reverbEffect;
|
|
1913
|
+
if (0 < state.reverbSendLevel) {
|
|
1914
|
+
if (0 < reverbSendLevel) {
|
|
1915
|
+
const now = this.audioContext.currentTime;
|
|
1916
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1917
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1918
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1919
|
+
}
|
|
1920
|
+
else {
|
|
1921
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1922
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1923
|
+
const note = noteList[i];
|
|
1924
|
+
if (!note)
|
|
1925
|
+
continue;
|
|
1926
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1927
|
+
continue;
|
|
1928
|
+
note.reverbEffectsSend.disconnect();
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
else {
|
|
1934
|
+
if (0 < reverbSendLevel) {
|
|
1935
|
+
const now = this.audioContext.currentTime;
|
|
1936
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1937
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1938
|
+
const note = noteList[i];
|
|
1939
|
+
if (!note)
|
|
1940
|
+
continue;
|
|
1941
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1942
|
+
}
|
|
1943
|
+
});
|
|
1944
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1945
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1946
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1951
|
+
const channel = this.channels[channelNumber];
|
|
1952
|
+
const state = channel.state;
|
|
1953
|
+
const chorusEffect = this.chorusEffect;
|
|
1954
|
+
if (0 < state.chorusSendLevel) {
|
|
1955
|
+
if (0 < chorusSendLevel) {
|
|
1956
|
+
const now = this.audioContext.currentTime;
|
|
1957
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1958
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1959
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1960
|
+
}
|
|
1961
|
+
else {
|
|
1962
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1963
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1964
|
+
const note = noteList[i];
|
|
1965
|
+
if (!note)
|
|
1966
|
+
continue;
|
|
1967
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1968
|
+
continue;
|
|
1969
|
+
note.chorusEffectsSend.disconnect();
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
if (0 < chorusSendLevel) {
|
|
1976
|
+
const now = this.audioContext.currentTime;
|
|
1977
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1978
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1979
|
+
const note = noteList[i];
|
|
1980
|
+
if (!note)
|
|
1981
|
+
continue;
|
|
1982
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1986
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1987
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1858
1991
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1859
1992
|
if (maxLSB < channel.dataLSB) {
|
|
1860
1993
|
channel.dataMSB++;
|
|
@@ -1923,59 +2056,49 @@ class Midy {
|
|
|
1923
2056
|
this.channels[channelNumber].dataMSB = value;
|
|
1924
2057
|
this.handleRPN(channelNumber, 0);
|
|
1925
2058
|
}
|
|
1926
|
-
updateDetune(channel, detune) {
|
|
1927
|
-
const now = this.audioContext.currentTime;
|
|
1928
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1929
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1930
|
-
const note = noteList[i];
|
|
1931
|
-
if (!note)
|
|
1932
|
-
continue;
|
|
1933
|
-
const { bufferSource } = note;
|
|
1934
|
-
bufferSource.detune
|
|
1935
|
-
.cancelScheduledValues(now)
|
|
1936
|
-
.setValueAtTime(detune, now);
|
|
1937
|
-
}
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1940
2059
|
handlePitchBendRangeRPN(channelNumber) {
|
|
1941
2060
|
const channel = this.channels[channelNumber];
|
|
1942
2061
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1943
2062
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1944
2063
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1945
2064
|
}
|
|
1946
|
-
setPitchBendRange(channelNumber,
|
|
2065
|
+
setPitchBendRange(channelNumber, value) {
|
|
1947
2066
|
const channel = this.channels[channelNumber];
|
|
1948
2067
|
const state = channel.state;
|
|
1949
|
-
|
|
1950
|
-
const
|
|
1951
|
-
|
|
2068
|
+
const prev = state.pitchWheelSensitivity;
|
|
2069
|
+
const next = value / 128;
|
|
2070
|
+
state.pitchWheelSensitivity = next;
|
|
2071
|
+
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2072
|
+
this.updateDetune(channel);
|
|
1952
2073
|
this.applyVoiceParams(channel, 16);
|
|
1953
2074
|
}
|
|
1954
2075
|
handleFineTuningRPN(channelNumber) {
|
|
1955
2076
|
const channel = this.channels[channelNumber];
|
|
1956
2077
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1957
|
-
const fineTuning =
|
|
2078
|
+
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1958
2079
|
this.setFineTuning(channelNumber, fineTuning);
|
|
1959
2080
|
}
|
|
1960
|
-
setFineTuning(channelNumber,
|
|
2081
|
+
setFineTuning(channelNumber, value) {
|
|
1961
2082
|
const channel = this.channels[channelNumber];
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2083
|
+
const prev = channel.fineTuning;
|
|
2084
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2085
|
+
channel.fineTuning = next;
|
|
2086
|
+
channel.detune += next - prev;
|
|
2087
|
+
this.updateDetune(channel);
|
|
1966
2088
|
}
|
|
1967
2089
|
handleCoarseTuningRPN(channelNumber) {
|
|
1968
2090
|
const channel = this.channels[channelNumber];
|
|
1969
2091
|
this.limitDataMSB(channel, 0, 127);
|
|
1970
|
-
const coarseTuning = channel.dataMSB
|
|
1971
|
-
this.
|
|
2092
|
+
const coarseTuning = channel.dataMSB;
|
|
2093
|
+
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1972
2094
|
}
|
|
1973
|
-
setCoarseTuning(channelNumber,
|
|
2095
|
+
setCoarseTuning(channelNumber, value) {
|
|
1974
2096
|
const channel = this.channels[channelNumber];
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2097
|
+
const prev = channel.coarseTuning;
|
|
2098
|
+
const next = (value - 64) * 100; // cent
|
|
2099
|
+
channel.coarseTuning = next;
|
|
2100
|
+
channel.detune += next - prev;
|
|
2101
|
+
this.updateDetune(channel);
|
|
1979
2102
|
}
|
|
1980
2103
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1981
2104
|
const channel = this.channels[channelNumber];
|
|
@@ -2035,6 +2158,15 @@ class Midy {
|
|
|
2035
2158
|
}
|
|
2036
2159
|
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2037
2160
|
switch (data[2]) {
|
|
2161
|
+
case 8:
|
|
2162
|
+
switch (data[3]) {
|
|
2163
|
+
case 8:
|
|
2164
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2165
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2166
|
+
default:
|
|
2167
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2168
|
+
}
|
|
2169
|
+
break;
|
|
2038
2170
|
case 9:
|
|
2039
2171
|
switch (data[3]) {
|
|
2040
2172
|
case 1:
|
|
@@ -2083,7 +2215,7 @@ class Midy {
|
|
|
2083
2215
|
return this.handleMasterFineTuningSysEx(data);
|
|
2084
2216
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2085
2217
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
2086
|
-
case 5:
|
|
2218
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2087
2219
|
return this.handleGlobalParameterControlSysEx(data);
|
|
2088
2220
|
default:
|
|
2089
2221
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2091,18 +2223,17 @@ class Midy {
|
|
|
2091
2223
|
break;
|
|
2092
2224
|
case 8:
|
|
2093
2225
|
switch (data[3]) {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2226
|
+
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2227
|
+
// TODO: realtime
|
|
2228
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2097
2229
|
default:
|
|
2098
2230
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2099
2231
|
}
|
|
2100
2232
|
break;
|
|
2101
2233
|
case 9:
|
|
2102
2234
|
switch (data[3]) {
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
// return this.setChannelPressure();
|
|
2235
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2236
|
+
return this.handleChannelPressureSysEx(data);
|
|
2106
2237
|
// case 3:
|
|
2107
2238
|
// // TODO
|
|
2108
2239
|
// return this.setControlChange();
|
|
@@ -2112,9 +2243,8 @@ class Midy {
|
|
|
2112
2243
|
break;
|
|
2113
2244
|
case 10:
|
|
2114
2245
|
switch (data[3]) {
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2246
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2247
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2118
2248
|
default:
|
|
2119
2249
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2120
2250
|
}
|
|
@@ -2138,28 +2268,26 @@ class Midy {
|
|
|
2138
2268
|
}
|
|
2139
2269
|
}
|
|
2140
2270
|
handleMasterFineTuningSysEx(data) {
|
|
2141
|
-
const fineTuning =
|
|
2271
|
+
const fineTuning = data[5] * 128 + data[4];
|
|
2142
2272
|
this.setMasterFineTuning(fineTuning);
|
|
2143
2273
|
}
|
|
2144
|
-
setMasterFineTuning(
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
}
|
|
2274
|
+
setMasterFineTuning(value) {
|
|
2275
|
+
const prev = this.masterFineTuning;
|
|
2276
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2277
|
+
this.masterFineTuning = next;
|
|
2278
|
+
channel.detune += next - prev;
|
|
2279
|
+
this.updateDetune(channel);
|
|
2151
2280
|
}
|
|
2152
2281
|
handleMasterCoarseTuningSysEx(data) {
|
|
2153
2282
|
const coarseTuning = data[4];
|
|
2154
2283
|
this.setMasterCoarseTuning(coarseTuning);
|
|
2155
2284
|
}
|
|
2156
|
-
setMasterCoarseTuning(
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
}
|
|
2285
|
+
setMasterCoarseTuning(value) {
|
|
2286
|
+
const prev = this.masterCoarseTuning;
|
|
2287
|
+
const next = (value - 64) * 100; // cent
|
|
2288
|
+
this.masterCoarseTuning = next;
|
|
2289
|
+
channel.detune += next - prev;
|
|
2290
|
+
this.updateDetune(channel);
|
|
2163
2291
|
}
|
|
2164
2292
|
handleGlobalParameterControlSysEx(data) {
|
|
2165
2293
|
if (data[7] === 1) {
|
|
@@ -2347,6 +2475,66 @@ class Midy {
|
|
|
2347
2475
|
getChorusSendToReverb(value) {
|
|
2348
2476
|
return value * 0.00787;
|
|
2349
2477
|
}
|
|
2478
|
+
getChannelBitmap(data) {
|
|
2479
|
+
const bitmap = new Array(16).fill(false);
|
|
2480
|
+
const ff = data[4] & 0b11;
|
|
2481
|
+
const gg = data[5] & 0x7F;
|
|
2482
|
+
const hh = data[6] & 0x7F;
|
|
2483
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2484
|
+
if (hh & (1 << bit))
|
|
2485
|
+
bitmap[bit] = true;
|
|
2486
|
+
}
|
|
2487
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2488
|
+
if (gg & (1 << bit))
|
|
2489
|
+
bitmap[bit + 7] = true;
|
|
2490
|
+
}
|
|
2491
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2492
|
+
if (ff & (1 << bit))
|
|
2493
|
+
bitmap[bit + 14] = true;
|
|
2494
|
+
}
|
|
2495
|
+
return bitmap;
|
|
2496
|
+
}
|
|
2497
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2498
|
+
if (data.length < 18) {
|
|
2499
|
+
console.error("Data length is too short");
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2503
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2504
|
+
if (!channelBitmap[i])
|
|
2505
|
+
continue;
|
|
2506
|
+
for (let j = 0; j < 12; j++) {
|
|
2507
|
+
const value = data[j + 7] - 64; // cent
|
|
2508
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2509
|
+
}
|
|
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
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2522
|
+
const index = keyNumber * 128 + controllerType;
|
|
2523
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2524
|
+
return (controlValue + 64) / 64;
|
|
2525
|
+
}
|
|
2526
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2527
|
+
const channelNumber = data[4];
|
|
2528
|
+
const keyNumber = data[5];
|
|
2529
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2530
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2531
|
+
const controllerType = data[i];
|
|
2532
|
+
const value = data[i + 1];
|
|
2533
|
+
const index = keyNumber * 128 + controllerType;
|
|
2534
|
+
table[index] = value - 64;
|
|
2535
|
+
}
|
|
2536
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2537
|
+
}
|
|
2350
2538
|
handleExclusiveMessage(data) {
|
|
2351
2539
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2352
2540
|
}
|
|
@@ -2379,6 +2567,10 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2379
2567
|
writable: true,
|
|
2380
2568
|
value: {
|
|
2381
2569
|
currentBufferSource: null,
|
|
2570
|
+
detune: 0,
|
|
2571
|
+
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2572
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2573
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2382
2574
|
program: 0,
|
|
2383
2575
|
bank: 121 * 128,
|
|
2384
2576
|
bankMSB: 121,
|