@marmooo/midy 0.0.2 → 0.0.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
@@ -8,12 +8,6 @@ export class Midy {
8
8
  writable: true,
9
9
  value: 120
10
10
  });
11
- Object.defineProperty(this, "secondsPerBeat", {
12
- enumerable: true,
13
- configurable: true,
14
- writable: true,
15
- value: 0.5
16
- });
17
11
  Object.defineProperty(this, "totalTime", {
18
12
  enumerable: true,
19
13
  configurable: true,
@@ -174,10 +168,10 @@ export class Midy {
174
168
  const response = await fetch(midiUrl);
175
169
  const arrayBuffer = await response.arrayBuffer();
176
170
  const midi = parseMidi(new Uint8Array(arrayBuffer));
171
+ this.ticksPerBeat = midi.header.ticksPerBeat;
177
172
  const midiData = this.extractMidiData(midi);
178
173
  this.instruments = midiData.instruments;
179
174
  this.timeline = midiData.timeline;
180
- this.ticksPerBeat = midi.header.ticksPerBeat;
181
175
  this.totalTime = this.calcTotalTime();
182
176
  }
183
177
  setChannelAudioNodes(audioContext) {
@@ -212,6 +206,12 @@ export class Midy {
212
206
  ...this.setChannelAudioNodes(audioContext),
213
207
  scheduledNotes: new Map(),
214
208
  sostenutoNotes: new Map(),
209
+ polyphonicKeyPressure: {
210
+ ...Midy.controllerDestinationSettings,
211
+ },
212
+ channelPressure: {
213
+ ...Midy.controllerDestinationSettings,
214
+ },
215
215
  };
216
216
  });
217
217
  return channels;
@@ -263,28 +263,36 @@ export class Midy {
263
263
  async scheduleTimelineEvents(t, offset, queueIndex) {
264
264
  while (queueIndex < this.timeline.length) {
265
265
  const event = this.timeline[queueIndex];
266
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
267
- if (time > t + this.lookAhead)
266
+ if (event.startTime > t + this.lookAhead)
268
267
  break;
269
268
  switch (event.type) {
270
- case "controller":
271
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
272
- break;
273
269
  case "noteOn":
274
- await this.scheduleNoteOn(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
275
- break;
270
+ if (event.velocity !== 0) {
271
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
272
+ break;
273
+ }
274
+ /* falls through */
276
275
  case "noteOff": {
277
- const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
276
+ const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
278
277
  if (notePromise) {
279
278
  this.notePromises.push(notePromise);
280
279
  }
281
280
  break;
282
281
  }
282
+ case "noteAftertouch":
283
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
284
+ break;
285
+ case "controller":
286
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
287
+ break;
283
288
  case "programChange":
284
289
  this.handleProgramChange(event.channel, event.programNumber);
285
290
  break;
286
- case "setTempo":
287
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
291
+ case "channelAftertouch":
292
+ this.handleChannelPressure(event.channel, event.amount);
293
+ break;
294
+ case "pitchBend":
295
+ this.handlePitchBend(event.channel, event.value);
288
296
  break;
289
297
  case "sysEx":
290
298
  this.handleSysEx(event.data);
@@ -294,9 +302,8 @@ export class Midy {
294
302
  return queueIndex;
295
303
  }
296
304
  getQueueIndex(second) {
297
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
298
305
  for (let i = 0; i < this.timeline.length; i++) {
299
- if (ticks <= this.timeline[i].ticks) {
306
+ if (second <= this.timeline[i].startTime) {
300
307
  return i;
301
308
  }
302
309
  }
@@ -438,18 +445,28 @@ export class Midy {
438
445
  timeline.push(event);
439
446
  });
440
447
  });
448
+ const priority = {
449
+ setTempo: 0,
450
+ controller: 1,
451
+ };
441
452
  timeline.sort((a, b) => {
442
- if (a.ticks !== b.ticks) {
453
+ if (a.ticks !== b.ticks)
443
454
  return a.ticks - b.ticks;
444
- }
445
- if (a.type !== "controller" && b.type === "controller") {
446
- return -1;
447
- }
448
- if (a.type === "controller" && b.type !== "controller") {
449
- return 1;
450
- }
451
- return 0;
455
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
452
456
  });
457
+ let prevTempoTime = 0;
458
+ let prevTempoTicks = 0;
459
+ let secondsPerBeat = 0.5;
460
+ for (let i = 0; i < timeline.length; i++) {
461
+ const event = timeline[i];
462
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
463
+ event.startTime = prevTempoTime + timeFromPrevTempo;
464
+ if (event.type === "setTempo") {
465
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
466
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
467
+ prevTempoTicks = event.ticks;
468
+ }
469
+ }
453
470
  return { instruments, timeline };
454
471
  }
455
472
  stopNotes() {
@@ -501,32 +518,12 @@ export class Midy {
501
518
  }
502
519
  }
503
520
  calcTotalTime() {
504
- const endOfTracks = [];
505
- let prevTicks = 0;
506
521
  let totalTime = 0;
507
- let secondsPerBeat = 0.5;
508
522
  for (let i = 0; i < this.timeline.length; i++) {
509
523
  const event = this.timeline[i];
510
- switch (event.type) {
511
- case "setTempo": {
512
- const durationTicks = event.ticks - prevTicks;
513
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
514
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
515
- prevTicks = event.ticks;
516
- break;
517
- }
518
- case "endOfTrack":
519
- endOfTracks.push(event);
520
- }
524
+ if (totalTime < event.startTime)
525
+ totalTime = event.startTime;
521
526
  }
522
- let maxTicks = 0;
523
- for (let i = 0; i < endOfTracks.length; i++) {
524
- const event = endOfTracks[i];
525
- if (maxTicks < event.ticks)
526
- maxTicks = event.ticks;
527
- }
528
- const durationTicks = maxTicks - prevTicks;
529
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
530
527
  return totalTime;
531
528
  }
532
529
  currentTime() {
@@ -554,11 +551,8 @@ export class Midy {
554
551
  const lfo = new OscillatorNode(audioContext, {
555
552
  frequency: 5,
556
553
  });
557
- const lfoGain = new GainNode(audioContext);
558
- lfo.connect(lfoGain);
559
554
  return {
560
555
  lfo,
561
- lfoGain,
562
556
  };
563
557
  }
564
558
  createReverbEffect(audioContext, options = {}) {
@@ -663,15 +657,19 @@ export class Midy {
663
657
  centToHz(cent) {
664
658
  return 8.176 * Math.pow(2, cent / 1200);
665
659
  }
666
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
660
+ calcSemitoneOffset(channel) {
667
661
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
668
662
  const channelTuning = channel.coarseTuning + channel.fineTuning;
669
663
  const tuning = masterTuning + channelTuning;
670
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
671
- const playbackRate = noteInfo.playbackRate(noteNumber) *
672
- Math.pow(2, semitoneOffset / 12);
664
+ return channel.pitchBend * channel.pitchBendRange + tuning;
665
+ }
666
+ calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
667
+ return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
668
+ }
669
+ async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
673
670
  const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
674
- bufferSource.playbackRate.value = playbackRate;
671
+ const semitoneOffset = this.calcSemitoneOffset(channel);
672
+ bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
675
673
  // volume envelope
676
674
  const gainNode = new GainNode(this.audioContext, {
677
675
  gain: 0,
@@ -690,12 +688,6 @@ export class Midy {
690
688
  .exponentialRampToValueAtTime(attackVolume, volAttack)
691
689
  .setValueAtTime(attackVolume, volHold)
692
690
  .linearRampToValueAtTime(sustainVolume, volDecay);
693
- if (channel.modulation > 0) {
694
- const lfoGain = channel.modulationEffect.lfoGain;
695
- lfoGain.connect(bufferSource.detune);
696
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
697
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
698
- }
699
691
  // filter envelope
700
692
  const softPedalFactor = 1 -
701
693
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
@@ -721,6 +713,19 @@ export class Midy {
721
713
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
722
714
  .setValueAtTime(adjustedPeekFreq, modHold)
723
715
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
716
+ let lfoGain;
717
+ if (channel.modulation > 0) {
718
+ const vibratoDelay = startTime + channel.vibratoDelay;
719
+ const vibratoAttack = vibratoDelay + 0.1;
720
+ lfoGain = new GainNode(this.audioContext, {
721
+ gain: 0,
722
+ });
723
+ lfoGain.gain
724
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
725
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
726
+ channel.modulationEffect.lfo.connect(lfoGain);
727
+ lfoGain.connect(bufferSource.detune);
728
+ }
724
729
  bufferSource.connect(filterNode);
725
730
  filterNode.connect(gainNode);
726
731
  if (this.mono && channel.currentBufferSource) {
@@ -728,7 +733,7 @@ export class Midy {
728
733
  channel.currentBufferSource = bufferSource;
729
734
  }
730
735
  bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
731
- return { bufferSource, gainNode, filterNode };
736
+ return { bufferSource, gainNode, filterNode, lfoGain };
732
737
  }
733
738
  calcBank(channel, channelNumber) {
734
739
  if (channel.bankMSB === 121) {
@@ -750,7 +755,7 @@ export class Midy {
750
755
  const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
751
756
  if (!noteInfo)
752
757
  return;
753
- const { bufferSource, gainNode, filterNode } = await this
758
+ const { bufferSource, gainNode, filterNode, lfoGain } = await this
754
759
  .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
755
760
  this.connectNoteEffects(channel, gainNode);
756
761
  if (channel.sostenutoPedal) {
@@ -764,11 +769,12 @@ export class Midy {
764
769
  }
765
770
  const scheduledNotes = channel.scheduledNotes;
766
771
  const scheduledNote = {
767
- gainNode,
768
- filterNode,
769
772
  bufferSource,
770
- noteNumber,
773
+ filterNode,
774
+ gainNode,
775
+ lfoGain,
771
776
  noteInfo,
777
+ noteNumber,
772
778
  startTime,
773
779
  };
774
780
  if (scheduledNotes.has(noteNumber)) {
@@ -797,7 +803,7 @@ export class Midy {
797
803
  continue;
798
804
  if (targetNote.ending)
799
805
  continue;
800
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
806
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
801
807
  const velocityRate = (velocity + 127) / 127;
802
808
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
803
809
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -819,6 +825,8 @@ export class Midy {
819
825
  bufferSource.disconnect(0);
820
826
  filterNode.disconnect(0);
821
827
  gainNode.disconnect(0);
828
+ if (lfoGain)
829
+ lfoGain.disconnect(0);
822
830
  resolve();
823
831
  };
824
832
  bufferSource.stop(volEndTime);
@@ -829,41 +837,34 @@ export class Midy {
829
837
  const now = this.audioContext.currentTime;
830
838
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
831
839
  }
832
- releaseSustainPedal(channelNumber) {
833
- const now = this.audioContext.currentTime;
840
+ releaseSustainPedal(channelNumber, halfVelocity) {
841
+ const velocity = halfVelocity * 2;
834
842
  const channel = this.channels[channelNumber];
843
+ const promises = [];
835
844
  channel.sustainPedal = false;
836
845
  channel.scheduledNotes.forEach((scheduledNotes) => {
837
846
  scheduledNotes.forEach((scheduledNote) => {
838
847
  if (scheduledNote) {
839
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
840
- const volEndTime = now + noteInfo.volRelease;
841
- gainNode.gain.cancelScheduledValues(now);
842
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
843
- const maxFreq = this.audioContext.sampleRate / 2;
844
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
845
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
846
- const modEndTime = now + noteInfo.modRelease;
847
- filterNode.frequency
848
- .cancelScheduledValues(stopTime)
849
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
850
- bufferSource.stop(volEndTime);
848
+ const { noteNumber } = scheduledNote;
849
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
850
+ promises.push(promise);
851
851
  }
852
852
  });
853
853
  });
854
+ return promises;
854
855
  }
855
- releaseSostenuto(channelNumber) {
856
- const now = this.audioContext.currentTime;
856
+ releaseSostenutoPedal(channelNumber, halfVelocity) {
857
+ const velocity = halfVelocity * 2;
857
858
  const channel = this.channels[channelNumber];
859
+ const promises = [];
858
860
  channel.sostenutoPedal = false;
859
861
  channel.sostenutoNotes.forEach((activeNote) => {
860
- const { gainNode, bufferSource, noteInfo } = activeNote;
861
- const fadeTime = noteInfo.volRelease;
862
- gainNode.gain.cancelScheduledValues(now);
863
- gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
864
- bufferSource.stop(now + fadeTime);
862
+ const { noteNumber } = activeNote;
863
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
864
+ promises.push(promise);
865
865
  });
866
866
  channel.sostenutoNotes.clear();
867
+ return promises;
867
868
  }
868
869
  handleMIDIMessage(statusByte, data1, data2) {
869
870
  const channelNumber = omni ? 0 : statusByte & 0x0F;
@@ -874,7 +875,7 @@ export class Midy {
874
875
  case 0x90:
875
876
  return this.noteOn(channelNumber, data1, data2);
876
877
  case 0xA0:
877
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
878
+ return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
878
879
  case 0xB0:
879
880
  return this.handleControlChange(channelNumber, data1, data2);
880
881
  case 0xC0:
@@ -882,7 +883,7 @@ export class Midy {
882
883
  case 0xD0:
883
884
  return this.handleChannelPressure(channelNumber, data1);
884
885
  case 0xE0:
885
- return this.handlePitchBend(channelNumber, data1, data2);
886
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
886
887
  default:
887
888
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
888
889
  }
@@ -890,17 +891,16 @@ export class Midy {
890
891
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
891
892
  const now = this.audioContext.currentTime;
892
893
  const channel = this.channels[channelNumber];
893
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
894
- pressure /= 127;
895
- if (scheduledNotes) {
896
- scheduledNotes.forEach((scheduledNote) => {
897
- if (scheduledNote) {
898
- const { initialAttenuation } = scheduledNote.noteInfo;
899
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
900
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
901
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
902
- }
903
- });
894
+ pressure /= 64;
895
+ const activeNotes = this.getActiveNotes(channel);
896
+ if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
897
+ if (activeNotes.has(noteNumber)) {
898
+ const activeNote = activeNotes.get(noteNumber);
899
+ const gain = activeNote.gainNode.gain.value;
900
+ activeNote.gainNode.gain
901
+ .cancelScheduledValues(now)
902
+ .setValueAtTime(gain * pressure, now);
903
+ }
904
904
  }
905
905
  }
906
906
  handleProgramChange(channelNumber, program) {
@@ -909,11 +909,37 @@ export class Midy {
909
909
  channel.program = program;
910
910
  }
911
911
  handleChannelPressure(channelNumber, pressure) {
912
- this.channels[channelNumber].channelPressure = pressure;
912
+ const now = this.audioContext.currentTime;
913
+ const channel = this.channels[channelNumber];
914
+ pressure /= 64;
915
+ channel.channelPressure = pressure;
916
+ const activeNotes = this.getActiveNotes(channel);
917
+ if (channel.channelPressure.amplitudeControl !== 1) {
918
+ activeNotes.forEach((activeNote) => {
919
+ const gain = activeNote.gainNode.gain.value;
920
+ activeNote.gainNode.gain
921
+ .cancelScheduledValues(now)
922
+ .setValueAtTime(gain * pressure, now);
923
+ });
924
+ }
925
+ }
926
+ handlePitchBendMessage(channelNumber, lsb, msb) {
927
+ const pitchBend = msb * 128 + lsb;
928
+ this.handlePitchBend(channelNumber, pitchBend);
913
929
  }
914
- handlePitchBend(channelNumber, lsb, msb) {
915
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
916
- this.channels[channelNumber].pitchBend = pitchBend;
930
+ handlePitchBend(channelNumber, pitchBend) {
931
+ const now = this.audioContext.currentTime;
932
+ const channel = this.channels[channelNumber];
933
+ channel.pitchBend = (pitchBend - 8192) / 8192;
934
+ const semitoneOffset = this.calcSemitoneOffset(channel);
935
+ const activeNotes = this.getActiveNotes(channel);
936
+ activeNotes.forEach((activeNote) => {
937
+ const { bufferSource, noteInfo, noteNumber } = activeNote;
938
+ const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
939
+ bufferSource.playbackRate
940
+ .cancelScheduledValues(now)
941
+ .setValueAtTime(playbackRate * pressure, now);
942
+ });
917
943
  }
918
944
  handleControlChange(channelNumber, controller, value) {
919
945
  switch (controller) {
@@ -984,13 +1010,9 @@ export class Midy {
984
1010
  this.channels[channelNumber].bankMSB = msb;
985
1011
  }
986
1012
  setModulation(channelNumber, modulation) {
987
- const now = this.audioContext.currentTime;
988
1013
  const channel = this.channels[channelNumber];
989
- channel.modulation = (modulation * 100 / 127) *
990
- channel.modulationDepthRange;
991
- const lfoGain = channel.modulationEffect.lfoGain;
992
- lfoGain.gain.cancelScheduledValues(now);
993
- lfoGain.gain.setValueAtTime(channel.modulation, now);
1014
+ channel.modulation = (modulation / 127) *
1015
+ (channel.modulationDepthRange * 100);
994
1016
  }
995
1017
  setPortamentoTime(channelNumber, portamentoTime) {
996
1018
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1025,7 +1047,7 @@ export class Midy {
1025
1047
  const isOn = value >= 64;
1026
1048
  this.channels[channelNumber].sustainPedal = isOn;
1027
1049
  if (!isOn) {
1028
- this.releaseSustainPedal(channelNumber);
1050
+ this.releaseSustainPedal(channelNumber, value);
1029
1051
  }
1030
1052
  }
1031
1053
  setPortamento(channelNumber, value) {
@@ -1054,25 +1076,29 @@ export class Midy {
1054
1076
  const activeNotes = this.getActiveNotes(channel);
1055
1077
  channel.sostenutoNotes = new Map(activeNotes);
1056
1078
  }
1079
+ else {
1080
+ this.releaseSostenutoPedal(channelNumber, value);
1081
+ }
1057
1082
  }
1058
1083
  setSoftPedal(channelNumber, softPedal) {
1059
1084
  const channel = this.channels[channelNumber];
1060
1085
  channel.softPedal = softPedal / 127;
1061
- this.updateChannelGain(channel);
1062
1086
  }
1063
1087
  setVibratoRate(channelNumber, vibratoRate) {
1064
1088
  const now = this.audioContext.currentTime;
1065
1089
  const channel = this.channels[channelNumber];
1066
1090
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1067
- channel.modulationEffect.lfo.frequency.cancelScheduledValues(now);
1068
- channel.modulationEffect.lfo.frequency.setValueAtTime(depth, now);
1091
+ channel.modulationEffect.lfo.frequency
1092
+ .cancelScheduledValues(now)
1093
+ .setValueAtTime(channel.vibratoRate, now);
1069
1094
  }
1070
1095
  setVibratoDepth(channelNumber, vibratoDepth) {
1071
1096
  const now = this.audioContext.currentTime;
1072
1097
  const channel = this.channels[channelNumber];
1073
1098
  channel.vibratoDepth = vibratoDepth / 127;
1074
- channel.modulationEffect.lfoGain.gain.cancelScheduledValues(now);
1075
- channel.modulationEffect.lfoGain.gain.setValueAtTime(depth, now);
1099
+ channel.modulationEffect.lfoGain.gain
1100
+ .cancelScheduledValues(now)
1101
+ .setValueAtTime(channel.vibratoDepth, now);
1076
1102
  }
1077
1103
  setVibratoDelay(channelNumber, vibratoDelay) {
1078
1104
  // Access Virus: 0-10sec
@@ -1258,10 +1284,10 @@ export class Midy {
1258
1284
  switch (data[3]) {
1259
1285
  // case 1:
1260
1286
  // // TODO
1261
- // return this.handleChannelPressure();
1287
+ // return this.setChannelPressure();
1262
1288
  // case 3:
1263
1289
  // // TODO
1264
- // return this.handleControlChange();
1290
+ // return this.setControlChange();
1265
1291
  default:
1266
1292
  console.warn(`Unsupported Exclusive Message ${data}`);
1267
1293
  }
@@ -1280,20 +1306,25 @@ export class Midy {
1280
1306
  }
1281
1307
  }
1282
1308
  handleMasterVolumeSysEx(data) {
1283
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
1309
+ const volume = (data[5] * 128 + data[4]) / 16383;
1284
1310
  this.handleMasterVolume(volume);
1285
1311
  }
1286
1312
  handleMasterVolume(volume) {
1287
- const now = this.audioContext.currentTime;
1288
- this.masterGain.gain.cancelScheduledValues(now);
1289
- this.masterGain.gain.setValueAtTime(volume * volume, now);
1313
+ if (volume < 0 && 1 < volume) {
1314
+ console.error("Master Volume is out of range");
1315
+ }
1316
+ else {
1317
+ const now = this.audioContext.currentTime;
1318
+ this.masterGain.gain.cancelScheduledValues(now);
1319
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
1320
+ }
1290
1321
  }
1291
1322
  handleMasterFineTuningSysEx(data) {
1292
1323
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1293
1324
  this.handleMasterFineTuning(fineTuning);
1294
1325
  }
1295
1326
  handleMasterFineTuning(fineTuning) {
1296
- if (fineTuning < 0 && 1 < fineTuning) {
1327
+ if (fineTuning < -1 && 1 < fineTuning) {
1297
1328
  console.error("Master Fine Tuning value is out of range");
1298
1329
  }
1299
1330
  else {
@@ -1343,7 +1374,7 @@ Object.defineProperty(Midy, "channelSettings", {
1343
1374
  writable: true,
1344
1375
  value: {
1345
1376
  currentBufferSource: null,
1346
- volume: 1,
1377
+ volume: 100 / 127,
1347
1378
  pan: 0,
1348
1379
  portamentoTime: 0,
1349
1380
  reverb: 0,
@@ -1360,7 +1391,7 @@ Object.defineProperty(Midy, "channelSettings", {
1360
1391
  pitchBend: 0,
1361
1392
  fineTuning: 0,
1362
1393
  coarseTuning: 0,
1363
- modulationDepthRange: 2,
1394
+ modulationDepthRange: 0.5,
1364
1395
  }
1365
1396
  });
1366
1397
  Object.defineProperty(Midy, "effectSettings", {
@@ -1380,3 +1411,16 @@ Object.defineProperty(Midy, "effectSettings", {
1380
1411
  pitchBendRange: 2,
1381
1412
  }
1382
1413
  });
1414
+ Object.defineProperty(Midy, "controllerDestinationSettings", {
1415
+ enumerable: true,
1416
+ configurable: true,
1417
+ writable: true,
1418
+ value: {
1419
+ pitchControl: 0,
1420
+ filterCutoffControl: 0,
1421
+ amplitudeControl: 1,
1422
+ lfoPitchDepth: 0,
1423
+ lfoFilterDepth: 0,
1424
+ lfoAmplitudeDepth: 0,
1425
+ }
1426
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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",
@@ -24,7 +24,6 @@ export class MidyGM1 {
24
24
  };
25
25
  constructor(audioContext: any);
26
26
  ticksPerBeat: number;
27
- secondsPerBeat: number;
28
27
  totalTime: number;
29
28
  noteCheckInterval: number;
30
29
  lookAhead: number;
@@ -50,7 +49,6 @@ export class MidyGM1 {
50
49
  pannerNode: any;
51
50
  modulationEffect: {
52
51
  lfo: any;
53
- lfoGain: any;
54
52
  };
55
53
  expression: number;
56
54
  modulation: number;
@@ -81,7 +79,6 @@ export class MidyGM1 {
81
79
  pannerNode: any;
82
80
  modulationEffect: {
83
81
  lfo: any;
84
- lfoGain: any;
85
82
  };
86
83
  };
87
84
  createChannels(audioContext: any): {
@@ -91,7 +88,6 @@ export class MidyGM1 {
91
88
  pannerNode: any;
92
89
  modulationEffect: {
93
90
  lfo: any;
94
- lfoGain: any;
95
91
  };
96
92
  expression: number;
97
93
  modulation: number;
@@ -137,31 +133,27 @@ export class MidyGM1 {
137
133
  getActiveChannelNotes(scheduledNotes: any): any;
138
134
  createModulationEffect(audioContext: any): {
139
135
  lfo: any;
140
- lfoGain: any;
141
- };
142
- createReverbEffect(audioContext: any, options?: {}): {
143
- convolverNode: any;
144
- dryGain: any;
145
- wetGain: any;
146
136
  };
147
137
  connectNoteEffects(channel: any, gainNode: any): void;
148
138
  cbToRatio(cb: any): number;
149
139
  centToHz(cent: any): number;
140
+ calcSemitoneOffset(channel: any): any;
141
+ calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
150
142
  createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
151
143
  bufferSource: any;
152
144
  gainNode: any;
153
145
  filterNode: any;
146
+ lfoGain: any;
154
147
  }>;
155
148
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
156
149
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
157
150
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
158
151
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
159
- releaseSustainPedal(channelNumber: any): void;
152
+ releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
160
153
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
161
- handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
162
154
  handleProgramChange(channelNumber: any, program: any): void;
163
- handleChannelPressure(channelNumber: any, pressure: any): void;
164
- handlePitchBend(channelNumber: any, lsb: any, msb: any): void;
155
+ handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
156
+ handlePitchBend(channelNumber: any, pitchBend: any): void;
165
157
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
166
158
  setModulation(channelNumber: any, modulation: any): void;
167
159
  setVolume(channelNumber: any, volume: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAoBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAlDD,qBAAmB;IACnB,uBAAqB;IACrB,kBAAc;IACd,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;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA6CC;IAED,mCAQC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA0DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBA2BC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;;MAUC;IAED;;;;MAoCC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAwEC;IAED,kGAsCC;IAED,0EAGC;IAED,sIA0CC;IAED,0FAGC;IAED,8CAuBC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;IAED,mFA+BC;IAED,yDAQC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAjDD,qBAAmB;IACnB,kBAAc;IACd,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;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,sCAGC;IAED,8EAEC;IAED;;;;;OA8EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}