@marmooo/midy 0.4.1 → 0.4.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.
@@ -17,23 +17,23 @@ class Note {
17
17
  writable: true,
18
18
  value: void 0
19
19
  });
20
- Object.defineProperty(this, "index", {
20
+ Object.defineProperty(this, "adjustedBaseFreq", {
21
21
  enumerable: true,
22
22
  configurable: true,
23
23
  writable: true,
24
- value: -1
24
+ value: 20000
25
25
  });
26
- Object.defineProperty(this, "ending", {
26
+ Object.defineProperty(this, "index", {
27
27
  enumerable: true,
28
28
  configurable: true,
29
29
  writable: true,
30
- value: false
30
+ value: -1
31
31
  });
32
- Object.defineProperty(this, "pending", {
32
+ Object.defineProperty(this, "ending", {
33
33
  enumerable: true,
34
34
  configurable: true,
35
35
  writable: true,
36
- value: true
36
+ value: false
37
37
  });
38
38
  Object.defineProperty(this, "bufferSource", {
39
39
  enumerable: true,
@@ -110,6 +110,9 @@ class Note {
110
110
  this.noteNumber = noteNumber;
111
111
  this.velocity = velocity;
112
112
  this.startTime = startTime;
113
+ this.ready = new Promise((resolve) => {
114
+ this.resolveReady = resolve;
115
+ });
113
116
  }
114
117
  }
115
118
  const drumExclusiveClassesByKit = new Array(57);
@@ -231,8 +234,15 @@ const pitchEnvelopeKeys = [
231
234
  "playbackRate",
232
235
  ];
233
236
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
234
- class MidyGM2 {
237
+ const defaultPressureValues = new Int8Array([64, 64, 64, 0, 0, 0]);
238
+ function cbToRatio(cb) {
239
+ return Math.pow(10, cb / 200);
240
+ }
241
+ const decayCurve = 1 / (-Math.log(cbToRatio(-1000)));
242
+ const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
243
+ class MidyGM2 extends EventTarget {
235
244
  constructor(audioContext) {
245
+ super();
236
246
  Object.defineProperty(this, "mode", {
237
247
  enumerable: true,
238
248
  configurable: true,
@@ -393,6 +403,26 @@ class MidyGM2 {
393
403
  writable: true,
394
404
  value: false
395
405
  });
406
+ Object.defineProperty(this, "totalTimeEventTypes", {
407
+ enumerable: true,
408
+ configurable: true,
409
+ writable: true,
410
+ value: new Set([
411
+ "noteOff",
412
+ ])
413
+ });
414
+ Object.defineProperty(this, "tempo", {
415
+ enumerable: true,
416
+ configurable: true,
417
+ writable: true,
418
+ value: 1
419
+ });
420
+ Object.defineProperty(this, "loop", {
421
+ enumerable: true,
422
+ configurable: true,
423
+ writable: true,
424
+ value: false
425
+ });
396
426
  Object.defineProperty(this, "playPromise", {
397
427
  enumerable: true,
398
428
  configurable: true,
@@ -506,13 +536,13 @@ class MidyGM2 {
506
536
  this.totalTime = this.calcTotalTime();
507
537
  }
508
538
  cacheVoiceIds() {
509
- const timeline = this.timeline;
539
+ const { channels, timeline, voiceCounter } = this;
510
540
  for (let i = 0; i < timeline.length; i++) {
511
541
  const event = timeline[i];
512
542
  switch (event.type) {
513
543
  case "noteOn": {
514
- const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
515
- this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
544
+ const audioBufferId = this.getVoiceId(channels[event.channel], event.noteNumber, event.velocity);
545
+ voiceCounter.set(audioBufferId, (voiceCounter.get(audioBufferId) ?? 0) + 1);
516
546
  break;
517
547
  }
518
548
  case "controller":
@@ -527,9 +557,9 @@ class MidyGM2 {
527
557
  this.setProgramChange(event.channel, event.programNumber, event.startTime);
528
558
  }
529
559
  }
530
- for (const [audioBufferId, count] of this.voiceCounter) {
560
+ for (const [audioBufferId, count] of voiceCounter) {
531
561
  if (count === 1)
532
- this.voiceCounter.delete(audioBufferId);
562
+ voiceCounter.delete(audioBufferId);
533
563
  }
534
564
  this.GM2SystemOn();
535
565
  }
@@ -538,8 +568,12 @@ class MidyGM2 {
538
568
  const bankTable = this.soundFontTable[programNumber];
539
569
  if (!bankTable)
540
570
  return;
541
- const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
542
- const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
571
+ let bank = channel.isDrum ? 128 : channel.bankLSB;
572
+ if (bankTable[bank] === undefined) {
573
+ if (channel.isDrum)
574
+ return;
575
+ bank = 0;
576
+ }
543
577
  const soundFontIndex = bankTable[bank];
544
578
  if (soundFontIndex === undefined)
545
579
  return;
@@ -565,7 +599,7 @@ class MidyGM2 {
565
599
  resetChannelTable(channel) {
566
600
  channel.controlTable.fill(-1);
567
601
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
568
- channel.channelPressureTable.fill(-1);
602
+ channel.channelPressureTable.set(defaultPressureValues);
569
603
  channel.keyBasedTable.fill(-1);
570
604
  }
571
605
  createChannels(audioContext) {
@@ -581,7 +615,7 @@ class MidyGM2 {
581
615
  sostenutoNotes: [],
582
616
  controlTable: this.initControlTable(),
583
617
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
584
- channelPressureTable: new Int8Array(6).fill(-1),
618
+ channelPressureTable: new Int8Array(defaultPressureValues),
585
619
  keyBasedTable: new Int8Array(128 * 128).fill(-1),
586
620
  keyBasedGainLs: new Array(128),
587
621
  keyBasedGainRs: new Array(128),
@@ -612,19 +646,21 @@ class MidyGM2 {
612
646
  }
613
647
  return bufferSource;
614
648
  }
615
- async scheduleTimelineEvents(scheduleTime, queueIndex) {
649
+ scheduleTimelineEvents(scheduleTime, queueIndex) {
616
650
  const timeOffset = this.resumeTime - this.startTime;
617
651
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
618
652
  const schedulingOffset = this.startDelay - timeOffset;
619
653
  const timeline = this.timeline;
654
+ const inverseTempo = 1 / this.tempo;
620
655
  while (queueIndex < timeline.length) {
621
656
  const event = timeline[queueIndex];
622
- if (lookAheadCheckTime < event.startTime)
657
+ const t = event.startTime * inverseTempo;
658
+ if (lookAheadCheckTime < t)
623
659
  break;
624
- const startTime = event.startTime + schedulingOffset;
660
+ const startTime = t + schedulingOffset;
625
661
  switch (event.type) {
626
662
  case "noteOn":
627
- await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
663
+ this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
628
664
  break;
629
665
  case "noteOff": {
630
666
  this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
@@ -650,8 +686,10 @@ class MidyGM2 {
650
686
  return queueIndex;
651
687
  }
652
688
  getQueueIndex(second) {
653
- for (let i = 0; i < this.timeline.length; i++) {
654
- if (second <= this.timeline[i].startTime) {
689
+ const timeline = this.timeline;
690
+ const inverseTempo = 1 / this.tempo;
691
+ for (let i = 0; i < timeline.length; i++) {
692
+ if (second <= timeline[i].startTime * inverseTempo) {
655
693
  return i;
656
694
  }
657
695
  }
@@ -662,87 +700,120 @@ class MidyGM2 {
662
700
  this.drumExclusiveClassNotes.fill(undefined);
663
701
  this.voiceCache.clear();
664
702
  this.realtimeVoiceCache.clear();
665
- for (let i = 0; i < this.channels.length; i++) {
666
- this.channels[i].scheduledNotes = [];
703
+ const channels = this.channels;
704
+ for (let i = 0; i < channels.length; i++) {
705
+ channels[i].scheduledNotes = [];
667
706
  this.resetChannelStates(i);
668
707
  }
669
708
  }
670
709
  updateStates(queueIndex, nextQueueIndex) {
710
+ const { timeline, resumeTime } = this;
711
+ const inverseTempo = 1 / this.tempo;
712
+ const now = this.audioContext.currentTime;
671
713
  if (nextQueueIndex < queueIndex)
672
714
  queueIndex = 0;
673
715
  for (let i = queueIndex; i < nextQueueIndex; i++) {
674
- const event = this.timeline[i];
716
+ const event = timeline[i];
675
717
  switch (event.type) {
676
718
  case "controller":
677
- this.setControlChange(event.channel, event.controllerType, event.value, 0);
719
+ this.setControlChange(event.channel, event.controllerType, event.value, now - resumeTime + event.startTime * inverseTempo);
678
720
  break;
679
721
  case "programChange":
680
- this.setProgramChange(event.channel, event.programNumber, 0);
722
+ this.setProgramChange(event.channel, event.programNumber, now - resumeTime + event.startTime * inverseTempo);
681
723
  break;
682
724
  case "pitchBend":
683
- this.setPitchBend(event.channel, event.value + 8192, 0);
725
+ this.setPitchBend(event.channel, event.value + 8192, now - resumeTime + event.startTime * inverseTempo);
684
726
  break;
685
727
  case "sysEx":
686
- this.handleSysEx(event.data, 0);
728
+ this.handleSysEx(event.data, now - resumeTime + event.startTime * inverseTempo);
687
729
  }
688
730
  }
689
731
  }
690
732
  async playNotes() {
691
- if (this.audioContext.state === "suspended") {
692
- await this.audioContext.resume();
733
+ const audioContext = this.audioContext;
734
+ if (audioContext.state === "suspended") {
735
+ await audioContext.resume();
693
736
  }
737
+ const paused = this.isPaused;
694
738
  this.isPlaying = true;
695
739
  this.isPaused = false;
696
- this.startTime = this.audioContext.currentTime;
740
+ this.startTime = audioContext.currentTime;
741
+ if (paused) {
742
+ this.dispatchEvent(new Event("resumed"));
743
+ }
744
+ else {
745
+ this.dispatchEvent(new Event("started"));
746
+ }
697
747
  let queueIndex = this.getQueueIndex(this.resumeTime);
698
- let finished = false;
748
+ let exitReason;
699
749
  this.notePromises = [];
700
- while (queueIndex < this.timeline.length) {
701
- const now = this.audioContext.currentTime;
750
+ while (true) {
751
+ const now = audioContext.currentTime;
702
752
  if (0 < this.lastActiveSensing &&
703
753
  this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
704
754
  await this.stopNotes(0, true, now);
705
- await this.audioContext.suspend();
706
- finished = true;
755
+ await audioContext.suspend();
756
+ exitReason = "aborted";
707
757
  break;
708
758
  }
759
+ if (this.totalTime < this.currentTime() ||
760
+ this.timeline.length <= queueIndex) {
761
+ await this.stopNotes(0, true, now);
762
+ if (this.loop) {
763
+ this.resetAllStates();
764
+ this.startTime = audioContext.currentTime;
765
+ this.resumeTime = 0;
766
+ queueIndex = 0;
767
+ this.dispatchEvent(new Event("looped"));
768
+ continue;
769
+ }
770
+ else {
771
+ await audioContext.suspend();
772
+ exitReason = "ended";
773
+ break;
774
+ }
775
+ }
709
776
  if (this.isPausing) {
710
777
  await this.stopNotes(0, true, now);
711
- await this.audioContext.suspend();
712
- this.notePromises = [];
778
+ await audioContext.suspend();
779
+ this.isPausing = false;
780
+ exitReason = "paused";
713
781
  break;
714
782
  }
715
783
  else if (this.isStopping) {
716
784
  await this.stopNotes(0, true, now);
717
- await this.audioContext.suspend();
718
- finished = true;
785
+ await audioContext.suspend();
786
+ this.isStopping = false;
787
+ exitReason = "stopped";
719
788
  break;
720
789
  }
721
790
  else if (this.isSeeking) {
722
- await this.stopNotes(0, true, now);
723
- this.startTime = this.audioContext.currentTime;
791
+ this.stopNotes(0, true, now);
792
+ this.startTime = audioContext.currentTime;
724
793
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
725
794
  this.updateStates(queueIndex, nextQueueIndex);
726
795
  queueIndex = nextQueueIndex;
727
796
  this.isSeeking = false;
797
+ this.dispatchEvent(new Event("seeked"));
728
798
  continue;
729
799
  }
730
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
800
+ queueIndex = this.scheduleTimelineEvents(now, queueIndex);
731
801
  const waitTime = now + this.noteCheckInterval;
732
802
  await this.scheduleTask(() => { }, waitTime);
733
803
  }
734
- if (this.timeline.length <= queueIndex) {
735
- const now = this.audioContext.currentTime;
736
- await this.stopNotes(0, true, now);
737
- await this.audioContext.suspend();
738
- finished = true;
739
- }
740
- if (finished) {
741
- this.notePromises = [];
804
+ if (exitReason !== "paused") {
742
805
  this.resetAllStates();
743
806
  this.lastActiveSensing = 0;
744
807
  }
745
808
  this.isPlaying = false;
809
+ if (exitReason === "paused") {
810
+ this.isPaused = true;
811
+ this.dispatchEvent(new Event("paused"));
812
+ }
813
+ else {
814
+ this.isPaused = false;
815
+ this.dispatchEvent(new Event(exitReason));
816
+ }
746
817
  }
747
818
  ticksToSecond(ticks, secondsPerBeat) {
748
819
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -859,11 +930,13 @@ class MidyGM2 {
859
930
  return Promise.all(promises);
860
931
  }
861
932
  stopNotes(velocity, force, scheduleTime) {
862
- const promises = [];
863
- for (let i = 0; i < this.channels.length; i++) {
864
- promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
933
+ const channels = this.channels;
934
+ for (let i = 0; i < channels.length; i++) {
935
+ this.stopChannelNotes(i, velocity, force, scheduleTime);
865
936
  }
866
- return Promise.all(this.notePromises);
937
+ const stopPromise = Promise.all(this.notePromises);
938
+ this.notePromises = [];
939
+ return stopPromise;
867
940
  }
868
941
  async start() {
869
942
  if (this.isPlaying || this.isPaused)
@@ -879,24 +952,20 @@ class MidyGM2 {
879
952
  return;
880
953
  this.isStopping = true;
881
954
  await this.playPromise;
882
- this.isStopping = false;
883
955
  }
884
956
  async pause() {
885
957
  if (!this.isPlaying || this.isPaused)
886
958
  return;
887
959
  const now = this.audioContext.currentTime;
888
- this.resumeTime = now - this.startTime - this.startDelay;
960
+ this.resumeTime = now + this.resumeTime - this.startTime;
889
961
  this.isPausing = true;
890
962
  await this.playPromise;
891
- this.isPausing = false;
892
- this.isPaused = true;
893
963
  }
894
964
  async resume() {
895
965
  if (!this.isPaused)
896
966
  return;
897
967
  this.playPromise = this.playNotes();
898
968
  await this.playPromise;
899
- this.isPaused = false;
900
969
  }
901
970
  seekTo(second) {
902
971
  this.resumeTime = second;
@@ -905,11 +974,17 @@ class MidyGM2 {
905
974
  }
906
975
  }
907
976
  calcTotalTime() {
977
+ const totalTimeEventTypes = this.totalTimeEventTypes;
978
+ const timeline = this.timeline;
979
+ const inverseTempo = 1 / this.tempo;
908
980
  let totalTime = 0;
909
- for (let i = 0; i < this.timeline.length; i++) {
910
- const event = this.timeline[i];
911
- if (totalTime < event.startTime)
912
- totalTime = event.startTime;
981
+ for (let i = 0; i < timeline.length; i++) {
982
+ const event = timeline[i];
983
+ if (!totalTimeEventTypes.has(event.type))
984
+ continue;
985
+ const t = event.startTime * inverseTempo;
986
+ if (totalTime < t)
987
+ totalTime = t;
913
988
  }
914
989
  return totalTime + this.startDelay;
915
990
  }
@@ -919,19 +994,23 @@ class MidyGM2 {
919
994
  const now = this.audioContext.currentTime;
920
995
  return now + this.resumeTime - this.startTime;
921
996
  }
922
- processScheduledNotes(channel, callback) {
997
+ async processScheduledNotes(channel, callback) {
923
998
  const scheduledNotes = channel.scheduledNotes;
999
+ const tasks = [];
924
1000
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
925
1001
  const note = scheduledNotes[i];
926
1002
  if (!note)
927
1003
  continue;
928
1004
  if (note.ending)
929
1005
  continue;
930
- callback(note);
1006
+ const task = note.ready.then(() => callback(note));
1007
+ tasks.push(task);
931
1008
  }
1009
+ await Promise.all(tasks);
932
1010
  }
933
- processActiveNotes(channel, scheduleTime, callback) {
1011
+ async processActiveNotes(channel, scheduleTime, callback) {
934
1012
  const scheduledNotes = channel.scheduledNotes;
1013
+ const tasks = [];
935
1014
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
936
1015
  const note = scheduledNotes[i];
937
1016
  if (!note)
@@ -940,8 +1019,10 @@ class MidyGM2 {
940
1019
  continue;
941
1020
  if (scheduleTime < note.startTime)
942
1021
  break;
943
- callback(note);
1022
+ const task = note.ready.then(() => callback(note));
1023
+ tasks.push(task);
944
1024
  }
1025
+ await Promise.all(tasks);
945
1026
  }
946
1027
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
947
1028
  const sampleRate = audioContext.sampleRate;
@@ -1085,9 +1166,6 @@ class MidyGM2 {
1085
1166
  feedbackGains,
1086
1167
  };
1087
1168
  }
1088
- cbToRatio(cb) {
1089
- return Math.pow(10, cb / 200);
1090
- }
1091
1169
  rateToCent(rate) {
1092
1170
  return 1200 * Math.log2(rate);
1093
1171
  }
@@ -1206,45 +1284,45 @@ class MidyGM2 {
1206
1284
  }
1207
1285
  setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
1208
1286
  const { voiceParams, startTime } = note;
1209
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1287
+ const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
1210
1288
  (1 + this.getAmplitudeControl(channel));
1211
1289
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1212
- const volDelay = startTime + voiceParams.volDelay;
1213
- const volAttack = volDelay + voiceParams.volAttack;
1214
- const volHold = volAttack + voiceParams.volHold;
1290
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1215
1291
  note.volumeEnvelopeNode.gain
1216
1292
  .cancelScheduledValues(scheduleTime)
1217
- .setValueAtTime(sustainVolume, volHold);
1293
+ .linearRampToValueAtTime(sustainVolume, portamentoTime);
1218
1294
  }
1219
1295
  setVolumeEnvelope(channel, note, scheduleTime) {
1220
1296
  const { voiceParams, startTime } = note;
1221
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1297
+ const attackVolume = cbToRatio(-voiceParams.initialAttenuation) *
1222
1298
  (1 + this.getAmplitudeControl(channel));
1223
1299
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1224
1300
  const volDelay = startTime + voiceParams.volDelay;
1225
1301
  const volAttack = volDelay + voiceParams.volAttack;
1226
1302
  const volHold = volAttack + voiceParams.volHold;
1227
- const volDecay = volHold + voiceParams.volDecay;
1303
+ const decayDuration = voiceParams.volDecay;
1228
1304
  note.volumeEnvelopeNode.gain
1229
1305
  .cancelScheduledValues(scheduleTime)
1230
1306
  .setValueAtTime(0, startTime)
1231
- .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
1232
- .exponentialRampToValueAtTime(attackVolume, volAttack)
1307
+ .setValueAtTime(0, volDelay)
1308
+ .linearRampToValueAtTime(attackVolume, volAttack)
1233
1309
  .setValueAtTime(attackVolume, volHold)
1234
- .linearRampToValueAtTime(sustainVolume, volDecay);
1310
+ .setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
1235
1311
  }
1236
1312
  setPortamentoPitchEnvelope(note, scheduleTime) {
1237
1313
  const baseRate = note.voiceParams.playbackRate;
1314
+ const portamentoTime = note.startTime +
1315
+ this.getPortamentoTime(channel, note);
1238
1316
  note.bufferSource.playbackRate
1239
1317
  .cancelScheduledValues(scheduleTime)
1240
- .setValueAtTime(baseRate, scheduleTime);
1318
+ .linearRampToValueAtTime(baseRate, portamentoTime);
1241
1319
  }
1242
1320
  setPitchEnvelope(note, scheduleTime) {
1243
1321
  const { voiceParams } = note;
1244
1322
  const baseRate = voiceParams.playbackRate;
1245
1323
  note.bufferSource.playbackRate
1246
1324
  .cancelScheduledValues(scheduleTime)
1247
- .setValueAtTime(baseRate, scheduleTime);
1325
+ .setValueAtTime(baseRate, note.startTime);
1248
1326
  const modEnvToPitch = voiceParams.modEnvToPitch;
1249
1327
  if (modEnvToPitch === 0)
1250
1328
  return;
@@ -1254,12 +1332,12 @@ class MidyGM2 {
1254
1332
  const modDelay = note.startTime + voiceParams.modDelay;
1255
1333
  const modAttack = modDelay + voiceParams.modAttack;
1256
1334
  const modHold = modAttack + voiceParams.modHold;
1257
- const modDecay = modHold + voiceParams.modDecay;
1335
+ const decayDuration = voiceParams.modDecay;
1258
1336
  note.bufferSource.playbackRate
1259
1337
  .setValueAtTime(baseRate, modDelay)
1260
- .exponentialRampToValueAtTime(peekRate, modAttack)
1338
+ .linearRampToValueAtTime(peekRate, modAttack)
1261
1339
  .setValueAtTime(peekRate, modHold)
1262
- .linearRampToValueAtTime(baseRate, modDecay);
1340
+ .setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
1263
1341
  }
1264
1342
  clampCutoffFrequency(frequency) {
1265
1343
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -1268,17 +1346,18 @@ class MidyGM2 {
1268
1346
  }
1269
1347
  setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1270
1348
  const { voiceParams, startTime } = note;
1271
- const softPedalFactor = this.getSoftPedalFactor(channel, note);
1349
+ const scale = this.getSoftPedalFactor(channel, note);
1272
1350
  const baseCent = voiceParams.initialFilterFc +
1273
1351
  this.getFilterCutoffControl(channel);
1274
- const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1275
- const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1276
- const sustainFreq = baseFreq +
1277
- (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1278
- const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1279
- const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1352
+ const sustainCent = baseCent +
1353
+ voiceParams.modEnvToFilterFc * (1 - voiceParams.modSustain);
1354
+ const baseFreq = this.centToHz(baseCent);
1355
+ const sustainFreq = this.centToHz(sustainCent);
1356
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq * scale);
1357
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq * scale);
1280
1358
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1281
1359
  const modDelay = startTime + voiceParams.modDelay;
1360
+ note.adjustedBaseFreq = adjustedSustainFreq;
1282
1361
  note.filterNode.frequency
1283
1362
  .cancelScheduledValues(scheduleTime)
1284
1363
  .setValueAtTime(adjustedBaseFreq, startTime)
@@ -1287,40 +1366,44 @@ class MidyGM2 {
1287
1366
  }
1288
1367
  setFilterEnvelope(channel, note, scheduleTime) {
1289
1368
  const { voiceParams, startTime } = note;
1290
- const softPedalFactor = this.getSoftPedalFactor(channel, note);
1369
+ const modEnvToFilterFc = voiceParams.modEnvToFilterFc;
1291
1370
  const baseCent = voiceParams.initialFilterFc +
1292
1371
  this.getFilterCutoffControl(channel);
1372
+ const peekCent = baseCent + modEnvToFilterFc;
1373
+ const sustainCent = baseCent +
1374
+ modEnvToFilterFc * (1 - voiceParams.modSustain);
1375
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1293
1376
  const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1294
- const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
1295
- softPedalFactor;
1296
- const sustainFreq = baseFreq +
1297
- (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1377
+ const peekFreq = this.centToHz(peekCent) * softPedalFactor;
1378
+ const sustainFreq = this.centToHz(sustainCent) * softPedalFactor;
1298
1379
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1299
1380
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
1300
1381
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1301
1382
  const modDelay = startTime + voiceParams.modDelay;
1302
1383
  const modAttack = modDelay + voiceParams.modAttack;
1303
1384
  const modHold = modAttack + voiceParams.modHold;
1304
- const modDecay = modHold + voiceParams.modDecay;
1385
+ const decayDuration = voiceParams.modDecay;
1386
+ note.adjustedBaseFreq = adjustedBaseFreq;
1305
1387
  note.filterNode.frequency
1306
1388
  .cancelScheduledValues(scheduleTime)
1307
1389
  .setValueAtTime(adjustedBaseFreq, startTime)
1308
1390
  .setValueAtTime(adjustedBaseFreq, modDelay)
1309
- .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
1391
+ .linearRampToValueAtTime(adjustedPeekFreq, modAttack)
1310
1392
  .setValueAtTime(adjustedPeekFreq, modHold)
1311
- .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
1393
+ .setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
1312
1394
  }
1313
1395
  startModulation(channel, note, scheduleTime) {
1396
+ const audioContext = this.audioContext;
1314
1397
  const { voiceParams } = note;
1315
- note.modulationLFO = new OscillatorNode(this.audioContext, {
1398
+ note.modulationLFO = new OscillatorNode(audioContext, {
1316
1399
  frequency: this.centToHz(voiceParams.freqModLFO),
1317
1400
  });
1318
- note.filterDepth = new GainNode(this.audioContext, {
1401
+ note.filterDepth = new GainNode(audioContext, {
1319
1402
  gain: voiceParams.modLfoToFilterFc,
1320
1403
  });
1321
- note.modulationDepth = new GainNode(this.audioContext);
1404
+ note.modulationDepth = new GainNode(audioContext);
1322
1405
  this.setModLfoToPitch(channel, note, scheduleTime);
1323
- note.volumeDepth = new GainNode(this.audioContext);
1406
+ note.volumeDepth = new GainNode(audioContext);
1324
1407
  this.setModLfoToVolume(note, scheduleTime);
1325
1408
  note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
1326
1409
  note.modulationLFO.connect(note.filterDepth);
@@ -1373,7 +1456,8 @@ class MidyGM2 {
1373
1456
  }
1374
1457
  }
1375
1458
  async setNoteAudioNode(channel, note, realtime) {
1376
- const now = this.audioContext.currentTime;
1459
+ const audioContext = this.audioContext;
1460
+ const now = audioContext.currentTime;
1377
1461
  const { noteNumber, velocity, startTime } = note;
1378
1462
  const state = channel.state;
1379
1463
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
@@ -1381,8 +1465,8 @@ class MidyGM2 {
1381
1465
  note.voiceParams = voiceParams;
1382
1466
  const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
1383
1467
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1384
- note.volumeEnvelopeNode = new GainNode(this.audioContext);
1385
- note.filterNode = new BiquadFilterNode(this.audioContext, {
1468
+ note.volumeEnvelopeNode = new GainNode(audioContext);
1469
+ note.filterNode = new BiquadFilterNode(audioContext, {
1386
1470
  type: "lowpass",
1387
1471
  Q: voiceParams.initialFilterQ / 10, // dB
1388
1472
  });
@@ -1495,8 +1579,12 @@ class MidyGM2 {
1495
1579
  const bankTable = this.soundFontTable[programNumber];
1496
1580
  if (!bankTable)
1497
1581
  return;
1498
- const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
1499
- const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
1582
+ let bank = channel.isDrum ? 128 : channel.bankLSB;
1583
+ if (bankTable[bank] === undefined) {
1584
+ if (channel.isDrum)
1585
+ return;
1586
+ bank = 0;
1587
+ }
1500
1588
  const soundFontIndex = bankTable[bank];
1501
1589
  if (soundFontIndex === undefined)
1502
1590
  return;
@@ -1506,11 +1594,7 @@ class MidyGM2 {
1506
1594
  return;
1507
1595
  await this.setNoteAudioNode(channel, note, realtime);
1508
1596
  this.setNoteRouting(channelNumber, note, startTime);
1509
- note.pending = false;
1510
- const off = note.offEvent;
1511
- if (off) {
1512
- this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
1513
- }
1597
+ note.resolveReady();
1514
1598
  }
1515
1599
  disconnectNote(note) {
1516
1600
  note.bufferSource.disconnect();
@@ -1534,27 +1618,27 @@ class MidyGM2 {
1534
1618
  }
1535
1619
  releaseNote(channel, note, endTime) {
1536
1620
  endTime ??= this.audioContext.currentTime;
1537
- const volRelease = endTime + note.voiceParams.volRelease;
1621
+ const duration = note.voiceParams.volRelease;
1622
+ const volRelease = endTime + duration;
1538
1623
  const modRelease = endTime + note.voiceParams.modRelease;
1539
- const stopTime = Math.min(volRelease, modRelease);
1540
1624
  note.filterNode.frequency
1541
1625
  .cancelScheduledValues(endTime)
1542
- .linearRampToValueAtTime(0, modRelease);
1626
+ .linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
1543
1627
  note.volumeEnvelopeNode.gain
1544
1628
  .cancelScheduledValues(endTime)
1545
- .linearRampToValueAtTime(0, volRelease);
1629
+ .setTargetAtTime(0, endTime, duration * releaseCurve);
1546
1630
  return new Promise((resolve) => {
1547
1631
  this.scheduleTask(() => {
1548
1632
  const bufferSource = note.bufferSource;
1549
1633
  bufferSource.loop = false;
1550
- bufferSource.stop(stopTime);
1634
+ bufferSource.stop(volRelease);
1551
1635
  this.disconnectNote(note);
1552
1636
  channel.scheduledNotes[note.index] = undefined;
1553
1637
  resolve();
1554
- }, stopTime);
1638
+ }, volRelease);
1555
1639
  });
1556
1640
  }
1557
- noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1641
+ noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1558
1642
  const channel = this.channels[channelNumber];
1559
1643
  const state = channel.state;
1560
1644
  if (!force) {
@@ -1573,13 +1657,11 @@ class MidyGM2 {
1573
1657
  if (index < 0)
1574
1658
  return;
1575
1659
  const note = channel.scheduledNotes[index];
1576
- if (note.pending) {
1577
- note.offEvent = { velocity, startTime: endTime };
1578
- return;
1579
- }
1580
1660
  note.ending = true;
1581
1661
  this.setNoteIndex(channel, index);
1582
- const promise = this.releaseNote(channel, note, endTime);
1662
+ const promise = note.ready.then(() => {
1663
+ return this.releaseNote(channel, note, endTime);
1664
+ });
1583
1665
  this.notePromises.push(promise);
1584
1666
  return promise;
1585
1667
  }
@@ -1675,6 +1757,8 @@ class MidyGM2 {
1675
1757
  }
1676
1758
  }
1677
1759
  setChannelPressure(channelNumber, value, scheduleTime) {
1760
+ if (!(0 <= scheduleTime))
1761
+ scheduleTime = this.audioContext.currentTime;
1678
1762
  const channel = this.channels[channelNumber];
1679
1763
  if (channel.isDrum)
1680
1764
  return;
@@ -1690,7 +1774,7 @@ class MidyGM2 {
1690
1774
  this.processActiveNotes(channel, scheduleTime, (note) => {
1691
1775
  this.setEffects(channel, note, table, scheduleTime);
1692
1776
  });
1693
- this.applyVoiceParams(channel, 13);
1777
+ this.applyVoiceParams(channel, 13, scheduleTime);
1694
1778
  }
1695
1779
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1696
1780
  const pitchBend = msb * 128 + lsb;
@@ -1700,7 +1784,8 @@ class MidyGM2 {
1700
1784
  const channel = this.channels[channelNumber];
1701
1785
  if (channel.isDrum)
1702
1786
  return;
1703
- scheduleTime ??= this.audioContext.currentTime;
1787
+ if (!(0 <= scheduleTime))
1788
+ scheduleTime = this.audioContext.currentTime;
1704
1789
  const state = channel.state;
1705
1790
  const prev = state.pitchWheel * 2 - 1;
1706
1791
  const next = (value - 8192) / 8192;
@@ -1747,7 +1832,7 @@ class MidyGM2 {
1747
1832
  }
1748
1833
  setModLfoToVolume(channel, note, scheduleTime) {
1749
1834
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1750
- const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1835
+ const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
1751
1836
  const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
1752
1837
  (1 + this.getLFOAmplitudeDepth(channel));
1753
1838
  note.volumeDepth.gain
@@ -1994,7 +2079,8 @@ class MidyGM2 {
1994
2079
  const channel = this.channels[channelNumber];
1995
2080
  if (channel.isDrum)
1996
2081
  return;
1997
- scheduleTime ??= this.audioContext.currentTime;
2082
+ if (!(0 <= scheduleTime))
2083
+ scheduleTime = this.audioContext.currentTime;
1998
2084
  channel.state.modulationDepthMSB = modulation / 127;
1999
2085
  this.updateModulation(channel, scheduleTime);
2000
2086
  }
@@ -2017,7 +2103,8 @@ class MidyGM2 {
2017
2103
  });
2018
2104
  }
2019
2105
  setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
2020
- scheduleTime ??= this.audioContext.currentTime;
2106
+ if (!(0 <= scheduleTime))
2107
+ scheduleTime = this.audioContext.currentTime;
2021
2108
  const channel = this.channels[channelNumber];
2022
2109
  channel.state.portamentoTimeMSB = portamentoTime / 127;
2023
2110
  if (channel.isDrum)
@@ -2025,7 +2112,8 @@ class MidyGM2 {
2025
2112
  this.updatePortamento(channel, scheduleTime);
2026
2113
  }
2027
2114
  setVolume(channelNumber, volume, scheduleTime) {
2028
- scheduleTime ??= this.audioContext.currentTime;
2115
+ if (!(0 <= scheduleTime))
2116
+ scheduleTime = this.audioContext.currentTime;
2029
2117
  const channel = this.channels[channelNumber];
2030
2118
  channel.state.volumeMSB = volume / 127;
2031
2119
  if (channel.isDrum) {
@@ -2045,7 +2133,8 @@ class MidyGM2 {
2045
2133
  };
2046
2134
  }
2047
2135
  setPan(channelNumber, pan, scheduleTime) {
2048
- scheduleTime ??= this.audioContext.currentTime;
2136
+ if (!(0 <= scheduleTime))
2137
+ scheduleTime = this.audioContext.currentTime;
2049
2138
  const channel = this.channels[channelNumber];
2050
2139
  channel.state.panMSB = pan / 127;
2051
2140
  if (channel.isDrum) {
@@ -2058,7 +2147,8 @@ class MidyGM2 {
2058
2147
  }
2059
2148
  }
2060
2149
  setExpression(channelNumber, expression, scheduleTime) {
2061
- scheduleTime ??= this.audioContext.currentTime;
2150
+ if (!(0 <= scheduleTime))
2151
+ scheduleTime = this.audioContext.currentTime;
2062
2152
  const channel = this.channels[channelNumber];
2063
2153
  channel.state.expressionMSB = expression / 127;
2064
2154
  this.updateChannelVolume(channel, scheduleTime);
@@ -2107,7 +2197,8 @@ class MidyGM2 {
2107
2197
  const channel = this.channels[channelNumber];
2108
2198
  if (channel.isDrum)
2109
2199
  return;
2110
- scheduleTime ??= this.audioContext.currentTime;
2200
+ if (!(0 <= scheduleTime))
2201
+ scheduleTime = this.audioContext.currentTime;
2111
2202
  channel.state.sustainPedal = value / 127;
2112
2203
  if (64 <= value) {
2113
2204
  this.processScheduledNotes(channel, (note) => {
@@ -2125,7 +2216,8 @@ class MidyGM2 {
2125
2216
  const channel = this.channels[channelNumber];
2126
2217
  if (channel.isDrum)
2127
2218
  return;
2128
- scheduleTime ??= this.audioContext.currentTime;
2219
+ if (!(0 <= scheduleTime))
2220
+ scheduleTime = this.audioContext.currentTime;
2129
2221
  channel.state.portamento = value / 127;
2130
2222
  this.updatePortamento(channel, scheduleTime);
2131
2223
  }
@@ -2133,7 +2225,8 @@ class MidyGM2 {
2133
2225
  const channel = this.channels[channelNumber];
2134
2226
  if (channel.isDrum)
2135
2227
  return;
2136
- scheduleTime ??= this.audioContext.currentTime;
2228
+ if (!(0 <= scheduleTime))
2229
+ scheduleTime = this.audioContext.currentTime;
2137
2230
  channel.state.sostenutoPedal = value / 127;
2138
2231
  if (64 <= value) {
2139
2232
  const sostenutoNotes = [];
@@ -2154,7 +2247,8 @@ class MidyGM2 {
2154
2247
  if (channel.isDrum)
2155
2248
  return;
2156
2249
  const state = channel.state;
2157
- scheduleTime ??= this.audioContext.currentTime;
2250
+ if (!(0 <= scheduleTime))
2251
+ scheduleTime = this.audioContext.currentTime;
2158
2252
  state.softPedal = softPedal / 127;
2159
2253
  this.processScheduledNotes(channel, (note) => {
2160
2254
  if (this.isPortamento(channel, note)) {
@@ -2168,7 +2262,8 @@ class MidyGM2 {
2168
2262
  });
2169
2263
  }
2170
2264
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
2171
- scheduleTime ??= this.audioContext.currentTime;
2265
+ if (!(0 <= scheduleTime))
2266
+ scheduleTime = this.audioContext.currentTime;
2172
2267
  const channel = this.channels[channelNumber];
2173
2268
  const state = channel.state;
2174
2269
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2177,7 +2272,8 @@ class MidyGM2 {
2177
2272
  });
2178
2273
  }
2179
2274
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2180
- scheduleTime ??= this.audioContext.currentTime;
2275
+ if (!(0 <= scheduleTime))
2276
+ scheduleTime = this.audioContext.currentTime;
2181
2277
  const channel = this.channels[channelNumber];
2182
2278
  const state = channel.state;
2183
2279
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2251,7 +2347,8 @@ class MidyGM2 {
2251
2347
  const channel = this.channels[channelNumber];
2252
2348
  if (channel.isDrum)
2253
2349
  return;
2254
- scheduleTime ??= this.audioContext.currentTime;
2350
+ if (!(0 <= scheduleTime))
2351
+ scheduleTime = this.audioContext.currentTime;
2255
2352
  const state = channel.state;
2256
2353
  const prev = state.pitchWheelSensitivity;
2257
2354
  const next = value / 12800;
@@ -2271,7 +2368,8 @@ class MidyGM2 {
2271
2368
  const channel = this.channels[channelNumber];
2272
2369
  if (channel.isDrum)
2273
2370
  return;
2274
- scheduleTime ??= this.audioContext.currentTime;
2371
+ if (!(0 <= scheduleTime))
2372
+ scheduleTime = this.audioContext.currentTime;
2275
2373
  const prev = channel.fineTuning;
2276
2374
  const next = value;
2277
2375
  channel.fineTuning = next;
@@ -2288,7 +2386,8 @@ class MidyGM2 {
2288
2386
  const channel = this.channels[channelNumber];
2289
2387
  if (channel.isDrum)
2290
2388
  return;
2291
- scheduleTime ??= this.audioContext.currentTime;
2389
+ if (!(0 <= scheduleTime))
2390
+ scheduleTime = this.audioContext.currentTime;
2292
2391
  const prev = channel.coarseTuning;
2293
2392
  const next = value;
2294
2393
  channel.coarseTuning = next;
@@ -2305,12 +2404,14 @@ class MidyGM2 {
2305
2404
  const channel = this.channels[channelNumber];
2306
2405
  if (channel.isDrum)
2307
2406
  return;
2308
- scheduleTime ??= this.audioContext.currentTime;
2407
+ if (!(0 <= scheduleTime))
2408
+ scheduleTime = this.audioContext.currentTime;
2309
2409
  channel.modulationDepthRange = value;
2310
2410
  this.updateModulation(channel, scheduleTime);
2311
2411
  }
2312
2412
  allSoundOff(channelNumber, _value, scheduleTime) {
2313
- scheduleTime ??= this.audioContext.currentTime;
2413
+ if (!(0 <= scheduleTime))
2414
+ scheduleTime = this.audioContext.currentTime;
2314
2415
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2315
2416
  }
2316
2417
  resetChannelStates(channelNumber) {
@@ -2369,7 +2470,8 @@ class MidyGM2 {
2369
2470
  }
2370
2471
  }
2371
2472
  allNotesOff(channelNumber, _value, scheduleTime) {
2372
- scheduleTime ??= this.audioContext.currentTime;
2473
+ if (!(0 <= scheduleTime))
2474
+ scheduleTime = this.audioContext.currentTime;
2373
2475
  return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2374
2476
  }
2375
2477
  omniOff(channelNumber, value, scheduleTime) {
@@ -2418,30 +2520,34 @@ class MidyGM2 {
2418
2520
  }
2419
2521
  }
2420
2522
  GM1SystemOn(scheduleTime) {
2421
- scheduleTime ??= this.audioContext.currentTime;
2523
+ const channels = this.channels;
2524
+ if (!(0 <= scheduleTime))
2525
+ scheduleTime = this.audioContext.currentTime;
2422
2526
  this.mode = "GM1";
2423
- for (let i = 0; i < this.channels.length; i++) {
2527
+ for (let i = 0; i < channels.length; i++) {
2424
2528
  this.allSoundOff(i, 0, scheduleTime);
2425
- const channel = this.channels[i];
2529
+ const channel = channels[i];
2426
2530
  channel.bankMSB = 0;
2427
2531
  channel.bankLSB = 0;
2428
2532
  channel.isDrum = false;
2429
2533
  }
2430
- this.channels[9].bankMSB = 1;
2431
- this.channels[9].isDrum = true;
2534
+ channels[9].bankMSB = 1;
2535
+ channels[9].isDrum = true;
2432
2536
  }
2433
2537
  GM2SystemOn(scheduleTime) {
2434
- scheduleTime ??= this.audioContext.currentTime;
2538
+ const channels = this.channels;
2539
+ if (!(0 <= scheduleTime))
2540
+ scheduleTime = this.audioContext.currentTime;
2435
2541
  this.mode = "GM2";
2436
- for (let i = 0; i < this.channels.length; i++) {
2542
+ for (let i = 0; i < channels.length; i++) {
2437
2543
  this.allSoundOff(i, 0, scheduleTime);
2438
- const channel = this.channels[i];
2544
+ const channel = channels[i];
2439
2545
  channel.bankMSB = 121;
2440
2546
  channel.bankLSB = 0;
2441
2547
  channel.isDrum = false;
2442
2548
  }
2443
- this.channels[9].bankMSB = 120;
2444
- this.channels[9].isDrum = true;
2549
+ channels[9].bankMSB = 120;
2550
+ channels[9].isDrum = true;
2445
2551
  }
2446
2552
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
2447
2553
  switch (data[2]) {
@@ -2486,7 +2592,8 @@ class MidyGM2 {
2486
2592
  this.setMasterVolume(volume, scheduleTime);
2487
2593
  }
2488
2594
  setMasterVolume(value, scheduleTime) {
2489
- scheduleTime ??= this.audioContext.currentTime;
2595
+ if (!(0 <= scheduleTime))
2596
+ scheduleTime = this.audioContext.currentTime;
2490
2597
  this.masterVolume.gain
2491
2598
  .cancelScheduledValues(scheduleTime)
2492
2599
  .setValueAtTime(value * value, scheduleTime);
@@ -2754,9 +2861,9 @@ class MidyGM2 {
2754
2861
  getAmplitudeControl(channel) {
2755
2862
  const channelPressureRaw = channel.channelPressureTable[2];
2756
2863
  const channelPressure = (0 <= channelPressureRaw)
2757
- ? channelPressureRaw * channel.state.channelPressure
2864
+ ? channel.state.channelPressure * 127 / channelPressureRaw
2758
2865
  : 0;
2759
- return channelPressure / 64;
2866
+ return channelPressure;
2760
2867
  }
2761
2868
  getLFOPitchDepth(channel) {
2762
2869
  const channelPressureRaw = channel.channelPressureTable[3];
@@ -2780,27 +2887,27 @@ class MidyGM2 {
2780
2887
  return channelPressure / 127;
2781
2888
  }
2782
2889
  setEffects(channel, note, table, scheduleTime) {
2783
- if (0 <= table[0])
2890
+ if (0 < table[0])
2784
2891
  this.updateDetune(channel, note, scheduleTime);
2785
2892
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2786
- if (0 <= table[1]) {
2893
+ if (0 < table[1]) {
2787
2894
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2788
2895
  }
2789
- if (0 <= table[2]) {
2896
+ if (0 < table[2]) {
2790
2897
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2791
2898
  }
2792
2899
  }
2793
2900
  else {
2794
- if (0 <= table[1])
2901
+ if (0 < table[1])
2795
2902
  this.setFilterEnvelope(channel, note, scheduleTime);
2796
- if (0 <= table[2])
2903
+ if (0 < table[2])
2797
2904
  this.setVolumeEnvelope(channel, note, scheduleTime);
2798
2905
  }
2799
- if (0 <= table[3])
2906
+ if (0 < table[3])
2800
2907
  this.setModLfoToPitch(channel, note, scheduleTime);
2801
- if (0 <= table[4])
2908
+ if (0 < table[4])
2802
2909
  this.setModLfoToFilterFc(channel, note, scheduleTime);
2803
- if (0 <= table[5])
2910
+ if (0 < table[5])
2804
2911
  this.setModLfoToVolume(channel, note, scheduleTime);
2805
2912
  }
2806
2913
  handlePressureSysEx(data, tableName, scheduleTime) {