@marmooo/midy 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/midy.js CHANGED
@@ -86,6 +86,12 @@ class Note {
86
86
  writable: true,
87
87
  value: void 0
88
88
  });
89
+ Object.defineProperty(this, "pressure", {
90
+ enumerable: true,
91
+ configurable: true,
92
+ writable: true,
93
+ value: 0
94
+ });
89
95
  this.noteNumber = noteNumber;
90
96
  this.velocity = velocity;
91
97
  this.startTime = startTime;
@@ -97,7 +103,7 @@ class Note {
97
103
  const defaultControllerState = {
98
104
  noteOnVelocity: { type: 2, defaultValue: 0 },
99
105
  noteOnKeyNumber: { type: 3, defaultValue: 0 },
100
- polyPressure: { type: 10, defaultValue: 0 },
106
+ polyphonicKeyPressure: { type: 10, defaultValue: 0 },
101
107
  channelPressure: { type: 13, defaultValue: 0 },
102
108
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
103
109
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
@@ -359,15 +365,15 @@ export class Midy {
359
365
  });
360
366
  this.audioContext = audioContext;
361
367
  this.options = { ...this.defaultOptions, ...options };
362
- this.masterGain = new GainNode(audioContext);
368
+ this.masterVolume = new GainNode(audioContext);
363
369
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
364
370
  this.controlChangeHandlers = this.createControlChangeHandlers();
365
371
  this.channels = this.createChannels(audioContext);
366
372
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
367
373
  this.chorusEffect = this.createChorusEffect(audioContext);
368
- this.chorusEffect.output.connect(this.masterGain);
369
- this.reverbEffect.output.connect(this.masterGain);
370
- this.masterGain.connect(audioContext.destination);
374
+ this.chorusEffect.output.connect(this.masterVolume);
375
+ this.reverbEffect.output.connect(this.masterVolume);
376
+ this.masterVolume.connect(audioContext.destination);
371
377
  this.GM2SystemOn();
372
378
  }
