@marmooo/midy 0.0.2 → 0.0.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/script/midy.js CHANGED
@@ -11,12 +11,6 @@ class Midy {
11
11
  writable: true,
12
12
  value: 120
13
13
  });
14
- Object.defineProperty(this, "secondsPerBeat", {
15
- enumerable: true,
16
- configurable: true,
17
- writable: true,
18
- value: 0.5
19
- });
20
14
  Object.defineProperty(this, "totalTime", {
21
15
  enumerable: true,
22
16
  configurable: true,
@@ -177,10 +171,10 @@ class Midy {
177
171
  const response = await fetch(midiUrl);
178
172
  const arrayBuffer = await response.arrayBuffer();
179
173
  const midi = (0, _esm_js_1.parseMidi)(new Uint8Array(arrayBuffer));
174
+ this.ticksPerBeat = midi.header.ticksPerBeat;
180
175
  const midiData = this.extractMidiData(midi);
181
176
  this.instruments = midiData.instruments;
182
177
  this.timeline = midiData.timeline;
183
- this.ticksPerBeat = midi.header.ticksPerBeat;
184
178
  this.totalTime = this.calcTotalTime();
185
179
  }
186
180
  setChannelAudioNodes(audioContext) {
@@ -266,18 +260,20 @@ class Midy {
266
260
  async scheduleTimelineEvents(t, offset, queueIndex) {
267
261
  while (queueIndex < this.timeline.length) {
268
262
  const event = this.timeline[queueIndex];
269
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
270
- if (time > t + this.lookAhead)
263
+ if (event.startTime > t + this.lookAhead)
271
264
  break;
272
265
  switch (event.type) {
273
266
  case "controller":
274
267
  this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
275
268
  break;
276
269
  case "noteOn":
277
- await this.scheduleNoteOn(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
278
- 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 */
279
275
  case "noteOff": {
280
- 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);
281
277
  if (notePromise) {
282
278
  this.notePromises.push(notePromise);
283
279
  }
@@ -286,9 +282,6 @@ class Midy {
286
282
  case "programChange":
287
283
  this.handleProgramChange(event.channel, event.programNumber);
288
284
  break;
289
- case "setTempo":
290
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
291
- break;
292
285
  case "sysEx":
293
286
  this.handleSysEx(event.data);
294
287
  }
@@ -297,9 +290,8 @@ class Midy {
297
290
  return queueIndex;
298
291
  }
299
292
  getQueueIndex(second) {
300
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
301
293
  for (let i = 0; i < this.timeline.length; i++) {
302
- if (ticks <= this.timeline[i].ticks) {
294
+ if (second <= this.timeline[i].startTime) {
303
295
  return i;
304
296
  }
305
297
  }
@@ -441,18 +433,28 @@ class Midy {
441
433
  timeline.push(event);
442
434
  });
443
435
  });
436
+ const priority = {
437
+ setTempo: 0,
438
+ controller: 1,
439
+ };
444
440
  timeline.sort((a, b) => {
445
- if (a.ticks !== b.ticks) {
441
+ if (a.ticks !== b.ticks)
446
442
  return a.ticks - b.ticks;
447
- }
448
- if (a.type !== "controller" && b.type === "controller") {
449
- return -1;
450
- }
451
- if (a.type === "controller" && b.type !== "controller") {
452
- return 1;
453
- }
454
- return 0;
443
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
455
444
  });
445
+ let prevTempoTime = 0;
446
+ let prevTempoTicks = 0;
447
+ let secondsPerBeat = 0.5;
448
+ for (let i = 0; i < timeline.length; i++) {
449
+ const event = timeline[i];
450
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
451
+ event.startTime = prevTempoTime + timeFromPrevTempo;
452
+ if (event.type === "setTempo") {
453
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
454
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
455
+ prevTempoTicks = event.ticks;
456
+ }
457
+ }
456
458
  return { instruments, timeline };
457
459
  }
458
460
  stopNotes() {
@@ -504,32 +506,12 @@ class Midy {
504
506
  }
505
507
  }
506
508
  calcTotalTime() {
507
- const endOfTracks = [];
508
- let prevTicks = 0;
509
509
  let totalTime = 0;
510
- let secondsPerBeat = 0.5;
511
510
  for (let i = 0; i < this.timeline.length; i++) {
512
511
  const event = this.timeline[i];
513
- switch (event.type) {
514
- case "setTempo": {
515
- const durationTicks = event.ticks - prevTicks;
516
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
517
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
518
- prevTicks = event.ticks;
519
- break;
520
- }
521
- case "endOfTrack":
522
- endOfTracks.push(event);
523
- }
524
- }
525
- let maxTicks = 0;
526
- for (let i = 0; i < endOfTracks.length; i++) {
527
- const event = endOfTracks[i];
528
- if (maxTicks < event.ticks)
529
- maxTicks = event.ticks;
512
+ if (totalTime < event.startTime)
513
+ totalTime = event.startTime;
530
514
  }
531
- const durationTicks = maxTicks - prevTicks;
532
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
533
515
  return totalTime;
534
516
  }
535
517
  currentTime() {
@@ -557,11 +539,8 @@ class Midy {
557
539
  const lfo = new OscillatorNode(audioContext, {
558
540
  frequency: 5,
559
541
  });
560
- const lfoGain = new GainNode(audioContext);
561
- lfo.connect(lfoGain);
562
542
  return {
563
543
  lfo,
564
- lfoGain,
565
544
  };
566
545
  }
567
546
  createReverbEffect(audioContext, options = {}) {
@@ -693,12 +672,6 @@ class Midy {
693
672
  .exponentialRampToValueAtTime(attackVolume, volAttack)
694
673
  .setValueAtTime(attackVolume, volHold)
695
674
  .linearRampToValueAtTime(sustainVolume, volDecay);
696
- if (channel.modulation > 0) {
697
- const lfoGain = channel.modulationEffect.lfoGain;
698
- lfoGain.connect(bufferSource.detune);
699
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
700
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
701
- }
702
675
  // filter envelope
703
676
  const softPedalFactor = 1 -
704
677
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
@@ -724,6 +697,19 @@ class Midy {
724
697
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
725
698
  .setValueAtTime(adjustedPeekFreq, modHold)
726
699
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
700
+ let lfoGain;
701
+ if (channel.modulation > 0) {
702
+ const vibratoDelay = startTime + channel.vibratoDelay;
703
+ const vibratoAttack = vibratoDelay + 0.1;
704
+ lfoGain = new GainNode(this.audioContext, {
705
+ gain: 0,
706
+ });
707
+ lfoGain.gain
708
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
709
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
710
+ channel.modulationEffect.lfo.connect(lfoGain);
711
+ lfoGain.connect(bufferSource.detune);
712
+ }
727
713
  bufferSource.connect(filterNode);
728
714
  filterNode.connect(gainNode);
729
715
  if (this.mono && channel.currentBufferSource) {
@@ -767,11 +753,12 @@ class Midy {
767
753
  }
768
754
  const scheduledNotes = channel.scheduledNotes;
769
755
  const scheduledNote = {
770
- gainNode,
771
- filterNode,
772
756
  bufferSource,
773
- noteNumber,
757
+ filterNode,
758
+ gainNode,
759
+ lfoGain,
774
760
  noteInfo,
761
+ noteNumber,
775
762
  startTime,
776
763
  };
777
764
  if (scheduledNotes.has(noteNumber)) {
@@ -800,7 +787,7 @@ class Midy {
800
787
  continue;
801
788
  if (targetNote.ending)
802
789
  continue;
803
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
790
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
804
791
  const velocityRate = (velocity + 127) / 127;
805
792
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
806
793
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -822,6 +809,8 @@ class Midy {
822
809
  bufferSource.disconnect(0);
823
810
  filterNode.disconnect(0);
824
811
  gainNode.disconnect(0);
812
+ if (lfoGain)
813
+ lfoGain.disconnect(0);
825
814
  resolve();
826
815
  };
827
816
  bufferSource.stop(volEndTime);
@@ -832,41 +821,34 @@ class Midy {
832
821
  const now = this.audioContext.currentTime;
833
822
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
834
823
  }
835
- releaseSustainPedal(channelNumber) {
836
- const now = this.audioContext.currentTime;
824
+ releaseSustainPedal(channelNumber, halfVelocity) {
825
+ const velocity = halfVelocity * 2;
837
826
  const channel = this.channels[channelNumber];
827
+ const promises = [];
838
828
  channel.sustainPedal = false;
839
829
  channel.scheduledNotes.forEach((scheduledNotes) => {
840
830
  scheduledNotes.forEach((scheduledNote) => {
841
831
  if (scheduledNote) {
842
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
843
- const volEndTime = now + noteInfo.volRelease;
844
- gainNode.gain.cancelScheduledValues(now);
845
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
846
- const maxFreq = this.audioContext.sampleRate / 2;
847
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
848
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
849
- const modEndTime = now + noteInfo.modRelease;
850
- filterNode.frequency
851
- .cancelScheduledValues(stopTime)
852
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
853
- bufferSource.stop(volEndTime);
832
+ const { noteNumber } = scheduledNote;
833
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
834
+ promises.push(promise);
854
835
  }
855
836
  });
856
837
  });
838
+ return promises;
857
839
  }
858
- releaseSostenuto(channelNumber) {
859
- const now = this.audioContext.currentTime;
840
+ releaseSostenutoPedal(channelNumber, halfVelocity) {
841
+ const velocity = halfVelocity * 2;
860
842
  const channel = this.channels[channelNumber];
843
+ const promises = [];
861
844
  channel.sostenutoPedal = false;
862
845
  channel.sostenutoNotes.forEach((activeNote) => {
863
- const { gainNode, bufferSource, noteInfo } = activeNote;
864
- const fadeTime = noteInfo.volRelease;
865
- gainNode.gain.cancelScheduledValues(now);
866
- gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
867
- bufferSource.stop(now + fadeTime);
846
+ const { noteNumber } = activeNote;
847
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
848
+ promises.push(promise);
868
849
  });
869
850
  channel.sostenutoNotes.clear();
851
+ return promises;
870
852
  }
871
853
  handleMIDIMessage(statusByte, data1, data2) {
872
854
  const channelNumber = omni ? 0 : statusByte & 0x0F;
@@ -987,13 +969,9 @@ class Midy {
987
969
  this.channels[channelNumber].bankMSB = msb;
988
970
  }
989
971
  setModulation(channelNumber, modulation) {
990
- const now = this.audioContext.currentTime;
991
972
  const channel = this.channels[channelNumber];
992
- channel.modulation = (modulation * 100 / 127) *
993
- channel.modulationDepthRange;
994
- const lfoGain = channel.modulationEffect.lfoGain;
995
- lfoGain.gain.cancelScheduledValues(now);
996
- lfoGain.gain.setValueAtTime(channel.modulation, now);
973
+ channel.modulation = (modulation / 127) *
974
+ (channel.modulationDepthRange * 100);
997
975
  }
998
976
  setPortamentoTime(channelNumber, portamentoTime) {
999
977
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1028,7 +1006,7 @@ class Midy {
1028
1006
  const isOn = value >= 64;
1029
1007
  this.channels[channelNumber].sustainPedal = isOn;
1030
1008
  if (!isOn) {
1031
- this.releaseSustainPedal(channelNumber);
1009
+ this.releaseSustainPedal(channelNumber, value);
1032
1010
  }
1033
1011
  }
1034
1012
  setPortamento(channelNumber, value) {
@@ -1057,25 +1035,29 @@ class Midy {
1057
1035
  const activeNotes = this.getActiveNotes(channel);
1058
1036
  channel.sostenutoNotes = new Map(activeNotes);
1059
1037
  }
1038
+ else {
1039
+ this.releaseSostenutoPedal(channelNumber, value);
1040
+ }
1060
1041
  }
1061
1042
  setSoftPedal(channelNumber, softPedal) {
1062
1043
  const channel = this.channels[channelNumber];
1063
1044
  channel.softPedal = softPedal / 127;
1064
- this.updateChannelGain(channel);
1065
1045
  }
1066
1046
  setVibratoRate(channelNumber, vibratoRate) {
1067
1047
  const now = this.audioContext.currentTime;
1068
1048
  const channel = this.channels[channelNumber];
1069
1049
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1070
- channel.modulationEffect.lfo.frequency.cancelScheduledValues(now);
1071
- channel.modulationEffect.lfo.frequency.setValueAtTime(depth, now);
1050
+ channel.modulationEffect.lfo.frequency
1051
+ .cancelScheduledValues(now)
1052
+ .setValueAtTime(channel.vibratoRate, now);
1072
1053
  }
1073
1054
  setVibratoDepth(channelNumber, vibratoDepth) {
1074
1055
  const now = this.audioContext.currentTime;
1075
1056
  const channel = this.channels[channelNumber];
1076
1057
  channel.vibratoDepth = vibratoDepth / 127;
1077
- channel.modulationEffect.lfoGain.gain.cancelScheduledValues(now);
1078
- channel.modulationEffect.lfoGain.gain.setValueAtTime(depth, now);
1058
+ channel.modulationEffect.lfoGain.gain
1059
+ .cancelScheduledValues(now)
1060
+ .setValueAtTime(channel.vibratoDepth, now);
1079
1061
  }
1080
1062
  setVibratoDelay(channelNumber, vibratoDelay) {
1081
1063
  // Access Virus: 0-10sec
@@ -1347,7 +1329,7 @@ Object.defineProperty(Midy, "channelSettings", {
1347
1329
  writable: true,
1348
1330
  value: {
1349
1331
  currentBufferSource: null,
1350
- volume: 1,
1332
+ volume: 100 / 127,
1351
1333
  pan: 0,
1352
1334
  portamentoTime: 0,
1353
1335
  reverb: 0,
@@ -1364,7 +1346,7 @@ Object.defineProperty(Midy, "channelSettings", {
1364
1346
  pitchBend: 0,
1365
1347
  fineTuning: 0,
1366
1348
  coarseTuning: 0,
1367
- modulationDepthRange: 2,
1349
+ modulationDepthRange: 0.5,
1368
1350
  }
1369
1351
  });
1370
1352
  Object.defineProperty(Midy, "effectSettings", {