@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.
@@ -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,
@@ -127,7 +87,7 @@ const defaultControllerState = {
127
87
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
128
88
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
129
89
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
130
- pan: { type: 128 + 10, defaultValue: 0.5 },
90
+ pan: { type: 128 + 10, defaultValue: 64 / 127 },
131
91
  expression: { type: 128 + 11, defaultValue: 1 },
132
92
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
133
93
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
@@ -338,7 +298,7 @@ export class MidyGMLite {
338
298
  initSoundFontTable() {
339
299
  const table = new Array(128);
340
300
  for (let i = 0; i < 128; i++) {
341
- table[i] = new SparseMap(128);
301
+ table[i] = new Map();
342
302
  }
343
303
  return table;
344
304
  }
@@ -354,17 +314,37 @@ export class MidyGMLite {
354
314
  }
355
315
  }
356
316
  }
357
- async loadSoundFont(soundFontUrl) {
358
- const response = await fetch(soundFontUrl);
359
- const arrayBuffer = await response.arrayBuffer();
360
- const parsed = parse(new Uint8Array(arrayBuffer));
317
+ async loadSoundFont(input) {
318
+ let uint8Array;
319
+ if (typeof input === "string") {
320
+ const response = await fetch(input);
321
+ const arrayBuffer = await response.arrayBuffer();
322
+ uint8Array = new Uint8Array(arrayBuffer);
323
+ }
324
+ else if (input instanceof Uint8Array) {
325
+ uint8Array = input;
326
+ }
327
+ else {
328
+ throw new TypeError("input must be a URL string or Uint8Array");
329
+ }
330
+ const parsed = parse(uint8Array);
361
331
  const soundFont = new SoundFont(parsed);
362
332
  this.addSoundFont(soundFont);
363
333
  }
364
- async loadMIDI(midiUrl) {
365
- const response = await fetch(midiUrl);
366
- const arrayBuffer = await response.arrayBuffer();
367
- const midi = parseMidi(new Uint8Array(arrayBuffer));
334
+ async loadMIDI(input) {
335
+ let uint8Array;
336
+ if (typeof input === "string") {
337
+ const response = await fetch(input);
338
+ const arrayBuffer = await response.arrayBuffer();
339
+ uint8Array = new Uint8Array(arrayBuffer);
340
+ }
341
+ else if (input instanceof Uint8Array) {
342
+ uint8Array = input;
343
+ }
344
+ else {
345
+ throw new TypeError("input must be a URL string or Uint8Array");
346
+ }
347
+ const midi = parseMidi(uint8Array);
368
348
  this.ticksPerBeat = midi.header.ticksPerBeat;
369
349
  const midiData = this.extractMidiData(midi);
370
350
  this.instruments = midiData.instruments;
@@ -390,10 +370,10 @@ export class MidyGMLite {
390
370
  return {
391
371
  currentBufferSource: null,
392
372
  isDrum: false,
393
- ...this.constructor.channelSettings,
394
373
  state: new ControllerState(),
374
+ ...this.constructor.channelSettings,
395
375
  ...this.setChannelAudioNodes(audioContext),
396
- scheduledNotes: new SparseMap(128),
376
+ scheduledNotes: [],
397
377
  sustainNotes: [],
398
378
  };
399
379
  });
@@ -428,34 +408,33 @@ export class MidyGMLite {
428
408
  return audioBuffer;
429
409
  }
430
410
  }
431
- createBufferSource(voiceParams, audioBuffer) {
411
+ createBufferSource(channel, voiceParams, audioBuffer) {
432
412
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
433
413
  bufferSource.buffer = audioBuffer;
434
414
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
415
+ if (channel.isDrum)
416
+ bufferSource.loop = false;
435
417
  if (bufferSource.loop) {
436
418
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
437
419
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
438
420
  }
439
421
  return bufferSource;
440
422
  }
441
- async scheduleTimelineEvents(t, offset, queueIndex) {
423
+ async scheduleTimelineEvents(t, resumeTime, queueIndex) {
442
424
  while (queueIndex < this.timeline.length) {
443
425
  const event = this.timeline[queueIndex];
444
426
  if (event.startTime > t + this.lookAhead)
445
427
  break;
446
- const startTime = event.startTime + this.startDelay - offset;
428
+ const delay = this.startDelay - resumeTime;
429
+ const startTime = event.startTime + delay;
447
430
  switch (event.type) {
448
431
  case "noteOn":
449
- if (0 < event.velocity) {
450
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
451
- break;
452
- }
453
- /* falls through */
432
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
433
+ break;
454
434
  case "noteOff": {
455
435
  const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
456
- if (notePromise) {
436
+ if (notePromise)
457
437
  this.notePromises.push(notePromise);
458
- }
459
438
  break;
460
439
  }
461
440
  case "controller":
@@ -488,44 +467,53 @@ export class MidyGMLite {
488
467
  this.isPaused = false;
489
468
  this.startTime = this.audioContext.currentTime;
490
469
  let queueIndex = this.getQueueIndex(this.resumeTime);
491
- let offset = this.resumeTime - this.startTime;
470
+ let resumeTime = this.resumeTime - this.startTime;
492
471
  this.notePromises = [];
493
472
  const schedulePlayback = async () => {
494
473
  if (queueIndex >= this.timeline.length) {
495
474
  await Promise.all(this.notePromises);
496
475
  this.notePromises = [];
497
476
  this.exclusiveClassNotes.fill(undefined);
477
+ this.drumExclusiveClassNotes.fill(undefined);
498
478
  this.audioBufferCache.clear();
479
+ for (let i = 0; i < this.channels.length; i++) {
480
+ this.resetAllStates(i);
481
+ }
499
482
  resolve();
500
483
  return;
501
484
  }
502
485
  const now = this.audioContext.currentTime;
503
- const t = now + offset;
504
- queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
486
+ const t = now + resumeTime;
487
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
505
488
  if (this.isPausing) {
506
489
  await this.stopNotes(0, true, now);
507
490
  this.notePromises = [];
508
- resolve();
509
491
  this.isPausing = false;
510
492
  this.isPaused = true;
493
+ resolve();
511
494
  return;
512
495
  }
513
496
  else if (this.isStopping) {
514
497
  await this.stopNotes(0, true, now);
515
498
  this.notePromises = [];
516
499
  this.exclusiveClassNotes.fill(undefined);
500
+ this.drumExclusiveClassNotes.fill(undefined);
517
501
  this.audioBufferCache.clear();
518
- resolve();
502
+ for (let i = 0; i < this.channels.length; i++) {
503
+ this.resetAllStates(i);
504
+ }
519
505
  this.isStopping = false;
520
506
  this.isPaused = false;
507
+ resolve();
521
508
  return;
522
509
  }
523
510
  else if (this.isSeeking) {
524
511
  this.stopNotes(0, true, now);
525
512
  this.exclusiveClassNotes.fill(undefined);
513
+ this.drumExclusiveClassNotes.fill(undefined);
526
514
  this.startTime = this.audioContext.currentTime;
527
515
  queueIndex = this.getQueueIndex(this.resumeTime);
528
- offset = this.resumeTime - this.startTime;
516
+ resumeTime = this.resumeTime - this.startTime;
529
517
  this.isSeeking = false;
530
518
  await schedulePlayback();
531
519
  }
@@ -548,6 +536,7 @@ export class MidyGMLite {
548
536
  return `${programNumber}:${noteNumber}:${velocity}`;
549
537
  }
550
538
  extractMidiData(midi) {
539
+ this.audioBufferCounter.clear();
551
540
  const instruments = new Set();
552
541
  const timeline = [];
553
542
  const tmpChannels = new Array(this.channels.length);
@@ -616,9 +605,8 @@ export class MidyGMLite {
616
605
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
617
606
  const channel = this.channels[channelNumber];
618
607
  const promises = [];
619
- const activeNotes = this.getActiveNotes(channel, scheduleTime);
620
- activeNotes.forEach((note) => {
621
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
608
+ this.processActiveNotes(channel, scheduleTime, (note) => {
609
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
622
610
  this.notePromises.push(promise);
623
611
  promises.push(promise);
624
612
  });
@@ -632,7 +620,7 @@ export class MidyGMLite {
632
620
  this.notePromises.push(promise);
633
621
  promises.push(promise);
634
622
  });
635
- channel.scheduledNotes.clear();
623
+ channel.scheduledNotes = [];
636
624
  return Promise.all(promises);
637
625
  }
638
626
  stopNotes(velocity, force, scheduleTime) {
@@ -653,9 +641,6 @@ export class MidyGMLite {
653
641
  if (!this.isPlaying)
654
642
  return;
655
643
  this.isStopping = true;
656
- for (let i = 0; i < this.channels.length; i++) {
657
- this.resetAllStates(i);
658
- }
659
644
  }
660
645
  pause() {
661
646
  if (!this.isPlaying || this.isPaused)
@@ -690,37 +675,28 @@ export class MidyGMLite {
690
675
  return this.resumeTime + now - this.startTime - this.startDelay;
691
676
  }
692
677
  processScheduledNotes(channel, callback) {
693
- channel.scheduledNotes.forEach((noteList) => {
694
- for (let i = 0; i < noteList.length; i++) {
695
- const note = noteList[i];
696
- if (!note)
697
- continue;
698
- if (note.ending)
699
- continue;
700
- callback(note);
701
- }
702
- });
703
- }
704
- getActiveNotes(channel, scheduleTime) {
705
- const activeNotes = new SparseMap(128);
706
- channel.scheduledNotes.forEach((noteList) => {
707
- const activeNote = this.getActiveNote(noteList, scheduleTime);
708
- if (activeNote) {
709
- activeNotes.set(activeNote.noteNumber, activeNote);
710
- }
711
- });
712
- return activeNotes;
678
+ const scheduledNotes = channel.scheduledNotes;
679
+ for (let i = 0; i < scheduledNotes.length; i++) {
680
+ const note = scheduledNotes[i];
681
+ if (!note)
682
+ continue;
683
+ if (note.ending)
684
+ continue;
685
+ callback(note);
686
+ }
713
687
  }
714
- getActiveNote(noteList, scheduleTime) {
715
- for (let i = noteList.length - 1; i >= 0; i--) {
716
- const note = noteList[i];
688
+ processActiveNotes(channel, scheduleTime, callback) {
689
+ const scheduledNotes = channel.scheduledNotes;
690
+ for (let i = 0; i < scheduledNotes.length; i++) {
691
+ const note = scheduledNotes[i];
717
692
  if (!note)
718
- return;
693
+ continue;
694
+ if (note.ending)
695
+ continue;
719
696
  if (scheduleTime < note.startTime)
720
697
  continue;
721
- return (note.ending) ? null : note;
698
+ callback(note);
722
699
  }
723
- return noteList[0];
724
700
  }
725
701
  cbToRatio(cb) {
726
702
  return Math.pow(10, cb / 200);
@@ -858,7 +834,7 @@ export class MidyGMLite {
858
834
  const voiceParams = voice.getAllParams(controllerState);
859
835
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
860
836
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
861
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
837
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
862
838
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
863
839
  note.filterNode = new BiquadFilterNode(this.audioContext, {
864
840
  type: "lowpass",
@@ -867,6 +843,7 @@ export class MidyGMLite {
867
843
  this.setVolumeEnvelope(note, now);
868
844
  this.setFilterEnvelope(note, now);
869
845
  this.setPitchEnvelope(note, now);
846
+ this.updateDetune(channel, note, now);
870
847
  if (0 < state.modulationDepth) {
871
848
  this.startModulation(channel, note, now);
872
849
  }
@@ -904,7 +881,7 @@ export class MidyGMLite {
904
881
  }
905
882
  this.drumExclusiveClassNotes[index] = note;
906
883
  }
907
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
884
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
908
885
  const channel = this.channels[channelNumber];
909
886
  const bankNumber = channel.bank;
910
887
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -916,6 +893,7 @@ export class MidyGMLite {
916
893
  return;
917
894
  const isSF3 = soundFont.parsed.info.version.major === 3;
918
895
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
896
+ note.noteOffEvent = noteOffEvent;
919
897
  note.volumeEnvelopeNode.connect(channel.gainL);
920
898
  note.volumeEnvelopeNode.connect(channel.gainR);
921
899
  if (0.5 <= channel.state.sustainPedal) {
@@ -924,31 +902,12 @@ export class MidyGMLite {
924
902
  this.handleExclusiveClass(note, channelNumber, startTime);
925
903
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
926
904
  const scheduledNotes = channel.scheduledNotes;
927
- let noteList = scheduledNotes.get(noteNumber);
928
- if (noteList) {
929
- noteList.push(note);
930
- }
931
- else {
932
- noteList = [note];
933
- scheduledNotes.set(noteNumber, noteList);
934
- }
935
- if (channel.isDrum) {
936
- const stopTime = startTime + note.bufferSource.buffer.duration;
937
- const index = noteList.length - 1;
938
- const promise = new Promise((resolve) => {
939
- note.bufferSource.onended = () => {
940
- noteList[index] = undefined;
941
- this.disconnectNote(note);
942
- resolve();
943
- };
944
- note.bufferSource.stop(stopTime);
945
- });
946
- this.notePromises.push(promise);
947
- }
905
+ note.index = scheduledNotes.length;
906
+ scheduledNotes.push(note);
948
907
  }
949
908
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
950
909
  scheduleTime ??= this.audioContext.currentTime;
951
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
910
+ return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
952
911
  }
953
912
  disconnectNote(note) {
954
913
  note.bufferSource.disconnect();
@@ -960,57 +919,54 @@ export class MidyGMLite {
960
919
  note.modulationLFO.stop();
961
920
  }
962
921
  }
963
- stopNote(endTime, stopTime, noteList, index) {
964
- const note = noteList[index];
922
+ releaseNote(channel, note, endTime) {
923
+ const volRelease = endTime + note.voiceParams.volRelease;
924
+ const modRelease = endTime + note.voiceParams.modRelease;
925
+ const stopTime = Math.min(volRelease, modRelease);
926
+ note.filterNode.frequency
927
+ .cancelScheduledValues(endTime)
928
+ .linearRampToValueAtTime(0, modRelease);
965
929
  note.volumeEnvelopeNode.gain
966
930
  .cancelScheduledValues(endTime)
967
- .linearRampToValueAtTime(0, stopTime);
968
- note.ending = true;
969
- this.scheduleTask(() => {
970
- note.bufferSource.loop = false;
971
- }, stopTime);
931
+ .linearRampToValueAtTime(0, volRelease);
972
932
  return new Promise((resolve) => {
973
- note.bufferSource.onended = () => {
974
- noteList[index] = undefined;
933
+ this.scheduleTask(() => {
934
+ const bufferSource = note.bufferSource;
935
+ bufferSource.loop = false;
936
+ bufferSource.stop(stopTime);
975
937
  this.disconnectNote(note);
938
+ channel.scheduledNotes[note.index] = undefined;
976
939
  resolve();
977
- };
978
- note.bufferSource.stop(stopTime);
940
+ }, stopTime);
979
941
  });
980
942
  }
981
- findNoteOffTarget(noteList) {
982
- for (let i = 0; i < noteList.length; i++) {
983
- const note = noteList[i];
943
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
944
+ const channel = this.channels[channelNumber];
945
+ if (!force) {
946
+ if (channel.isDrum)
947
+ return;
948
+ if (0.5 <= channel.state.sustainPedal)
949
+ return;
950
+ }
951
+ const note = this.findNoteOffTarget(channel, noteNumber);
952
+ if (!note)
953
+ return;
954
+ note.ending = true;
955
+ this.releaseNote(channel, note, endTime);
956
+ }
957
+ findNoteOffTarget(channel, noteNumber) {
958
+ const scheduledNotes = channel.scheduledNotes;
959
+ for (let i = 0; i < scheduledNotes.length; i++) {
960
+ const note = scheduledNotes[i];
984
961
  if (!note)
985
962
  continue;
986
963
  if (note.ending)
987
964
  continue;
988
- return [note, i];
965
+ if (note.noteNumber !== noteNumber)
966
+ continue;
967
+ return note;
989
968
  }
990
969
  }
991
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
992
- const channel = this.channels[channelNumber];
993
- if (channel.isDrum)
994
- return;
995
- if (!force && 0.5 <= channel.state.sustainPedal)
996
- return;
997
- if (!channel.scheduledNotes.has(noteNumber))
998
- return;
999
- const noteList = channel.scheduledNotes.get(noteNumber);
1000
- if (!noteList)
1001
- return; // be careful with drum channel
1002
- const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1003
- if (!noteOffTarget)
1004
- return;
1005
- const [note, i] = noteOffTarget;
1006
- const volRelease = endTime + note.voiceParams.volRelease;
1007
- const modRelease = endTime + note.voiceParams.modRelease;
1008
- note.filterNode.frequency
1009
- .cancelScheduledValues(endTime)
1010
- .linearRampToValueAtTime(0, modRelease);
1011
- const stopTime = Math.min(volRelease, modRelease);
1012
- return this.stopNote(endTime, stopTime, noteList, i);
1013
- }
1014
970
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1015
971
  scheduleTime ??= this.audioContext.currentTime;
1016
972
  return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
@@ -1020,7 +976,7 @@ export class MidyGMLite {
1020
976
  const channel = this.channels[channelNumber];
1021
977
  const promises = [];
1022
978
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1023
- const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
979
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1024
980
  promises.push(promise);
1025
981
  }
1026
982
  channel.sustainNotes = [];
@@ -1176,20 +1132,20 @@ export class MidyGMLite {
1176
1132
  });
1177
1133
  }
1178
1134
  createControlChangeHandlers() {
1179
- return {
1180
- 1: this.setModulationDepth,
1181
- 6: this.dataEntryMSB,
1182
- 7: this.setVolume,
1183
- 10: this.setPan,
1184
- 11: this.setExpression,
1185
- 38: this.dataEntryLSB,
1186
- 64: this.setSustainPedal,
1187
- 100: this.setRPNLSB,
1188
- 101: this.setRPNMSB,
1189
- 120: this.allSoundOff,
1190
- 121: this.resetAllControllers,
1191
- 123: this.allNotesOff,
1192
- };
1135
+ const handlers = new Array(128);
1136
+ handlers[1] = this.setModulationDepth;
1137
+ handlers[6] = this.dataEntryMSB;
1138
+ handlers[7] = this.setVolume;
1139
+ handlers[10] = this.setPan;
1140
+ handlers[11] = this.setExpression;
1141
+ handlers[38] = this.dataEntryLSB;
1142
+ handlers[64] = this.setSustainPedal;
1143
+ handlers[100] = this.setRPNLSB;
1144
+ handlers[101] = this.setRPNMSB;
1145
+ handlers[120] = this.allSoundOff;
1146
+ handlers[121] = this.resetAllControllers;
1147
+ handlers[123] = this.allNotesOff;
1148
+ return handlers;
1193
1149
  }
1194
1150
  handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1195
1151
  const handler = this.controlChangeHandlers[controllerType];
@@ -1334,19 +1290,26 @@ export class MidyGMLite {
1334
1290
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1335
1291
  }
1336
1292
  resetAllStates(channelNumber) {
1293
+ const scheduleTime = this.audioContext.currentTime;
1337
1294
  const channel = this.channels[channelNumber];
1338
1295
  const state = channel.state;
1339
- for (const type of Object.keys(defaultControllerState)) {
1340
- state[type] = defaultControllerState[type].defaultValue;
1296
+ const entries = Object.entries(defaultControllerState);
1297
+ for (const [key, { type, defaultValue }] of entries) {
1298
+ if (128 <= type) {
1299
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1300
+ }
1301
+ else {
1302
+ state[key] = defaultValue;
1303
+ }
1341
1304
  }
1342
- for (const type of Object.keys(this.constructor.channelSettings)) {
1343
- channel[type] = this.constructor.channelSettings[type];
1305
+ for (const key of Object.keys(this.constructor.channelSettings)) {
1306
+ channel[key] = this.constructor.channelSettings[key];
1344
1307
  }
1345
1308
  this.mode = "GM1";
1346
1309
  }
1347
1310
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
1348
- resetAllControllers(channelNumber) {
1349
- const stateTypes = [
1311
+ resetAllControllers(channelNumber, _value, scheduleTime) {
1312
+ const keys = [
1350
1313
  "pitchWheel",
1351
1314
  "expression",
1352
1315
  "modulationDepth",
@@ -1354,10 +1317,17 @@ export class MidyGMLite {
1354
1317
  ];
1355
1318
  const channel = this.channels[channelNumber];
1356
1319
  const state = channel.state;
1357
- for (let i = 0; i < stateTypes.length; i++) {
1358
- const type = stateTypes[i];
1359
- state[type] = defaultControllerState[type].defaultValue;
1320
+ for (let i = 0; i < keys.length; i++) {
1321
+ const key = keys[i];
1322
+ const { type, defaultValue } = defaultControllerState[key];
1323
+ if (128 <= type) {
1324
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1325
+ }
1326
+ else {
1327
+ state[key] = defaultValue;
1328
+ }
1360
1329
  }
1330
+ this.setPitchBend(channelNumber, 8192, scheduleTime);
1361
1331
  const settingTypes = [
1362
1332
  "rpnMSB",
1363
1333
  "rpnLSB",