@marmooo/midy 0.3.1 → 0.3.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/esm/midy-GM1.js CHANGED
@@ -1,59 +1,19 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
- // 2-3 times faster than Map
4
- class SparseMap {
5
- constructor(size) {
6
- this.data = new Array(size);
7
- this.activeIndices = [];
8
- }
9
- set(key, value) {
10
- if (this.data[key] === undefined) {
11
- this.activeIndices.push(key);
12
- }
13
- this.data[key] = value;
14
- }
15
- get(key) {
16
- return this.data[key];
17
- }
18
- delete(key) {
19
- if (this.data[key] !== undefined) {
20
- this.data[key] = undefined;
21
- const index = this.activeIndices.indexOf(key);
22
- if (index !== -1) {
23
- this.activeIndices.splice(index, 1);
24
- }
25
- return true;
26
- }
27
- return false;
28
- }
29
- has(key) {
30
- return this.data[key] !== undefined;
31
- }
32
- get size() {
33
- return this.activeIndices.length;
34
- }
35
- clear() {
36
- for (let i = 0; i < this.activeIndices.length; i++) {
37
- const key = this.activeIndices[i];
38
- this.data[key] = undefined;
39
- }
40
- this.activeIndices = [];
41
- }
42
- *[Symbol.iterator]() {
43
- for (let i = 0; i < this.activeIndices.length; i++) {
44
- const key = this.activeIndices[i];
45
- yield [key, this.data[key]];
46
- }
47
- }
48
- forEach(callback) {
49
- for (let i = 0; i < this.activeIndices.length; i++) {
50
- const key = this.activeIndices[i];
51
- callback(this.data[key], key, this);
52
- }
53
- }
54
- }
55
3
  class Note {
56
4
  constructor(noteNumber, velocity, startTime, voice, voiceParams) {
5
+ Object.defineProperty(this, "index", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: -1
10
+ });
11
+ Object.defineProperty(this, "ending", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: false
16
+ });
57
17
  Object.defineProperty(this, "bufferSource", {
58
18
  enumerable: true,
59
19
  configurable: true,
@@ -114,7 +74,7 @@ const defaultControllerState = {
114
74
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
115
75
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
116
76
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
117
- pan: { type: 128 + 10, defaultValue: 0.5 },
77
+ pan: { type: 128 + 10, defaultValue: 64 / 127 },
118
78
  expression: { type: 128 + 11, defaultValue: 1 },
119
79
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
120
80
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
@@ -319,7 +279,7 @@ export class MidyGM1 {
319
279
  initSoundFontTable() {
320
280
  const table = new Array(128);
321
281
  for (let i = 0; i < 128; i++) {
322
- table[i] = new SparseMap(128);
282
+ table[i] = new Map();
323
283
  }
324
284
  return table;
325
285
  }
@@ -335,17 +295,37 @@ export class MidyGM1 {
335
295
  }
336
296
  }
337
297
  }
338
- async loadSoundFont(soundFontUrl) {
339
- const response = await fetch(soundFontUrl);
340
- const arrayBuffer = await response.arrayBuffer();
341
- const parsed = parse(new Uint8Array(arrayBuffer));
298
+ async loadSoundFont(input) {
299
+ let uint8Array;
300
+ if (typeof input === "string") {
301
+ const response = await fetch(input);
302
+ const arrayBuffer = await response.arrayBuffer();
303
+ uint8Array = new Uint8Array(arrayBuffer);
304
+ }
305
+ else if (input instanceof Uint8Array) {
306
+ uint8Array = input;
307
+ }
308
+ else {
309
+ throw new TypeError("input must be a URL string or Uint8Array");
310
+ }
311
+ const parsed = parse(uint8Array);
342
312
  const soundFont = new SoundFont(parsed);
343
313
  this.addSoundFont(soundFont);
344
314
  }
345
- async loadMIDI(midiUrl) {
346
- const response = await fetch(midiUrl);
347
- const arrayBuffer = await response.arrayBuffer();
348
- const midi = parseMidi(new Uint8Array(arrayBuffer));
315
+ async loadMIDI(input) {
316
+ let uint8Array;
317
+ if (typeof input === "string") {
318
+ const response = await fetch(input);
319
+ const arrayBuffer = await response.arrayBuffer();
320
+ uint8Array = new Uint8Array(arrayBuffer);
321
+ }
322
+ else if (input instanceof Uint8Array) {
323
+ uint8Array = input;
324
+ }
325
+ else {
326
+ throw new TypeError("input must be a URL string or Uint8Array");
327
+ }
328
+ const midi = parseMidi(uint8Array);
349
329
  this.ticksPerBeat = midi.header.ticksPerBeat;
350
330
  const midiData = this.extractMidiData(midi);
351
331
  this.instruments = midiData.instruments;
@@ -371,10 +351,10 @@ export class MidyGM1 {
371
351
  return {
372
352
  currentBufferSource: null,
373
353
  isDrum: false,
374
- ...this.constructor.channelSettings,
375
354
  state: new ControllerState(),
355
+ ...this.constructor.channelSettings,
376
356
  ...this.setChannelAudioNodes(audioContext),
377
- scheduledNotes: new SparseMap(128),
357
+ scheduledNotes: [],
378
358
  sustainNotes: [],
379
359
  };
380
360
  });
@@ -409,7 +389,7 @@ export class MidyGM1 {
409
389
  return audioBuffer;
410
390
  }
411
391
  }
412
- createBufferSource(audioBuffer, voiceParams) {
392
+ createBufferSource(voiceParams, audioBuffer) {
413
393
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
414
394
  bufferSource.buffer = audioBuffer;
415
395
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
@@ -419,24 +399,21 @@ export class MidyGM1 {
419
399
  }
420
400
  return bufferSource;
421
401
  }
422
- async scheduleTimelineEvents(t, offset, queueIndex) {
402
+ async scheduleTimelineEvents(t, resumeTime, queueIndex) {
423
403
  while (queueIndex < this.timeline.length) {
424
404
  const event = this.timeline[queueIndex];
425
405
  if (event.startTime > t + this.lookAhead)
426
406
  break;
427
- const startTime = event.startTime + this.startDelay - offset;
407
+ const delay = this.startDelay - resumeTime;
408
+ const startTime = event.startTime + delay;
428
409
  switch (event.type) {
429
410
  case "noteOn":
430
- if (0 < event.velocity) {
431
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
432
- break;
433
- }
434
- /* falls through */
411
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
412
+ break;
435
413
  case "noteOff": {
436
414
  const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
437
- if (notePromise) {
415
+ if (notePromise)
438
416
  this.notePromises.push(notePromise);
439
- }
440
417
  break;
441
418
  }
442
419
  case "controller":
@@ -469,7 +446,7 @@ export class MidyGM1 {
469
446
  this.isPaused = false;
470
447
  this.startTime = this.audioContext.currentTime;
471
448
  let queueIndex = this.getQueueIndex(this.resumeTime);
472
- let offset = this.resumeTime - this.startTime;
449
+ let resumeTime = this.resumeTime - this.startTime;
473
450
  this.notePromises = [];
474
451
  const schedulePlayback = async () => {
475
452
  if (queueIndex >= this.timeline.length) {
@@ -477,18 +454,21 @@ export class MidyGM1 {
477
454
  this.notePromises = [];
478
455
  this.exclusiveClassNotes.fill(undefined);
479
456
  this.audioBufferCache.clear();
457
+ for (let i = 0; i < this.channels.length; i++) {
458
+ this.resetAllStates(i);
459
+ }
480
460
  resolve();
481
461
  return;
482
462
  }
483
463
  const now = this.audioContext.currentTime;
484
- const t = now + offset;
485
- queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
464
+ const t = now + resumeTime;
465
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
486
466
  if (this.isPausing) {
487
467
  await this.stopNotes(0, true, now);
488
468
  this.notePromises = [];
489
- resolve();
490
469
  this.isPausing = false;
491
470
  this.isPaused = true;
471
+ resolve();
492
472
  return;
493
473
  }
494
474
  else if (this.isStopping) {
@@ -496,9 +476,12 @@ export class MidyGM1 {
496
476
  this.notePromises = [];
497
477
  this.exclusiveClassNotes.fill(undefined);
498
478
  this.audioBufferCache.clear();
499
- resolve();
479
+ for (let i = 0; i < this.channels.length; i++) {
480
+ this.resetAllStates(i);
481
+ }
500
482
  this.isStopping = false;
501
483
  this.isPaused = false;
484
+ resolve();
502
485
  return;
503
486
  }
504
487
  else if (this.isSeeking) {
@@ -506,7 +489,7 @@ export class MidyGM1 {
506
489
  this.exclusiveClassNotes.fill(undefined);
507
490
  this.startTime = this.audioContext.currentTime;
508
491
  queueIndex = this.getQueueIndex(this.resumeTime);
509
- offset = this.resumeTime - this.startTime;
492
+ resumeTime = this.resumeTime - this.startTime;
510
493
  this.isSeeking = false;
511
494
  await schedulePlayback();
512
495
  }
@@ -529,6 +512,7 @@ export class MidyGM1 {
529
512
  return `${programNumber}:${noteNumber}:${velocity}`;
530
513
  }
531
514
  extractMidiData(midi) {
515
+ this.audioBufferCounter.clear();
532
516
  const instruments = new Set();
533
517
  const timeline = [];
534
518
  const tmpChannels = new Array(this.channels.length);
@@ -597,9 +581,8 @@ export class MidyGM1 {
597
581
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
598
582
  const channel = this.channels[channelNumber];
599
583
  const promises = [];
600
- const activeNotes = this.getActiveNotes(channel, scheduleTime);
601
- activeNotes.forEach((note) => {
602
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
584
+ this.processActiveNotes(channel, scheduleTime, (note) => {
585
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
603
586
  this.notePromises.push(promise);
604
587
  promises.push(promise);
605
588
  });
@@ -613,7 +596,7 @@ export class MidyGM1 {
613
596
  this.notePromises.push(promise);
614
597
  promises.push(promise);
615
598
  });
616
- channel.scheduledNotes.clear();
599
+ channel.scheduledNotes = [];
617
600
  return Promise.all(promises);
618
601
  }
619
602
  stopNotes(velocity, force, scheduleTime) {
@@ -634,9 +617,6 @@ export class MidyGM1 {
634
617
  if (!this.isPlaying)
635
618
  return;
636
619
  this.isStopping = true;
637
- for (let i = 0; i < this.channels.length; i++) {
638
- this.resetAllStates(i);
639
- }
640
620
  }
641
621
  pause() {
642
622
  if (!this.isPlaying || this.isPaused)
@@ -671,37 +651,28 @@ export class MidyGM1 {
671
651
  return this.resumeTime + now - this.startTime - this.startDelay;
672
652
  }
673
653
  processScheduledNotes(channel, callback) {
674
- channel.scheduledNotes.forEach((noteList) => {
675
- for (let i = 0; i < noteList.length; i++) {
676
- const note = noteList[i];
677
- if (!note)
678
- continue;
679
- if (note.ending)
680
- continue;
681
- callback(note);
682
- }
683
- });
684
- }
685
- getActiveNotes(channel, scheduleTime) {
686
- const activeNotes = new SparseMap(128);
687
- channel.scheduledNotes.forEach((noteList) => {
688
- const activeNote = this.getActiveNote(noteList, scheduleTime);
689
- if (activeNote) {
690
- activeNotes.set(activeNote.noteNumber, activeNote);
691
- }
692
- });
693
- return activeNotes;
654
+ const scheduledNotes = channel.scheduledNotes;
655
+ for (let i = 0; i < scheduledNotes.length; i++) {
656
+ const note = scheduledNotes[i];
657
+ if (!note)
658
+ continue;
659
+ if (note.ending)
660
+ continue;
661
+ callback(note);
662
+ }
694
663
  }
695
- getActiveNote(noteList, scheduleTime) {
696
- for (let i = noteList.length - 1; i >= 0; i--) {
697
- const note = noteList[i];
664
+ processActiveNotes(channel, scheduleTime, callback) {
665
+ const scheduledNotes = channel.scheduledNotes;
666
+ for (let i = 0; i < scheduledNotes.length; i++) {
667
+ const note = scheduledNotes[i];
698
668
  if (!note)
699
- return;
669
+ continue;
670
+ if (note.ending)
671
+ continue;
700
672
  if (scheduleTime < note.startTime)
701
673
  continue;
702
- return (note.ending) ? null : note;
674
+ callback(note);
703
675
  }
704
- return noteList[0];
705
676
  }
706
677
  cbToRatio(cb) {
707
678
  return Math.pow(10, cb / 200);
@@ -841,7 +812,7 @@ export class MidyGM1 {
841
812
  const voiceParams = voice.getAllParams(controllerState);
842
813
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
843
814
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
844
- note.bufferSource = this.createBufferSource(audioBuffer, voiceParams);
815
+ note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
845
816
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
846
817
  note.filterNode = new BiquadFilterNode(this.audioContext, {
847
818
  type: "lowpass",
@@ -850,6 +821,7 @@ export class MidyGM1 {
850
821
  this.setVolumeEnvelope(note, now);
851
822
  this.setFilterEnvelope(note, now);
852
823
  this.setPitchEnvelope(note, now);
824
+ this.updateDetune(channel, note, now);
853
825
  if (0 < state.modulationDepth) {
854
826
  this.startModulation(channel, note, now);
855
827
  }
@@ -872,7 +844,7 @@ export class MidyGM1 {
872
844
  }
873
845
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
874
846
  }
875
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
847
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
876
848
  const channel = this.channels[channelNumber];
877
849
  const bankNumber = channel.bank;
878
850
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -884,6 +856,7 @@ export class MidyGM1 {
884
856
  return;
885
857
  const isSF3 = soundFont.parsed.info.version.major === 3;
886
858
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
859
+ note.noteOffEvent = noteOffEvent;
887
860
  note.volumeEnvelopeNode.connect(channel.gainL);
888
861
  note.volumeEnvelopeNode.connect(channel.gainR);
889
862
  if (0.5 <= channel.state.sustainPedal) {
@@ -891,18 +864,12 @@ export class MidyGM1 {
891
864
  }
892
865
  this.handleExclusiveClass(note, channelNumber, startTime);
893
866
  const scheduledNotes = channel.scheduledNotes;
894
- let noteList = scheduledNotes.get(noteNumber);
895
- if (noteList) {
896
- noteList.push(note);
897
- }
898
- else {
899
- noteList = [note];
900
- scheduledNotes.set(noteNumber, noteList);
901
- }
867
+ note.index = scheduledNotes.length;
868
+ scheduledNotes.push(note);
902
869
  }
903
870
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
904
871
  scheduleTime ??= this.audioContext.currentTime;
905
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
872
+ return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
906
873
  }
907
874
  disconnectNote(note) {
908
875
  note.bufferSource.disconnect();
@@ -914,54 +881,49 @@ export class MidyGM1 {
914
881
  note.modulationLFO.stop();
915
882
  }
916
883
  }
917
- stopNote(endTime, stopTime, noteList, index) {
918
- const note = noteList[index];
884
+ releaseNote(channel, note, endTime) {
885
+ const volRelease = endTime + note.voiceParams.volRelease;
886
+ const modRelease = endTime + note.voiceParams.modRelease;
887
+ const stopTime = Math.min(volRelease, modRelease);
888
+ note.filterNode.frequency
889
+ .cancelScheduledValues(endTime)
890
+ .linearRampToValueAtTime(0, modRelease);
919
891
  note.volumeEnvelopeNode.gain
920
892
  .cancelScheduledValues(endTime)
921
- .linearRampToValueAtTime(0, stopTime);
922
- note.ending = true;
923
- this.scheduleTask(() => {
924
- note.bufferSource.loop = false;
925
- }, stopTime);
893
+ .linearRampToValueAtTime(0, volRelease);
926
894
  return new Promise((resolve) => {
927
- note.bufferSource.onended = () => {
928
- noteList[index] = undefined;
895
+ this.scheduleTask(() => {
896
+ const bufferSource = note.bufferSource;
897
+ bufferSource.loop = false;
898
+ bufferSource.stop(stopTime);
929
899
  this.disconnectNote(note);
900
+ channel.scheduledNotes[note.index] = undefined;
930
901
  resolve();
931
- };
932
- note.bufferSource.stop(stopTime);
902
+ }, stopTime);
933
903
  });
934
904
  }
935
- findNoteOffTarget(noteList) {
936
- for (let i = 0; i < noteList.length; i++) {
937
- const note = noteList[i];
938
- if (!note)
939
- continue;
940
- if (note.ending)
941
- continue;
942
- return [note, i];
943
- }
944
- }
945
905
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
946
906
  const channel = this.channels[channelNumber];
947
907
  if (!force && 0.5 <= channel.state.sustainPedal)
948
908
  return;
949
- if (!channel.scheduledNotes.has(noteNumber))
909
+ const note = this.findNoteOffTarget(channel, noteNumber);
910
+ if (!note)
950
911
  return;
951
- const noteList = channel.scheduledNotes.get(noteNumber);
952
- if (!noteList)
953
- return; // be careful with drum channel
954
- const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
955
- if (!noteOffTarget)
956
- return;
957
- const [note, i] = noteOffTarget;
958
- const volRelease = endTime + note.voiceParams.volRelease;
959
- const modRelease = endTime + note.voiceParams.modRelease;
960
- note.filterNode.frequency
961
- .cancelScheduledValues(endTime)
962
- .linearRampToValueAtTime(0, modRelease);
963
- const stopTime = Math.min(volRelease, modRelease);
964
- return this.stopNote(endTime, stopTime, noteList, i);
912
+ note.ending = true;
913
+ this.releaseNote(channel, note, endTime);
914
+ }
915
+ findNoteOffTarget(channel, noteNumber) {
916
+ const scheduledNotes = channel.scheduledNotes;
917
+ for (let i = 0; i < scheduledNotes.length; i++) {
918
+ const note = scheduledNotes[i];
919
+ if (!note)
920
+ continue;
921
+ if (note.ending)
922
+ continue;
923
+ if (note.noteNumber !== noteNumber)
924
+ continue;
925
+ return note;
926
+ }
965
927
  }
966
928
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
967
929
  scheduleTime ??= this.audioContext.currentTime;
@@ -972,7 +934,7 @@ export class MidyGM1 {
972
934
  const channel = this.channels[channelNumber];
973
935
  const promises = [];
974
936
  for (let i = 0; i < channel.sustainNotes.length; i++) {
975
- const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
937
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
976
938
  promises.push(promise);
977
939
  }
978
940
  channel.sustainNotes = [];
@@ -1128,20 +1090,19 @@ export class MidyGM1 {
1128
1090
  });
1129
1091
  }
1130
1092
  createControlChangeHandlers() {
1131
- return {
1132
- 1: this.setModulationDepth,
1133
- 6: this.dataEntryMSB,
1134
- 7: this.setVolume,
1135
- 10: this.setPan,
1136
- 11: this.setExpression,
1137
- 38: this.dataEntryLSB,
1138
- 64: this.setSustainPedal,
1139
- 100: this.setRPNLSB,
1140
- 101: this.setRPNMSB,
1141
- 120: this.allSoundOff,
1142
- 121: this.resetAllControllers,
1143
- 123: this.allNotesOff,
1144
- };
1093
+ const handlers = new Array(128);
1094
+ handlers[1] = this.setModulationDepth;
1095
+ handlers[6] = this.dataEntryMSB;
1096
+ handlers[7] = this.setVolume;
1097
+ handlers[10] = this.setPan;
1098
+ handlers[11] = this.setExpression;
1099
+ handlers[38] = this.dataEntryLSB;
1100
+ handlers[64] = this.setSustainPedal;
1101
+ handlers[100] = this.setRPNLSB;
1102
+ handlers[101] = this.setRPNMSB;
1103
+ handlers[120] = this.allSoundOff;
1104
+ handlers[121] = this.resetAllControllers;
1105
+ return handlers;
1145
1106
  }
1146
1107
  handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1147
1108
  const handler = this.controlChangeHandlers[controllerType];
@@ -1330,19 +1291,26 @@ export class MidyGM1 {
1330
1291
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1331
1292
  }
1332
1293
  resetAllStates(channelNumber) {
1294
+ const scheduleTime = this.audioContext.currentTime;
1333
1295
  const channel = this.channels[channelNumber];
1334
1296
  const state = channel.state;
1335
- for (const type of Object.keys(defaultControllerState)) {
1336
- state[type] = defaultControllerState[type].defaultValue;
1297
+ const entries = Object.entries(defaultControllerState);
1298
+ for (const [key, { type, defaultValue }] of entries) {
1299
+ if (128 <= type) {
1300
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1301
+ }
1302
+ else {
1303
+ state[key] = defaultValue;
1304
+ }
1337
1305
  }
1338
- for (const type of Object.keys(this.constructor.channelSettings)) {
1339
- channel[type] = this.constructor.channelSettings[type];
1306
+ for (const key of Object.keys(this.constructor.channelSettings)) {
1307
+ channel[key] = this.constructor.channelSettings[key];
1340
1308
  }
1341
1309
  this.mode = "GM1";
1342
1310
  }
1343
1311
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
1344
- resetAllControllers(channelNumber) {
1345
- const stateTypes = [
1312
+ resetAllControllers(channelNumber, _value, scheduleTime) {
1313
+ const keys = [
1346
1314
  "pitchWheel",
1347
1315
  "expression",
1348
1316
  "modulationDepth",
@@ -1350,10 +1318,17 @@ export class MidyGM1 {
1350
1318
  ];
1351
1319
  const channel = this.channels[channelNumber];
1352
1320
  const state = channel.state;
1353
- for (let i = 0; i < stateTypes.length; i++) {
1354
- const type = stateTypes[i];
1355
- state[type] = defaultControllerState[type].defaultValue;
1321
+ for (let i = 0; i < keys.length; i++) {
1322
+ const key = keys[i];
1323
+ const { type, defaultValue } = defaultControllerState[key];
1324
+ if (128 <= type) {
1325
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1326
+ }
1327
+ else {
1328
+ state[key] = defaultValue;
1329
+ }
1356
1330
  }
1331
+ this.setPitchBend(channelNumber, 8192, scheduleTime);
1357
1332
  const settingTypes = [
1358
1333
  "rpnMSB",
1359
1334
  "rpnLSB",