373
379
  initSoundFontTable() {
@@ -413,7 +419,7 @@ export class Midy {
413
419
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
414
420
  gainL.connect(merger, 0, 0);
415
421
  gainR.connect(merger, 0, 1);
416
- merger.connect(this.masterGain);
422
+ merger.connect(this.masterVolume);
417
423
  return {
418
424
  gainL,
419
425
  gainR,
@@ -425,15 +431,10 @@ export class Midy {
425
431
  return {
426
432
  ...this.constructor.channelSettings,
427
433
  state: new ControllerState(),
434
+ controlTable: this.initControlTable(),
428
435
  ...this.setChannelAudioNodes(audioContext),
429
436
  scheduledNotes: new Map(),
430
437
  sostenutoNotes: new Map(),
431
- polyphonicKeyPressure: {
432
- ...this.constructor.controllerDestinationSettings,
433
- },
434
- channelPressure: {
435
- ...this.constructor.controllerDestinationSettings,
436
- },
437
438
  };
438
439
  });
439
440
  return channels;
@@ -938,28 +939,31 @@ export class Midy {
938
939
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
939
940
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
940
941
  const pitch = pitchWheel * pitchWheelSensitivity;
941
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
942
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
942
943
  const pressure = pressureDepth * channel.state.channelPressure;
943
944
  return tuning + pitch + pressure;
944
945
  }
945
946
  calcNoteDetune(channel, note) {
946
947
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
947
948
  }
948
- updateDetune(channel) {
949
- const now = this.audioContext.currentTime;
949
+ updateChannelDetune(channel) {
950
950
  channel.scheduledNotes.forEach((noteList) => {
951
951
  for (let i = 0; i < noteList.length; i++) {
952
952
  const note = noteList[i];
953
953
  if (!note)
954
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);
955
+ this.updateDetune(channel, note, 0);
960
956
  }
961
957
  });
962
958
  }
959
+ updateDetune(channel, note, pressure) {
960
+ const now = this.audioContext.currentTime;
961
+ const noteDetune = this.calcNoteDetune(channel, note);
962
+ const detune = channel.detune + noteDetune + pressure;
963
+ note.bufferSource.detune
964
+ .cancelScheduledValues(now)
965
+ .setValueAtTime(detune, now);
966
+ }
963
967
  getPortamentoTime(channel) {
964
968
  const factor = 5 * Math.log(10) / 127;
965
969
  const time = channel.state.portamentoTime;
@@ -977,14 +981,12 @@ export class Midy {
977
981
  .setValueAtTime(0, volDelay)
978
982
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
979
983
  }
980
- setVolumeEnvelope(channel, note) {
984
+ setVolumeEnvelope(channel, note, pressure) {
981
985
  const now = this.audioContext.currentTime;
982
986
  const state = channel.state;
983
987
  const { voiceParams, startTime } = note;
984
- const pressureDepth = channel.pressureTable[2] / 64;
985
- const pressure = 1 + pressureDepth * channel.state.channelPressure;
986
988
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
987
- pressure;
989
+ (1 + pressure);
988
990
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
989
991
  const volDelay = startTime + voiceParams.volDelay;
990
992
  const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
@@ -1032,10 +1034,8 @@ export class Midy {
1032
1034
  const { voiceParams, noteNumber, startTime } = note;
1033
1035
  const softPedalFactor = 1 -
1034
1036
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
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 *
1037
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1038
+ softPedalFactor *
1039
1039
  state.brightness * 2;
1040
1040
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
1041
1041
  const sustainFreq = baseFreq +
@@ -1050,15 +1050,17 @@ export class Midy {
1050
1050
  .setValueAtTime(adjustedBaseFreq, modDelay)
1051
1051
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1052
1052
  }
1053
- setFilterEnvelope(channel, note) {
1053
+ setFilterEnvelope(channel, note, pressure) {
1054
1054
  const now = this.audioContext.currentTime;
1055
1055
  const state = channel.state;
1056
1056
  const { voiceParams, noteNumber, startTime } = note;
1057
1057
  const softPedalFactor = 1 -
1058
1058
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1059
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1059
+ const baseCent = voiceParams.initialFilterFc + pressure;
1060
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor *
1061
+ state.brightness * 2;
1062
+ const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
1060
1063
  softPedalFactor * state.brightness * 2;
1061
- const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
1062
1064
  const sustainFreq = baseFreq +
1063
1065
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1064
1066
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
@@ -1085,9 +1087,9 @@ export class Midy {
1085
1087
  gain: voiceParams.modLfoToFilterFc,
1086
1088
  });
1087
1089
  note.modulationDepth = new GainNode(this.audioContext);
1088
- this.setModLfoToPitch(channel, note);
1090
+ this.setModLfoToPitch(channel, note, 0);
1089
1091
  note.volumeDepth = new GainNode(this.audioContext);
1090
- this.setModLfoToVolume(channel, note);
1092
+ this.setModLfoToVolume(note, 0);
1091
1093
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1092
1094
  note.modulationLFO.connect(note.filterDepth);
1093
1095
  note.filterDepth.connect(note.filterNode.frequency);
@@ -1100,8 +1102,7 @@ export class Midy {
1100
1102
  const { voiceParams } = note;
1101
1103
  const state = channel.state;
1102
1104
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
1103
- frequency: this.centToHz(voiceParams.freqVibLFO) *
1104
- state.vibratoRate,
1105
+ frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
1105
1106
  });
1106
1107
  note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1107
1108
  note.vibratoDepth = new GainNode(this.audioContext);
@@ -1130,8 +1131,8 @@ export class Midy {
1130
1131
  }
1131
1132
  else {
1132
1133
  note.portamento = false;
1133
- this.setVolumeEnvelope(channel, note);
1134
- this.setFilterEnvelope(channel, note);
1134
+ this.setVolumeEnvelope(channel, note, 0);
1135
+ this.setFilterEnvelope(channel, note, 0);
1135
1136
  }
1136
1137
  if (0 < state.vibratoDepth) {
1137
1138
  this.startVibrato(channel, note, startTime);
@@ -1346,16 +1347,12 @@ export class Midy {
1346
1347
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
1347
1348
  const now = this.audioContext.currentTime;
1348
1349
  const channel = this.channels[channelNumber];
1349
- pressure /= 64;
1350
+ channel.state.polyphonicKeyPressure = pressure / 127;
1351
+ const table = channel.polyphonicKeyPressureTable;
1350
1352
  const activeNotes = this.getActiveNotes(channel, now);
1351
- if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
1352
- if (activeNotes.has(noteNumber)) {
1353
- const activeNote = activeNotes.get(noteNumber);
1354
- const gain = activeNote.gainL.gain.value;
1355
- activeNote.volumeNode.gain
1356
- .cancelScheduledValues(now)
1357
- .setValueAtTime(gain * pressure, now);
1358
- }
1353
+ if (activeNotes.has(noteNumber)) {
1354
+ const note = activeNotes.get(noteNumber);
1355
+ this.applyDestinationSettings(channel, note, table);
1359
1356
  }
1360
1357
  // this.applyVoiceParams(channel, 10);
1361
1358
  }
@@ -1369,40 +1366,21 @@ export class Midy {
1369
1366
  const prev = channel.state.channelPressure;
1370
1367
  const next = value / 127;
1371
1368
  channel.state.channelPressure = next;
1372
- if (channel.pressureTable[0] !== 64) {
1373
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
1369
+ if (channel.channelPressureTable[0] !== 64) {
1370
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1374
1371
  channel.detune += pressureDepth * (next - prev);
1375
1372
  }
1373
+ const table = channel.channelPressureTable;
1376
1374
  channel.scheduledNotes.forEach((noteList) => {
1377
1375
  for (let i = 0; i < noteList.length; i++) {
1378
1376
  const note = noteList[i];
1379
1377
  if (!note)
1380
1378
  continue;
1381
- this.setChannelPressure(channel, note);
1379
+ this.applyDestinationSettings(channel, note, table);
1382
1380
  }
1383
1381
  });
1384
1382
  // this.applyVoiceParams(channel, 13);
1385
1383
  }
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
- }
1406
1384
  handlePitchBendMessage(channelNumber, lsb, msb) {
1407
1385
  const pitchBend = msb * 128 + lsb;
1408
1386
  this.setPitchBend(channelNumber, pitchBend);
@@ -1414,16 +1392,13 @@ export class Midy {
1414
1392
  const next = (value - 8192) / 8192;
1415
1393
  state.pitchWheel = value / 16383;
1416
1394
  channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1417
- this.updateDetune(channel);
1395
+ this.updateChannelDetune(channel);
1418
1396
  this.applyVoiceParams(channel, 14);
1419
1397
  }
1420
- setModLfoToPitch(channel, note) {
1398
+ setModLfoToPitch(channel, note, pressure) {
1421
1399
  const now = this.audioContext.currentTime;
1422
- const pressureDepth = channel.pressureTable[3] / 127 * 600;
1423
- const pressure = pressureDepth * channel.state.channelPressure;
1424
1400
  const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1425
- const baseDepth = Math.abs(modLfoToPitch) +
1426
- channel.state.modulationDepth;
1401
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1427
1402
  const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1428
1403
  note.modulationDepth.gain
1429
1404
  .cancelScheduledValues(now)
@@ -1439,22 +1414,18 @@ export class Midy {
1439
1414
  .cancelScheduledValues(now)
1440
1415
  .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1441
1416
  }
1442
- setModLfoToFilterFc(channel, note) {
1417
+ setModLfoToFilterFc(note, pressure) {
1443
1418
  const now = this.audioContext.currentTime;
1444
- const pressureDepth = channel.pressureTable[4] / 127 * 2400;
1445
- const pressure = pressureDepth * channel.state.channelPressure;
1446
1419
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1447
1420
  note.filterDepth.gain
1448
1421
  .cancelScheduledValues(now)
1449
1422
  .setValueAtTime(modLfoToFilterFc, now);
1450
1423
  }
1451
- setModLfoToVolume(channel, note) {
1424
+ setModLfoToVolume(note, pressure) {
1452
1425
  const now = this.audioContext.currentTime;
1453
1426
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1454
1427
  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;
1428
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
1458
1429
  note.volumeDepth.gain
1459
1430
  .cancelScheduledValues(now)
1460
1431
  .setValueAtTime(volumeDepth, now);
@@ -1527,11 +1498,18 @@ export class Midy {
1527
1498
  .cancelScheduledValues(now)
1528
1499
  .setValueAtTime(freqModLFO, now);
1529
1500
  }
1501
+ setFreqVibLFO(channel, note) {
1502
+ const now = this.audioContext.currentTime;
1503
+ const freqVibLFO = note.voiceParams.freqVibLFO;
1504
+ note.vibratoLFO.frequency
1505
+ .cancelScheduledValues(now)
1506
+ .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
1507
+ }
1530
1508
  createVoiceParamsHandlers() {
1531
1509
  return {
1532
1510
  modLfoToPitch: (channel, note, _prevValue) => {
1533
1511
  if (0 < channel.state.modulationDepth) {
1534
- this.setModLfoToPitch(channel, note);
1512
+ this.setModLfoToPitch(channel, note, 0);
1535
1513
  }
1536
1514
  },
1537
1515
  vibLfoToPitch: (channel, note, _prevValue) => {
@@ -1541,12 +1519,12 @@ export class Midy {
1541
1519
  },
1542
1520
  modLfoToFilterFc: (channel, note, _prevValue) => {
1543
1521
  if (0 < channel.state.modulationDepth) {
1544
- this.setModLfoToFilterFc(channel, note);
1522
+ this.setModLfoToFilterFc(note, 0);
1545
1523
  }
1546
1524
  },
1547
- modLfoToVolume: (channel, note) => {
1525
+ modLfoToVolume: (channel, note, _prevValue) => {
1548
1526
  if (0 < channel.state.modulationDepth) {
1549
- this.setModLfoToVolume(channel, note);
1527
+ this.setModLfoToVolume(note, 0);
1550
1528
  }
1551
1529
  },
1552
1530
  chorusEffectsSend: (channel, note, prevValue) => {
@@ -1560,22 +1538,19 @@ export class Midy {
1560
1538
  delayVibLFO: (channel, note, prevValue) => {
1561
1539
  if (0 < channel.state.vibratoDepth) {
1562
1540
  const now = this.audioContext.currentTime;
1563
- const prevStartTime = note.startTime +
1564
- prevValue * channel.state.vibratoDelay * 2;
1541
+ const vibratoDelay = channel.state.vibratoDelay * 2;
1542
+ const prevStartTime = note.startTime + prevValue * vibratoDelay;
1565
1543
  if (now < prevStartTime)
1566
1544
  return;
1567
- const startTime = note.startTime +
1568
- value * channel.state.vibratoDelay * 2;
1545
+ const value = note.voiceParams.delayVibLFO;
1546
+ const startTime = note.startTime + value * vibratoDelay;
1569
1547
  note.vibratoLFO.stop(now);
1570
1548
  note.vibratoLFO.start(startTime);
1571
1549
  }
1572
1550
  },
1573
1551
  freqVibLFO: (channel, note, _prevValue) => {
1574
1552
  if (0 < channel.state.vibratoDepth) {
1575
- const now = this.audioContext.currentTime;
1576
- note.vibratoLFO.frequency
1577
- .cancelScheduledValues(now)
1578
- .setValueAtTime(value * sate.vibratoRate, now);
1553
+ this.setFreqVibLFO(channel, note);
1579
1554
  }
1580
1555
  },
1581
1556
  };
@@ -1619,7 +1594,7 @@ export class Midy {
1619
1594
  this.setPortamentoStartFilterEnvelope(channel, note);
1620
1595
  }
1621
1596
  else {
1622
- this.setFilterEnvelope(channel, note);
1597
+ this.setFilterEnvelope(channel, note, 0);
1623
1598
  }
1624
1599
  this.setPitchEnvelope(note);
1625
1600
  }
@@ -1633,7 +1608,7 @@ export class Midy {
1633
1608
  if (key in voiceParams)
1634
1609
  noteVoiceParams[key] = voiceParams[key];
1635
1610
  }
1636
- this.setVolumeEnvelope(channel, note);
1611
+ this.setVolumeEnvelope(channel, note, 0);
1637
1612
  }
1638
1613
  }
1639
1614
  }
@@ -1682,8 +1657,8 @@ export class Midy {
1682
1657
  if (handler) {
1683
1658
  handler.call(this, channelNumber, value);
1684
1659
  const channel = this.channels[channelNumber];
1685
- const controller = 128 + controllerType;
1686
- this.applyVoiceParams(channel, controller);
1660
+ this.applyVoiceParams(channel, controllerType + 128);
1661
+ this.applyControlTable(channel, controllerType);
1687
1662
  }
1688
1663
  else {
1689
1664
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1694,13 +1669,14 @@ export class Midy {
1694
1669
  }
1695
1670
  updateModulation(channel) {
1696
1671
  const now = this.audioContext.currentTime;
1672
+ const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1697
1673
  channel.scheduledNotes.forEach((noteList) => {
1698
1674
  for (let i = 0; i < noteList.length; i++) {
1699
1675
  const note = noteList[i];
1700
1676
  if (!note)
1701
1677
  continue;
1702
1678
  if (note.modulationDepth) {
1703
- note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1679
+ note.modulationDepth.gain.setValueAtTime(depth, now);
1704
1680
  }
1705
1681
  else {
1706
1682
  this.setPitchEnvelope(note);
@@ -1711,8 +1687,7 @@ export class Midy {
1711
1687
  }
1712
1688
  setModulationDepth(channelNumber, modulation) {
1713
1689
  const channel = this.channels[channelNumber];
1714
- channel.state.modulationDepth = (modulation / 127) *
1715
- channel.modulationDepthRange;
1690
+ channel.state.modulationDepth = modulation / 127;
1716
1691
  this.updateModulation(channel);
1717
1692
  }
1718
1693
  setPortamentoTime(channelNumber, portamentoTime) {
@@ -1854,7 +1829,7 @@ export class Midy {
1854
1829
  continue;
1855
1830
  if (note.startTime < now)
1856
1831
  continue;
1857
- this.setVolumeEnvelope(channel, note);
1832
+ this.setVolumeEnvelope(channel, note, 0);
1858
1833
  }
1859
1834
  });
1860
1835
  }
@@ -1866,7 +1841,12 @@ export class Midy {
1866
1841
  const note = noteList[i];
1867
1842
  if (!note)
1868
1843
  continue;
1869
- this.setFilterEnvelope(channel, note);
1844
+ if (note.portamento) {
1845
+ this.setPortamentoStartFilterEnvelope(channel, note);
1846
+ }
1847
+ else {
1848
+ this.setFilterEnvelope(channel, note, 0);
1849
+ }
1870
1850
  }
1871
1851
  });
1872
1852
  }
@@ -1878,7 +1858,7 @@ export class Midy {
1878
1858
  const note = noteList[i];
1879
1859
  if (!note)
1880
1860
  continue;
1881
- this.setVolumeEnvelope(channel, note);
1861
+ this.setVolumeEnvelope(channel, note, 0);
1882
1862
  }
1883
1863
  });
1884
1864
  }
@@ -1887,21 +1867,53 @@ export class Midy {
1887
1867
  channel.state.vibratoRate = vibratoRate / 64;
1888
1868
  if (channel.vibratoDepth <= 0)
1889
1869
  return;
1890
- const now = this.audioContext.currentTime;
1891
- const activeNotes = this.getActiveNotes(channel, now);
1892
- activeNotes.forEach((activeNote) => {
1893
- activeNote.vibratoLFO.frequency
1894
- .cancelScheduledValues(now)
1895
- .setValueAtTime(channel.state.vibratoRate, now);
1870
+ channel.scheduledNotes.forEach((noteList) => {
1871
+ for (let i = 0; i < noteList.length; i++) {
1872
+ const note = noteList[i];
1873
+ if (!note)
1874
+ continue;
1875
+ this.setVibLfoToPitch(channel, note);
1876
+ }
1896
1877
  });
1897
1878
  }
1898
1879
  setVibratoDepth(channelNumber, vibratoDepth) {
1899
1880
  const channel = this.channels[channelNumber];
1881
+ const prev = channel.state.vibratoDepth;
1900
1882
  channel.state.vibratoDepth = vibratoDepth / 64;
1883
+ if (0 < prev) {
1884
+ channel.scheduledNotes.forEach((noteList) => {
1885
+ for (let i = 0; i < noteList.length; i++) {
1886
+ const note = noteList[i];
1887
+ if (!note)
1888
+ continue;
1889
+ this.setFreqVibLFO(channel, note);
1890
+ }
1891
+ });
1892
+ }
1893
+ else {
1894
+ channel.scheduledNotes.forEach((noteList) => {
1895
+ for (let i = 0; i < noteList.length; i++) {
1896
+ const note = noteList[i];
1897
+ if (!note)
1898
+ continue;
1899
+ this.startVibrato(channel, note, note.startTime);
1900
+ }
1901
+ });
1902
+ }
1901
1903
  }
1902
1904
  setVibratoDelay(channelNumber, vibratoDelay) {
1903
1905
  const channel = this.channels[channelNumber];
1904
1906
  channel.state.vibratoDelay = vibratoDelay / 64;
1907
+ if (0 < channel.state.vibratoDepth) {
1908
+ channel.scheduledNotes.forEach((noteList) => {
1909
+ for (let i = 0; i < noteList.length; i++) {
1910
+ const note = noteList[i];
1911
+ if (!note)
1912
+ continue;
1913
+ this.startVibrato(channel, note, note.startTime);
1914
+ }
1915
+ });
1916
+ }
1905
1917
  }
1906
1918
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1907
1919
  const channel = this.channels[channelNumber];
@@ -2066,7 +2078,7 @@ export class Midy {
2066
2078
  const next = value / 128;
2067
2079
  state.pitchWheelSensitivity = next;
2068
2080
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
2069
- this.updateDetune(channel);
2081
+ this.updateChannelDetune(channel);
2070
2082
  this.applyVoiceParams(channel, 16);
2071
2083
  }
2072
2084
  handleFineTuningRPN(channelNumber) {
@@ -2081,7 +2093,7 @@ export class Midy {
2081
2093
  const next = (value - 8192) / 8.192; // cent
2082
2094
  channel.fineTuning = next;
2083
2095
  channel.detune += next - prev;
2084
- this.updateDetune(channel);
2096
+ this.updateChannelDetune(channel);
2085
2097
  }
2086
2098
  handleCoarseTuningRPN(channelNumber) {
2087
2099
  const channel = this.channels[channelNumber];
@@ -2095,7 +2107,7 @@ export class Midy {
2095
2107
  const next = (value - 64) * 100; // cent
2096
2108
  channel.coarseTuning = next;
2097
2109
  channel.detune += next - prev;
2098
- this.updateDetune(channel);
2110
+ this.updateChannelDetune(channel);
2099
2111
  }
2100
2112
  handleModulationDepthRangeRPN(channelNumber) {
2101
2113
  const channel = this.channels[channelNumber];
@@ -2106,7 +2118,6 @@ export class Midy {
2106
2118
  setModulationDepthRange(channelNumber, modulationDepthRange) {
2107
2119
  const channel = this.channels[channelNumber];
2108
2120
  channel.modulationDepthRange = modulationDepthRange;
2109
- channel.modulationDepth = (modulation / 127) * modulationDepthRange;
2110
2121
  this.updateModulation(channel);
2111
2122
  }
2112
2123
  allSoundOff(channelNumber) {
@@ -2127,7 +2138,7 @@ export class Midy {
2127
2138
  const state = channel.state;
2128
2139
  for (let i = 0; i < stateTypes.length; i++) {
2129
2140
  const type = stateTypes[i];
2130
- state[type] = defaultControllerState[type];
2141
+ state[type] = defaultControllerState[type].defaultValue;
2131
2142
  }
2132
2143
  const settingTypes = [
2133
2144
  "rpnMSB",
@@ -2230,10 +2241,11 @@ export class Midy {
2230
2241
  case 9:
2231
2242
  switch (data[3]) {
2232
2243
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2233
- return this.handleChannelPressureSysEx(data);
2234
- // case 3:
2235
- // // TODO
2236
- // return this.setControlChange();
2244
+ return this.handlePressureSysEx(data, "channelPressureTable");
2245
+ case 2: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2246
+ return this.handlePressureSysEx(data, "polyphonicKeyPressureTable");
2247
+ case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2248
+ return this.handleControlChangeSysEx(data);
2237
2249
  default:
2238
2250
  console.warn(`Unsupported Exclusive Message: ${data}`);
2239
2251
  }
@@ -2260,8 +2272,8 @@ export class Midy {
2260
2272
  }
2261
2273
  else {
2262
2274
  const now = this.audioContext.currentTime;
2263
- this.masterGain.gain.cancelScheduledValues(now);
2264
- this.masterGain.gain.setValueAtTime(volume * volume, now);
2275
+ this.masterVolume.gain.cancelScheduledValues(now);
2276
+ this.masterVolume.gain.setValueAtTime(volume * volume, now);
2265
2277
  }
2266
2278
  }
2267
2279
  handleMasterFineTuningSysEx(data) {
@@ -2273,7 +2285,7 @@ export class Midy {
2273
2285
  const next = (value - 8192) / 8.192; // cent
2274
2286
  this.masterFineTuning = next;
2275
2287
  channel.detune += next - prev;
2276
- this.updateDetune(channel);
2288
+ this.updateChannelDetune(channel);
2277
2289
  }
2278
2290
  handleMasterCoarseTuningSysEx(data) {
2279
2291
  const coarseTuning = data[4];
@@ -2284,7 +2296,7 @@ export class Midy {
2284
2296
  const next = (value - 64) * 100; // cent
2285
2297
  this.masterCoarseTuning = next;
2286
2298
  channel.detune += next - prev;
2287
- this.updateDetune(channel);
2299
+ this.updateChannelDetune(channel);
2288
2300
  }
2289
2301
  handleGlobalParameterControlSysEx(data) {
2290
2302
  if (data[7] === 1) {
@@ -2506,15 +2518,105 @@ export class Midy {
2506
2518
  }
2507
2519
  }
2508
2520
  }
2509
- handleChannelPressureSysEx(data) {
2521
+ applyDestinationSettings(channel, note, table) {
2522
+ if (table[0] !== 64) {
2523
+ const polyphonicKeyPressure = (0 < note.pressure)
2524
+ ? channel.polyphonicKeyPressureTable[0] * note.pressure
2525
+ : 0;
2526
+ const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
2527
+ this.updateDetune(channel, note, pressure);
2528
+ }
2529
+ if (!note.portamento) {
2530
+ if (table[1] !== 64) {
2531
+ const channelPressure = channel.channelPressureTable[1] *
2532
+ channel.state.channelPressure;
2533
+ const polyphonicKeyPressure = (0 < note.pressure)
2534
+ ? channel.polyphonicKeyPressureTable[1] * note.pressure
2535
+ : 0;
2536
+ const pressure = (channelPressure + polyphonicKeyPressure - 128) * 15;
2537
+ this.setFilterEnvelope(channel, note, pressure);
2538
+ }
2539
+ if (table[2] !== 64) {
2540
+ const channelPressure = channel.channelPressureTable[2] *
2541
+ channel.state.channelPressure;
2542
+ const polyphonicKeyPressure = (0 < note.pressure)
2543
+ ? channel.polyphonicKeyPressureTable[2] * note.pressure
2544
+ : 0;
2545
+ const pressure = (channelPressure + polyphonicKeyPressure) / 128;
2546
+ this.setVolumeEnvelope(channel, note, pressure);
2547
+ }
2548
+ }
2549
+ if (table[3] !== 0) {
2550
+ const channelPressure = channel.channelPressureTable[3] *
2551
+ channel.state.channelPressure;
2552
+ const polyphonicKeyPressure = (0 < note.pressure)
2553
+ ? channel.polyphonicKeyPressureTable[3] * note.pressure
2554
+ : 0;
2555
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
2556
+ this.setModLfoToPitch(channel, note, pressure);
2557
+ }
2558
+ if (table[4] !== 0) {
2559
+ const channelPressure = channel.channelPressureTable[4] *
2560
+ channel.state.channelPressure;
2561
+ const polyphonicKeyPressure = (0 < note.pressure)
2562
+ ? channel.polyphonicKeyPressureTable[4] * note.pressure
2563
+ : 0;
2564
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2565
+ this.setModLfoToFilterFc(note, pressure);
2566
+ }
2567
+ if (table[5] !== 0) {
2568
+ const channelPressure = channel.channelPressureTable[5] *
2569
+ channel.state.channelPressure;
2570
+ const polyphonicKeyPressure = (0 < note.pressure)
2571
+ ? channel.polyphonicKeyPressureTable[5] * note.pressure
2572
+ : 0;
2573
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254;
2574
+ this.setModLfoToVolume(note, pressure);
2575
+ }
2576
+ }
2577
+ handleChannelPressureSysEx(data, tableName) {
2510
2578
  const channelNumber = data[4];
2511
- const table = this.channels[channelNumber].pressureTable;
2579
+ const table = this.channels[channelNumber][tableName];
2512
2580
  for (let i = 5; i < data.length - 1; i += 2) {
2513
2581
  const pp = data[i];
2514
2582
  const rr = data[i + 1];
2515
2583
  table[pp] = rr;
2516
2584
  }
2517
2585
  }
2586
+ initControlTable() {
2587
+ const channelCount = 128;
2588
+ const slotSize = 6;
2589
+ const defaultValues = [64, 64, 64, 0, 0, 0];
2590
+ const table = new Uint8Array(channelCount * slotSize);
2591
+ for (let ch = 0; ch < channelCount; ch++) {
2592
+ const offset = ch * slotSize;
2593
+ table.set(defaultValues, offset);
2594
+ }
2595
+ return table;
2596
+ }
2597
+ applyControlTable(channel, controllerType) {
2598
+ const slotSize = 6;
2599
+ const offset = controllerType * slotSize;
2600
+ const table = channel.controlTable.subarray(offset, offset + slotSize);
2601
+ channel.scheduledNotes.forEach((noteList) => {
2602
+ for (let i = 0; i < noteList.length; i++) {
2603
+ const note = noteList[i];
2604
+ if (!note)
2605
+ continue;
2606
+ this.applyDestinationSettings(channel, note, table);
2607
+ }
2608
+ });
2609
+ }
2610
+ handleControlChangeSysEx(data) {
2611
+ const channelNumber = data[4];
2612
+ const controllerType = data[5];
2613
+ const table = this.channels[channelNumber].controlTable[controllerType];
2614
+ for (let i = 6; i < data.length - 1; i += 2) {
2615
+ const pp = data[i];
2616
+ const rr = data[i + 1];
2617
+ table[pp] = rr;
2618
+ }
2619
+ }
2518
2620
  getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2519
2621
  const index = keyNumber * 128 + controllerType;
2520
2622
  const controlValue = channel.keyBasedInstrumentControlTable[index];
@@ -2565,7 +2667,8 @@ Object.defineProperty(Midy, "channelSettings", {
2565
2667
  currentBufferSource: null,
2566
2668
  detune: 0,
2567
2669
  scaleOctaveTuningTable: new Array(12).fill(0), // cent
2568
- pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2670
+ channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2671
+ polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2569
2672
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2570
2673
  program: 0,
2571
2674
  bank: 121 * 128,
@@ -2580,16 +2683,3 @@ Object.defineProperty(Midy, "channelSettings", {
2580
2683
  modulationDepthRange: 50, // cent
2581
2684
  }
2582
2685
  });
2583
- Object.defineProperty(Midy, "controllerDestinationSettings", {
2584
- enumerable: true,
2585
- configurable: true,
2586
- writable: true,
2587
- value: {
2588
- pitchControl: 0,
2589
- filterCutoffControl: 0,
2590
- amplitudeControl: 1,
2591
- lfoPitchDepth: 0,
2592
- lfoFilterDepth: 0,
2593
- lfoAmplitudeDepth: 0,
2594
- }
2595
- });