@marmooo/midy 0.3.5 → 0.3.7

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
@@ -18,6 +18,11 @@ This library provides several files depending on the implementation level.
18
18
  [ja v1.0](https://amei.or.jp/midistandardcommittee/Recommended_Practice/GM2_japanese.pdf))
19
19
  - midy.js: full implementation (in progress)
20
20
 
21
+ ## Demo
22
+
23
+ - [@marmooo/midi-player](https://marmooo.github.io/midi-player/) - GUI library
24
+ - [Humidy](https://marmooo.github.io/humidy/) - GM2 MIDI mixer app
25
+
21
26
  ## Usage
22
27
 
23
28
  ### Initialization
@@ -39,8 +44,8 @@ await midy.start();
39
44
 
40
45
  ```js
41
46
  await midy.start();
42
- midy.stop();
43
- midy.pause();
47
+ await midy.stop();
48
+ await midy.pause();
44
49
  await midy.resume();
45
50
  midy.seekTo(second);
46
51
  ```
@@ -118,13 +123,6 @@ await midy.loadSoundFont(paths);
118
123
  deno task build
119
124
  ```
120
125
 
121
- ## Test
122
-
123
- WebAudio only works on web browsers currently, so we are testing this library
124
- using the following GUI libraries.
125
-
126
- - [@marmooo/midi-player](https://github.com/marmooo/midi-player)
127
-
128
126
  ## License
129
127
 
130
128
  Apache-2.0
package/esm/midy-GM1.d.ts CHANGED
@@ -30,25 +30,26 @@ export class MidyGM1 {
30
30
  isPaused: boolean;
31
31
  isStopping: boolean;
32
32
  isSeeking: boolean;
33
+ playPromise: any;
33
34
  timeline: any[];
34
- instruments: any[];
35
35
  notePromises: any[];
36
+ instruments: Set<any>;
36
37
  exclusiveClassNotes: any[];
37
38
  audioContext: any;
38
39
  masterVolume: any;
39
40
  scheduler: any;
40
41
  schedulerBuffer: any;
41
42
  voiceParamsHandlers: {
42
- modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
43
- vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
44
- modLfoToFilterFc: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
45
- modLfoToVolume: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
46
- chorusEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
47
- reverbEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
48
- delayModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
49
- freqModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
50
- delayVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
51
- freqVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
43
+ modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
44
+ vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
45
+ modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
46
+ modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
47
+ chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
48
+ reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
49
+ delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
50
+ freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
51
+ delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
52
+ freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
52
53
  };
53
54
  controlChangeHandlers: any[];
54
55
  channels: any[];
@@ -58,7 +59,7 @@ export class MidyGM1 {
58
59
  loadSoundFont(input: any): Promise<void>;
59
60
  loadMIDI(input: any): Promise<void>;
60
61
  cacheVoiceIds(): void;
61
- getVoiceId(channel: any, noteNumber: any, velocity: any): string | undefined;
62
+ getVoiceId(channel: any, noteNumber: any, velocity: any): any;
62
63
  createChannelAudioNodes(audioContext: any): {
63
64
  gainL: any;
64
65
  gainR: any;
@@ -69,7 +70,9 @@ export class MidyGM1 {
69
70
  createBufferSource(voiceParams: any, audioBuffer: any): any;
70
71
  scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
71
72
  getQueueIndex(second: any): number;
72
- playNotes(): Promise<any>;
73
+ resetAllStates(): void;
74
+ updateStates(queueIndex: any, nextQueueIndex: any): void;
75
+ playNotes(): Promise<void>;
73
76
  ticksToSecond(ticks: any, secondsPerBeat: any): number;
74
77
  secondToTicks(second: any, secondsPerBeat: any): number;
75
78
  extractMidiData(midi: any): {
@@ -80,8 +83,8 @@ export class MidyGM1 {
80
83
  stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
81
84
  stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
82
85
  start(): Promise<void>;
83
- stop(): void;
84
- pause(): void;
86
+ stop(): Promise<void>;
87
+ pause(): Promise<void>;
85
88
  resume(): Promise<void>;
86
89
  seekTo(second: any): void;
87
90
  calcTotalTime(): number;
@@ -122,16 +125,16 @@ export class MidyGM1 {
122
125
  setDelayModLFO(note: any, scheduleTime: any): void;
123
126
  setFreqModLFO(note: any, scheduleTime: any): void;
124
127
  createVoiceParamsHandlers(): {
125
- modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
126
- vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
127
- modLfoToFilterFc: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
128
- modLfoToVolume: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
129
- chorusEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
130
- reverbEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
131
- delayModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
132
- freqModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
133
- delayVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
134
- freqVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
128
+ modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
129
+ vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
130
+ modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
131
+ modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
132
+ chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
133
+ reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
134
+ delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
135
+ freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
136
+ delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
137
+ freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
135
138
  };
136
139
  getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
137
140
  applyVoiceParams(channel: any, controllerType: any, scheduleTime: any): void;
@@ -162,7 +165,7 @@ export class MidyGM1 {
162
165
  handleCoarseTuningRPN(channelNumber: any, scheduleTime: any): void;
163
166
  setCoarseTuning(channelNumber: any, value: any, scheduleTime: any): void;
164
167
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
165
- resetAllStates(channelNumber: any): void;
168
+ resetChannelStates(channelNumber: any): void;
166
169
  resetAllControllers(channelNumber: any, _value: any, scheduleTime: any): void;
167
170
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
168
171
  handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AA4FA;IAwBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAlDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IAgBnC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,6EAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,4DASC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAgEC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;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,6FAyBC;IAED,oGAuCC;IAED,0EAiBC;IAED,kGAmCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAeC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;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,iEAKC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA//CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AA4FA;IAyBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,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;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,4DASC;IAED,+EAkDC;IAED,mCAOC;IAED,uBAQC;IAED,yDA2BC;IAED,2BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAKC;IAED,uBAQC;IAED,wBAKC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;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,6FAyBC;IAED,oGAuCC;IAED,0EAiBC;IAED,kGAmCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAeC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;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,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAhiDD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
package/esm/midy-GM1.js CHANGED
@@ -244,13 +244,13 @@ export class MidyGM1 {
244
244
  writable: true,
245
245
  value: false
246
246
  });
247
- Object.defineProperty(this, "timeline", {
247
+ Object.defineProperty(this, "playPromise", {
248
248
  enumerable: true,
249
249
  configurable: true,
250
250
  writable: true,
251
- value: []
251
+ value: void 0
252
252
  });
253
- Object.defineProperty(this, "instruments", {
253
+ Object.defineProperty(this, "timeline", {
254
254
  enumerable: true,
255
255
  configurable: true,
256
256
  writable: true,
@@ -262,6 +262,12 @@ export class MidyGM1 {
262
262
  writable: true,
263
263
  value: []
264
264
  });
265
+ Object.defineProperty(this, "instruments", {
266
+ enumerable: true,
267
+ configurable: true,
268
+ writable: true,
269
+ value: new Set()
270
+ });
265
271
  Object.defineProperty(this, "exclusiveClassNotes", {
266
272
  enumerable: true,
267
273
  configurable: true,
@@ -382,7 +388,7 @@ export class MidyGM1 {
382
388
  const soundFont = this.soundFonts[soundFontIndex];
383
389
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
384
390
  const { instrument, sampleID } = voice.generators;
385
- return `${soundFontIndex}:${instrument}:${sampleID}`;
391
+ return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
386
392
  }
387
393
  createChannelAudioNodes(audioContext) {
388
394
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
@@ -470,69 +476,80 @@ export class MidyGM1 {
470
476
  }
471
477
  return 0;
472
478
  }
473
- playNotes() {
474
- return new Promise((resolve) => {
475
- this.isPlaying = true;
476
- this.isPaused = false;
477
- this.startTime = this.audioContext.currentTime;
478
- let queueIndex = this.getQueueIndex(this.resumeTime);
479
- let resumeTime = this.resumeTime - this.startTime;
479
+ resetAllStates() {
480
+ this.exclusiveClassNotes.fill(undefined);
481
+ this.drumExclusiveClassNotes.fill(undefined);
482
+ this.voiceCache.clear();
483
+ for (let i = 0; i < this.channels.length; i++) {
484
+ this.channels[i].scheduledNotes = [];
485
+ this.resetChannelStates(i);
486
+ }
487
+ }
488
+ updateStates(queueIndex, nextQueueIndex) {
489
+ if (nextQueueIndex < queueIndex)
490
+ queueIndex = 0;
491
+ for (let i = queueIndex; i < nextQueueIndex; i++) {
492
+ const event = this.timeline[i];
493
+ switch (event.type) {
494
+ case "controller":
495
+ this.setControlChange(event.channel, event.controllerType, event.value, 0);
496
+ break;
497
+ case "programChange":
498
+ this.setProgramChange(event.channel, event.programNumber, 0);
499
+ break;
500
+ case "pitchBend":
501
+ this.setPitchBend(event.channel, event.value + 8192, 0);
502
+ break;
503
+ case "sysEx":
504
+ this.handleSysEx(event.data, 0);
505
+ }
506
+ }
507
+ }
508
+ async playNotes() {
509
+ if (this.audioContext.state === "suspended") {
510
+ await this.audioContext.resume();
511
+ }
512
+ this.isPlaying = true;
513
+ this.isPaused = false;
514
+ this.startTime = this.audioContext.currentTime;
515
+ let queueIndex = this.getQueueIndex(this.resumeTime);
516
+ let resumeTime = this.resumeTime - this.startTime;
517
+ let finished = false;
518
+ this.notePromises = [];
519
+ while (queueIndex < this.timeline.length) {
520
+ const now = this.audioContext.currentTime;
521
+ const t = now + resumeTime;
522
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
523
+ if (this.isPausing) {
524
+ await this.stopNotes(0, true, now);
525
+ await this.audioContext.suspend();
526
+ this.notePromises = [];
527
+ break;
528
+ }
529
+ else if (this.isStopping) {
530
+ await this.stopNotes(0, true, now);
531
+ await this.audioContext.suspend();
532
+ finished = true;
533
+ break;
534
+ }
535
+ else if (this.isSeeking) {
536
+ await this.stopNotes(0, true, now);
537
+ this.startTime = this.audioContext.currentTime;
538
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
539
+ this.updateStates(queueIndex, nextQueueIndex);
540
+ queueIndex = nextQueueIndex;
541
+ resumeTime = this.resumeTime - this.startTime;
542
+ this.isSeeking = false;
543
+ continue;
544
+ }
545
+ const waitTime = now + this.noteCheckInterval;
546
+ await this.scheduleTask(() => { }, waitTime);
547
+ }
548
+ if (finished) {
480
549
  this.notePromises = [];
481
- const schedulePlayback = async () => {
482
- if (queueIndex >= this.timeline.length) {
483
- await Promise.all(this.notePromises);
484
- this.notePromises = [];
485
- this.exclusiveClassNotes.fill(undefined);
486
- this.voiceCache.clear();
487
- for (let i = 0; i < this.channels.length; i++) {
488
- this.channels[i].scheduledNotes = [];
489
- this.resetAllStates(i);
490
- }
491
- resolve();
492
- return;
493
- }
494
- const now = this.audioContext.currentTime;
495
- const t = now + resumeTime;
496
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
497
- if (this.isPausing) {
498
- await this.stopNotes(0, true, now);
499
- this.notePromises = [];
500
- this.isPausing = false;
501
- this.isPaused = true;
502
- resolve();
503
- return;
504
- }
505
- else if (this.isStopping) {
506
- await this.stopNotes(0, true, now);
507
- this.notePromises = [];
508
- this.exclusiveClassNotes.fill(undefined);
509
- this.voiceCache.clear();
510
- for (let i = 0; i < this.channels.length; i++) {
511
- this.channels[i].scheduledNotes = [];
512
- this.resetAllStates(i);
513
- }
514
- this.isStopping = false;
515
- this.isPaused = false;
516
- resolve();
517
- return;
518
- }
519
- else if (this.isSeeking) {
520
- this.stopNotes(0, true, now);
521
- this.exclusiveClassNotes.fill(undefined);
522
- this.startTime = this.audioContext.currentTime;
523
- queueIndex = this.getQueueIndex(this.resumeTime);
524
- resumeTime = this.resumeTime - this.startTime;
525
- this.isSeeking = false;
526
- await schedulePlayback();
527
- }
528
- else {
529
- const waitTime = now + this.noteCheckInterval;
530
- await this.scheduleTask(() => { }, waitTime);
531
- await schedulePlayback();
532
- }
533
- };
534
- schedulePlayback();
535
- });
550
+ this.resetAllStates();
551
+ }
552
+ this.isPlaying = false;
536
553
  }
537
554
  ticksToSecond(ticks, secondsPerBeat) {
538
555
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -633,26 +650,32 @@ export class MidyGM1 {
633
650
  this.resumeTime = 0;
634
651
  if (this.voiceCounter.size === 0)
635
652
  this.cacheVoiceIds();
636
- await this.playNotes();
637
- this.isPlaying = false;
653
+ this.playPromise = this.playNotes();
654
+ await this.playPromise;
638
655
  }
639
- stop() {
656
+ async stop() {
640
657
  if (!this.isPlaying)
641
658
  return;
642
659
  this.isStopping = true;
660
+ await this.playPromise;
661
+ this.isStopping = false;
643
662
  }
644
- pause() {
663
+ async pause() {
645
664
  if (!this.isPlaying || this.isPaused)
646
665
  return;
647
666
  const now = this.audioContext.currentTime;
648
667
  this.resumeTime += now - this.startTime - this.startDelay;
649
668
  this.isPausing = true;
669
+ await this.playPromise;
670
+ this.isPausing = false;
671
+ this.isPaused = true;
650
672
  }
651
673
  async resume() {
652
674
  if (!this.isPaused)
653
675
  return;
654
- await this.playNotes();
655
- this.isPlaying = false;
676
+ this.playPromise = this.playNotes();
677
+ await this.playPromise;
678
+ this.isPaused = false;
656
679
  }
657
680
  seekTo(second) {
658
681
  this.resumeTime = second;
@@ -1015,13 +1038,17 @@ export class MidyGM1 {
1015
1038
  this.applyVoiceParams(channel, 14, scheduleTime);
1016
1039
  }
1017
1040
  setModLfoToPitch(channel, note, scheduleTime) {
1018
- const modLfoToPitch = note.voiceParams.modLfoToPitch;
1019
- const baseDepth = Math.abs(modLfoToPitch) +
1020
- channel.state.modulationDepth;
1021
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1022
- note.modulationDepth.gain
1023
- .cancelScheduledValues(scheduleTime)
1024
- .setValueAtTime(modulationDepth, scheduleTime);
1041
+ if (note.modulationDepth) {
1042
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1043
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1044
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1045
+ note.modulationDepth.gain
1046
+ .cancelScheduledValues(scheduleTime)
1047
+ .setValueAtTime(modulationDepth, scheduleTime);
1048
+ }
1049
+ else {
1050
+ this.startModulation(channel, note, scheduleTime);
1051
+ }
1025
1052
  }
1026
1053
  setModLfoToFilterFc(note, scheduleTime) {
1027
1054
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
@@ -1053,28 +1080,36 @@ export class MidyGM1 {
1053
1080
  }
1054
1081
  createVoiceParamsHandlers() {
1055
1082
  return {
1056
- modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1083
+ modLfoToPitch: (channel, note, scheduleTime) => {
1057
1084
  if (0 < channel.state.modulationDepth) {
1058
1085
  this.setModLfoToPitch(channel, note, scheduleTime);
1059
1086
  }
1060
1087
  },
1061
- vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
1062
- modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
1088
+ vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
1089
+ modLfoToFilterFc: (channel, note, scheduleTime) => {
1063
1090
  if (0 < channel.state.modulationDepth) {
1064
1091
  this.setModLfoToFilterFc(note, scheduleTime);
1065
1092
  }
1066
1093
  },
1067
- modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
1094
+ modLfoToVolume: (channel, note, scheduleTime) => {
1068
1095
  if (0 < channel.state.modulationDepth) {
1069
1096
  this.setModLfoToVolume(note, scheduleTime);
1070
1097
  }
1071
1098
  },
1072
- chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1073
- reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1074
- delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
1075
- freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
1076
- delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1077
- freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1099
+ chorusEffectsSend: (_channel, _note, _scheduleTime) => { },
1100
+ reverbEffectsSend: (_channel, _note, _scheduleTime) => { },
1101
+ delayModLFO: (_channel, note, scheduleTime) => {
1102
+ if (0 < channel.state.modulationDepth) {
1103
+ this.setDelayModLFO(note, scheduleTime);
1104
+ }
1105
+ },
1106
+ freqModLFO: (_channel, note, scheduleTime) => {
1107
+ if (0 < channel.state.modulationDepth) {
1108
+ this.setFreqModLFO(note, scheduleTime);
1109
+ }
1110
+ },
1111
+ delayVibLFO: (_channel, _note, _scheduleTime) => { },
1112
+ freqVibLFO: (_channel, _note, _scheduleTime) => { },
1078
1113
  };
1079
1114
  }
1080
1115
  getControllerState(channel, noteNumber, velocity) {
@@ -1097,7 +1132,7 @@ export class MidyGM1 {
1097
1132
  continue;
1098
1133
  note.voiceParams[key] = value;
1099
1134
  if (key in this.voiceParamsHandlers) {
1100
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1135
+ this.voiceParamsHandlers[key](channel, note, scheduleTime);
1101
1136
  }
1102
1137
  else {
1103
1138
  if (volumeEnvelopeKeySet.has(key))
@@ -1150,7 +1185,6 @@ export class MidyGM1 {
1150
1185
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1151
1186
  }
1152
1187
  else {
1153
- this.setPitchEnvelope(note, scheduleTime);
1154
1188
  this.startModulation(channel, note, scheduleTime);
1155
1189
  }
1156
1190
  });
@@ -1269,8 +1303,8 @@ export class MidyGM1 {
1269
1303
  }
1270
1304
  handlePitchBendRangeRPN(channelNumber, scheduleTime) {
1271
1305
  const channel = this.channels[channelNumber];
1272
- this.limitData(channel, 0, 127, 0, 99);
1273
- const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1306
+ this.limitData(channel, 0, 127, 0, 127);
1307
+ const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
1274
1308
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1275
1309
  }
1276
1310
  setPitchBendRange(channelNumber, value, scheduleTime) {
@@ -1278,7 +1312,7 @@ export class MidyGM1 {
1278
1312
  scheduleTime ??= this.audioContext.currentTime;
1279
1313
  const state = channel.state;
1280
1314
  const prev = state.pitchWheelSensitivity;
1281
- const next = value / 128;
1315
+ const next = value / 12800;
1282
1316
  state.pitchWheelSensitivity = next;
1283
1317
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1284
1318
  this.updateChannelDetune(channel, scheduleTime);
@@ -1287,14 +1321,15 @@ export class MidyGM1 {
1287
1321
  handleFineTuningRPN(channelNumber, scheduleTime) {
1288
1322
  const channel = this.channels[channelNumber];
1289
1323
  this.limitData(channel, 0, 127, 0, 127);
1290
- const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1324
+ const value = channel.dataMSB * 128 + channel.dataLSB;
1325
+ const fineTuning = (value - 8192) / 8192 * 100;
1291
1326
  this.setFineTuning(channelNumber, fineTuning, scheduleTime);
1292
1327
  }
1293
1328
  setFineTuning(channelNumber, value, scheduleTime) {
1294
1329
  const channel = this.channels[channelNumber];
1295
1330
  scheduleTime ??= this.audioContext.currentTime;
1296
1331
  const prev = channel.fineTuning;
1297
- const next = (value - 8192) / 8.192; // cent
1332
+ const next = value;
1298
1333
  channel.fineTuning = next;
1299
1334
  channel.detune += next - prev;
1300
1335
  this.updateChannelDetune(channel, scheduleTime);
@@ -1302,14 +1337,14 @@ export class MidyGM1 {
1302
1337
  handleCoarseTuningRPN(channelNumber, scheduleTime) {
1303
1338
  const channel = this.channels[channelNumber];
1304
1339
  this.limitDataMSB(channel, 0, 127);
1305
- const coarseTuning = channel.dataMSB;
1340
+ const coarseTuning = (channel.dataMSB - 64) * 100;
1306
1341
  this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
1307
1342
  }
1308
1343
  setCoarseTuning(channelNumber, value, scheduleTime) {
1309
1344
  const channel = this.channels[channelNumber];
1310
1345
  scheduleTime ??= this.audioContext.currentTime;
1311
1346
  const prev = channel.coarseTuning;
1312
- const next = (value - 64) * 100; // cent
1347
+ const next = value;
1313
1348
  channel.coarseTuning = next;
1314
1349
  channel.detune += next - prev;
1315
1350
  this.updateChannelDetune(channel, scheduleTime);
@@ -1318,7 +1353,7 @@ export class MidyGM1 {
1318
1353
  scheduleTime ??= this.audioContext.currentTime;
1319
1354
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1320
1355
  }
1321
- resetAllStates(channelNumber) {
1356
+ resetChannelStates(channelNumber) {
1322
1357
  const scheduleTime = this.audioContext.currentTime;
1323
1358
  const channel = this.channels[channelNumber];
1324
1359
  const state = channel.state;
@@ -1471,7 +1506,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
1471
1506
  rpnMSB: 127,
1472
1507
  rpnLSB: 127,
1473
1508
  modulationDepthRange: 50, // cent
1474
- fineTuning: 0, // cb
1475
- coarseTuning: 0, // cb
1509
+ fineTuning: 0, // cent
1510
+ coarseTuning: 0, // cent
1476
1511
  }
1477
1512
  });