@marmooo/midy 0.4.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.4.0",
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",
@@ -22,7 +22,7 @@
22
22
  "test": "node test_runner.js"
23
23
  },
24
24
  "dependencies": {
25
- "@marmooo/soundfont-parser": "^0.1.4",
25
+ "@marmooo/soundfont-parser": "^0.1.5",
26
26
  "midi-file": "^1.2.4"
27
27
  },
28
28
  "devDependencies": {
@@ -1,4 +1,4 @@
1
- export class MidyGM1 {
1
+ export class MidyGM1 extends EventTarget {
2
2
  static channelSettings: {
3
3
  scheduleIndex: number;
4
4
  detune: number;
@@ -31,6 +31,7 @@ export class MidyGM1 {
31
31
  isPaused: boolean;
32
32
  isStopping: boolean;
33
33
  isSeeking: boolean;
34
+ loop: boolean;
34
35
  playPromise: any;
35
36
  timeline: any[];
36
37
  notePromises: any[];
@@ -69,7 +70,7 @@ export class MidyGM1 {
69
70
  createChannels(audioContext: any): any[];
70
71
  createAudioBuffer(voiceParams: any): Promise<any>;
71
72
  createBufferSource(voiceParams: any, audioBuffer: any): any;
72
- scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
73
+ scheduleTimelineEvents(scheduleTime: any, queueIndex: any): any;
73
74
  getQueueIndex(second: any): number;
74
75
  resetAllStates(): void;
75
76
  updateStates(queueIndex: any, nextQueueIndex: any): void;
@@ -91,8 +92,8 @@ export class MidyGM1 {
91
92
  seekTo(second: any): void;
92
93
  calcTotalTime(): number;
93
94
  currentTime(): number;
94
- processScheduledNotes(channel: any, callback: any): void;
95
- processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
95
+ processScheduledNotes(channel: any, callback: any): Promise<void>;
96
+ processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
96
97
  cbToRatio(cb: any): number;
97
98
  rateToCent(rate: any): number;
98
99
  centToRate(cent: any): number;
@@ -112,13 +113,13 @@ export class MidyGM1 {
112
113
  noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
113
114
  disconnectNote(note: any): void;
114
115
  releaseNote(channel: any, note: any, endTime: any): Promise<any>;
115
- noteOff(channelNumber: any, noteNumber: any, velocity: any, endTime: any, force: any): void;
116
+ noteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): any;
116
117
  setNoteIndex(channel: any, index: any): void;
117
118
  findNoteOffIndex(channel: any, noteNumber: any): any;
118
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
119
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
119
120
  createMessageHandlers(): any[];
120
121
  handleMessage(data: any, scheduleTime: any): void;
121
- handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
122
+ handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): any;
122
123
  setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
123
124
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
124
125
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AA6FA;IA0BE;;;;;;;;;;;MAWE;IAEF,+BAeC;IArDD,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,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IAgBnC,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,kDAUC;IAED,4DASC;IAED,yEAqDC;IAED,mCAOC;IAED,uBASC;IAED,yDA2BC;IAED,2BAwCC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAKC;IAED,uBAQC;IAED,wBAKC;IAED,0BAKC;IAED,wBAOC;IAED,sBAIC;IAED,yDAQC;IAED,yEASC;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,uEAmCC;IAED,0EAiBC;IAED,oEASC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAmBC;IAED,6CAUC;IAED,qDAUC;IAED,sFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,uGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDASC;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":"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"}
@@ -29,12 +29,6 @@ class Note {
29
29
  writable: true,
30
30
  value: false
31
31
  });
32
- Object.defineProperty(this, "pending", {
33
- enumerable: true,
34
- configurable: true,
35
- writable: true,
36
- value: true
37
- });
38
32
  Object.defineProperty(this, "bufferSource", {
39
33
  enumerable: true,
40
34
  configurable: true,
@@ -80,6 +74,9 @@ class Note {
80
74
  this.noteNumber = noteNumber;
81
75
  this.velocity = velocity;
82
76
  this.startTime = startTime;
77
+ this.ready = new Promise((resolve) => {
78
+ this.resolveReady = resolve;
79
+ });
83
80
  }
84
81
  }
85
82
  // normalized to 0-1 for use with the SF2 modulator model
@@ -89,11 +86,11 @@ const defaultControllerState = {
89
86
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
90
87
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
91
88
  link: { type: 127, defaultValue: 0 },
92
- modulationDepth: { type: 128 + 1, defaultValue: 0 },
89
+ modulationDepthMSB: { type: 128 + 1, defaultValue: 0 },
93
90
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
94
- volume: { type: 128 + 7, defaultValue: 100 / 127 },
95
- pan: { type: 128 + 10, defaultValue: 64 / 127 },
96
- expression: { type: 128 + 11, defaultValue: 1 },
91
+ volumeMSB: { type: 128 + 7, defaultValue: 100 / 127 },
92
+ panMSB: { type: 128 + 10, defaultValue: 64 / 127 },
93
+ expressionMSB: { type: 128 + 11, defaultValue: 1 },
97
94
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
98
95
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
99
96
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
@@ -153,8 +150,9 @@ const pitchEnvelopeKeys = [
153
150
  "playbackRate",
154
151
  ];
155
152
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
156
- class MidyGM1 {
153
+ class MidyGM1 extends EventTarget {
157
154
  constructor(audioContext) {
155
+ super();
158
156
  Object.defineProperty(this, "mode", {
159
157
  enumerable: true,
160
158
  configurable: true,
@@ -269,6 +267,12 @@ class MidyGM1 {
269
267
  writable: true,
270
268
  value: false
271
269
  });
270
+ Object.defineProperty(this, "loop", {
271
+ enumerable: true,
272
+ configurable: true,
273
+ writable: true,
274
+ value: false
275
+ });
272
276
  Object.defineProperty(this, "playPromise", {
273
277
  enumerable: true,
274
278
  configurable: true,
@@ -413,7 +417,7 @@ class MidyGM1 {
413
417
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
414
418
  }
415
419
  createChannelAudioNodes(audioContext) {
416
- const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
420
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.panMSB.defaultValue);
417
421
  const gainL = new GainNode(audioContext, { gain: gainLeft });
418
422
  const gainR = new GainNode(audioContext, { gain: gainRight });
419
423
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -441,10 +445,9 @@ class MidyGM1 {
441
445
  return channels;
442
446
  }
443
447
  async createAudioBuffer(voiceParams) {
444
- const sample = voiceParams.sample;
445
- const sampleStart = voiceParams.start;
446
- const sampleEnd = sample.data.length + voiceParams.end;
447
- const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
448
+ const { sample, start, end } = voiceParams;
449
+ const sampleEnd = sample.data.length + end;
450
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
448
451
  return audioBuffer;
449
452
  }
450
453
  createBufferSource(voiceParams, audioBuffer) {
@@ -457,7 +460,7 @@ class MidyGM1 {
457
460
  }
458
461
  return bufferSource;
459
462
  }
460
- async scheduleTimelineEvents(scheduleTime, queueIndex) {
463
+ scheduleTimelineEvents(scheduleTime, queueIndex) {
461
464
  const timeOffset = this.resumeTime - this.startTime;
462
465
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
463
466
  const schedulingOffset = this.startDelay - timeOffset;
@@ -469,12 +472,10 @@ class MidyGM1 {
469
472
  const startTime = event.startTime + schedulingOffset;
470
473
  switch (event.type) {
471
474
  case "noteOn":
472
- await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
475
+ this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
473
476
  break;
474
477
  case "noteOff": {
475
- const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
476
- if (notePromise)
477
- this.notePromises.push(notePromise);
478
+ this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
478
479
  break;
479
480
  }
480
481
  case "controller":
@@ -512,22 +513,23 @@ class MidyGM1 {
512
513
  }
513
514
  }
514
515
  updateStates(queueIndex, nextQueueIndex) {
516
+ const now = this.audioContext.currentTime;
515
517
  if (nextQueueIndex < queueIndex)
516
518
  queueIndex = 0;
517
519
  for (let i = queueIndex; i < nextQueueIndex; i++) {
518
520
  const event = this.timeline[i];
519
521
  switch (event.type) {
520
522
  case "controller":
521
- this.setControlChange(event.channel, event.controllerType, event.value, 0);
523
+ this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
522
524
  break;
523
525
  case "programChange":
524
- this.setProgramChange(event.channel, event.programNumber, 0);
526
+ this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
525
527
  break;
526
528
  case "pitchBend":
527
- this.setPitchBend(event.channel, event.value + 8192, 0);
529
+ this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
528
530
  break;
529
531
  case "sysEx":
530
- this.handleSysEx(event.data, 0);
532
+ this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
531
533
  }
532
534
  }
533
535
  }
@@ -535,44 +537,80 @@ class MidyGM1 {
535
537
  if (this.audioContext.state === "suspended") {
536
538
  await this.audioContext.resume();
537
539
  }
540
+ const paused = this.isPaused;
538
541
  this.isPlaying = true;
539
542
  this.isPaused = false;
540
543
  this.startTime = this.audioContext.currentTime;
544
+ if (paused) {
545
+ this.dispatchEvent(new Event("resumed"));
546
+ }
547
+ else {
548
+ this.dispatchEvent(new Event("started"));
549
+ }
541
550
  let queueIndex = this.getQueueIndex(this.resumeTime);
542
- let finished = false;
551
+ let exitReason;
543
552
  this.notePromises = [];
544
- while (queueIndex < this.timeline.length) {
553
+ while (true) {
545
554
  const now = this.audioContext.currentTime;
555
+ if (this.timeline.length <= queueIndex) {
556
+ await this.stopNotes(0, true, now);
557
+ if (this.loop) {
558
+ this.notePromises = [];
559
+ this.resetAllStates();
560
+ this.startTime = this.audioContext.currentTime;
561
+ this.resumeTime = 0;
562
+ queueIndex = 0;
563
+ this.dispatchEvent(new Event("looped"));
564
+ continue;
565
+ }
566
+ else {
567
+ await this.audioContext.suspend();
568
+ exitReason = "ended";
569
+ break;
570
+ }
571
+ }
546
572
  if (this.isPausing) {
547
573
  await this.stopNotes(0, true, now);
548
574
  await this.audioContext.suspend();
549
575
  this.notePromises = [];
576
+ this.isPausing = false;
577
+ exitReason = "paused";
550
578
  break;
551
579
  }
552
580
  else if (this.isStopping) {
553
581
  await this.stopNotes(0, true, now);
554
582
  await this.audioContext.suspend();
555
- finished = true;
583
+ this.isStopping = false;
584
+ exitReason = "stopped";
556
585
  break;
557
586
  }
558
587
  else if (this.isSeeking) {
559
- await this.stopNotes(0, true, now);
588
+ this.stopNotes(0, true, now);
560
589
  this.startTime = this.audioContext.currentTime;
561
590
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
562
591
  this.updateStates(queueIndex, nextQueueIndex);
563
592
  queueIndex = nextQueueIndex;
564
593
  this.isSeeking = false;
594
+ this.dispatchEvent(new Event("seeked"));
565
595
  continue;
566
596
  }
567
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
597
+ queueIndex = this.scheduleTimelineEvents(now, queueIndex);
568
598
  const waitTime = now + this.noteCheckInterval;
569
599
  await this.scheduleTask(() => { }, waitTime);
570
600
  }
571
- if (finished) {
601
+ if (exitReason !== "paused") {
572
602
  this.notePromises = [];
573
603
  this.resetAllStates();
574
604
  }
575
605
  this.isPlaying = false;
606
+ if (exitReason === "paused") {
607
+ this.isPaused = true;
608
+ this.dispatchEvent(new Event("paused"));
609
+ }
610
+ else {
611
+ this.isPaused = false;
612
+ this.dispatchEvent(new Event(exitReason));
613
+ }
576
614
  }
577
615
  ticksToSecond(ticks, secondsPerBeat) {
578
616
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -679,24 +717,20 @@ class MidyGM1 {
679
717
  return;
680
718
  this.isStopping = true;
681
719
  await this.playPromise;
682
- this.isStopping = false;
683
720
  }
684
721
  async pause() {
685
722
  if (!this.isPlaying || this.isPaused)
686
723
  return;
687
724
  const now = this.audioContext.currentTime;
688
- this.resumeTime = now - this.startTime - this.startDelay;
725
+ this.resumeTime = now + this.resumeTime - this.startTime;
689
726
  this.isPausing = true;
690
727
  await this.playPromise;
691
- this.isPausing = false;
692
- this.isPaused = true;
693
728
  }
694
729
  async resume() {
695
730
  if (!this.isPaused)
696
731
  return;
697
732
  this.playPromise = this.playNotes();
698
733
  await this.playPromise;
699
- this.isPaused = false;
700
734
  }
701
735
  seekTo(second) {
702
736
  this.resumeTime = second;
@@ -719,19 +753,23 @@ class MidyGM1 {
719
753
  const now = this.audioContext.currentTime;
720
754
  return now + this.resumeTime - this.startTime;
721
755
  }
722
- processScheduledNotes(channel, callback) {
756
+ async processScheduledNotes(channel, callback) {
723
757
  const scheduledNotes = channel.scheduledNotes;
758
+ const tasks = [];
724
759
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
725
760
  const note = scheduledNotes[i];
726
761
  if (!note)
727
762
  continue;
728
763
  if (note.ending)
729
764
  continue;
730
- callback(note);
765
+ const task = note.ready.then(() => callback(note));
766
+ tasks.push(task);
731
767
  }
768
+ await Promise.all(tasks);
732
769
  }
733
- processActiveNotes(channel, scheduleTime, callback) {
770
+ async processActiveNotes(channel, scheduleTime, callback) {
734
771
  const scheduledNotes = channel.scheduledNotes;
772
+ const tasks = [];
735
773
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
736
774
  const note = scheduledNotes[i];
737
775
  if (!note)
@@ -740,8 +778,10 @@ class MidyGM1 {
740
778
  continue;
741
779
  if (scheduleTime < note.startTime)
742
780
  break;
743
- callback(note);
781
+ const task = note.ready.then(() => callback(note));
782
+ tasks.push(task);
744
783
  }
784
+ await Promise.all(tasks);
745
785
  }
746
786
  cbToRatio(cb) {
747
787
  return Math.pow(10, cb / 200);
@@ -907,7 +947,13 @@ class MidyGM1 {
907
947
  }
908
948
  note.bufferSource.connect(note.filterNode);
909
949
  note.filterNode.connect(note.volumeEnvelopeNode);
910
- note.bufferSource.start(startTime);
950
+ if (voiceParams.sample.type === "compressed") {
951
+ const offset = voiceParams.start / audioBuffer.sampleRate;
952
+ note.bufferSource.start(startTime, offset);
953
+ }
954
+ else {
955
+ note.bufferSource.start(startTime);
956
+ }
911
957
  return note;
912
958
  }
913
959
  handleExclusiveClass(note, channelNumber, startTime) {
@@ -957,11 +1003,7 @@ class MidyGM1 {
957
1003
  return;
958
1004
  await this.setNoteAudioNode(channel, note, realtime);
959
1005
  this.setNoteRouting(channelNumber, note, startTime);
960
- note.pending = false;
961
- const off = note.offEvent;
962
- if (off) {
963
- this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
964
- }
1006
+ note.resolveReady();
965
1007
  }
966
1008
  disconnectNote(note) {
967
1009
  note.bufferSource.disconnect();
@@ -995,7 +1037,7 @@ class MidyGM1 {
995
1037
  }, stopTime);
996
1038
  });
997
1039
  }
998
- noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1040
+ noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
999
1041
  const channel = this.channels[channelNumber];
1000
1042
  if (!force && 0.5 <= channel.state.sustainPedal)
1001
1043
  return;
@@ -1003,13 +1045,13 @@ class MidyGM1 {
1003
1045
  if (index < 0)
1004
1046
  return;
1005
1047
  const note = channel.scheduledNotes[index];
1006
- if (note.pending) {
1007
- note.offEvent = { velocity, startTime: endTime };
1008
- return;
1009
- }
1010
1048
  note.ending = true;
1011
1049
  this.setNoteIndex(channel, index);
1012
- this.releaseNote(channel, note, endTime);
1050
+ const promise = note.ready.then(() => {
1051
+ return this.releaseNote(channel, note, endTime);
1052
+ });
1053
+ this.notePromises.push(promise);
1054
+ return promise;
1013
1055
  }
1014
1056
  setNoteIndex(channel, index) {
1015
1057
  let allEnds = true;
@@ -1095,7 +1137,8 @@ class MidyGM1 {
1095
1137
  }
1096
1138
  setPitchBend(channelNumber, value, scheduleTime) {
1097
1139
  const channel = this.channels[channelNumber];
1098
- scheduleTime ??= this.audioContext.currentTime;
1140
+ if (!(0 <= scheduleTime))
1141
+ scheduleTime = this.audioContext.currentTime;
1099
1142
  const state = channel.state;
1100
1143
  const prev = state.pitchWheel * 2 - 1;
1101
1144
  const next = (value - 8192) / 8192;
@@ -1107,11 +1150,12 @@ class MidyGM1 {
1107
1150
  setModLfoToPitch(channel, note, scheduleTime) {
1108
1151
  if (note.modulationDepth) {
1109
1152
  const modLfoToPitch = note.voiceParams.modLfoToPitch;
1110
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1111
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1153
+ const baseDepth = Math.abs(modLfoToPitch) +
1154
+ channel.state.modulationDepthMSB;
1155
+ const depth = baseDepth * Math.sign(modLfoToPitch);
1112
1156
  note.modulationDepth.gain
1113
1157
  .cancelScheduledValues(scheduleTime)
1114
- .setValueAtTime(modulationDepth, scheduleTime);
1158
+ .setValueAtTime(depth, scheduleTime);
1115
1159
  }
1116
1160
  else {
1117
1161
  this.startModulation(channel, note, scheduleTime);
@@ -1148,18 +1192,18 @@ class MidyGM1 {
1148
1192
  createVoiceParamsHandlers() {
1149
1193
  return {
1150
1194
  modLfoToPitch: (channel, note, scheduleTime) => {
1151
- if (0 < channel.state.modulationDepth) {
1195
+ if (0 < channel.state.modulationDepthMSB) {
1152
1196
  this.setModLfoToPitch(channel, note, scheduleTime);
1153
1197
  }
1154
1198
  },
1155
1199
  vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
1156
1200
  modLfoToFilterFc: (channel, note, scheduleTime) => {
1157
- if (0 < channel.state.modulationDepth) {
1201
+ if (0 < channel.state.modulationDepthMSB) {
1158
1202
  this.setModLfoToFilterFc(note, scheduleTime);
1159
1203
  }
1160
1204
  },
1161
1205
  modLfoToVolume: (channel, note, scheduleTime) => {
1162
- if (0 < channel.state.modulationDepth) {
1206
+ if (0 < channel.state.modulationDepthMSB) {
1163
1207
  this.setModLfoToVolume(note, scheduleTime);
1164
1208
  }
1165
1209
  },
@@ -1171,7 +1215,7 @@ class MidyGM1 {
1171
1215
  }
1172
1216
  },
1173
1217
  freqModLFO: (_channel, note, scheduleTime) => {
1174
- if (0 < channel.state.modulationDepth) {
1218
+ if (0 < channel.state.modulationDepthMSB) {
1175
1219
  this.setFreqModLFO(note, scheduleTime);
1176
1220
  }
1177
1221
  },
@@ -1246,7 +1290,8 @@ class MidyGM1 {
1246
1290
  }
1247
1291
  }
1248
1292
  updateModulation(channel, scheduleTime) {
1249
- const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1293
+ const depth = channel.state.modulationDepthMSB *
1294
+ channel.modulationDepthRange;
1250
1295
  this.processScheduledNotes(channel, (note) => {
1251
1296
  if (note.modulationDepth) {
1252
1297
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
@@ -1258,14 +1303,16 @@ class MidyGM1 {
1258
1303
  }
1259
1304
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1260
1305
  const channel = this.channels[channelNumber];
1261
- scheduleTime ??= this.audioContext.currentTime;
1262
- channel.state.modulationDepth = modulation / 127;
1306
+ if (!(0 <= scheduleTime))
1307
+ scheduleTime = this.audioContext.currentTime;
1308
+ channel.state.modulationDepthMSB = modulation / 127;
1263
1309
  this.updateModulation(channel, scheduleTime);
1264
1310
  }
1265
1311
  setVolume(channelNumber, volume, scheduleTime) {
1266
- scheduleTime ??= this.audioContext.currentTime;
1312
+ if (!(0 <= scheduleTime))
1313
+ scheduleTime = this.audioContext.currentTime;
1267
1314
  const channel = this.channels[channelNumber];
1268
- channel.state.volume = volume / 127;
1315
+ channel.state.volumeMSB = volume / 127;
1269
1316
  this.updateChannelVolume(channel, scheduleTime);
1270
1317
  }
1271
1318
  panToGain(pan) {
@@ -1276,15 +1323,17 @@ class MidyGM1 {
1276
1323
  };
1277
1324
  }
1278
1325
  setPan(channelNumber, pan, scheduleTime) {
1279
- scheduleTime ??= this.audioContext.currentTime;
1326
+ if (!(0 <= scheduleTime))
1327
+ scheduleTime = this.audioContext.currentTime;
1280
1328
  const channel = this.channels[channelNumber];
1281
- channel.state.pan = pan / 127;
1329
+ channel.state.panMSB = pan / 127;
1282
1330
  this.updateChannelVolume(channel, scheduleTime);
1283
1331
  }
1284
1332
  setExpression(channelNumber, expression, scheduleTime) {
1285
- scheduleTime ??= this.audioContext.currentTime;
1333
+ if (!(0 <= scheduleTime))
1334
+ scheduleTime = this.audioContext.currentTime;
1286
1335
  const channel = this.channels[channelNumber];
1287
- channel.state.expression = expression / 127;
1336
+ channel.state.expressionMSB = expression / 127;
1288
1337
  this.updateChannelVolume(channel, scheduleTime);
1289
1338
  }
1290
1339
  dataEntryLSB(channelNumber, value, scheduleTime) {
@@ -1293,8 +1342,8 @@ class MidyGM1 {
1293
1342
  }
1294
1343
  updateChannelVolume(channel, scheduleTime) {
1295
1344
  const state = channel.state;
1296
- const volume = state.volume * state.expression;
1297
- const { gainLeft, gainRight } = this.panToGain(state.pan);
1345
+ const volume = state.volumeMSB * state.expressionMSB;
1346
+ const { gainLeft, gainRight } = this.panToGain(state.panMSB);
1298
1347
  channel.gainL.gain
1299
1348
  .cancelScheduledValues(scheduleTime)
1300
1349
  .setValueAtTime(volume * gainLeft, scheduleTime);
@@ -1304,7 +1353,8 @@ class MidyGM1 {
1304
1353
  }
1305
1354
  setSustainPedal(channelNumber, value, scheduleTime) {
1306
1355
  const channel = this.channels[channelNumber];
1307
- scheduleTime ??= this.audioContext.currentTime;
1356
+ if (!(0 <= scheduleTime))
1357
+ scheduleTime = this.audioContext.currentTime;
1308
1358
  channel.state.sustainPedal = value / 127;
1309
1359
  if (64 <= value) {
1310
1360
  this.processScheduledNotes(channel, (note) => {
@@ -1376,7 +1426,8 @@ class MidyGM1 {
1376
1426
  }
1377
1427
  setPitchBendRange(channelNumber, value, scheduleTime) {
1378
1428
  const channel = this.channels[channelNumber];
1379
- scheduleTime ??= this.audioContext.currentTime;
1429
+ if (!(0 <= scheduleTime))
1430
+ scheduleTime = this.audioContext.currentTime;
1380
1431
  const state = channel.state;
1381
1432
  const prev = state.pitchWheelSensitivity;
1382
1433
  const next = value / 12800;
@@ -1394,7 +1445,8 @@ class MidyGM1 {
1394
1445
  }
1395
1446
  setFineTuning(channelNumber, value, scheduleTime) {
1396
1447
  const channel = this.channels[channelNumber];
1397
- scheduleTime ??= this.audioContext.currentTime;
1448
+ if (!(0 <= scheduleTime))
1449
+ scheduleTime = this.audioContext.currentTime;
1398
1450
  const prev = channel.fineTuning;
1399
1451
  const next = value;
1400
1452
  channel.fineTuning = next;
@@ -1409,7 +1461,8 @@ class MidyGM1 {
1409
1461
  }
1410
1462
  setCoarseTuning(channelNumber, value, scheduleTime) {
1411
1463
  const channel = this.channels[channelNumber];
1412
- scheduleTime ??= this.audioContext.currentTime;
1464
+ if (!(0 <= scheduleTime))
1465
+ scheduleTime = this.audioContext.currentTime;
1413
1466
  const prev = channel.coarseTuning;
1414
1467
  const next = value;
1415
1468
  channel.coarseTuning = next;
@@ -1417,7 +1470,8 @@ class MidyGM1 {
1417
1470
  this.updateChannelDetune(channel, scheduleTime);
1418
1471
  }
1419
1472
  allSoundOff(channelNumber, _value, scheduleTime) {
1420
- scheduleTime ??= this.audioContext.currentTime;
1473
+ if (!(0 <= scheduleTime))
1474
+ scheduleTime = this.audioContext.currentTime;
1421
1475
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1422
1476
  }
1423
1477
  resetChannelStates(channelNumber) {
@@ -1442,8 +1496,8 @@ class MidyGM1 {
1442
1496
  resetAllControllers(channelNumber, _value, scheduleTime) {
1443
1497
  const keys = [
1444
1498
  "pitchWheel",
1445
- "expression",
1446
- "modulationDepth",
1499
+ "expressionMSB",
1500
+ "modulationDepthMSB",
1447
1501
  "sustainPedal",
1448
1502
  ];
1449
1503
  const channel = this.channels[channelNumber];
@@ -1469,7 +1523,8 @@ class MidyGM1 {
1469
1523
  }
1470
1524
  }
1471
1525
  allNotesOff(channelNumber, _value, scheduleTime) {
1472
- scheduleTime ??= this.audioContext.currentTime;
1526
+ if (!(0 <= scheduleTime))
1527
+ scheduleTime = this.audioContext.currentTime;
1473
1528
  return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
1474
1529
  }
1475
1530
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
@@ -1490,7 +1545,8 @@ class MidyGM1 {
1490
1545
  }
1491
1546
  }
1492
1547
  GM1SystemOn(scheduleTime) {
1493
- scheduleTime ??= this.audioContext.currentTime;
1548
+ if (!(0 <= scheduleTime))
1549
+ scheduleTime = this.audioContext.currentTime;
1494
1550
  this.mode = "GM1";
1495
1551
  for (let i = 0; i < this.channels.length; i++) {
1496
1552
  this.allSoundOff(i, 0, scheduleTime);
@@ -1518,7 +1574,8 @@ class MidyGM1 {
1518
1574
  this.setMasterVolume(volume, scheduleTime);
1519
1575
  }
1520
1576
  setMasterVolume(value, scheduleTime) {
1521
- scheduleTime ??= this.audioContext.currentTime;
1577
+ if (!(0 <= scheduleTime))
1578
+ scheduleTime = this.audioContext.currentTime;
1522
1579
  this.masterVolume.gain
1523
1580
  .cancelScheduledValues(scheduleTime)
1524
1581
  .setValueAtTime(value * value, scheduleTime);
@@ -1,4 +1,4 @@
1
- export class MidyGM2 {
1
+ export class MidyGM2 extends EventTarget {
2
2
  static channelSettings: {
3
3
  scheduleIndex: number;
4
4
  detune: number;
@@ -50,6 +50,7 @@ export class MidyGM2 {
50
50
  isPaused: boolean;
51
51
  isStopping: boolean;
52
52
  isSeeking: boolean;
53
+ loop: boolean;
53
54
  playPromise: any;
54
55
  timeline: any[];
55
56
  notePromises: any[];
@@ -105,7 +106,7 @@ export class MidyGM2 {
105
106
  createAudioBuffer(voiceParams: any): Promise<any>;
106
107
  isLoopDrum(channel: any, noteNumber: any): boolean;
107
108
  createBufferSource(channel: any, noteNumber: any, voiceParams: any, audioBuffer: any): any;
108
- scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
109
+ scheduleTimelineEvents(scheduleTime: any, queueIndex: any): any;
109
110
  getQueueIndex(second: any): number;
110
111
  resetAllStates(): void;
111
112
  updateStates(queueIndex: any, nextQueueIndex: any): void;
@@ -127,8 +128,8 @@ export class MidyGM2 {
127
128
  seekTo(second: any): void;
128
129
  calcTotalTime(): number;
129
130
  currentTime(): number;
130
- processScheduledNotes(channel: any, callback: any): void;
131
- processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
131
+ processScheduledNotes(channel: any, callback: any): Promise<void>;
132
+ processActiveNotes(channel: any, scheduleTime: any, callback: any): Promise<void>;
132
133
  createConvolutionReverbImpulse(audioContext: any, decay: any, preDecay: any): any;
133
134
  createConvolutionReverb(audioContext: any, impulse: any): {
134
135
  input: any;
@@ -182,11 +183,11 @@ export class MidyGM2 {
182
183
  noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
183
184
  disconnectNote(note: any): void;
184
185
  releaseNote(channel: any, note: any, endTime: any): Promise<any>;
185
- noteOff(channelNumber: any, noteNumber: any, velocity: any, endTime: any, force: any): void;
186
+ noteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): any;
186
187
  setNoteIndex(channel: any, index: any): void;
187
188
  findNoteOffIndex(channel: any, noteNumber: any): any;
188
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
189
- releaseSostenutoPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
189
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
190
+ releaseSostenutoPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
190
191
  createMessageHandlers(): any[];
191
192
  handleMessage(data: any, scheduleTime: any): void;
192
193
  activeSensing(): void;