@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/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,
|
|
@@ -904,16 +922,48 @@ export class Midy {
|
|
|
904
922
|
cbToRatio(cb) {
|
|
905
923
|
return Math.pow(10, cb / 200);
|
|
906
924
|
}
|
|
925
|
+
rateToCent(rate) {
|
|
926
|
+
return 1200 * Math.log2(rate);
|
|
927
|
+
}
|
|
928
|
+
centToRate(cent) {
|
|
929
|
+
return Math.pow(2, cent / 1200);
|
|
930
|
+
}
|
|
907
931
|
centToHz(cent) {
|
|
908
|
-
return 8.176 *
|
|
932
|
+
return 8.176 * this.centToRate(cent);
|
|
909
933
|
}
|
|
910
|
-
|
|
934
|
+
calcChannelDetune(channel) {
|
|
911
935
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
912
936
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
937
|
+
const tuning = masterTuning + channelTuning;
|
|
913
938
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
914
|
-
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity *
|
|
939
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
915
940
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
916
|
-
|
|
941
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
942
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
943
|
+
return tuning + pitch + pressure;
|
|
944
|
+
}
|
|
945
|
+
calcNoteDetune(channel, note) {
|
|
946
|
+
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
947
|
+
}
|
|
948
|
+
updateDetune(channel) {
|
|
949
|
+
const now = this.audioContext.currentTime;
|
|
950
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
951
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
952
|
+
const note = noteList[i];
|
|
953
|
+
if (!note)
|
|
954
|
+
continue;
|
|
955
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
956
|
+
const detune = channel.detune + noteDetune;
|
|
957
|
+
note.bufferSource.detune
|
|
958
|
+
.cancelScheduledValues(now)
|
|
959
|
+
.setValueAtTime(detune, now);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
getPortamentoTime(channel) {
|
|
964
|
+
const factor = 5 * Math.log(10) / 127;
|
|
965
|
+
const time = channel.state.portamentoTime;
|
|
966
|
+
return Math.log(time) / factor;
|
|
917
967
|
}
|
|
918
968
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
919
969
|
const now = this.audioContext.currentTime;
|
|
@@ -921,8 +971,8 @@ export class Midy {
|
|
|
921
971
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
922
972
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
923
973
|
const volDelay = startTime + voiceParams.volDelay;
|
|
924
|
-
const portamentoTime = volDelay + channel
|
|
925
|
-
note.
|
|
974
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
975
|
+
note.volumeEnvelopeNode.gain
|
|
926
976
|
.cancelScheduledValues(now)
|
|
927
977
|
.setValueAtTime(0, volDelay)
|
|
928
978
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
@@ -931,13 +981,16 @@ export class Midy {
|
|
|
931
981
|
const now = this.audioContext.currentTime;
|
|
932
982
|
const state = channel.state;
|
|
933
983
|
const { voiceParams, startTime } = note;
|
|
934
|
-
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;
|
|
935
988
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
936
989
|
const volDelay = startTime + voiceParams.volDelay;
|
|
937
990
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
938
991
|
const volHold = volAttack + voiceParams.volHold;
|
|
939
992
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
940
|
-
note.
|
|
993
|
+
note.volumeEnvelopeNode.gain
|
|
941
994
|
.cancelScheduledValues(now)
|
|
942
995
|
.setValueAtTime(0, startTime)
|
|
943
996
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -945,32 +998,28 @@ export class Midy {
|
|
|
945
998
|
.setValueAtTime(attackVolume, volHold)
|
|
946
999
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
947
1000
|
}
|
|
948
|
-
|
|
1001
|
+
setPitchEnvelope(note) {
|
|
949
1002
|
const now = this.audioContext.currentTime;
|
|
1003
|
+
const { voiceParams } = note;
|
|
1004
|
+
const baseRate = voiceParams.playbackRate;
|
|
950
1005
|
note.bufferSource.playbackRate
|
|
951
1006
|
.cancelScheduledValues(now)
|
|
952
|
-
.setValueAtTime(
|
|
953
|
-
|
|
954
|
-
setPitch(channel, note) {
|
|
955
|
-
const now = this.audioContext.currentTime;
|
|
956
|
-
const { startTime } = note;
|
|
957
|
-
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
958
|
-
note.bufferSource.detune
|
|
959
|
-
.cancelScheduledValues(now)
|
|
960
|
-
.setValueAtTime(basePitch, startTime);
|
|
961
|
-
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
1007
|
+
.setValueAtTime(baseRate, now);
|
|
1008
|
+
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
962
1009
|
if (modEnvToPitch === 0)
|
|
963
1010
|
return;
|
|
1011
|
+
const basePitch = this.rateToCent(baseRate);
|
|
964
1012
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1013
|
+
const peekRate = this.centToRate(peekPitch);
|
|
965
1014
|
const modDelay = startTime + voiceParams.modDelay;
|
|
966
1015
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
967
1016
|
const modHold = modAttack + voiceParams.modHold;
|
|
968
1017
|
const modDecay = modHold + voiceParams.modDecay;
|
|
969
|
-
note.bufferSource.
|
|
970
|
-
.setValueAtTime(
|
|
971
|
-
.exponentialRampToValueAtTime(
|
|
972
|
-
.setValueAtTime(
|
|
973
|
-
.linearRampToValueAtTime(
|
|
1018
|
+
note.bufferSource.playbackRate
|
|
1019
|
+
.setValueAtTime(baseRate, modDelay)
|
|
1020
|
+
.exponentialRampToValueAtTime(peekRate, modAttack)
|
|
1021
|
+
.setValueAtTime(peekRate, modHold)
|
|
1022
|
+
.linearRampToValueAtTime(baseRate, modDecay);
|
|
974
1023
|
}
|
|
975
1024
|
clampCutoffFrequency(frequency) {
|
|
976
1025
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -983,14 +1032,17 @@ export class Midy {
|
|
|
983
1032
|
const { voiceParams, noteNumber, startTime } = note;
|
|
984
1033
|
const softPedalFactor = 1 -
|
|
985
1034
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
986
|
-
const
|
|
987
|
-
|
|
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;
|
|
988
1040
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
989
1041
|
const sustainFreq = baseFreq +
|
|
990
1042
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
991
1043
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
992
1044
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
993
|
-
const portamentoTime = startTime + channel
|
|
1045
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
994
1046
|
const modDelay = startTime + voiceParams.modDelay;
|
|
995
1047
|
note.filterNode.frequency
|
|
996
1048
|
.cancelScheduledValues(now)
|
|
@@ -1035,14 +1087,14 @@ export class Midy {
|
|
|
1035
1087
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1036
1088
|
this.setModLfoToPitch(channel, note);
|
|
1037
1089
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1038
|
-
this.setModLfoToVolume(note);
|
|
1090
|
+
this.setModLfoToVolume(channel, note);
|
|
1039
1091
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1040
1092
|
note.modulationLFO.connect(note.filterDepth);
|
|
1041
1093
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1042
1094
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1043
1095
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1044
1096
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1045
|
-
note.volumeDepth.connect(note.
|
|
1097
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1046
1098
|
}
|
|
1047
1099
|
startVibrato(channel, note, startTime) {
|
|
1048
1100
|
const { voiceParams } = note;
|
|
@@ -1064,6 +1116,9 @@ export class Midy {
|
|
|
1064
1116
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1065
1117
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1066
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);
|
|
1067
1122
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1068
1123
|
type: "lowpass",
|
|
1069
1124
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
@@ -1081,9 +1136,8 @@ export class Midy {
|
|
|
1081
1136
|
if (0 < state.vibratoDepth) {
|
|
1082
1137
|
this.startVibrato(channel, note, startTime);
|
|
1083
1138
|
}
|
|
1084
|
-
this.
|
|
1139
|
+
this.setPitchEnvelope(note);
|
|
1085
1140
|
if (0 < state.modulationDepth) {
|
|
1086
|
-
this.setPitch(channel, note);
|
|
1087
1141
|
this.startModulation(channel, note, startTime);
|
|
1088
1142
|
}
|
|
1089
1143
|
if (this.mono && channel.currentBufferSource) {
|
|
@@ -1091,7 +1145,10 @@ export class Midy {
|
|
|
1091
1145
|
channel.currentBufferSource = note.bufferSource;
|
|
1092
1146
|
}
|
|
1093
1147
|
note.bufferSource.connect(note.filterNode);
|
|
1094
|
-
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);
|
|
1095
1152
|
if (0 < channel.chorusSendLevel) {
|
|
1096
1153
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1097
1154
|
}
|
|
@@ -1122,8 +1179,8 @@ export class Midy {
|
|
|
1122
1179
|
if (!voice)
|
|
1123
1180
|
return;
|
|
1124
1181
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1125
|
-
note.
|
|
1126
|
-
note.
|
|
1182
|
+
note.gainL.connect(channel.gainL);
|
|
1183
|
+
note.gainR.connect(channel.gainR);
|
|
1127
1184
|
if (channel.state.sostenutoPedal) {
|
|
1128
1185
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1129
1186
|
}
|
|
@@ -1154,7 +1211,7 @@ export class Midy {
|
|
|
1154
1211
|
}
|
|
1155
1212
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1156
1213
|
const note = scheduledNotes[index];
|
|
1157
|
-
note.
|
|
1214
|
+
note.volumeEnvelopeNode.gain
|
|
1158
1215
|
.cancelScheduledValues(endTime)
|
|
1159
1216
|
.linearRampToValueAtTime(0, stopTime);
|
|
1160
1217
|
note.ending = true;
|
|
@@ -1165,8 +1222,11 @@ export class Midy {
|
|
|
1165
1222
|
note.bufferSource.onended = () => {
|
|
1166
1223
|
scheduledNotes[index] = null;
|
|
1167
1224
|
note.bufferSource.disconnect();
|
|
1168
|
-
note.volumeNode.disconnect();
|
|
1169
1225
|
note.filterNode.disconnect();
|
|
1226
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1227
|
+
note.volumeNode.disconnect();
|
|
1228
|
+
note.gainL.disconnect();
|
|
1229
|
+
note.gainR.disconnect();
|
|
1170
1230
|
if (note.modulationDepth) {
|
|
1171
1231
|
note.volumeDepth.disconnect();
|
|
1172
1232
|
note.modulationDepth.disconnect();
|
|
@@ -1216,12 +1276,13 @@ export class Midy {
|
|
|
1216
1276
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1217
1277
|
}
|
|
1218
1278
|
else {
|
|
1219
|
-
const portamentoTime = endTime +
|
|
1220
|
-
const
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1279
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1280
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1281
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1282
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1283
|
+
note.bufferSource.playbackRate
|
|
1223
1284
|
.cancelScheduledValues(endTime)
|
|
1224
|
-
.linearRampToValueAtTime(
|
|
1285
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1225
1286
|
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1226
1287
|
}
|
|
1227
1288
|
}
|
|
@@ -1290,7 +1351,7 @@ export class Midy {
|
|
|
1290
1351
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1291
1352
|
if (activeNotes.has(noteNumber)) {
|
|
1292
1353
|
const activeNote = activeNotes.get(noteNumber);
|
|
1293
|
-
const gain = activeNote.
|
|
1354
|
+
const gain = activeNote.gainL.gain.value;
|
|
1294
1355
|
activeNote.volumeNode.gain
|
|
1295
1356
|
.cancelScheduledValues(now)
|
|
1296
1357
|
.setValueAtTime(gain * pressure, now);
|
|
@@ -1303,22 +1364,45 @@ export class Midy {
|
|
|
1303
1364
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1304
1365
|
channel.program = program;
|
|
1305
1366
|
}
|
|
1306
|
-
handleChannelPressure(channelNumber,
|
|
1307
|
-
const now = this.audioContext.currentTime;
|
|
1367
|
+
handleChannelPressure(channelNumber, value) {
|
|
1308
1368
|
const channel = this.channels[channelNumber];
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if (channel.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
activeNote.volumeNode.gain
|
|
1316
|
-
.cancelScheduledValues(now)
|
|
1317
|
-
.setValueAtTime(gain * pressure, now);
|
|
1318
|
-
});
|
|
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);
|
|
1319
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
|
+
});
|
|
1320
1384
|
// this.applyVoiceParams(channel, 13);
|
|
1321
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
|
+
}
|
|
1322
1406
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1323
1407
|
const pitchBend = msb * 128 + lsb;
|
|
1324
1408
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1326,98 +1410,107 @@ export class Midy {
|
|
|
1326
1410
|
setPitchBend(channelNumber, value) {
|
|
1327
1411
|
const channel = this.channels[channelNumber];
|
|
1328
1412
|
const state = channel.state;
|
|
1413
|
+
const prev = state.pitchWheel * 2 - 1;
|
|
1414
|
+
const next = (value - 8192) / 8192;
|
|
1329
1415
|
state.pitchWheel = value / 16383;
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
this.updateDetune(channel, detuneChange);
|
|
1416
|
+
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1417
|
+
this.updateDetune(channel);
|
|
1333
1418
|
this.applyVoiceParams(channel, 14);
|
|
1334
1419
|
}
|
|
1335
1420
|
setModLfoToPitch(channel, note) {
|
|
1336
1421
|
const now = this.audioContext.currentTime;
|
|
1337
|
-
const
|
|
1338
|
-
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) +
|
|
1339
1426
|
channel.state.modulationDepth;
|
|
1340
|
-
const
|
|
1427
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1341
1428
|
note.modulationDepth.gain
|
|
1342
1429
|
.cancelScheduledValues(now)
|
|
1343
|
-
.setValueAtTime(modulationDepth
|
|
1430
|
+
.setValueAtTime(modulationDepth, now);
|
|
1344
1431
|
}
|
|
1345
|
-
|
|
1432
|
+
setVibLfoToPitch(channel, note) {
|
|
1433
|
+
const now = this.audioContext.currentTime;
|
|
1434
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1435
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1436
|
+
2;
|
|
1437
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1438
|
+
note.vibratoDepth.gain
|
|
1439
|
+
.cancelScheduledValues(now)
|
|
1440
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1441
|
+
}
|
|
1442
|
+
setModLfoToFilterFc(channel, note) {
|
|
1443
|
+
const now = this.audioContext.currentTime;
|
|
1444
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1445
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1446
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1447
|
+
note.filterDepth.gain
|
|
1448
|
+
.cancelScheduledValues(now)
|
|
1449
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1450
|
+
}
|
|
1451
|
+
setModLfoToVolume(channel, note) {
|
|
1346
1452
|
const now = this.audioContext.currentTime;
|
|
1347
1453
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1348
|
-
const
|
|
1349
|
-
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;
|
|
1350
1458
|
note.volumeDepth.gain
|
|
1351
1459
|
.cancelScheduledValues(now)
|
|
1352
|
-
.setValueAtTime(volumeDepth
|
|
1460
|
+
.setValueAtTime(volumeDepth, now);
|
|
1353
1461
|
}
|
|
1354
|
-
|
|
1462
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1355
1463
|
if (0 < prevValue) {
|
|
1356
|
-
if (0 < note.voiceParams.
|
|
1464
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1357
1465
|
const now = this.audioContext.currentTime;
|
|
1358
|
-
const
|
|
1359
|
-
note.
|
|
1466
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1467
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1468
|
+
note.reverbEffectsSend.gain
|
|
1360
1469
|
.cancelScheduledValues(now)
|
|
1361
1470
|
.setValueAtTime(value, now);
|
|
1362
1471
|
}
|
|
1363
1472
|
else {
|
|
1364
|
-
note.
|
|
1473
|
+
note.reverbEffectsSend.disconnect();
|
|
1365
1474
|
}
|
|
1366
1475
|
}
|
|
1367
1476
|
else {
|
|
1368
|
-
if (0 < note.voiceParams.
|
|
1369
|
-
if (!note.
|
|
1370
|
-
note.
|
|
1371
|
-
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,
|
|
1372
1481
|
});
|
|
1373
|
-
note.volumeNode.connect(note.
|
|
1482
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1374
1483
|
}
|
|
1375
|
-
note.
|
|
1484
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1376
1485
|
}
|
|
1377
1486
|
}
|
|
1378
1487
|
}
|
|
1379
|
-
|
|
1488
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1380
1489
|
if (0 < prevValue) {
|
|
1381
|
-
if (0 < note.voiceParams.
|
|
1490
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1382
1491
|
const now = this.audioContext.currentTime;
|
|
1383
|
-
const
|
|
1384
|
-
note.
|
|
1492
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1493
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1494
|
+
note.chorusEffectsSend.gain
|
|
1385
1495
|
.cancelScheduledValues(now)
|
|
1386
1496
|
.setValueAtTime(value, now);
|
|
1387
1497
|
}
|
|
1388
1498
|
else {
|
|
1389
|
-
note.
|
|
1499
|
+
note.chorusEffectsSend.disconnect();
|
|
1390
1500
|
}
|
|
1391
1501
|
}
|
|
1392
1502
|
else {
|
|
1393
|
-
if (0 < note.voiceParams.
|
|
1394
|
-
if (!note.
|
|
1395
|
-
note.
|
|
1396
|
-
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,
|
|
1397
1507
|
});
|
|
1398
|
-
note.volumeNode.connect(note.
|
|
1508
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1399
1509
|
}
|
|
1400
|
-
note.
|
|
1510
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1401
1511
|
}
|
|
1402
1512
|
}
|
|
1403
1513
|
}
|
|
1404
|
-
setVibLfoToPitch(channel, note) {
|
|
1405
|
-
const now = this.audioContext.currentTime;
|
|
1406
|
-
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1407
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1408
|
-
2;
|
|
1409
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1410
|
-
note.vibratoDepth.gain
|
|
1411
|
-
.cancelScheduledValues(now)
|
|
1412
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1413
|
-
}
|
|
1414
|
-
setModLfoToFilterFc(note) {
|
|
1415
|
-
const now = this.audioContext.currentTime;
|
|
1416
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1417
|
-
note.filterDepth.gain
|
|
1418
|
-
.cancelScheduledValues(now)
|
|
1419
|
-
.setValueAtTime(modLfoToFilterFc, now);
|
|
1420
|
-
}
|
|
1421
1514
|
setDelayModLFO(note) {
|
|
1422
1515
|
const now = this.audioContext.currentTime;
|
|
1423
1516
|
const startTime = note.startTime;
|
|
@@ -1447,18 +1540,20 @@ export class Midy {
|
|
|
1447
1540
|
}
|
|
1448
1541
|
},
|
|
1449
1542
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1450
|
-
if (0 < channel.state.modulationDepth)
|
|
1451
|
-
this.setModLfoToFilterFc(note);
|
|
1543
|
+
if (0 < channel.state.modulationDepth) {
|
|
1544
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1545
|
+
}
|
|
1452
1546
|
},
|
|
1453
1547
|
modLfoToVolume: (channel, note) => {
|
|
1454
|
-
if (0 < channel.state.modulationDepth)
|
|
1455
|
-
this.setModLfoToVolume(note);
|
|
1548
|
+
if (0 < channel.state.modulationDepth) {
|
|
1549
|
+
this.setModLfoToVolume(channel, note);
|
|
1550
|
+
}
|
|
1456
1551
|
},
|
|
1457
|
-
chorusEffectsSend: (
|
|
1458
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1552
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1553
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1459
1554
|
},
|
|
1460
|
-
reverbEffectsSend: (
|
|
1461
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1555
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1556
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1462
1557
|
},
|
|
1463
1558
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1464
1559
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1526,7 +1621,7 @@ export class Midy {
|
|
|
1526
1621
|
else {
|
|
1527
1622
|
this.setFilterEnvelope(channel, note);
|
|
1528
1623
|
}
|
|
1529
|
-
this.
|
|
1624
|
+
this.setPitchEnvelope(note);
|
|
1530
1625
|
}
|
|
1531
1626
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1532
1627
|
if (appliedVolumeEnvelope)
|
|
@@ -1608,7 +1703,7 @@ export class Midy {
|
|
|
1608
1703
|
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1609
1704
|
}
|
|
1610
1705
|
else {
|
|
1611
|
-
this.
|
|
1706
|
+
this.setPitchEnvelope(note);
|
|
1612
1707
|
this.startModulation(channel, note, now);
|
|
1613
1708
|
}
|
|
1614
1709
|
}
|
|
@@ -1625,10 +1720,27 @@ export class Midy {
|
|
|
1625
1720
|
const factor = 5 * Math.log(10) / 127;
|
|
1626
1721
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1627
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
|
+
}
|
|
1628
1739
|
setVolume(channelNumber, volume) {
|
|
1629
1740
|
const channel = this.channels[channelNumber];
|
|
1630
1741
|
channel.state.volume = volume / 127;
|
|
1631
1742
|
this.updateChannelVolume(channel);
|
|
1743
|
+
this.setKeyBasedVolume(channel);
|
|
1632
1744
|
}
|
|
1633
1745
|
panToGain(pan) {
|
|
1634
1746
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1637,10 +1749,31 @@ export class Midy {
|
|
|
1637
1749
|
gainRight: Math.sin(theta),
|
|
1638
1750
|
};
|
|
1639
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
|
+
}
|
|
1640
1772
|
setPan(channelNumber, pan) {
|
|
1641
1773
|
const channel = this.channels[channelNumber];
|
|
1642
1774
|
channel.state.pan = pan / 127;
|
|
1643
1775
|
this.updateChannelVolume(channel);
|
|
1776
|
+
this.setKeyBasedPan(channel);
|
|
1644
1777
|
}
|
|
1645
1778
|
setExpression(channelNumber, expression) {
|
|
1646
1779
|
const channel = this.channels[channelNumber];
|
|
@@ -1675,88 +1808,6 @@ export class Midy {
|
|
|
1675
1808
|
setPortamento(channelNumber, value) {
|
|
1676
1809
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1677
1810
|
}
|
|
1678
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1679
|
-
const channel = this.channels[channelNumber];
|
|
1680
|
-
const state = channel.state;
|
|
1681
|
-
const reverbEffect = this.reverbEffect;
|
|
1682
|
-
if (0 < state.reverbSendLevel) {
|
|
1683
|
-
if (0 < reverbSendLevel) {
|
|
1684
|
-
const now = this.audioContext.currentTime;
|
|
1685
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1686
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1687
|
-
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1691
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1692
|
-
const note = noteList[i];
|
|
1693
|
-
if (!note)
|
|
1694
|
-
continue;
|
|
1695
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1696
|
-
continue;
|
|
1697
|
-
note.reverbEffectsSend.disconnect();
|
|
1698
|
-
}
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
else {
|
|
1703
|
-
if (0 < reverbSendLevel) {
|
|
1704
|
-
const now = this.audioContext.currentTime;
|
|
1705
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1706
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1707
|
-
const note = noteList[i];
|
|
1708
|
-
if (!note)
|
|
1709
|
-
continue;
|
|
1710
|
-
this.setReverbEffectsSend(note, 0);
|
|
1711
|
-
}
|
|
1712
|
-
});
|
|
1713
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1714
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1715
|
-
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1720
|
-
const channel = this.channels[channelNumber];
|
|
1721
|
-
const state = channel.state;
|
|
1722
|
-
const chorusEffect = this.chorusEffect;
|
|
1723
|
-
if (0 < state.chorusSendLevel) {
|
|
1724
|
-
if (0 < chorusSendLevel) {
|
|
1725
|
-
const now = this.audioContext.currentTime;
|
|
1726
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1727
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1728
|
-
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1729
|
-
}
|
|
1730
|
-
else {
|
|
1731
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1732
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1733
|
-
const note = noteList[i];
|
|
1734
|
-
if (!note)
|
|
1735
|
-
continue;
|
|
1736
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1737
|
-
continue;
|
|
1738
|
-
note.chorusEffectsSend.disconnect();
|
|
1739
|
-
}
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
else {
|
|
1744
|
-
if (0 < chorusSendLevel) {
|
|
1745
|
-
const now = this.audioContext.currentTime;
|
|
1746
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1747
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1748
|
-
const note = noteList[i];
|
|
1749
|
-
if (!note)
|
|
1750
|
-
continue;
|
|
1751
|
-
this.setChorusEffectsSend(note, 0);
|
|
1752
|
-
}
|
|
1753
|
-
});
|
|
1754
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1755
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1756
|
-
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
1811
|
setSostenutoPedal(channelNumber, value) {
|
|
1761
1812
|
const channel = this.channels[channelNumber];
|
|
1762
1813
|
channel.state.sostenutoPedal = value / 127;
|
|
@@ -1852,6 +1903,88 @@ export class Midy {
|
|
|
1852
1903
|
const channel = this.channels[channelNumber];
|
|
1853
1904
|
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1854
1905
|
}
|
|
1906
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1907
|
+
const channel = this.channels[channelNumber];
|
|
1908
|
+
const state = channel.state;
|
|
1909
|
+
const reverbEffect = this.reverbEffect;
|
|
1910
|
+
if (0 < state.reverbSendLevel) {
|
|
1911
|
+
if (0 < reverbSendLevel) {
|
|
1912
|
+
const now = this.audioContext.currentTime;
|
|
1913
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1914
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1915
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1919
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1920
|
+
const note = noteList[i];
|
|
1921
|
+
if (!note)
|
|
1922
|
+
continue;
|
|
1923
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1924
|
+
continue;
|
|
1925
|
+
note.reverbEffectsSend.disconnect();
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
else {
|
|
1931
|
+
if (0 < reverbSendLevel) {
|
|
1932
|
+
const now = this.audioContext.currentTime;
|
|
1933
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1934
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1935
|
+
const note = noteList[i];
|
|
1936
|
+
if (!note)
|
|
1937
|
+
continue;
|
|
1938
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1942
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1943
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1948
|
+
const channel = this.channels[channelNumber];
|
|
1949
|
+
const state = channel.state;
|
|
1950
|
+
const chorusEffect = this.chorusEffect;
|
|
1951
|
+
if (0 < state.chorusSendLevel) {
|
|
1952
|
+
if (0 < chorusSendLevel) {
|
|
1953
|
+
const now = this.audioContext.currentTime;
|
|
1954
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1955
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1956
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1957
|
+
}
|
|
1958
|
+
else {
|
|
1959
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1960
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1961
|
+
const note = noteList[i];
|
|
1962
|
+
if (!note)
|
|
1963
|
+
continue;
|
|
1964
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1965
|
+
continue;
|
|
1966
|
+
note.chorusEffectsSend.disconnect();
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
else {
|
|
1972
|
+
if (0 < chorusSendLevel) {
|
|
1973
|
+
const now = this.audioContext.currentTime;
|
|
1974
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1975
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1976
|
+
const note = noteList[i];
|
|
1977
|
+
if (!note)
|
|
1978
|
+
continue;
|
|
1979
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1983
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1984
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1855
1988
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1856
1989
|
if (maxLSB < channel.dataLSB) {
|
|
1857
1990
|
channel.dataMSB++;
|
|
@@ -1920,59 +2053,49 @@ export class Midy {
|
|
|
1920
2053
|
this.channels[channelNumber].dataMSB = value;
|
|
1921
2054
|
this.handleRPN(channelNumber, 0);
|
|
1922
2055
|
}
|
|
1923
|
-
updateDetune(channel, detune) {
|
|
1924
|
-
const now = this.audioContext.currentTime;
|
|
1925
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1926
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1927
|
-
const note = noteList[i];
|
|
1928
|
-
if (!note)
|
|
1929
|
-
continue;
|
|
1930
|
-
const { bufferSource } = note;
|
|
1931
|
-
bufferSource.detune
|
|
1932
|
-
.cancelScheduledValues(now)
|
|
1933
|
-
.setValueAtTime(detune, now);
|
|
1934
|
-
}
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
2056
|
handlePitchBendRangeRPN(channelNumber) {
|
|
1938
2057
|
const channel = this.channels[channelNumber];
|
|
1939
2058
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1940
2059
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1941
2060
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1942
2061
|
}
|
|
1943
|
-
setPitchBendRange(channelNumber,
|
|
2062
|
+
setPitchBendRange(channelNumber, value) {
|
|
1944
2063
|
const channel = this.channels[channelNumber];
|
|
1945
2064
|
const state = channel.state;
|
|
1946
|
-
|
|
1947
|
-
const
|
|
1948
|
-
|
|
2065
|
+
const prev = state.pitchWheelSensitivity;
|
|
2066
|
+
const next = value / 128;
|
|
2067
|
+
state.pitchWheelSensitivity = next;
|
|
2068
|
+
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2069
|
+
this.updateDetune(channel);
|
|
1949
2070
|
this.applyVoiceParams(channel, 16);
|
|
1950
2071
|
}
|
|
1951
2072
|
handleFineTuningRPN(channelNumber) {
|
|
1952
2073
|
const channel = this.channels[channelNumber];
|
|
1953
2074
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1954
|
-
const fineTuning =
|
|
2075
|
+
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1955
2076
|
this.setFineTuning(channelNumber, fineTuning);
|
|
1956
2077
|
}
|
|
1957
|
-
setFineTuning(channelNumber,
|
|
2078
|
+
setFineTuning(channelNumber, value) {
|
|
1958
2079
|
const channel = this.channels[channelNumber];
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2080
|
+
const prev = channel.fineTuning;
|
|
2081
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2082
|
+
channel.fineTuning = next;
|
|
2083
|
+
channel.detune += next - prev;
|
|
2084
|
+
this.updateDetune(channel);
|
|
1963
2085
|
}
|
|
1964
2086
|
handleCoarseTuningRPN(channelNumber) {
|
|
1965
2087
|
const channel = this.channels[channelNumber];
|
|
1966
2088
|
this.limitDataMSB(channel, 0, 127);
|
|
1967
|
-
const coarseTuning = channel.dataMSB
|
|
1968
|
-
this.
|
|
2089
|
+
const coarseTuning = channel.dataMSB;
|
|
2090
|
+
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1969
2091
|
}
|
|
1970
|
-
setCoarseTuning(channelNumber,
|
|
2092
|
+
setCoarseTuning(channelNumber, value) {
|
|
1971
2093
|
const channel = this.channels[channelNumber];
|
|
1972
|
-
const
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2094
|
+
const prev = channel.coarseTuning;
|
|
2095
|
+
const next = (value - 64) * 100; // cent
|
|
2096
|
+
channel.coarseTuning = next;
|
|
2097
|
+
channel.detune += next - prev;
|
|
2098
|
+
this.updateDetune(channel);
|
|
1976
2099
|
}
|
|
1977
2100
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1978
2101
|
const channel = this.channels[channelNumber];
|
|
@@ -2032,6 +2155,15 @@ export class Midy {
|
|
|
2032
2155
|
}
|
|
2033
2156
|
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2034
2157
|
switch (data[2]) {
|
|
2158
|
+
case 8:
|
|
2159
|
+
switch (data[3]) {
|
|
2160
|
+
case 8:
|
|
2161
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2162
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2163
|
+
default:
|
|
2164
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2165
|
+
}
|
|
2166
|
+
break;
|
|
2035
2167
|
case 9:
|
|
2036
2168
|
switch (data[3]) {
|
|
2037
2169
|
case 1:
|
|
@@ -2080,7 +2212,7 @@ export class Midy {
|
|
|
2080
2212
|
return this.handleMasterFineTuningSysEx(data);
|
|
2081
2213
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2082
2214
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
2083
|
-
case 5:
|
|
2215
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2084
2216
|
return this.handleGlobalParameterControlSysEx(data);
|
|
2085
2217
|
default:
|
|
2086
2218
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2088,18 +2220,17 @@ export class Midy {
|
|
|
2088
2220
|
break;
|
|
2089
2221
|
case 8:
|
|
2090
2222
|
switch (data[3]) {
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2223
|
+
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2224
|
+
// TODO: realtime
|
|
2225
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2094
2226
|
default:
|
|
2095
2227
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2096
2228
|
}
|
|
2097
2229
|
break;
|
|
2098
2230
|
case 9:
|
|
2099
2231
|
switch (data[3]) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
// return this.setChannelPressure();
|
|
2232
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2233
|
+
return this.handleChannelPressureSysEx(data);
|
|
2103
2234
|
// case 3:
|
|
2104
2235
|
// // TODO
|
|
2105
2236
|
// return this.setControlChange();
|
|
@@ -2109,9 +2240,8 @@ export class Midy {
|
|
|
2109
2240
|
break;
|
|
2110
2241
|
case 10:
|
|
2111
2242
|
switch (data[3]) {
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2243
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2244
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2115
2245
|
default:
|
|
2116
2246
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2117
2247
|
}
|
|
@@ -2135,28 +2265,26 @@ export class Midy {
|
|
|
2135
2265
|
}
|
|
2136
2266
|
}
|
|
2137
2267
|
handleMasterFineTuningSysEx(data) {
|
|
2138
|
-
const fineTuning =
|
|
2268
|
+
const fineTuning = data[5] * 128 + data[4];
|
|
2139
2269
|
this.setMasterFineTuning(fineTuning);
|
|
2140
2270
|
}
|
|
2141
|
-
setMasterFineTuning(
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2271
|
+
setMasterFineTuning(value) {
|
|
2272
|
+
const prev = this.masterFineTuning;
|
|
2273
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2274
|
+
this.masterFineTuning = next;
|
|
2275
|
+
channel.detune += next - prev;
|
|
2276
|
+
this.updateDetune(channel);
|
|
2148
2277
|
}
|
|
2149
2278
|
handleMasterCoarseTuningSysEx(data) {
|
|
2150
2279
|
const coarseTuning = data[4];
|
|
2151
2280
|
this.setMasterCoarseTuning(coarseTuning);
|
|
2152
2281
|
}
|
|
2153
|
-
setMasterCoarseTuning(
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
}
|
|
2282
|
+
setMasterCoarseTuning(value) {
|
|
2283
|
+
const prev = this.masterCoarseTuning;
|
|
2284
|
+
const next = (value - 64) * 100; // cent
|
|
2285
|
+
this.masterCoarseTuning = next;
|
|
2286
|
+
channel.detune += next - prev;
|
|
2287
|
+
this.updateDetune(channel);
|
|
2160
2288
|
}
|
|
2161
2289
|
handleGlobalParameterControlSysEx(data) {
|
|
2162
2290
|
if (data[7] === 1) {
|
|
@@ -2344,6 +2472,66 @@ export class Midy {
|
|
|
2344
2472
|
getChorusSendToReverb(value) {
|
|
2345
2473
|
return value * 0.00787;
|
|
2346
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
|
+
}
|
|
2347
2535
|
handleExclusiveMessage(data) {
|
|
2348
2536
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2349
2537
|
}
|
|
@@ -2375,6 +2563,10 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2375
2563
|
writable: true,
|
|
2376
2564
|
value: {
|
|
2377
2565
|
currentBufferSource: null,
|
|
2566
|
+
detune: 0,
|
|
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]
|
|
2378
2570
|
program: 0,
|
|
2379
2571
|
bank: 121 * 128,
|
|
2380
2572
|
bankMSB: 121,
|