@marmooo/midy 0.4.2 → 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.
package/README.md CHANGED
@@ -23,6 +23,7 @@ This library provides several files depending on the implementation level.
23
23
  - [@marmooo/midi-player](https://marmooo.github.io/midi-player/) - GUI library
24
24
  - [Humidy](https://marmooo.github.io/humidy/) - GM2 MIDI mixer app
25
25
  - [Timidy](https://marmooo.github.io/timidy/) - Timidity++ style MIDI player
26
+ - [4x4pad](https://marmooo.github.io/4x4pad/) - 4x4 grid style MIDI controller
26
27
 
27
28
  ## Support Status
28
29
 
package/esm/midy-GM1.d.ts CHANGED
@@ -31,6 +31,8 @@ export class MidyGM1 extends EventTarget {
31
31
  isPaused: boolean;
32
32
  isStopping: boolean;
33
33
  isSeeking: boolean;
34
+ totalTimeEventTypes: Set<string>;
35
+ tempo: number;
34
36
  loop: boolean;
35
37
  playPromise: any;
36
38
  timeline: any[];
@@ -94,7 +96,6 @@ export class MidyGM1 extends EventTarget {
94
96
  currentTime(): number;
95
97
  processScheduledNotes(channel: any, callback: any): Promise<void>;
96
98
  processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
97
- cbToRatio(cb: any): number;
98
99
  rateToCent(rate: any): number;
99
100
  centToRate(cent: any): number;
100
101
  centToHz(cent: any): number;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AA+FA;IA2BE;;;;;;;;;;;MAWE;IAEF,+BAgBC;IAvDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,cAAa;IACb,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IAiBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAWC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,4DASC;IAED,gEAoDC;IAED,mCAOC;IAED,uBASC;IAED,yDAgCC;IAED,2BAyEC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,4GAkCC;IAED,uEAwCC;IAED,0EAiBC;IAED,oEASC;IAED,0FAoBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAmBC;IAED,6CAUC;IAED,qDAUC;IAED,qFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,sFA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAYC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAMC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCASC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAuGA;IA+BE;;;;;;;;;;;MAWE;IAEF,+BAgBC;IA3DD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iCAEG;IACH,cAAU;IACV,cAAa;IACb,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IAiBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAeC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,4DASC;IAED,gEAsDC;IAED,mCASC;IAED,uBAUC;IAED,yDAqCC;IAED,2BA0EC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAQC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,wBAYC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDA6BC;IAED,kEAqBC;IAED,4GAkCC;IAED,uEAyCC;IAED,0EAiBC;IAED,oEASC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAmBC;IAED,6CAUC;IAED,qDAUC;IAED,qFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,sFA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAYC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAMC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAUC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
package/esm/midy-GM1.js CHANGED
@@ -14,6 +14,12 @@ class Note {
14
14
  writable: true,
15
15
  value: void 0
16
16
  });
17
+ Object.defineProperty(this, "adjustedBaseFreq", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: 20000
22
+ });
17
23
  Object.defineProperty(this, "index", {
18
24
  enumerable: true,
19
25
  configurable: true,
@@ -147,6 +153,11 @@ const pitchEnvelopeKeys = [
147
153
  "playbackRate",
148
154
  ];
149
155
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
156
+ function cbToRatio(cb) {
157
+ return Math.pow(10, cb / 200);
158
+ }
159
+ const decayCurve = 1 / (-Math.log(cbToRatio(-1000)));
160
+ const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
150
161
  export class MidyGM1 extends EventTarget {
151
162
  constructor(audioContext) {
152
163
  super();
@@ -264,6 +275,20 @@ export class MidyGM1 extends EventTarget {
264
275
  writable: true,
265
276
  value: false
266
277
  });
278
+ Object.defineProperty(this, "totalTimeEventTypes", {
279
+ enumerable: true,
280
+ configurable: true,
281
+ writable: true,
282
+ value: new Set([
283
+ "noteOff",
284
+ ])
285
+ });
286
+ Object.defineProperty(this, "tempo", {
287
+ enumerable: true,
288
+ configurable: true,
289
+ writable: true,
290
+ value: 1
291
+ });
267
292
  Object.defineProperty(this, "loop", {
268
293
  enumerable: true,
269
294
  configurable: true,
@@ -372,13 +397,13 @@ export class MidyGM1 extends EventTarget {
372
397
  this.totalTime = this.calcTotalTime();
373
398
  }
374
399
  cacheVoiceIds() {
375
- const timeline = this.timeline;
400
+ const { channels, timeline, voiceCounter } = this;
376
401
  for (let i = 0; i < timeline.length; i++) {
377
402
  const event = timeline[i];
378
403
  switch (event.type) {
379
404
  case "noteOn": {
380
- const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
381
- this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
405
+ const audioBufferId = this.getVoiceId(channels[event.channel], event.noteNumber, event.velocity);
406
+ voiceCounter.set(audioBufferId, (voiceCounter.get(audioBufferId) ?? 0) + 1);
382
407
  break;
383
408
  }
384
409
  case "controller":
@@ -393,9 +418,9 @@ export class MidyGM1 extends EventTarget {
393
418
  this.setProgramChange(event.channel, event.programNumber, event.startTime);
394
419
  }
395
420
  }
396
- for (const [audioBufferId, count] of this.voiceCounter) {
421
+ for (const [audioBufferId, count] of voiceCounter) {
397
422
  if (count === 1)
398
- this.voiceCounter.delete(audioBufferId);
423
+ voiceCounter.delete(audioBufferId);
399
424
  }
400
425
  this.GM1SystemOn();
401
426
  }
@@ -404,7 +429,12 @@ export class MidyGM1 extends EventTarget {
404
429
  const bankTable = this.soundFontTable[programNumber];
405
430
  if (!bankTable)
406
431
  return;
407
- const bank = channel.isDrum ? 128 : 0;
432
+ let bank = channel.isDrum ? 128 : 0;
433
+ if (bankTable[bank] === undefined) {
434
+ if (channel.isDrum)
435
+ return;
436
+ bank = 0;
437
+ }
408
438
  const soundFontIndex = bankTable[bank];
409
439
  if (soundFontIndex === undefined)
410
440
  return;
@@ -462,11 +492,13 @@ export class MidyGM1 extends EventTarget {
462
492
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
463
493
  const schedulingOffset = this.startDelay - timeOffset;
464
494
  const timeline = this.timeline;
495
+ const inverseTempo = 1 / this.tempo;
465
496
  while (queueIndex < timeline.length) {
466
497
  const event = timeline[queueIndex];
467
- if (lookAheadCheckTime < event.startTime)
498
+ const t = event.startTime * inverseTempo;
499
+ if (lookAheadCheckTime < t)
468
500
  break;
469
- const startTime = event.startTime + schedulingOffset;
501
+ const startTime = t + schedulingOffset;
470
502
  switch (event.type) {
471
503
  case "noteOn":
472
504
  this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
@@ -492,8 +524,10 @@ export class MidyGM1 extends EventTarget {
492
524
  return queueIndex;
493
525
  }
494
526
  getQueueIndex(second) {
495
- for (let i = 0; i < this.timeline.length; i++) {
496
- if (second <= this.timeline[i].startTime) {
527
+ const timeline = this.timeline;
528
+ const inverseTempo = 1 / this.tempo;
529
+ for (let i = 0; i < timeline.length; i++) {
530
+ if (second <= timeline[i].startTime * inverseTempo) {
497
531
  return i;
498
532
  }
499
533
  }
@@ -504,40 +538,44 @@ export class MidyGM1 extends EventTarget {
504
538
  this.drumExclusiveClassNotes.fill(undefined);
505
539
  this.voiceCache.clear();
506
540
  this.realtimeVoiceCache.clear();
507
- for (let i = 0; i < this.channels.length; i++) {
508
- this.channels[i].scheduledNotes = [];
541
+ const channels = this.channels;
542
+ for (let i = 0; i < channels.length; i++) {
543
+ channels[i].scheduledNotes = [];
509
544
  this.resetChannelStates(i);
510
545
  }
511
546
  }
512
547
  updateStates(queueIndex, nextQueueIndex) {
548
+ const { timeline, resumeTime } = this;
549
+ const inverseTempo = 1 / this.tempo;
513
550
  const now = this.audioContext.currentTime;
514
551
  if (nextQueueIndex < queueIndex)
515
552
  queueIndex = 0;
516
553
  for (let i = queueIndex; i < nextQueueIndex; i++) {
517
- const event = this.timeline[i];
554
+ const event = timeline[i];
518
555
  switch (event.type) {
519
556
  case "controller":
520
- this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
557
+ this.setControlChange(event.channel, event.controllerType, event.value, now - resumeTime + event.startTime * inverseTempo);
521
558
  break;
522
559
  case "programChange":
523
- this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
560
+ this.setProgramChange(event.channel, event.programNumber, now - resumeTime + event.startTime * inverseTempo);
524
561
  break;
525
562
  case "pitchBend":
526
- this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
563
+ this.setPitchBend(event.channel, event.value + 8192, now - resumeTime + event.startTime * inverseTempo);
527
564
  break;
528
565
  case "sysEx":
529
- this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
566
+ this.handleSysEx(event.data, now - resumeTime + event.startTime * inverseTempo);
530
567
  }
531
568
  }
532
569
  }
533
570
  async playNotes() {
534
- if (this.audioContext.state === "suspended") {
535
- await this.audioContext.resume();
571
+ const audioContext = this.audioContext;
572
+ if (audioContext.state === "suspended") {
573
+ await audioContext.resume();
536
574
  }
537
575
  const paused = this.isPaused;
538
576
  this.isPlaying = true;
539
577
  this.isPaused = false;
540
- this.startTime = this.audioContext.currentTime;
578
+ this.startTime = audioContext.currentTime;
541
579
  if (paused) {
542
580
  this.dispatchEvent(new Event("resumed"));
543
581
  }
@@ -548,42 +586,41 @@ export class MidyGM1 extends EventTarget {
548
586
  let exitReason;
549
587
  this.notePromises = [];
550
588
  while (true) {
551
- const now = this.audioContext.currentTime;
552
- if (this.timeline.length <= queueIndex) {
589
+ const now = audioContext.currentTime;
590
+ if (this.totalTime < this.currentTime() ||
591
+ this.timeline.length <= queueIndex) {
553
592
  await this.stopNotes(0, true, now);
554
593
  if (this.loop) {
555
- this.notePromises = [];
556
594
  this.resetAllStates();
557
- this.startTime = this.audioContext.currentTime;
595
+ this.startTime = audioContext.currentTime;
558
596
  this.resumeTime = 0;
559
597
  queueIndex = 0;
560
598
  this.dispatchEvent(new Event("looped"));
561
599
  continue;
562
600
  }
563
601
  else {
564
- await this.audioContext.suspend();
602
+ await audioContext.suspend();
565
603
  exitReason = "ended";
566
604
  break;
567
605
  }
568
606
  }
569
607
  if (this.isPausing) {
570
608
  await this.stopNotes(0, true, now);
571
- await this.audioContext.suspend();
572
- this.notePromises = [];
609
+ await audioContext.suspend();
573
610
  this.isPausing = false;
574
611
  exitReason = "paused";
575
612
  break;
576
613
  }
577
614
  else if (this.isStopping) {
578
615
  await this.stopNotes(0, true, now);
579
- await this.audioContext.suspend();
616
+ await audioContext.suspend();
580
617
  this.isStopping = false;
581
618
  exitReason = "stopped";
582
619
  break;
583
620
  }
584
621
  else if (this.isSeeking) {
585
622
  this.stopNotes(0, true, now);
586
- this.startTime = this.audioContext.currentTime;
623
+ this.startTime = audioContext.currentTime;
587
624
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
588
625
  this.updateStates(queueIndex, nextQueueIndex);
589
626
  queueIndex = nextQueueIndex;
@@ -596,7 +633,6 @@ export class MidyGM1 extends EventTarget {
596
633
  await this.scheduleTask(() => { }, waitTime);
597
634
  }
598
635
  if (exitReason !== "paused") {
599
- this.notePromises = [];
600
636
  this.resetAllStates();
601
637
  }
602
638
  this.isPlaying = false;
@@ -694,11 +730,13 @@ export class MidyGM1 extends EventTarget {
694
730
  return Promise.all(promises);
695
731
  }
696
732
  stopNotes(velocity, force, scheduleTime) {
697
- const promises = [];
698
- for (let i = 0; i < this.channels.length; i++) {
699
- promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
733
+ const channels = this.channels;
734
+ for (let i = 0; i < channels.length; i++) {
735
+ this.stopChannelNotes(i, velocity, force, scheduleTime);
700
736
  }
701
- return Promise.all(this.notePromises);
737
+ const stopPromise = Promise.all(this.notePromises);
738
+ this.notePromises = [];
739
+ return stopPromise;
702
740
  }
703
741
  async start() {
704
742
  if (this.isPlaying || this.isPaused)
@@ -736,11 +774,17 @@ export class MidyGM1 extends EventTarget {
736
774
  }
737
775
  }
738
776
  calcTotalTime() {
777
+ const totalTimeEventTypes = this.totalTimeEventTypes;
778
+ const timeline = this.timeline;
779
+ const inverseTempo = 1 / this.tempo;
739
780
  let totalTime = 0;
740
- for (let i = 0; i < this.timeline.length; i++) {
741
- const event = this.timeline[i];
742
- if (totalTime < event.startTime)
743
- totalTime = event.startTime;
781
+ for (let i = 0; i < timeline.length; i++) {
782
+ const event = timeline[i];
783
+ if (!totalTimeEventTypes.has(event.type))
784
+ continue;
785
+ const t = event.startTime * inverseTempo;
786
+ if (totalTime < t)
787
+ totalTime = t;
744
788
  }
745
789
  return totalTime + this.startDelay;
746
790
  }
@@ -780,9 +824,6 @@ export class MidyGM1 extends EventTarget {
780
824
  }
781
825
  await Promise.all(tasks);
782
826
  }
783
- cbToRatio(cb) {
784
- return Math.pow(10, cb / 200);
785
- }
786
827
  rateToCent(rate) {
787
828
  return 1200 * Math.log2(rate);
788
829
  }
@@ -811,26 +852,26 @@ export class MidyGM1 extends EventTarget {
811
852
  }
812
853
  setVolumeEnvelope(note, scheduleTime) {
813
854
  const { voiceParams, startTime } = note;
814
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
855
+ const attackVolume = cbToRatio(-voiceParams.initialAttenuation);
815
856
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
816
857
  const volDelay = startTime + voiceParams.volDelay;
817
858
  const volAttack = volDelay + voiceParams.volAttack;
818
859
  const volHold = volAttack + voiceParams.volHold;
819
- const volDecay = volHold + voiceParams.volDecay;
860
+ const decayDuration = voiceParams.volDecay;
820
861
  note.volumeEnvelopeNode.gain
821
862
  .cancelScheduledValues(scheduleTime)
822
863
  .setValueAtTime(0, startTime)
823
- .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
824
- .exponentialRampToValueAtTime(attackVolume, volAttack)
864
+ .setValueAtTime(0, volDelay)
865
+ .linearRampToValueAtTime(attackVolume, volAttack)
825
866
  .setValueAtTime(attackVolume, volHold)
826
- .linearRampToValueAtTime(sustainVolume, volDecay);
867
+ .setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
827
868
  }
828
869
  setPitchEnvelope(note, scheduleTime) {
829
870
  const { voiceParams } = note;
830
871
  const baseRate = voiceParams.playbackRate;
831
872
  note.bufferSource.playbackRate
832
873
  .cancelScheduledValues(scheduleTime)
833
- .setValueAtTime(baseRate, scheduleTime);
874
+ .setValueAtTime(baseRate, note.startTime);
834
875
  const modEnvToPitch = voiceParams.modEnvToPitch;
835
876
  if (modEnvToPitch === 0)
836
877
  return;
@@ -840,12 +881,12 @@ export class MidyGM1 extends EventTarget {
840
881
  const modDelay = note.startTime + voiceParams.modDelay;
841
882
  const modAttack = modDelay + voiceParams.modAttack;
842
883
  const modHold = modAttack + voiceParams.modHold;
843
- const modDecay = modHold + voiceParams.modDecay;
884
+ const decayDuration = voiceParams.modDecay;
844
885
  note.bufferSource.playbackRate
845
886
  .setValueAtTime(baseRate, modDelay)
846
- .exponentialRampToValueAtTime(peekRate, modAttack)
887
+ .linearRampToValueAtTime(peekRate, modAttack)
847
888
  .setValueAtTime(peekRate, modHold)
848
- .linearRampToValueAtTime(baseRate, modDecay);
889
+ .setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
849
890
  }
850
891
  clampCutoffFrequency(frequency) {
851
892
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -854,36 +895,42 @@ export class MidyGM1 extends EventTarget {
854
895
  }
855
896
  setFilterEnvelope(note, scheduleTime) {
856
897
  const { voiceParams, startTime } = note;
857
- const baseFreq = this.centToHz(voiceParams.initialFilterFc);
858
- const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
859
- const sustainFreq = baseFreq +
860
- (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
898
+ const modEnvToFilterFc = voiceParams.modEnvToFilterFc;
899
+ const baseCent = voiceParams.initialFilterFc;
900
+ const peekCent = baseCent + modEnvToFilterFc;
901
+ const sustainCent = baseCent +
902
+ modEnvToFilterFc * (1 - voiceParams.modSustain);
903
+ const baseFreq = this.centToHz(baseCent);
904
+ const peekFreq = this.centToHz(peekCent);
905
+ const sustainFreq = this.centToHz(sustainCent);
861
906
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
862
907
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
863
908
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
864
909
  const modDelay = startTime + voiceParams.modDelay;
865
910
  const modAttack = modDelay + voiceParams.modAttack;
866
911
  const modHold = modAttack + voiceParams.modHold;
867
- const modDecay = modHold + voiceParams.modDecay;
912
+ const decayDuration = voiceParams.modDecay;
913
+ note.adjustedBaseFreq = adjustedBaseFreq;
868
914
  note.filterNode.frequency
869
915
  .cancelScheduledValues(scheduleTime)
870
916
  .setValueAtTime(adjustedBaseFreq, startTime)
871
917
  .setValueAtTime(adjustedBaseFreq, modDelay)
872
- .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
918
+ .linearRampToValueAtTime(adjustedPeekFreq, modAttack)
873
919
  .setValueAtTime(adjustedPeekFreq, modHold)
874
- .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
920
+ .setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
875
921
  }
876
922
  startModulation(channel, note, scheduleTime) {
923
+ const audioContext = this.audioContext;
877
924
  const { voiceParams } = note;
878
- note.modulationLFO = new OscillatorNode(this.audioContext, {
925
+ note.modulationLFO = new OscillatorNode(audioContext, {
879
926
  frequency: this.centToHz(voiceParams.freqModLFO),
880
927
  });
881
- note.filterDepth = new GainNode(this.audioContext, {
928
+ note.filterDepth = new GainNode(audioContext, {
882
929
  gain: voiceParams.modLfoToFilterFc,
883
930
  });
884
- note.modulationDepth = new GainNode(this.audioContext);
931
+ note.modulationDepth = new GainNode(audioContext);
885
932
  this.setModLfoToPitch(channel, note, scheduleTime);
886
- note.volumeDepth = new GainNode(this.audioContext);
933
+ note.volumeDepth = new GainNode(audioContext);
887
934
  this.setModLfoToVolume(note, scheduleTime);
888
935
  note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
889
936
  note.modulationLFO.connect(note.filterDepth);
@@ -922,7 +969,8 @@ export class MidyGM1 extends EventTarget {
922
969
  }
923
970
  }
924
971
  async setNoteAudioNode(channel, note, realtime) {
925
- const now = this.audioContext.currentTime;
972
+ const audioContext = this.audioContext;
973
+ const now = audioContext.currentTime;
926
974
  const { noteNumber, velocity, startTime } = note;
927
975
  const state = channel.state;
928
976
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
@@ -930,8 +978,8 @@ export class MidyGM1 extends EventTarget {
930
978
  note.voiceParams = voiceParams;
931
979
  const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
932
980
  note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
933
- note.volumeEnvelopeNode = new GainNode(this.audioContext);
934
- note.filterNode = new BiquadFilterNode(this.audioContext, {
981
+ note.volumeEnvelopeNode = new GainNode(audioContext);
982
+ note.filterNode = new BiquadFilterNode(audioContext, {
935
983
  type: "lowpass",
936
984
  Q: voiceParams.initialFilterQ / 10, // dB
937
985
  });
@@ -990,7 +1038,12 @@ export class MidyGM1 extends EventTarget {
990
1038
  const bankTable = this.soundFontTable[programNumber];
991
1039
  if (!bankTable)
992
1040
  return;
993
- const bank = channel.isDrum ? 128 : 0;
1041
+ let bank = channel.isDrum ? 128 : 0;
1042
+ if (bankTable[bank] === undefined) {
1043
+ if (channel.isDrum)
1044
+ return;
1045
+ bank = 0;
1046
+ }
994
1047
  const soundFontIndex = bankTable[bank];
995
1048
  if (soundFontIndex === undefined)
996
1049
  return;
@@ -1014,24 +1067,24 @@ export class MidyGM1 extends EventTarget {
1014
1067
  }
1015
1068
  releaseNote(channel, note, endTime) {
1016
1069
  endTime ??= this.audioContext.currentTime;
1017
- const volRelease = endTime + note.voiceParams.volRelease;
1070
+ const duration = note.voiceParams.volRelease * releaseTime;
1071
+ const volRelease = endTime + duration;
1018
1072
  const modRelease = endTime + note.voiceParams.modRelease;
1019
- const stopTime = Math.min(volRelease, modRelease);
1020
1073
  note.filterNode.frequency
1021
1074
  .cancelScheduledValues(endTime)
1022
- .linearRampToValueAtTime(0, modRelease);
1075
+ .linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
1023
1076
  note.volumeEnvelopeNode.gain
1024
1077
  .cancelScheduledValues(endTime)
1025
- .linearRampToValueAtTime(0, volRelease);
1078
+ .setTargetAtTime(0, endTime, duration * releaseCurve);
1026
1079
  return new Promise((resolve) => {
1027
1080
  this.scheduleTask(() => {
1028
1081
  const bufferSource = note.bufferSource;
1029
1082
  bufferSource.loop = false;
1030
- bufferSource.stop(stopTime);
1083
+ bufferSource.stop(volRelease);
1031
1084
  this.disconnectNote(note);
1032
1085
  channel.scheduledNotes[note.index] = undefined;
1033
1086
  resolve();
1034
- }, stopTime);
1087
+ }, volRelease);
1035
1088
  });
1036
1089
  }
1037
1090
  noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
@@ -1166,7 +1219,7 @@ export class MidyGM1 extends EventTarget {
1166
1219
  }
1167
1220
  setModLfoToVolume(note, scheduleTime) {
1168
1221
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1169
- const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1222
+ const baseDepth = cbToRatio(Math.abs(modLfoToVolume)) - 1;
1170
1223
  const volumeDepth = baseDepth * Math.sign(modLfoToVolume);
1171
1224
  note.volumeDepth.gain
1172
1225
  .cancelScheduledValues(scheduleTime)
@@ -1542,15 +1595,16 @@ export class MidyGM1 extends EventTarget {
1542
1595
  }
1543
1596
  }
1544
1597
  GM1SystemOn(scheduleTime) {
1598
+ const channels = this.channels;
1545
1599
  if (!(0 <= scheduleTime))
1546
1600
  scheduleTime = this.audioContext.currentTime;
1547
1601
  this.mode = "GM1";
1548
- for (let i = 0; i < this.channels.length; i++) {
1602
+ for (let i = 0; i < channels.length; i++) {
1549
1603
  this.allSoundOff(i, 0, scheduleTime);
1550
- const channel = this.channels[i];
1604
+ const channel = channels[i];
1551
1605
  channel.isDrum = false;
1552
1606
  }
1553
- this.channels[9].isDrum = true;
1607
+ channels[9].isDrum = true;
1554
1608
  }
1555
1609
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1556
1610
  switch (data[2]) {
package/esm/midy-GM2.d.ts CHANGED
@@ -50,6 +50,8 @@ export class MidyGM2 extends EventTarget {
50
50
  isPaused: boolean;
51
51
  isStopping: boolean;
52
52
  isSeeking: boolean;
53
+ totalTimeEventTypes: Set<string>;
54
+ tempo: number;
53
55
  loop: boolean;
54
56
  playPromise: any;
55
57
  timeline: any[];
@@ -156,7 +158,6 @@ export class MidyGM2 extends EventTarget {
156
158
  delayNodes: any[];
157
159
  feedbackGains: any[];
158
160
  };
159
- cbToRatio(cb: any): number;
160
161
  rateToCent(rate: any): number;
161
162
  centToRate(cent: any): number;
162
163
  centToHz(cent: any): number;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}