@marmooo/midy 0.4.1 → 0.4.2

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
@@ -26,12 +26,6 @@ class Note {
26
26
  writable: true,
27
27
  value: false
28
28
  });
29
- Object.defineProperty(this, "pending", {
30
- enumerable: true,
31
- configurable: true,
32
- writable: true,
33
- value: true
34
- });
35
29
  Object.defineProperty(this, "bufferSource", {
36
30
  enumerable: true,
37
31
  configurable: true,
@@ -113,6 +107,9 @@ class Note {
113
107
  this.noteNumber = noteNumber;
114
108
  this.velocity = velocity;
115
109
  this.startTime = startTime;
110
+ this.ready = new Promise((resolve) => {
111
+ this.resolveReady = resolve;
112
+ });
116
113
  }
117
114
  }
118
115
  const drumExclusiveClassesByKit = new Array(57);
@@ -190,6 +187,7 @@ const defaultControllerState = {
190
187
  // dataDecrement: { type: 128 + 97, defaultValue: 0 },
191
188
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
192
189
  // rpnMSB: { type: 128 + 101, defaultValue: 127 },
190
+ // rpgMakerLoop: { type: 128 + 111, defaultValue: 0 },
193
191
  // allSoundOff: { type: 128 + 120, defaultValue: 0 },
194
192
  // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
195
193
  // allNotesOff: { type: 128 + 123, defaultValue: 0 },
@@ -249,8 +247,9 @@ const pitchEnvelopeKeys = [
249
247
  "playbackRate",
250
248
  ];
251
249
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
252
- export class Midy {
250
+ export class Midy extends EventTarget {
253
251
  constructor(audioContext) {
252
+ super();
254
253
  Object.defineProperty(this, "mode", {
255
254
  enumerable: true,
256
255
  configurable: true,
@@ -411,6 +410,18 @@ export class Midy {
411
410
  writable: true,
412
411
  value: false
413
412
  });
413
+ Object.defineProperty(this, "loop", {
414
+ enumerable: true,
415
+ configurable: true,
416
+ writable: true,
417
+ value: true
418
+ });
419
+ Object.defineProperty(this, "loopStart", {
420
+ enumerable: true,
421
+ configurable: true,
422
+ writable: true,
423
+ value: 0
424
+ });
414
425
  Object.defineProperty(this, "playPromise", {
415
426
  enumerable: true,
416
427
  configurable: true,
@@ -632,7 +643,7 @@ export class Midy {
632
643
  }
633
644
  return bufferSource;
634
645
  }
635
- async scheduleTimelineEvents(scheduleTime, queueIndex) {
646
+ scheduleTimelineEvents(scheduleTime, queueIndex) {
636
647
  const timeOffset = this.resumeTime - this.startTime;
637
648
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
638
649
  const schedulingOffset = this.startDelay - timeOffset;
@@ -644,7 +655,7 @@ export class Midy {
644
655
  const startTime = event.startTime + schedulingOffset;
645
656
  switch (event.type) {
646
657
  case "noteOn":
647
- await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
658
+ this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
648
659
  break;
649
660
  case "noteOff": {
650
661
  this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
@@ -691,22 +702,23 @@ export class Midy {
691
702
  }
692
703
  }
693
704
  updateStates(queueIndex, nextQueueIndex) {
705
+ const now = this.audioContext.currentTime;
694
706
  if (nextQueueIndex < queueIndex)
695
707
  queueIndex = 0;
696
708
  for (let i = queueIndex; i < nextQueueIndex; i++) {
697
709
  const event = this.timeline[i];
698
710
  switch (event.type) {
699
711
  case "controller":
700
- this.setControlChange(event.channel, event.controllerType, event.value, 0);
712
+ this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
701
713
  break;
702
714
  case "programChange":
703
- this.setProgramChange(event.channel, event.programNumber, 0);
715
+ this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
704
716
  break;
705
717
  case "pitchBend":
706
- this.setPitchBend(event.channel, event.value + 8192, 0);
718
+ this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
707
719
  break;
708
720
  case "sysEx":
709
- this.handleSysEx(event.data, 0);
721
+ this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
710
722
  }
711
723
  }
712
724
  }
@@ -714,58 +726,95 @@ export class Midy {
714
726
  if (this.audioContext.state === "suspended") {
715
727
  await this.audioContext.resume();
716
728
  }
729
+ const paused = this.isPaused;
717
730
  this.isPlaying = true;
718
731
  this.isPaused = false;
719
732
  this.startTime = this.audioContext.currentTime;
733
+ if (paused) {
734
+ this.dispatchEvent(new Event("resumed"));
735
+ }
736
+ else {
737
+ this.dispatchEvent(new Event("started"));
738
+ }
720
739
  let queueIndex = this.getQueueIndex(this.resumeTime);
721
- let finished = false;
740
+ let exitReason;
722
741
  this.notePromises = [];
723
- while (queueIndex < this.timeline.length) {
742
+ while (true) {
724
743
  const now = this.audioContext.currentTime;
725
744
  if (0 < this.lastActiveSensing &&
726
745
  this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
727
746
  await this.stopNotes(0, true, now);
728
747
  await this.audioContext.suspend();
729
- finished = true;
748
+ exitReason = "aborted";
730
749
  break;
731
750
  }
751
+ if (this.timeline.length <= queueIndex) {
752
+ await this.stopNotes(0, true, now);
753
+ if (this.loop) {
754
+ this.notePromises = [];
755
+ this.resetAllStates();
756
+ this.startTime = this.audioContext.currentTime;
757
+ this.resumeTime = this.loopStart;
758
+ if (0 < this.loopStart) {
759
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
760
+ this.updateStates(queueIndex, nextQueueIndex);
761
+ queueIndex = nextQueueIndex;
762
+ }
763
+ else {
764
+ queueIndex = 0;
765
+ }
766
+ this.dispatchEvent(new Event("looped"));
767
+ continue;
768
+ }
769
+ else {
770
+ await this.audioContext.suspend();
771
+ exitReason = "ended";
772
+ break;
773
+ }
774
+ }
732
775
  if (this.isPausing) {
733
776
  await this.stopNotes(0, true, now);
734
777
  await this.audioContext.suspend();
735
778
  this.notePromises = [];
779
+ this.isPausing = false;
780
+ exitReason = "paused";
736
781
  break;
737
782
  }
738
783
  else if (this.isStopping) {
739
784
  await this.stopNotes(0, true, now);
740
785
  await this.audioContext.suspend();
741
- finished = true;
786
+ this.isStopping = false;
787
+ exitReason = "stopped";
742
788
  break;
743
789
  }
744
790
  else if (this.isSeeking) {
745
- await this.stopNotes(0, true, now);
791
+ this.stopNotes(0, true, now);
746
792
  this.startTime = this.audioContext.currentTime;
747
793
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
748
794
  this.updateStates(queueIndex, nextQueueIndex);
749
795
  queueIndex = nextQueueIndex;
750
796
  this.isSeeking = false;
797
+ this.dispatchEvent(new Event("seeked"));
751
798
  continue;
752
799
  }
753
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
800
+ queueIndex = this.scheduleTimelineEvents(now, queueIndex);
754
801
  const waitTime = now + this.noteCheckInterval;
755
802
  await this.scheduleTask(() => { }, waitTime);
756
803
  }
757
- if (this.timeline.length <= queueIndex) {
758
- const now = this.audioContext.currentTime;
759
- await this.stopNotes(0, true, now);
760
- await this.audioContext.suspend();
761
- finished = true;
762
- }
763
- if (finished) {
804
+ if (exitReason !== "paused") {
764
805
  this.notePromises = [];
765
806
  this.resetAllStates();
766
807
  this.lastActiveSensing = 0;
767
808
  }
768
809
  this.isPlaying = false;
810
+ if (exitReason === "paused") {
811
+ this.isPaused = true;
812
+ this.dispatchEvent(new Event("paused"));
813
+ }
814
+ else {
815
+ this.isPaused = false;
816
+ this.dispatchEvent(new Event(exitReason));
817
+ }
769
818
  }
770
819
  ticksToSecond(ticks, secondsPerBeat) {
771
820
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -902,24 +951,20 @@ export class Midy {
902
951
  return;
903
952
  this.isStopping = true;
904
953
  await this.playPromise;
905
- this.isStopping = false;
906
954
  }
907
955
  async pause() {
908
956
  if (!this.isPlaying || this.isPaused)
909
957
  return;
910
958
  const now = this.audioContext.currentTime;
911
- this.resumeTime = now - this.startTime - this.startDelay;
959
+ this.resumeTime = now + this.resumeTime - this.startTime;
912
960
  this.isPausing = true;
913
961
  await this.playPromise;
914
- this.isPausing = false;
915
- this.isPaused = true;
916
962
  }
917
963
  async resume() {
918
964
  if (!this.isPaused)
919
965
  return;
920
966
  this.playPromise = this.playNotes();
921
967
  await this.playPromise;
922
- this.isPaused = false;
923
968
  }
924
969
  seekTo(second) {
925
970
  this.resumeTime = second;
@@ -942,19 +987,23 @@ export class Midy {
942
987
  const now = this.audioContext.currentTime;
943
988
  return now + this.resumeTime - this.startTime;
944
989
  }
945
- processScheduledNotes(channel, callback) {
990
+ async processScheduledNotes(channel, callback) {
946
991
  const scheduledNotes = channel.scheduledNotes;
992
+ const tasks = [];
947
993
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
948
994
  const note = scheduledNotes[i];
949
995
  if (!note)
950
996
  continue;
951
997
  if (note.ending)
952
998
  continue;
953
- callback(note);
999
+ const task = note.ready.then(() => callback(note));
1000
+ tasks.push(task);
954
1001
  }
1002
+ await Promise.all(tasks);
955
1003
  }
956
- processActiveNotes(channel, scheduleTime, callback) {
1004
+ async processActiveNotes(channel, scheduleTime, callback) {
957
1005
  const scheduledNotes = channel.scheduledNotes;
1006
+ const tasks = [];
958
1007
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
959
1008
  const note = scheduledNotes[i];
960
1009
  if (!note)
@@ -963,8 +1012,10 @@ export class Midy {
963
1012
  continue;
964
1013
  if (scheduleTime < note.startTime)
965
1014
  break;
966
- callback(note);
1015
+ const task = note.ready.then(() => callback(note));
1016
+ tasks.push(task);
967
1017
  }
1018
+ await Promise.all(tasks);
968
1019
  }
969
1020
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
970
1021
  const sampleRate = audioContext.sampleRate;
@@ -1544,11 +1595,7 @@ export class Midy {
1544
1595
  return;
1545
1596
  await this.setNoteAudioNode(channel, note, realtime);
1546
1597
  this.setNoteRouting(channelNumber, note, startTime);
1547
- note.pending = false;
1548
- const off = note.offEvent;
1549
- if (off) {
1550
- this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
1551
- }
1598
+ note.resolveReady();
1552
1599
  }
1553
1600
  disconnectNote(note) {
1554
1601
  note.bufferSource.disconnect();
@@ -1593,7 +1640,7 @@ export class Midy {
1593
1640
  }, stopTime);
1594
1641
  });
1595
1642
  }
1596
- noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1643
+ noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1597
1644
  const channel = this.channels[channelNumber];
1598
1645
  const state = channel.state;
1599
1646
  if (!force) {
@@ -1612,13 +1659,11 @@ export class Midy {
1612
1659
  if (index < 0)
1613
1660
  return;
1614
1661
  const note = channel.scheduledNotes[index];
1615
- if (note.pending) {
1616
- note.offEvent = { velocity, startTime: endTime };
1617
- return;
1618
- }
1619
1662
  note.ending = true;
1620
1663
  this.setNoteIndex(channel, index);
1621
- const promise = this.releaseNote(channel, note, endTime);
1664
+ const promise = note.ready.then(() => {
1665
+ return this.releaseNote(channel, note, endTime);
1666
+ });
1622
1667
  this.notePromises.push(promise);
1623
1668
  return promise;
1624
1669
  }
@@ -1719,7 +1764,7 @@ export class Midy {
1719
1764
  this.setEffects(channel, note, table, scheduleTime);
1720
1765
  }
1721
1766
  });
1722
- this.applyVoiceParams(channel, 10);
1767
+ this.applyVoiceParams(channel, 10, scheduleTime);
1723
1768
  }
1724
1769
  setProgramChange(channelNumber, programNumber, _scheduleTime) {
1725
1770
  const channel = this.channels[channelNumber];
@@ -1752,7 +1797,7 @@ export class Midy {
1752
1797
  this.processActiveNotes(channel, scheduleTime, (note) => {
1753
1798
  this.setEffects(channel, note, table, scheduleTime);
1754
1799
  });
1755
- this.applyVoiceParams(channel, 13);
1800
+ this.applyVoiceParams(channel, 13, scheduleTime);
1756
1801
  }
1757
1802
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1758
1803
  const pitchBend = msb * 128 + lsb;
@@ -1762,7 +1807,8 @@ export class Midy {
1762
1807
  const channel = this.channels[channelNumber];
1763
1808
  if (channel.isDrum)
1764
1809
  return;
1765
- scheduleTime ??= this.audioContext.currentTime;
1810
+ if (!(0 <= scheduleTime))
1811
+ scheduleTime = this.audioContext.currentTime;
1766
1812
  const state = channel.state;
1767
1813
  const prev = state.pitchWheel * 2 - 1;
1768
1814
  const next = (value - 8192) / 8192;
@@ -2040,6 +2086,7 @@ export class Midy {
2040
2086
  handlers[97] = this.dataDecrement;
2041
2087
  handlers[100] = this.setRPNLSB;
2042
2088
  handlers[101] = this.setRPNMSB;
2089
+ handlers[111] = this.setRPGMakerLoop;
2043
2090
  handlers[120] = this.allSoundOff;
2044
2091
  handlers[121] = this.resetAllControllers;
2045
2092
  handlers[123] = this.allNotesOff;
@@ -2081,7 +2128,8 @@ export class Midy {
2081
2128
  const channel = this.channels[channelNumber];
2082
2129
  if (channel.isDrum)
2083
2130
  return;
2084
- scheduleTime ??= this.audioContext.currentTime;
2131
+ if (!(0 <= scheduleTime))
2132
+ scheduleTime = this.audioContext.currentTime;
2085
2133
  const state = channel.state;
2086
2134
  const intPart = Math.trunc(value);
2087
2135
  state.modulationDepthMSB = intPart / 127;
@@ -2107,7 +2155,8 @@ export class Midy {
2107
2155
  });
2108
2156
  }
2109
2157
  setPortamentoTime(channelNumber, value, scheduleTime) {
2110
- scheduleTime ??= this.audioContext.currentTime;
2158
+ if (!(0 <= scheduleTime))
2159
+ scheduleTime = this.audioContext.currentTime;
2111
2160
  const channel = this.channels[channelNumber];
2112
2161
  const state = channel.state;
2113
2162
  const intPart = Math.trunc(value);
@@ -2118,7 +2167,8 @@ export class Midy {
2118
2167
  this.updatePortamento(channel, scheduleTime);
2119
2168
  }
2120
2169
  setVolume(channelNumber, value, scheduleTime) {
2121
- scheduleTime ??= this.audioContext.currentTime;
2170
+ if (!(0 <= scheduleTime))
2171
+ scheduleTime = this.audioContext.currentTime;
2122
2172
  const channel = this.channels[channelNumber];
2123
2173
  const state = channel.state;
2124
2174
  const intPart = Math.trunc(value);
@@ -2141,7 +2191,8 @@ export class Midy {
2141
2191
  };
2142
2192
  }
2143
2193
  setPan(channelNumber, value, scheduleTime) {
2144
- scheduleTime ??= this.audioContext.currentTime;
2194
+ if (!(0 <= scheduleTime))
2195
+ scheduleTime = this.audioContext.currentTime;
2145
2196
  const channel = this.channels[channelNumber];
2146
2197
  const state = channel.state;
2147
2198
  const intPart = Math.trunc(value);
@@ -2157,7 +2208,8 @@ export class Midy {
2157
2208
  }
2158
2209
  }
2159
2210
  setExpression(channelNumber, value, scheduleTime) {
2160
- scheduleTime ??= this.audioContext.currentTime;
2211
+ if (!(0 <= scheduleTime))
2212
+ scheduleTime = this.audioContext.currentTime;
2161
2213
  const channel = this.channels[channelNumber];
2162
2214
  const state = channel.state;
2163
2215
  const intPart = Math.trunc(value);
@@ -2214,7 +2266,8 @@ export class Midy {
2214
2266
  const channel = this.channels[channelNumber];
2215
2267
  if (channel.isDrum)
2216
2268
  return;
2217
- scheduleTime ??= this.audioContext.currentTime;
2269
+ if (!(0 <= scheduleTime))
2270
+ scheduleTime = this.audioContext.currentTime;
2218
2271
  channel.state.sustainPedal = value / 127;
2219
2272
  if (64 <= value) {
2220
2273
  this.processScheduledNotes(channel, (note) => {
@@ -2232,7 +2285,8 @@ export class Midy {
2232
2285
  const channel = this.channels[channelNumber];
2233
2286
  if (channel.isDrum)
2234
2287
  return;
2235
- scheduleTime ??= this.audioContext.currentTime;
2288
+ if (!(0 <= scheduleTime))
2289
+ scheduleTime = this.audioContext.currentTime;
2236
2290
  channel.state.portamento = value / 127;
2237
2291
  this.updatePortamento(channel, scheduleTime);
2238
2292
  }
@@ -2240,7 +2294,8 @@ export class Midy {
2240
2294
  const channel = this.channels[channelNumber];
2241
2295
  if (channel.isDrum)
2242
2296
  return;
2243
- scheduleTime ??= this.audioContext.currentTime;
2297
+ if (!(0 <= scheduleTime))
2298
+ scheduleTime = this.audioContext.currentTime;
2244
2299
  channel.state.sostenutoPedal = value / 127;
2245
2300
  if (64 <= value) {
2246
2301
  const sostenutoNotes = [];
@@ -2261,7 +2316,8 @@ export class Midy {
2261
2316
  if (channel.isDrum)
2262
2317
  return;
2263
2318
  const state = channel.state;
2264
- scheduleTime ??= this.audioContext.currentTime;
2319
+ if (!(0 <= scheduleTime))
2320
+ scheduleTime = this.audioContext.currentTime;
2265
2321
  state.softPedal = softPedal / 127;
2266
2322
  this.processScheduledNotes(channel, (note) => {
2267
2323
  if (this.isPortamento(channel, note)) {
@@ -2278,7 +2334,8 @@ export class Midy {
2278
2334
  const channel = this.channels[channelNumber];
2279
2335
  if (channel.isDrum)
2280
2336
  return;
2281
- scheduleTime ??= this.audioContext.currentTime;
2337
+ if (!(0 <= scheduleTime))
2338
+ scheduleTime = this.audioContext.currentTime;
2282
2339
  const state = channel.state;
2283
2340
  state.filterResonance = ccValue / 127;
2284
2341
  this.processScheduledNotes(channel, (note) => {
@@ -2299,14 +2356,16 @@ export class Midy {
2299
2356
  const channel = this.channels[channelNumber];
2300
2357
  if (channel.isDrum)
2301
2358
  return;
2302
- scheduleTime ??= this.audioContext.currentTime;
2359
+ if (!(0 <= scheduleTime))
2360
+ scheduleTime = this.audioContext.currentTime;
2303
2361
  channel.state.releaseTime = releaseTime / 127;
2304
2362
  }
2305
2363
  setAttackTime(channelNumber, attackTime, scheduleTime) {
2306
2364
  const channel = this.channels[channelNumber];
2307
2365
  if (channel.isDrum)
2308
2366
  return;
2309
- scheduleTime ??= this.audioContext.currentTime;
2367
+ if (!(0 <= scheduleTime))
2368
+ scheduleTime = this.audioContext.currentTime;
2310
2369
  channel.state.attackTime = attackTime / 127;
2311
2370
  this.processScheduledNotes(channel, (note) => {
2312
2371
  if (scheduleTime < note.startTime) {
@@ -2319,7 +2378,8 @@ export class Midy {
2319
2378
  if (channel.isDrum)
2320
2379
  return;
2321
2380
  const state = channel.state;
2322
- scheduleTime ??= this.audioContext.currentTime;
2381
+ if (!(0 <= scheduleTime))
2382
+ scheduleTime = this.audioContext.currentTime;
2323
2383
  state.brightness = brightness / 127;
2324
2384
  this.processScheduledNotes(channel, (note) => {
2325
2385
  if (this.isPortamento(channel, note)) {
@@ -2334,7 +2394,8 @@ export class Midy {
2334
2394
  const channel = this.channels[channelNumber];
2335
2395
  if (channel.isDrum)
2336
2396
  return;
2337
- scheduleTime ??= this.audioContext.currentTime;
2397
+ if (!(0 <= scheduleTime))
2398
+ scheduleTime = this.audioContext.currentTime;
2338
2399
  channel.state.decayTime = dacayTime / 127;
2339
2400
  this.processScheduledNotes(channel, (note) => {
2340
2401
  this.setVolumeEnvelope(channel, note, scheduleTime);
@@ -2344,7 +2405,8 @@ export class Midy {
2344
2405
  const channel = this.channels[channelNumber];
2345
2406
  if (channel.isDrum)
2346
2407
  return;
2347
- scheduleTime ??= this.audioContext.currentTime;
2408
+ if (!(0 <= scheduleTime))
2409
+ scheduleTime = this.audioContext.currentTime;
2348
2410
  channel.state.vibratoRate = vibratoRate / 127;
2349
2411
  if (channel.vibratoDepth <= 0)
2350
2412
  return;
@@ -2356,7 +2418,8 @@ export class Midy {
2356
2418
  const channel = this.channels[channelNumber];
2357
2419
  if (channel.isDrum)
2358
2420
  return;
2359
- scheduleTime ??= this.audioContext.currentTime;
2421
+ if (!(0 <= scheduleTime))
2422
+ scheduleTime = this.audioContext.currentTime;
2360
2423
  const prev = channel.state.vibratoDepth;
2361
2424
  channel.state.vibratoDepth = vibratoDepth / 127;
2362
2425
  if (0 < prev) {
@@ -2374,7 +2437,8 @@ export class Midy {
2374
2437
  const channel = this.channels[channelNumber];
2375
2438
  if (channel.isDrum)
2376
2439
  return;
2377
- scheduleTime ??= this.audioContext.currentTime;
2440
+ if (!(0 <= scheduleTime))
2441
+ scheduleTime = this.audioContext.currentTime;
2378
2442
  channel.state.vibratoDelay = vibratoDelay / 127;
2379
2443
  if (0 < channel.state.vibratoDepth) {
2380
2444
  this.processScheduledNotes(channel, (note) => {
@@ -2383,13 +2447,15 @@ export class Midy {
2383
2447
  }
2384
2448
  }
2385
2449
  setPortamentoNoteNumber(channelNumber, value, scheduleTime) {
2386
- scheduleTime ??= this.audioContext.currentTime;
2450
+ if (!(0 <= scheduleTime))
2451
+ scheduleTime = this.audioContext.currentTime;
2387
2452
  const channel = this.channels[channelNumber];
2388
2453
  channel.portamentoControl = true;
2389
2454
  channel.state.portamentoNoteNumber = value / 127;
2390
2455
  }
2391
2456
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
2392
- scheduleTime ??= this.audioContext.currentTime;
2457
+ if (!(0 <= scheduleTime))
2458
+ scheduleTime = this.audioContext.currentTime;
2393
2459
  const channel = this.channels[channelNumber];
2394
2460
  const state = channel.state;
2395
2461
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2398,7 +2464,8 @@ export class Midy {
2398
2464
  });
2399
2465
  }
2400
2466
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2401
- scheduleTime ??= this.audioContext.currentTime;
2467
+ if (!(0 <= scheduleTime))
2468
+ scheduleTime = this.audioContext.currentTime;
2402
2469
  const channel = this.channels[channelNumber];
2403
2470
  const state = channel.state;
2404
2471
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2458,12 +2525,14 @@ export class Midy {
2458
2525
  }
2459
2526
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
2460
2527
  dataIncrement(channelNumber, scheduleTime) {
2461
- scheduleTime ??= this.audioContext.currentTime;
2528
+ if (!(0 <= scheduleTime))
2529
+ scheduleTime = this.audioContext.currentTime;
2462
2530
  this.handleRPN(channelNumber, 1, scheduleTime);
2463
2531
  }
2464
2532
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
2465
2533
  dataDecrement(channelNumber, scheduleTime) {
2466
- scheduleTime ??= this.audioContext.currentTime;
2534
+ if (!(0 <= scheduleTime))
2535
+ scheduleTime = this.audioContext.currentTime;
2467
2536
  this.handleRPN(channelNumber, -1, scheduleTime);
2468
2537
  }
2469
2538
  setRPNMSB(channelNumber, value) {
@@ -2486,7 +2555,8 @@ export class Midy {
2486
2555
  const channel = this.channels[channelNumber];
2487
2556
  if (channel.isDrum)
2488
2557
  return;
2489
- scheduleTime ??= this.audioContext.currentTime;
2558
+ if (!(0 <= scheduleTime))
2559
+ scheduleTime = this.audioContext.currentTime;
2490
2560
  const state = channel.state;
2491
2561
  const prev = state.pitchWheelSensitivity;
2492
2562
  const next = value / 12800;
@@ -2506,7 +2576,8 @@ export class Midy {
2506
2576
  const channel = this.channels[channelNumber];
2507
2577
  if (channel.isDrum)
2508
2578
  return;
2509
- scheduleTime ??= this.audioContext.currentTime;
2579
+ if (!(0 <= scheduleTime))
2580
+ scheduleTime = this.audioContext.currentTime;
2510
2581
  const prev = channel.fineTuning;
2511
2582
  const next = value;
2512
2583
  channel.fineTuning = next;
@@ -2523,7 +2594,8 @@ export class Midy {
2523
2594
  const channel = this.channels[channelNumber];
2524
2595
  if (channel.isDrum)
2525
2596
  return;
2526
- scheduleTime ??= this.audioContext.currentTime;
2597
+ if (!(0 <= scheduleTime))
2598
+ scheduleTime = this.audioContext.currentTime;
2527
2599
  const prev = channel.coarseTuning;
2528
2600
  const next = value;
2529
2601
  channel.coarseTuning = next;
@@ -2540,12 +2612,18 @@ export class Midy {
2540
2612
  const channel = this.channels[channelNumber];
2541
2613
  if (channel.isDrum)
2542
2614
  return;
2543
- scheduleTime ??= this.audioContext.currentTime;
2615
+ if (!(0 <= scheduleTime))
2616
+ scheduleTime = this.audioContext.currentTime;
2544
2617
  channel.modulationDepthRange = value;
2545
2618
  this.updateModulation(channel, scheduleTime);
2546
2619
  }
2547
- allSoundOff(channelNumber, _value, scheduleTime) {
2620
+ setRPGMakerLoop(_channelNumber, _value, scheduleTime) {
2548
2621
  scheduleTime ??= this.audioContext.currentTime;
2622
+ this.loopStart = scheduleTime + this.resumeTime - this.startTime;
2623
+ }
2624
+ allSoundOff(channelNumber, _value, scheduleTime) {
2625
+ if (!(0 <= scheduleTime))
2626
+ scheduleTime = this.audioContext.currentTime;
2549
2627
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2550
2628
  }
2551
2629
  resetChannelStates(channelNumber) {
@@ -2607,7 +2685,8 @@ export class Midy {
2607
2685
  }
2608
2686
  }
2609
2687
  allNotesOff(channelNumber, _value, scheduleTime) {
2610
- scheduleTime ??= this.audioContext.currentTime;
2688
+ if (!(0 <= scheduleTime))
2689
+ scheduleTime = this.audioContext.currentTime;
2611
2690
  return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2612
2691
  }
2613
2692
  omniOff(channelNumber, value, scheduleTime) {
@@ -2659,7 +2738,8 @@ export class Midy {
2659
2738
  }
2660
2739
  }
2661
2740
  GM1SystemOn(scheduleTime) {
2662
- scheduleTime ??= this.audioContext.currentTime;
2741
+ if (!(0 <= scheduleTime))
2742
+ scheduleTime = this.audioContext.currentTime;
2663
2743
  this.mode = "GM1";
2664
2744
  for (let i = 0; i < this.channels.length; i++) {
2665
2745
  this.allSoundOff(i, 0, scheduleTime);
@@ -2672,7 +2752,8 @@ export class Midy {
2672
2752
  this.channels[9].isDrum = true;
2673
2753
  }
2674
2754
  GM2SystemOn(scheduleTime) {
2675
- scheduleTime ??= this.audioContext.currentTime;
2755
+ if (!(0 <= scheduleTime))
2756
+ scheduleTime = this.audioContext.currentTime;
2676
2757
  this.mode = "GM2";
2677
2758
  for (let i = 0; i < this.channels.length; i++) {
2678
2759
  this.allSoundOff(i, 0, scheduleTime);
@@ -2740,7 +2821,8 @@ export class Midy {
2740
2821
  this.setMasterVolume(volume, scheduleTime);
2741
2822
  }
2742
2823
  setMasterVolume(value, scheduleTime) {
2743
- scheduleTime ??= this.audioContext.currentTime;
2824
+ if (!(0 <= scheduleTime))
2825
+ scheduleTime = this.audioContext.currentTime;
2744
2826
  this.masterVolume.gain
2745
2827
  .cancelScheduledValues(scheduleTime)
2746
2828
  .setValueAtTime(value * value, scheduleTime);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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",