@marmooo/midy 0.2.0 → 0.2.1

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/script/midy.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export class Midy {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ detune: number;
5
+ scaleOctaveTuningTable: any[];
4
6
  program: number;
5
7
  bank: number;
6
8
  bankMSB: number;
@@ -192,12 +194,15 @@ export class Midy {
192
194
  feedbackGains: any[];
193
195
  };
194
196
  cbToRatio(cb: any): number;
197
+ rateToCent(rate: any): number;
198
+ centToRate(cent: any): number;
195
199
  centToHz(cent: any): number;
196
- calcSemitoneOffset(channel: any): any;
200
+ calcChannelDetune(channel: any): any;
201
+ calcNoteDetune(channel: any, note: any): any;
202
+ updateDetune(channel: any): void;
197
203
  setPortamentoStartVolumeEnvelope(channel: any, note: any): void;
198
204
  setVolumeEnvelope(channel: any, note: any): void;
199
- setPlaybackRate(note: any): void;
200
- setPitch(channel: any, note: any): void;
205
+ setPitchEnvelope(note: any): void;
201
206
  clampCutoffFrequency(frequency: any): number;
202
207
  setPortamentoStartFilterEnvelope(channel: any, note: any): void;
203
208
  setFilterEnvelope(channel: any, note: any): void;
@@ -219,11 +224,11 @@ export class Midy {
219
224
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
220
225
  setPitchBend(channelNumber: any, value: any): void;
221
226
  setModLfoToPitch(channel: any, note: any): void;
227
+ setVibLfoToPitch(channel: any, note: any): void;
228
+ setModLfoToFilterFc(note: any): void;
222
229
  setModLfoToVolume(note: any): void;
223
230
  setChorusEffectsSend(note: any, prevValue: any): void;
224
231
  setReverbEffectsSend(note: any, prevValue: any): void;
225
- setVibLfoToPitch(channel: any, note: any): void;
226
- setModLfoToFilterFc(note: any): void;
227
232
  setDelayModLFO(note: any): void;
228
233
  setFreqModLFO(note: any): void;
229
234
  createVoiceParamsHandlers(): {
@@ -293,8 +298,6 @@ export class Midy {
293
298
  updateChannelVolume(channel: any): void;
294
299
  setSustainPedal(channelNumber: any, value: any): void;
295
300
  setPortamento(channelNumber: any, value: any): void;
296
- setReverbSendLevel(channelNumber: any, reverbSendLevel: any): void;
297
- setChorusSendLevel(channelNumber: any, chorusSendLevel: any): void;
298
301
  setSostenutoPedal(channelNumber: any, value: any): void;
299
302
  setSoftPedal(channelNumber: any, softPedal: any): void;
300
303
  setFilterResonance(channelNumber: any, filterResonance: any): void;
@@ -305,6 +308,8 @@ export class Midy {
305
308
  setVibratoRate(channelNumber: any, vibratoRate: any): void;
306
309
  setVibratoDepth(channelNumber: any, vibratoDepth: any): void;
307
310
  setVibratoDelay(channelNumber: any, vibratoDelay: any): void;
311
+ setReverbSendLevel(channelNumber: any, reverbSendLevel: any): void;
312
+ setChorusSendLevel(channelNumber: any, chorusSendLevel: any): void;
308
313
  limitData(channel: any, minMSB: any, maxMSB: any, minLSB: any, maxLSB: any): void;
309
314
  limitDataMSB(channel: any, minMSB: any, maxMSB: any): void;
310
315
  handleRPN(channelNumber: any, value: any): void;
@@ -313,13 +318,12 @@ export class Midy {
313
318
  setRPNMSB(channelNumber: any, value: any): void;
314
319
  setRPNLSB(channelNumber: any, value: any): void;
315
320
  dataEntryMSB(channelNumber: any, value: any): void;
316
- updateDetune(channel: any, detune: any): void;
317
321
  handlePitchBendRangeRPN(channelNumber: any): void;
318
- setPitchBendRange(channelNumber: any, pitchWheelSensitivity: any): void;
322
+ setPitchBendRange(channelNumber: any, value: any): void;
319
323
  handleFineTuningRPN(channelNumber: any): void;
320
- setFineTuning(channelNumber: any, fineTuning: any): void;
324
+ setFineTuning(channelNumber: any, value: any): void;
321
325
  handleCoarseTuningRPN(channelNumber: any): void;
322
- setCoarseTuning(channelNumber: any, coarseTuning: any): void;
326
+ setCoarseTuning(channelNumber: any, value: any): void;
323
327
  handleModulationDepthRangeRPN(channelNumber: any): void;
324
328
  setModulationDepthRange(channelNumber: any, modulationDepthRange: any): void;
325
329
  allSoundOff(channelNumber: any): Promise<void>;
@@ -336,9 +340,11 @@ export class Midy {
336
340
  handleMasterVolumeSysEx(data: any): void;
337
341
  setMasterVolume(volume: any): void;
338
342
  handleMasterFineTuningSysEx(data: any): void;
339
- setMasterFineTuning(fineTuning: any): void;
343
+ setMasterFineTuning(value: any): void;
340
344
  handleMasterCoarseTuningSysEx(data: any): void;
341
- setMasterCoarseTuning(coarseTuning: any): void;
345
+ setMasterCoarseTuning(value: any): void;
346
+ getChannelBitmap(data: any): any[];
347
+ handleScaleOctaveTuning1ByteFormat(data: any): void;
342
348
  handleGlobalParameterControlSysEx(data: any): void;
343
349
  handleReverbParameterSysEx(data: any): void;
344
350
  setReverbType(type: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AA8GA;IAmCE;;;;;;;;;;;;;MAaE;IAEF;;;;;;;MAOE;IAgCF;;;;;OAaC;IArGD,qBAAmB;IACnB,kBAAc;IACd,yBAAqB;IACrB,2BAAuB;IACvB;;;MAGE;IACF;;;;;;MAME;IACF,cAAa;IACb,cAAa;IACb,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,iCAA8B;IA0B9B;;;;;MA4BE;IAGA,kBAAgC;IAChC;;;;;MAAqD;IACrD,gBAA4C;IAC5C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IACjD;;;MAA8D;IAC9D;;;;;;;;MAAyD;IAO3D,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAiBC;IAED,6DA2BC;IAED,iEAUC;IAED,2CAcC;IAED,2EA8DC;IAED,mCAOC;IAED,0BAkDC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAoGC;IAED,+EAoBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,kFAuBC;IAED;;;;MAWC;IAED,gFAUC;IAED,mFAYC;IAED,sGAcC;IAID;;;MA8BC;IAED;;;;;;;;MA0CC;IAED,2BAEC;IAED,4BAEC;IAED,sCAOC;IAED,gEAWC;IAED,iDAiBC;IAED,iCAKC;IAED,wCAmBC;IAED,6CAIC;IAED,gEAsBC;IAED,iDA2BC;IAED,+DAoBC;IAED,4DAcC;IAED,iIAwDC;IAED,gDAQC;IAED,mHA0DC;IAED,2FASC;IAED,qFAkCC;IAED,wJAuCC;IAED,qHAUC;IAED,kEAeC;IAED,oEAYC;IAED,gFAqBC;IAED,sFAeC;IAED,4DAIC;IAED,+DAeC;IAED,qEAGC;IAED,mDAQC;IAED,gDASC;IAED,mCAQC;IAED,sDAsBC;IAED,sDAsBC;IAED,gDASC;IAED,qCAMC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MA+CC;IAED,+EAMC;IAED,0DAiDC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAqCC;IAED,+EAYC;IAED,+CAEC;IAED,qCAiBC;IAED,8DAKC;IAED,iEAIC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,+CAEC;IAED,mDAGC;IAED,wCAWC;IAED,sDAKC;IAED,oDAEC;IAED,mEAmCC;IAED,mEAmCC;IAED,wDAUC;IAED,uDAGC;IAED,mEAaC;IAED,2DAGC;IAED,yDAYC;IAED,yDAUC;IAED,uDAUC;IAED,2DAWC;IAED,6DAGC;IAED,6DAGC;IAED,kFAeC;IAED,2DAMC;IAED,gDAyBC;IAGD,wCAEC;IAGD,wCAEC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,8CAYC;IAED,kDAKC;IAED,wEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,wDAKC;IAED,6EAKC;IAED,+CAEC;IAED,8CAyBC;IAED,+CAEC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBASC;IAED,oBASC;IAED,yDAiDC;IAED,yCAGC;IAED,mCAQC;IAED,6CAGC;IAED,2CAMC;IAED,+CAGC;IAED,+CAMC;IAED,mDAeC;IAED,4CAOC;IAED,+BAKC;IAED,qDAiBC;IAED,gCAIC;IAED,kCAEC;IA6BD,4CAEC;IAED,4CAaC;IAED,+BAiBC;IAED,wFAKC;IAED,mCAKC;IAED,qCAEC;IAED,oCAOC;IAED,sCAEC;IAED,oCAUC;IAED,sCAEC;IAED,wCAuBC;IAED,0CAEC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAz4ED;IAaE,0FAMC;IAlBD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IACb,uBAAkB;IAClB,uBAAkB;IAClB,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
package/script/midy.js CHANGED
@@ -907,16 +907,41 @@ class Midy {
907
907
  cbToRatio(cb) {
908
908
  return Math.pow(10, cb / 200);
909
909
  }
910
+ rateToCent(rate) {
911
+ return 1200 * Math.log2(rate);
912
+ }
913
+ centToRate(cent) {
914
+ return Math.pow(2, cent / 1200);
915
+ }
910
916
  centToHz(cent) {
911
- return 8.176 * Math.pow(2, cent / 1200);
917
+ return 8.176 * this.centToRate(cent);
912
918
  }
913
- calcSemitoneOffset(channel) {
919
+ calcChannelDetune(channel) {
914
920
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
915
921
  const channelTuning = channel.coarseTuning + channel.fineTuning;
922
+ const tuning = masterTuning + channelTuning;
916
923
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
917
- const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
924
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
918
925
  const pitch = pitchWheel * pitchWheelSensitivity;
919
- return masterTuning + channelTuning + pitch;
926
+ return tuning + pitch;
927
+ }
928
+ calcNoteDetune(channel, note) {
929
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
930
+ }
931
+ updateDetune(channel) {
932
+ const now = this.audioContext.currentTime;
933
+ channel.scheduledNotes.forEach((noteList) => {
934
+ for (let i = 0; i < noteList.length; i++) {
935
+ const note = noteList[i];
936
+ if (!note)
937
+ continue;
938
+ const noteDetune = this.calcNoteDetune(channel, note);
939
+ const detune = channel.detune + noteDetune;
940
+ note.bufferSource.detune
941
+ .cancelScheduledValues(now)
942
+ .setValueAtTime(detune, now);
943
+ }
944
+ });
920
945
  }
921
946
  setPortamentoStartVolumeEnvelope(channel, note) {
922
947
  const now = this.audioContext.currentTime;
@@ -948,32 +973,28 @@ class Midy {
948
973
  .setValueAtTime(attackVolume, volHold)
949
974
  .linearRampToValueAtTime(sustainVolume, volDecay);
950
975
  }
951
- setPlaybackRate(note) {
976
+ setPitchEnvelope(note) {
952
977
  const now = this.audioContext.currentTime;
978
+ const { voiceParams } = note;
979
+ const baseRate = voiceParams.playbackRate;
953
980
  note.bufferSource.playbackRate
954
981
  .cancelScheduledValues(now)
955
- .setValueAtTime(note.voiceParams.playbackRate, now);
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;
982
+ .setValueAtTime(baseRate, now);
983
+ const modEnvToPitch = voiceParams.modEnvToPitch;
965
984
  if (modEnvToPitch === 0)
966
985
  return;
986
+ const basePitch = this.rateToCent(baseRate);
967
987
  const peekPitch = basePitch + modEnvToPitch;
988
+ const peekRate = this.centToRate(peekPitch);
968
989
  const modDelay = startTime + voiceParams.modDelay;
969
990
  const modAttack = modDelay + voiceParams.modAttack;
970
991
  const modHold = modAttack + voiceParams.modHold;
971
992
  const modDecay = modHold + voiceParams.modDecay;
972
- note.bufferSource.detune
973
- .setValueAtTime(basePitch, modDelay)
974
- .exponentialRampToValueAtTime(peekPitch, modAttack)
975
- .setValueAtTime(peekPitch, modHold)
976
- .linearRampToValueAtTime(basePitch, modDecay);
993
+ note.bufferSource.playbackRate
994
+ .setValueAtTime(baseRate, modDelay)
995
+ .exponentialRampToValueAtTime(peekRate, modAttack)
996
+ .setValueAtTime(peekRate, modHold)
997
+ .linearRampToValueAtTime(baseRate, modDecay);
977
998
  }
978
999
  clampCutoffFrequency(frequency) {
979
1000
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -1084,9 +1105,8 @@ class Midy {
1084
1105
  if (0 < state.vibratoDepth) {
1085
1106
  this.startVibrato(channel, note, startTime);
1086
1107
  }
1087
- this.setPlaybackRate(note);
1108
+ this.setPitchEnvelope(note);
1088
1109
  if (0 < state.modulationDepth) {
1089
- this.setPitch(channel, note);
1090
1110
  this.startModulation(channel, note, startTime);
1091
1111
  }
1092
1112
  if (this.mono && channel.currentBufferSource) {
@@ -1220,11 +1240,12 @@ class Midy {
1220
1240
  }
1221
1241
  else {
1222
1242
  const portamentoTime = endTime + state.portamentoTime;
1223
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1224
- const detune = note.bufferSource.detune.value + detuneChange;
1225
- note.bufferSource.detune
1243
+ const deltaNote = portamentoNoteNumber - noteNumber;
1244
+ const baseRate = note.voiceParams.playbackRate;
1245
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1246
+ note.bufferSource.playbackRate
1226
1247
  .cancelScheduledValues(endTime)
1227
- .linearRampToValueAtTime(detune, portamentoTime);
1248
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1228
1249
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1229
1250
  }
1230
1251
  }
@@ -1329,10 +1350,11 @@ class Midy {
1329
1350
  setPitchBend(channelNumber, value) {
1330
1351
  const channel = this.channels[channelNumber];
1331
1352
  const state = channel.state;
1353
+ const prev = state.pitchWheel * 2 - 1;
1354
+ const next = (value - 8192) / 8192;
1332
1355
  state.pitchWheel = value / 16383;
1333
- const pitchWheel = (value - 8192) / 8192;
1334
- const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1335
- this.updateDetune(channel, detuneChange);
1356
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1357
+ this.updateDetune(channel);
1336
1358
  this.applyVoiceParams(channel, 14);
1337
1359
  }
1338
1360
  setModLfoToPitch(channel, note) {
@@ -1345,6 +1367,23 @@ class Midy {
1345
1367
  .cancelScheduledValues(now)
1346
1368
  .setValueAtTime(modulationDepth * modulationDepthSign, now);
1347
1369
  }
1370
+ setVibLfoToPitch(channel, note) {
1371
+ const now = this.audioContext.currentTime;
1372
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1373
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1374
+ 2;
1375
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1376
+ note.vibratoDepth.gain
1377
+ .cancelScheduledValues(now)
1378
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1379
+ }
1380
+ setModLfoToFilterFc(note) {
1381
+ const now = this.audioContext.currentTime;
1382
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1383
+ note.filterDepth.gain
1384
+ .cancelScheduledValues(now)
1385
+ .setValueAtTime(modLfoToFilterFc, now);
1386
+ }
1348
1387
  setModLfoToVolume(note) {
1349
1388
  const now = this.audioContext.currentTime;
1350
1389
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
@@ -1404,23 +1443,6 @@ class Midy {
1404
1443
  }
1405
1444
  }
1406
1445
  }
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
1446
  setDelayModLFO(note) {
1425
1447
  const now = this.audioContext.currentTime;
1426
1448
  const startTime = note.startTime;
@@ -1529,7 +1551,7 @@ class Midy {
1529
1551
  else {
1530
1552
  this.setFilterEnvelope(channel, note);
1531
1553
  }
1532
- this.setPitch(channel, note);
1554
+ this.setPitchEnvelope(note);
1533
1555
  }
1534
1556
  else if (volumeEnvelopeKeySet.has(key)) {
1535
1557
  if (appliedVolumeEnvelope)
@@ -1611,7 +1633,7 @@ class Midy {
1611
1633
  note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1612
1634
  }
1613
1635
  else {
1614
- this.setPitch(channel, note);
1636
+ this.setPitchEnvelope(note);
1615
1637
  this.startModulation(channel, note, now);
1616
1638
  }
1617
1639
  }
@@ -1678,88 +1700,6 @@ class Midy {
1678
1700
  setPortamento(channelNumber, value) {
1679
1701
  this.channels[channelNumber].state.portamento = value / 127;
1680
1702
  }
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
1703
  setSostenutoPedal(channelNumber, value) {
1764
1704
  const channel = this.channels[channelNumber];
1765
1705
  channel.state.sostenutoPedal = value / 127;
@@ -1855,6 +1795,88 @@ class Midy {
1855
1795
  const channel = this.channels[channelNumber];
1856
1796
  channel.state.vibratoDelay = vibratoDelay / 64;
1857
1797
  }
1798
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1799
+ const channel = this.channels[channelNumber];
1800
+ const state = channel.state;
1801
+ const reverbEffect = this.reverbEffect;
1802
+ if (0 < state.reverbSendLevel) {
1803
+ if (0 < reverbSendLevel) {
1804
+ const now = this.audioContext.currentTime;
1805
+ state.reverbSendLevel = reverbSendLevel / 127;
1806
+ reverbEffect.input.gain.cancelScheduledValues(now);
1807
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1808
+ }
1809
+ else {
1810
+ channel.scheduledNotes.forEach((noteList) => {
1811
+ for (let i = 0; i < noteList.length; i++) {
1812
+ const note = noteList[i];
1813
+ if (!note)
1814
+ continue;
1815
+ if (note.voiceParams.reverbEffectsSend <= 0)
1816
+ continue;
1817
+ note.reverbEffectsSend.disconnect();
1818
+ }
1819
+ });
1820
+ }
1821
+ }
1822
+ else {
1823
+ if (0 < reverbSendLevel) {
1824
+ const now = this.audioContext.currentTime;
1825
+ channel.scheduledNotes.forEach((noteList) => {
1826
+ for (let i = 0; i < noteList.length; i++) {
1827
+ const note = noteList[i];
1828
+ if (!note)
1829
+ continue;
1830
+ this.setReverbEffectsSend(note, 0);
1831
+ }
1832
+ });
1833
+ state.reverbSendLevel = reverbSendLevel / 127;
1834
+ reverbEffect.input.gain.cancelScheduledValues(now);
1835
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1836
+ }
1837
+ }
1838
+ }
1839
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1840
+ const channel = this.channels[channelNumber];
1841
+ const state = channel.state;
1842
+ const chorusEffect = this.chorusEffect;
1843
+ if (0 < state.chorusSendLevel) {
1844
+ if (0 < chorusSendLevel) {
1845
+ const now = this.audioContext.currentTime;
1846
+ state.chorusSendLevel = chorusSendLevel / 127;
1847
+ chorusEffect.input.gain.cancelScheduledValues(now);
1848
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1849
+ }
1850
+ else {
1851
+ channel.scheduledNotes.forEach((noteList) => {
1852
+ for (let i = 0; i < noteList.length; i++) {
1853
+ const note = noteList[i];
1854
+ if (!note)
1855
+ continue;
1856
+ if (note.voiceParams.chorusEffectsSend <= 0)
1857
+ continue;
1858
+ note.chorusEffectsSend.disconnect();
1859
+ }
1860
+ });
1861
+ }
1862
+ }
1863
+ else {
1864
+ if (0 < chorusSendLevel) {
1865
+ const now = this.audioContext.currentTime;
1866
+ channel.scheduledNotes.forEach((noteList) => {
1867
+ for (let i = 0; i < noteList.length; i++) {
1868
+ const note = noteList[i];
1869
+ if (!note)
1870
+ continue;
1871
+ this.setChorusEffectsSend(note, 0);
1872
+ }
1873
+ });
1874
+ state.chorusSendLevel = chorusSendLevel / 127;
1875
+ chorusEffect.input.gain.cancelScheduledValues(now);
1876
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1877
+ }
1878
+ }
1879
+ }
1858
1880
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1859
1881
  if (maxLSB < channel.dataLSB) {
1860
1882
  channel.dataMSB++;
@@ -1923,59 +1945,49 @@ class Midy {
1923
1945
  this.channels[channelNumber].dataMSB = value;
1924
1946
  this.handleRPN(channelNumber, 0);
1925
1947
  }
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
1948
  handlePitchBendRangeRPN(channelNumber) {
1941
1949
  const channel = this.channels[channelNumber];
1942
1950
  this.limitData(channel, 0, 127, 0, 99);
1943
1951
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1944
1952
  this.setPitchBendRange(channelNumber, pitchBendRange);
1945
1953
  }
1946
- setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1954
+ setPitchBendRange(channelNumber, value) {
1947
1955
  const channel = this.channels[channelNumber];
1948
1956
  const state = channel.state;
1949
- state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1950
- const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1951
- this.updateDetune(channel, detune);
1957
+ const prev = state.pitchWheelSensitivity;
1958
+ const next = value / 128;
1959
+ state.pitchWheelSensitivity = next;
1960
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1961
+ this.updateDetune(channel);
1952
1962
  this.applyVoiceParams(channel, 16);
1953
1963
  }
1954
1964
  handleFineTuningRPN(channelNumber) {
1955
1965
  const channel = this.channels[channelNumber];
1956
1966
  this.limitData(channel, 0, 127, 0, 127);
1957
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
1967
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1958
1968
  this.setFineTuning(channelNumber, fineTuning);
1959
1969
  }
1960
- setFineTuning(channelNumber, fineTuning) {
1970
+ setFineTuning(channelNumber, value) {
1961
1971
  const channel = this.channels[channelNumber];
1962
- const prevFineTuning = channel.fineTuning;
1963
- channel.fineTuning = fineTuning;
1964
- const detuneChange = channel.fineTuning - prevFineTuning;
1965
- this.updateDetune(channel, detuneChange);
1972
+ const prev = channel.fineTuning;
1973
+ const next = (value - 8192) / 8.192; // cent
1974
+ channel.fineTuning = next;
1975
+ channel.detune += next - prev;
1976
+ this.updateDetune(channel);
1966
1977
  }
1967
1978
  handleCoarseTuningRPN(channelNumber) {
1968
1979
  const channel = this.channels[channelNumber];
1969
1980
  this.limitDataMSB(channel, 0, 127);
1970
- const coarseTuning = channel.dataMSB - 64;
1971
- this.setFineTuning(channelNumber, coarseTuning);
1981
+ const coarseTuning = channel.dataMSB;
1982
+ this.setCoarseTuning(channelNumber, coarseTuning);
1972
1983
  }
1973
- setCoarseTuning(channelNumber, coarseTuning) {
1984
+ setCoarseTuning(channelNumber, value) {
1974
1985
  const channel = this.channels[channelNumber];
1975
- const prevCoarseTuning = channel.coarseTuning;
1976
- channel.coarseTuning = coarseTuning;
1977
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1978
- this.updateDetune(channel, detuneChange);
1986
+ const prev = channel.coarseTuning;
1987
+ const next = (value - 64) * 100; // cent
1988
+ channel.coarseTuning = next;
1989
+ channel.detune += next - prev;
1990
+ this.updateDetune(channel);
1979
1991
  }
1980
1992
  handleModulationDepthRangeRPN(channelNumber) {
1981
1993
  const channel = this.channels[channelNumber];
@@ -2035,6 +2047,15 @@ class Midy {
2035
2047
  }
2036
2048
  handleUniversalNonRealTimeExclusiveMessage(data) {
2037
2049
  switch (data[2]) {
2050
+ case 8:
2051
+ switch (data[3]) {
2052
+ case 8:
2053
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2054
+ return this.handleScaleOctaveTuning1ByteFormat(data);
2055
+ default:
2056
+ console.warn(`Unsupported Exclusive Message: ${data}`);
2057
+ }
2058
+ break;
2038
2059
  case 9:
2039
2060
  switch (data[3]) {
2040
2061
  case 1:
@@ -2091,9 +2112,10 @@ class Midy {
2091
2112
  break;
2092
2113
  case 8:
2093
2114
  switch (data[3]) {
2094
- // case 8:
2095
- // // TODO
2096
- // return this.handleScaleOctaveTuning1ByteFormat();
2115
+ case 8:
2116
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2117
+ // TODO: realtime
2118
+ return this.handleScaleOctaveTuning1ByteFormat(data);
2097
2119
  default:
2098
2120
  console.warn(`Unsupported Exclusive Message: ${data}`);
2099
2121
  }
@@ -2138,27 +2160,59 @@ class Midy {
2138
2160
  }
2139
2161
  }
2140
2162
  handleMasterFineTuningSysEx(data) {
2141
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2163
+ const fineTuning = data[5] * 128 + data[4];
2142
2164
  this.setMasterFineTuning(fineTuning);
2143
2165
  }
2144
- setMasterFineTuning(fineTuning) {
2145
- if (fineTuning < -1 && 1 < fineTuning) {
2146
- console.error("Master Fine Tuning value is out of range");
2147
- }
2148
- else {
2149
- this.masterFineTuning = fineTuning;
2150
- }
2166
+ setMasterFineTuning(value) {
2167
+ const prev = this.masterFineTuning;
2168
+ const next = (value - 8192) / 8.192; // cent
2169
+ this.masterFineTuning = next;
2170
+ channel.detune += next - prev;
2171
+ this.updateDetune(channel);
2151
2172
  }
2152
2173
  handleMasterCoarseTuningSysEx(data) {
2153
2174
  const coarseTuning = data[4];
2154
2175
  this.setMasterCoarseTuning(coarseTuning);
2155
2176
  }
2156
- setMasterCoarseTuning(coarseTuning) {
2157
- if (coarseTuning < 0 && 127 < coarseTuning) {
2158
- console.error("Master Coarse Tuning value is out of range");
2177
+ setMasterCoarseTuning(value) {
2178
+ const prev = this.masterCoarseTuning;
2179
+ const next = (value - 64) * 100; // cent
2180
+ this.masterCoarseTuning = next;
2181
+ channel.detune += next - prev;
2182
+ this.updateDetune(channel);
2183
+ }
2184
+ getChannelBitmap(data) {
2185
+ const bitmap = new Array(16).fill(false);
2186
+ const ff = data[4] & 0b11;
2187
+ const gg = data[5] & 0x7F;
2188
+ const hh = data[6] & 0x7F;
2189
+ for (let bit = 0; bit < 7; bit++) {
2190
+ if (hh & (1 << bit))
2191
+ bitmap[bit] = true;
2192
+ }
2193
+ for (let bit = 0; bit < 7; bit++) {
2194
+ if (gg & (1 << bit))
2195
+ bitmap[bit + 7] = true;
2196
+ }
2197
+ for (let bit = 0; bit < 2; bit++) {
2198
+ if (ff & (1 << bit))
2199
+ bitmap[bit + 14] = true;
2200
+ }
2201
+ return bitmap;
2202
+ }
2203
+ handleScaleOctaveTuning1ByteFormat(data) {
2204
+ if (data.length < 18) {
2205
+ console.error("Data length is too short");
2206
+ return;
2159
2207
  }
2160
- else {
2161
- this.masterCoarseTuning = coarseTuning - 64;
2208
+ const channelBitmap = this.getChannelBitmap(data);
2209
+ for (let i = 0; i < channelBitmap.length; i++) {
2210
+ if (!channelBitmap[i])
2211
+ continue;
2212
+ for (let j = 0; j < 12; j++) {
2213
+ const value = data[j + 7] - 64; // cent
2214
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2215
+ }
2162
2216
  }
2163
2217
  }
2164
2218
  handleGlobalParameterControlSysEx(data) {
@@ -2379,6 +2433,8 @@ Object.defineProperty(Midy, "channelSettings", {
2379
2433
  writable: true,
2380
2434
  value: {
2381
2435
  currentBufferSource: null,
2436
+ detune: 0,
2437
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2382
2438
  program: 0,
2383
2439
  bank: 121 * 128,
2384
2440
  bankMSB: 121,