@marmooo/midy 0.3.2 → 0.3.3

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
@@ -8,11 +8,11 @@ class Note {
8
8
  writable: true,
9
9
  value: -1
10
10
  });
11
- Object.defineProperty(this, "noteOffEvent", {
11
+ Object.defineProperty(this, "ending", {
12
12
  enumerable: true,
13
13
  configurable: true,
14
14
  writable: true,
15
- value: void 0
15
+ value: false
16
16
  });
17
17
  Object.defineProperty(this, "bufferSource", {
18
18
  enumerable: true,
@@ -470,17 +470,37 @@ export class Midy {
470
470
  }
471
471
  }
472
472
  }
473
- async loadSoundFont(soundFontUrl) {
474
- const response = await fetch(soundFontUrl);
475
- const arrayBuffer = await response.arrayBuffer();
476
- const parsed = parse(new Uint8Array(arrayBuffer));
473
+ async loadSoundFont(input) {
474
+ let uint8Array;
475
+ if (typeof input === "string") {
476
+ const response = await fetch(input);
477
+ const arrayBuffer = await response.arrayBuffer();
478
+ uint8Array = new Uint8Array(arrayBuffer);
479
+ }
480
+ else if (input instanceof Uint8Array) {
481
+ uint8Array = input;
482
+ }
483
+ else {
484
+ throw new TypeError("input must be a URL string or Uint8Array");
485
+ }
486
+ const parsed = parse(uint8Array);
477
487
  const soundFont = new SoundFont(parsed);
478
488
  this.addSoundFont(soundFont);
479
489
  }
480
- async loadMIDI(midiUrl) {
481
- const response = await fetch(midiUrl);
482
- const arrayBuffer = await response.arrayBuffer();
483
- const midi = parseMidi(new Uint8Array(arrayBuffer));
490
+ async loadMIDI(input) {
491
+ let uint8Array;
492
+ if (typeof input === "string") {
493
+ const response = await fetch(input);
494
+ const arrayBuffer = await response.arrayBuffer();
495
+ uint8Array = new Uint8Array(arrayBuffer);
496
+ }
497
+ else if (input instanceof Uint8Array) {
498
+ uint8Array = input;
499
+ }
500
+ else {
501
+ throw new TypeError("input must be a URL string or Uint8Array");
502
+ }
503
+ const midi = parseMidi(uint8Array);
484
504
  this.ticksPerBeat = midi.header.ticksPerBeat;
485
505
  const midiData = this.extractMidiData(midi);
486
506
  this.instruments = midiData.instruments;
@@ -506,7 +526,7 @@ export class Midy {
506
526
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
507
527
  channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
508
528
  channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
509
- channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
529
+ channel.keyBasedInstrumentControlTable.fill(-1);
510
530
  }
511
531
  createChannels(audioContext) {
512
532
  const channels = Array.from({ length: this.numChannels }, () => {
@@ -523,7 +543,7 @@ export class Midy {
523
543
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
524
544
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
525
545
  polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
526
- keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
546
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
527
547
  };
528
548
  });
529
549
  return channels;
@@ -557,10 +577,18 @@ export class Midy {
557
577
  return audioBuffer;
558
578
  }
559
579
  }
560
- createBufferSource(voiceParams, audioBuffer) {
580
+ isLoopDrum(channel, noteNumber) {
581
+ const programNumber = channel.programNumber;
582
+ return ((programNumber === 48 && noteNumber === 88) ||
583
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
584
+ }
585
+ createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
561
586
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
562
587
  bufferSource.buffer = audioBuffer;
563
588
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
589
+ if (channel.isDrum) {
590
+ bufferSource.loop = this.isLoopDrum(channel, noteNumber);
591
+ }
564
592
  if (bufferSource.loop) {
565
593
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
566
594
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -575,12 +603,13 @@ export class Midy {
575
603
  const delay = this.startDelay - resumeTime;
576
604
  const startTime = event.startTime + delay;
577
605
  switch (event.type) {
578
- case "noteOn": {
579
- const noteOffEvent = {
580
- ...event.noteOffEvent,
581
- startTime: event.noteOffEvent.startTime + delay,
582
- };
583
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
606
+ case "noteOn":
607
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
608
+ break;
609
+ case "noteOff": {
610
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
611
+ if (notePromise)
612
+ this.notePromises.push(notePromise);
584
613
  break;
585
614
  }
586
615
  case "noteAftertouch":
@@ -688,6 +717,7 @@ export class Midy {
688
717
  return `${programNumber}:${noteNumber}:${velocity}`;
689
718
  }
690
719
  extractMidiData(midi) {
720
+ this.audioBufferCounter.clear();
691
721
  const instruments = new Set();
692
722
  const timeline = [];
693
723
  const tmpChannels = new Array(this.channels.length);
@@ -787,38 +817,13 @@ export class Midy {
787
817
  prevTempoTicks = event.ticks;
788
818
  }
789
819
  }
790
- const activeNotes = new Array(this.channels.length * 128);
791
- for (let i = 0; i < activeNotes.length; i++) {
792
- activeNotes[i] = [];
793
- }
794
- for (let i = 0; i < timeline.length; i++) {
795
- const event = timeline[i];
796
- switch (event.type) {
797
- case "noteOn": {
798
- const index = event.channel * 128 + event.noteNumber;
799
- activeNotes[index].push(event);
800
- break;
801
- }
802
- case "noteOff": {
803
- const index = event.channel * 128 + event.noteNumber;
804
- const noteOn = activeNotes[index].pop();
805
- if (noteOn) {
806
- noteOn.noteOffEvent = event;
807
- }
808
- else {
809
- const eventString = JSON.stringify(event, null, 2);
810
- console.warn(`noteOff without matching noteOn: ${eventString}`);
811
- }
812
- }
813
- }
814
- }
815
820
  return { instruments, timeline };
816
821
  }
817
822
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
818
823
  const channel = this.channels[channelNumber];
819
824
  const promises = [];
820
825
  this.processActiveNotes(channel, scheduleTime, (note) => {
821
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
826
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
822
827
  this.notePromises.push(promise);
823
828
  promises.push(promise);
824
829
  });
@@ -828,7 +833,7 @@ export class Midy {
828
833
  const channel = this.channels[channelNumber];
829
834
  const promises = [];
830
835
  this.processScheduledNotes(channel, (note) => {
831
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
836
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
832
837
  this.notePromises.push(promise);
833
838
  promises.push(promise);
834
839
  });
@@ -905,9 +910,6 @@ export class Midy {
905
910
  continue;
906
911
  if (note.ending)
907
912
  continue;
908
- const noteOffEvent = note.noteOffEvent;
909
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
910
- continue;
911
913
  if (scheduleTime < note.startTime)
912
914
  continue;
913
915
  callback(note);
@@ -1220,9 +1222,8 @@ export class Midy {
1220
1222
  }
1221
1223
  setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1222
1224
  const state = channel.state;
1223
- const { voiceParams, noteNumber, startTime } = note;
1224
- const softPedalFactor = 1 -
1225
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1225
+ const { voiceParams, startTime } = note;
1226
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1226
1227
  const baseCent = voiceParams.initialFilterFc +
1227
1228
  this.getFilterCutoffControl(channel, note);
1228
1229
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1242,9 +1243,8 @@ export class Midy {
1242
1243
  }
1243
1244
  setFilterEnvelope(channel, note, scheduleTime) {
1244
1245
  const state = channel.state;
1245
- const { voiceParams, noteNumber, startTime } = note;
1246
- const softPedalFactor = 1 -
1247
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1246
+ const { voiceParams, startTime } = note;
1247
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1248
1248
  const baseCent = voiceParams.initialFilterFc +
1249
1249
  this.getFilterCutoffControl(channel, note);
1250
1250
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1325,7 +1325,7 @@ export class Midy {
1325
1325
  const voiceParams = voice.getAllParams(controllerState);
1326
1326
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1327
1327
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1328
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
1328
+ note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1329
1329
  note.volumeNode = new GainNode(this.audioContext);
1330
1330
  note.gainL = new GainNode(this.audioContext);
1331
1331
  note.gainR = new GainNode(this.audioContext);
@@ -1364,10 +1364,10 @@ export class Midy {
1364
1364
  note.volumeEnvelopeNode.connect(note.volumeNode);
1365
1365
  note.volumeNode.connect(note.gainL);
1366
1366
  note.volumeNode.connect(note.gainR);
1367
- if (0 < channel.chorusSendLevel) {
1367
+ if (0 < state.chorusSendLevel) {
1368
1368
  this.setChorusEffectsSend(channel, note, 0, now);
1369
1369
  }
1370
- if (0 < channel.reverbSendLevel) {
1370
+ if (0 < state.reverbSendLevel) {
1371
1371
  this.setReverbEffectsSend(channel, note, 0, now);
1372
1372
  }
1373
1373
  note.bufferSource.start(startTime);
@@ -1397,7 +1397,7 @@ export class Midy {
1397
1397
  if (prev) {
1398
1398
  const [prevNote, prevChannelNumber] = prev;
1399
1399
  if (prevNote && !prevNote.ending) {
1400
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1400
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1401
1401
  startTime, true);
1402
1402
  }
1403
1403
  }
@@ -1417,18 +1417,11 @@ export class Midy {
1417
1417
  channelNumber;
1418
1418
  const prevNote = this.drumExclusiveClassNotes[index];
1419
1419
  if (prevNote && !prevNote.ending) {
1420
- this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1420
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1421
1421
  startTime, true);
1422
1422
  }
1423
1423
  this.drumExclusiveClassNotes[index] = note;
1424
1424
  }
1425
- isDrumNoteOffException(channel, noteNumber) {
1426
- if (!channel.isDrum)
1427
- return false;
1428
- const programNumber = channel.programNumber;
1429
- return !((programNumber === 48 && noteNumber === 88) ||
1430
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1431
- }
1432
1425
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1433
1426
  const channel = this.channels[channelNumber];
1434
1427
  const bankNumber = this.calcBank(channel, channelNumber);
@@ -1452,31 +1445,6 @@ export class Midy {
1452
1445
  const scheduledNotes = channel.scheduledNotes;
1453
1446
  note.index = scheduledNotes.length;
1454
1447
  scheduledNotes.push(note);
1455
- if (this.isDrumNoteOffException(channel, noteNumber)) {
1456
- const stopTime = startTime + note.bufferSource.buffer.duration;
1457
- const promise = new Promise((resolve) => {
1458
- note.bufferSource.onended = () => {
1459
- scheduledNotes[note.index] = undefined;
1460
- this.disconnectNote(note);
1461
- resolve();
1462
- };
1463
- note.bufferSource.stop(stopTime);
1464
- });
1465
- this.notePromises.push(promise);
1466
- }
1467
- else if (noteOffEvent) {
1468
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1469
- const portamentoTime = this.getPortamentoTime(channel, note);
1470
- const portamentoEndTime = startTime + portamentoTime;
1471
- const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
1472
- Math.max(noteOffEvent.startTime, portamentoEndTime), false);
1473
- this.notePromises.push(notePromise);
1474
- }
1475
- else {
1476
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
1477
- this.notePromises.push(notePromise);
1478
- }
1479
- }
1480
1448
  }
1481
1449
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1482
1450
  scheduleTime ??= this.audioContext.currentTime;
@@ -1505,42 +1473,48 @@ export class Midy {
1505
1473
  note.chorusEffectsSend.disconnect();
1506
1474
  }
1507
1475
  }
1508
- stopNote(channel, note, endTime, stopTime) {
1476
+ releaseNote(channel, note, endTime) {
1477
+ const volRelease = endTime +
1478
+ note.voiceParams.volRelease * channel.state.releaseTime * 2;
1479
+ const modRelease = endTime + note.voiceParams.modRelease;
1480
+ const stopTime = Math.min(volRelease, modRelease);
1481
+ note.filterNode.frequency
1482
+ .cancelScheduledValues(endTime)
1483
+ .linearRampToValueAtTime(0, modRelease);
1509
1484
  note.volumeEnvelopeNode.gain
1510
1485
  .cancelScheduledValues(endTime)
1511
- .linearRampToValueAtTime(0, stopTime);
1512
- note.ending = true;
1513
- this.scheduleTask(() => {
1514
- note.bufferSource.loop = false;
1515
- }, stopTime);
1486
+ .linearRampToValueAtTime(0, volRelease);
1516
1487
  return new Promise((resolve) => {
1517
- note.bufferSource.onended = () => {
1518
- channel.scheduledNotes[note.index] = undefined;
1488
+ this.scheduleTask(() => {
1489
+ const bufferSource = note.bufferSource;
1490
+ bufferSource.loop = false;
1491
+ bufferSource.stop(stopTime);
1519
1492
  this.disconnectNote(note);
1493
+ channel.scheduledNotes[note.index] = undefined;
1520
1494
  resolve();
1521
- };
1522
- note.bufferSource.stop(stopTime);
1495
+ }, stopTime);
1523
1496
  });
1524
1497
  }
1525
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1498
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1526
1499
  const channel = this.channels[channelNumber];
1527
- if (this.isDrumNoteOffException(channel, note.noteNumber))
1528
- return;
1529
1500
  const state = channel.state;
1530
1501
  if (!force) {
1531
- if (0.5 <= state.sustainPedal)
1532
- return;
1533
- if (0.5 <= channel.state.sostenutoPedal)
1534
- return;
1502
+ if (channel.isDrum) {
1503
+ if (!this.isLoopDrum(channel, noteNumber))
1504
+ return;
1505
+ }
1506
+ else {
1507
+ if (0.5 <= state.sustainPedal)
1508
+ return;
1509
+ if (0.5 <= state.sostenutoPedal)
1510
+ return;
1511
+ }
1535
1512
  }
1536
- const volRelease = endTime +
1537
- note.voiceParams.volRelease * channel.state.releaseTime * 2;
1538
- const modRelease = endTime + note.voiceParams.modRelease;
1539
- note.filterNode.frequency
1540
- .cancelScheduledValues(endTime)
1541
- .linearRampToValueAtTime(0, modRelease);
1542
- const stopTime = Math.min(volRelease, modRelease);
1543
- return this.stopNote(channel, note, endTime, stopTime);
1513
+ const note = this.findNoteOffTarget(channel, noteNumber);
1514
+ if (!note)
1515
+ return;
1516
+ note.ending = true;
1517
+ this.releaseNote(channel, note, endTime);
1544
1518
  }
1545
1519
  findNoteOffTarget(channel, noteNumber) {
1546
1520
  const scheduledNotes = channel.scheduledNotes;
@@ -1557,16 +1531,14 @@ export class Midy {
1557
1531
  }
1558
1532
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1559
1533
  scheduleTime ??= this.audioContext.currentTime;
1560
- const channel = this.channels[channelNumber];
1561
- const note = this.findNoteOffTarget(channel, noteNumber);
1562
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1534
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1563
1535
  }
1564
1536
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1565
1537
  const velocity = halfVelocity * 2;
1566
1538
  const channel = this.channels[channelNumber];
1567
1539
  const promises = [];
1568
1540
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1569
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1541
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1570
1542
  promises.push(promise);
1571
1543
  }
1572
1544
  channel.sustainNotes = [];
@@ -1580,7 +1552,7 @@ export class Midy {
1580
1552
  channel.state.sostenutoPedal = 0;
1581
1553
  for (let i = 0; i < sostenutoNotes.length; i++) {
1582
1554
  const note = sostenutoNotes[i];
1583
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
1555
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1584
1556
  promises.push(promise);
1585
1557
  }
1586
1558
  channel.sostenutoNotes = [];
@@ -1703,10 +1675,13 @@ export class Midy {
1703
1675
  .setValueAtTime(volumeDepth, scheduleTime);
1704
1676
  }
1705
1677
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1678
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1679
+ let value = note.voiceParams.reverbEffectsSend;
1680
+ if (0 <= keyBasedValue) {
1681
+ value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1682
+ }
1706
1683
  if (0 < prevValue) {
1707
- if (0 < note.voiceParams.reverbEffectsSend) {
1708
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1709
- const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
1684
+ if (0 < value) {
1710
1685
  note.reverbEffectsSend.gain
1711
1686
  .cancelScheduledValues(scheduleTime)
1712
1687
  .setValueAtTime(value, scheduleTime);
@@ -1716,10 +1691,10 @@ export class Midy {
1716
1691
  }
1717
1692
  }
1718
1693
  else {
1719
- if (0 < note.voiceParams.reverbEffectsSend) {
1694
+ if (0 < value) {
1720
1695
  if (!note.reverbEffectsSend) {
1721
1696
  note.reverbEffectsSend = new GainNode(this.audioContext, {
1722
- gain: note.voiceParams.reverbEffectsSend,
1697
+ gain: value,
1723
1698
  });
1724
1699
  note.volumeNode.connect(note.reverbEffectsSend);
1725
1700
  }
@@ -1728,10 +1703,13 @@ export class Midy {
1728
1703
  }
1729
1704
  }
1730
1705
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1706
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1707
+ let value = note.voiceParams.chorusEffectsSend;
1708
+ if (0 <= keyBasedValue) {
1709
+ value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1710
+ }
1731
1711
  if (0 < prevValue) {
1732
- if (0 < note.voiceParams.chorusEffectsSend) {
1733
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1734
- const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
1712
+ if (0 < vaule) {
1735
1713
  note.chorusEffectsSend.gain
1736
1714
  .cancelScheduledValues(scheduleTime)
1737
1715
  .setValueAtTime(value, scheduleTime);
@@ -1741,10 +1719,10 @@ export class Midy {
1741
1719
  }
1742
1720
  }
1743
1721
  else {
1744
- if (0 < note.voiceParams.chorusEffectsSend) {
1722
+ if (0 < value) {
1745
1723
  if (!note.chorusEffectsSend) {
1746
1724
  note.chorusEffectsSend = new GainNode(this.audioContext, {
1747
- gain: note.voiceParams.chorusEffectsSend,
1725
+ gain: value,
1748
1726
  });
1749
1727
  note.volumeNode.connect(note.chorusEffectsSend);
1750
1728
  }
@@ -1981,10 +1959,10 @@ export class Midy {
1981
1959
  setKeyBasedVolume(channel, scheduleTime) {
1982
1960
  this.processScheduledNotes(channel, (note) => {
1983
1961
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1984
- if (keyBasedValue !== 0) {
1962
+ if (0 <= keyBasedValue) {
1985
1963
  note.volumeNode.gain
1986
1964
  .cancelScheduledValues(scheduleTime)
1987
- .setValueAtTime(1 + keyBasedValue, scheduleTime);
1965
+ .setValueAtTime(keyBasedValue / 127, scheduleTime);
1988
1966
  }
1989
1967
  });
1990
1968
  }
@@ -2005,8 +1983,8 @@ export class Midy {
2005
1983
  setKeyBasedPan(channel, scheduleTime) {
2006
1984
  this.processScheduledNotes(channel, (note) => {
2007
1985
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
2008
- if (keyBasedValue !== 0) {
2009
- const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
1986
+ if (0 <= keyBasedValue) {
1987
+ const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
2010
1988
  note.gainL.gain
2011
1989
  .cancelScheduledValues(scheduleTime)
2012
1990
  .setValueAtTime(gainLeft, scheduleTime);
@@ -2087,6 +2065,9 @@ export class Midy {
2087
2065
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
2088
2066
  }
2089
2067
  }
2068
+ getSoftPedalFactor(channel, note) {
2069
+ return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
2070
+ }
2090
2071
  setSoftPedal(channelNumber, softPedal, scheduleTime) {
2091
2072
  const channel = this.channels[channelNumber];
2092
2073
  if (channel.isDrum)
@@ -2220,7 +2201,8 @@ export class Midy {
2220
2201
  this.processScheduledNotes(channel, (note) => {
2221
2202
  if (note.voiceParams.reverbEffectsSend <= 0)
2222
2203
  return false;
2223
- note.reverbEffectsSend.disconnect();
2204
+ if (note.reverbEffectsSend)
2205
+ note.reverbEffectsSend.disconnect();
2224
2206
  });
2225
2207
  }
2226
2208
  }
@@ -2252,7 +2234,8 @@ export class Midy {
2252
2234
  this.processScheduledNotes(channel, (note) => {
2253
2235
  if (note.voiceParams.chorusEffectsSend <= 0)
2254
2236
  return false;
2255
- note.chorusEffectsSend.disconnect();
2237
+ if (note.chorusEffectsSend)
2238
+ note.chorusEffectsSend.disconnect();
2256
2239
  });
2257
2240
  }
2258
2241
  }
@@ -3005,7 +2988,7 @@ export class Midy {
3005
2988
  getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
3006
2989
  const index = keyNumber * 128 + controllerType;
3007
2990
  const controlValue = channel.keyBasedInstrumentControlTable[index];
3008
- return (controlValue + 64) / 64;
2991
+ return controlValue;
3009
2992
  }
3010
2993
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
3011
2994
  const channelNumber = data[4];
@@ -3018,7 +3001,7 @@ export class Midy {
3018
3001
  const controllerType = data[i];
3019
3002
  const value = data[i + 1];
3020
3003
  const index = keyNumber * 128 + controllerType;
3021
- table[index] = value - 64;
3004
+ table[index] = value;
3022
3005
  }
3023
3006
  this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
3024
3007
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -54,8 +54,8 @@ export class MidyGM1 {
54
54
  channels: any[];
55
55
  initSoundFontTable(): any[];
56
56
  addSoundFont(soundFont: any): void;
57
- loadSoundFont(soundFontUrl: any): Promise<void>;
58
- loadMIDI(midiUrl: any): Promise<void>;
57
+ loadSoundFont(input: any): Promise<void>;
58
+ loadMIDI(input: any): Promise<void>;
59
59
  setChannelAudioNodes(audioContext: any): {
60
60
  gainL: any;
61
61
  gainR: any;
@@ -104,12 +104,12 @@ export class MidyGM1 {
104
104
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, noteOffEvent: any): Promise<void>;
105
105
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
106
106
  disconnectNote(note: any): void;
107
- stopNote(channel: any, note: any, endTime: any, stopTime: any): Promise<any>;
108
- scheduleNoteOff(channelNumber: any, note: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
107
+ releaseNote(channel: any, note: any, endTime: any): Promise<any>;
108
+ scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): void;
109
109
  findNoteOffTarget(channel: any, noteNumber: any): any;
110
- noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
111
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
112
- handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
110
+ noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
111
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
112
+ handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
113
113
  handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
114
114
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
115
115
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -173,6 +173,7 @@ export class MidyGM1 {
173
173
  declare class Note {
174
174
  constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
175
175
  index: number;
176
+ ending: boolean;
176
177
  bufferSource: any;
177
178
  filterNode: any;
178
179
  filterDepth: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqFA;IAwBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAlDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IAgBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,+EA6CC;IAED,mCAOC;IAED,0BA8DC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MAoGC;IAED,kGAgBC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEAWC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAyCC;IAED,0EAiBC;IAED,qHAoDC;IAED,6FASC;IAED,gCASC;IAED,6EAgBC;IAED,mHAgBC;IAED,sDASC;IAED,yGAWC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED,qCAcC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAKC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA9+CD;IAUE,0FAMC;IAfD,cAAW;IACX,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAsFA;IAwBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAlDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IAgBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,yCAcC;IAED,oCAiBC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,+EAkDC;IAED,mCAOC;IAED,0BA8DC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA6EC;IAED,kGAeC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEASC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAyCC;IAED,0EAiBC;IAED,qHAwCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAaC;IAED,sDASC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED,qCAcC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAKC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA79CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}