@marmooo/midy 0.3.0 → 0.3.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.
@@ -3,60 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM2 = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
- // 2-3 times faster than Map
7
- class SparseMap {
8
- constructor(size) {
9
- this.data = new Array(size);
10
- this.activeIndices = [];
11
- }
12
- set(key, value) {
13
- if (this.data[key] === undefined) {
14
- this.activeIndices.push(key);
15
- }
16
- this.data[key] = value;
17
- }
18
- get(key) {
19
- return this.data[key];
20
- }
21
- delete(key) {
22
- if (this.data[key] !== undefined) {
23
- this.data[key] = undefined;
24
- const index = this.activeIndices.indexOf(key);
25
- if (index !== -1) {
26
- this.activeIndices.splice(index, 1);
27
- }
28
- return true;
29
- }
30
- return false;
31
- }
32
- has(key) {
33
- return this.data[key] !== undefined;
34
- }
35
- get size() {
36
- return this.activeIndices.length;
37
- }
38
- clear() {
39
- for (let i = 0; i < this.activeIndices.length; i++) {
40
- const key = this.activeIndices[i];
41
- this.data[key] = undefined;
42
- }
43
- this.activeIndices = [];
44
- }
45
- *[Symbol.iterator]() {
46
- for (let i = 0; i < this.activeIndices.length; i++) {
47
- const key = this.activeIndices[i];
48
- yield [key, this.data[key]];
49
- }
50
- }
51
- forEach(callback) {
52
- for (let i = 0; i < this.activeIndices.length; i++) {
53
- const key = this.activeIndices[i];
54
- callback(this.data[key], key, this);
55
- }
56
- }
57
- }
58
6
  class Note {
59
7
  constructor(noteNumber, velocity, startTime, voice, voiceParams) {
8
+ Object.defineProperty(this, "index", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: -1
13
+ });
14
+ Object.defineProperty(this, "noteOffEvent", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
60
20
  Object.defineProperty(this, "bufferSource", {
61
21
  enumerable: true,
62
22
  configurable: true,
@@ -141,11 +101,11 @@ class Note {
141
101
  writable: true,
142
102
  value: void 0
143
103
  });
144
- Object.defineProperty(this, "portamento", {
104
+ Object.defineProperty(this, "portamentoNoteNumber", {
145
105
  enumerable: true,
146
106
  configurable: true,
147
107
  writable: true,
148
- value: void 0
108
+ value: -1
149
109
  });
150
110
  this.noteNumber = noteNumber;
151
111
  this.velocity = velocity;
@@ -200,7 +160,7 @@ const defaultControllerState = {
200
160
  portamentoTime: { type: 128 + 5, defaultValue: 0 },
201
161
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
202
162
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
203
- pan: { type: 128 + 10, defaultValue: 0.5 },
163
+ pan: { type: 128 + 10, defaultValue: 64 / 127 },
204
164
  expression: { type: 128 + 11, defaultValue: 1 },
205
165
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
206
166
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
@@ -208,14 +168,6 @@ const defaultControllerState = {
208
168
  portamento: { type: 128 + 65, defaultValue: 0 },
209
169
  sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
210
170
  softPedal: { type: 128 + 67, defaultValue: 0 },
211
- filterResonance: { type: 128 + 71, defaultValue: 0.5 },
212
- releaseTime: { type: 128 + 72, defaultValue: 0.5 },
213
- attackTime: { type: 128 + 73, defaultValue: 0.5 },
214
- brightness: { type: 128 + 74, defaultValue: 0.5 },
215
- decayTime: { type: 128 + 75, defaultValue: 0.5 },
216
- vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
217
- vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
218
- vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
219
171
  reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
220
172
  chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
221
173
  // dataIncrement: { type: 128 + 96, defaultValue: 0 },
@@ -490,7 +442,7 @@ class MidyGM2 {
490
442
  initSoundFontTable() {
491
443
  const table = new Array(128);
492
444
  for (let i = 0; i < 128; i++) {
493
- table[i] = new SparseMap(128);
445
+ table[i] = new Map();
494
446
  }
495
447
  return table;
496
448
  }
@@ -537,18 +489,24 @@ class MidyGM2 {
537
489
  merger,
538
490
  };
539
491
  }
492
+ resetChannelTable(channel) {
493
+ this.resetControlTable(channel.controlTable);
494
+ channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
495
+ channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
496
+ channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
497
+ }
540
498
  createChannels(audioContext) {
541
499
  const channels = Array.from({ length: this.numChannels }, () => {
542
500
  return {
543
501
  currentBufferSource: null,
544
502
  isDrum: false,
545
- ...this.constructor.channelSettings,
546
503
  state: new ControllerState(),
547
- controlTable: this.initControlTable(),
504
+ ...this.constructor.channelSettings,
548
505
  ...this.setChannelAudioNodes(audioContext),
549
- scheduledNotes: new SparseMap(128),
506
+ scheduledNotes: [],
550
507
  sustainNotes: [],
551
- sostenutoNotes: new SparseMap(128),
508
+ sostenutoNotes: [],
509
+ controlTable: this.initControlTable(),
552
510
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
553
511
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
554
512
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
@@ -595,46 +553,20 @@ class MidyGM2 {
595
553
  }
596
554
  return bufferSource;
597
555
  }
598
- findPortamentoTarget(queueIndex) {
599
- const endEvent = this.timeline[queueIndex];
600
- if (!this.channels[endEvent.channel].portamento)
601
- return;
602
- const endTime = endEvent.startTime;
603
- let target;
604
- while (++queueIndex < this.timeline.length) {
605
- const event = this.timeline[queueIndex];
606
- if (endTime !== event.startTime)
607
- break;
608
- if (event.type !== "noteOn")
609
- continue;
610
- if (!target || event.noteNumber < target.noteNumber) {
611
- target = event;
612
- }
613
- }
614
- return target;
615
- }
616
- async scheduleTimelineEvents(t, offset, queueIndex) {
556
+ async scheduleTimelineEvents(t, resumeTime, queueIndex) {
617
557
  while (queueIndex < this.timeline.length) {
618
558
  const event = this.timeline[queueIndex];
619
559
  if (event.startTime > t + this.lookAhead)
620
560
  break;
621
- const startTime = event.startTime + this.startDelay - offset;
561
+ const delay = this.startDelay - resumeTime;
562
+ const startTime = event.startTime + delay;
622
563
  switch (event.type) {
623
- case "noteOn":
624
- if (event.velocity !== 0) {
625
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
626
- break;
627
- }
628
- /* falls through */
629
- case "noteOff": {
630
- const portamentoTarget = this.findPortamentoTarget(queueIndex);
631
- if (portamentoTarget)
632
- portamentoTarget.portamento = true;
633
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
634
- portamentoTarget?.noteNumber);
635
- if (notePromise) {
636
- this.notePromises.push(notePromise);
637
- }
564
+ case "noteOn": {
565
+ const noteOffEvent = {
566
+ ...event.noteOffEvent,
567
+ startTime: event.noteOffEvent.startTime + delay,
568
+ };
569
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
638
570
  break;
639
571
  }
640
572
  case "controller":
@@ -670,7 +602,7 @@ class MidyGM2 {
670
602
  this.isPaused = false;
671
603
  this.startTime = this.audioContext.currentTime;
672
604
  let queueIndex = this.getQueueIndex(this.resumeTime);
673
- let offset = this.resumeTime - this.startTime;
605
+ let resumeTime = this.resumeTime - this.startTime;
674
606
  this.notePromises = [];
675
607
  const schedulePlayback = async () => {
676
608
  if (queueIndex >= this.timeline.length) {
@@ -679,18 +611,21 @@ class MidyGM2 {
679
611
  this.exclusiveClassNotes.fill(undefined);
680
612
  this.drumExclusiveClassNotes.fill(undefined);
681
613
  this.audioBufferCache.clear();
614
+ for (let i = 0; i < this.channels.length; i++) {
615
+ this.resetAllStates(i);
616
+ }
682
617
  resolve();
683
618
  return;
684
619
  }
685
620
  const now = this.audioContext.currentTime;
686
- const t = now + offset;
687
- queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
621
+ const t = now + resumeTime;
622
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
688
623
  if (this.isPausing) {
689
624
  await this.stopNotes(0, true, now);
690
625
  this.notePromises = [];
691
- resolve();
692
626
  this.isPausing = false;
693
627
  this.isPaused = true;
628
+ resolve();
694
629
  return;
695
630
  }
696
631
  else if (this.isStopping) {
@@ -699,9 +634,12 @@ class MidyGM2 {
699
634
  this.exclusiveClassNotes.fill(undefined);
700
635
  this.drumExclusiveClassNotes.fill(undefined);
701
636
  this.audioBufferCache.clear();
702
- resolve();
637
+ for (let i = 0; i < this.channels.length; i++) {
638
+ this.resetAllStates(i);
639
+ }
703
640
  this.isStopping = false;
704
641
  this.isPaused = false;
642
+ resolve();
705
643
  return;
706
644
  }
707
645
  else if (this.isSeeking) {
@@ -710,7 +648,7 @@ class MidyGM2 {
710
648
  this.drumExclusiveClassNotes.fill(undefined);
711
649
  this.startTime = this.audioContext.currentTime;
712
650
  queueIndex = this.getQueueIndex(this.resumeTime);
713
- offset = this.resumeTime - this.startTime;
651
+ resumeTime = this.resumeTime - this.startTime;
714
652
  this.isSeeking = false;
715
653
  await schedulePlayback();
716
654
  }
@@ -832,17 +770,52 @@ class MidyGM2 {
832
770
  prevTempoTicks = event.ticks;
833
771
  }
834
772
  }
773
+ const activeNotes = new Array(this.channels.length * 128);
774
+ for (let i = 0; i < activeNotes.length; i++) {
775
+ activeNotes[i] = [];
776
+ }
777
+ for (let i = 0; i < timeline.length; i++) {
778
+ const event = timeline[i];
779
+ switch (event.type) {
780
+ case "noteOn": {
781
+ const index = event.channel * 128 + event.noteNumber;
782
+ activeNotes[index].push(event);
783
+ break;
784
+ }
785
+ case "noteOff": {
786
+ const index = event.channel * 128 + event.noteNumber;
787
+ const noteOn = activeNotes[index].pop();
788
+ if (noteOn) {
789
+ noteOn.noteOffEvent = event;
790
+ }
791
+ else {
792
+ const eventString = JSON.stringify(event, null, 2);
793
+ console.warn(`noteOff without matching noteOn: ${eventString}`);
794
+ }
795
+ }
796
+ }
797
+ }
835
798
  return { instruments, timeline };
836
799
  }
800
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
801
+ const channel = this.channels[channelNumber];
802
+ const promises = [];
803
+ this.processActiveNotes(channel, scheduleTime, (note) => {
804
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
805
+ this.notePromises.push(promise);
806
+ promises.push(promise);
807
+ });
808
+ return Promise.all(promises);
809
+ }
837
810
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
838
811
  const channel = this.channels[channelNumber];
839
812
  const promises = [];
840
813
  this.processScheduledNotes(channel, (note) => {
841
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
814
+ const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
842
815
  this.notePromises.push(promise);
843
816
  promises.push(promise);
844
817
  });
845
- channel.scheduledNotes.clear();
818
+ channel.scheduledNotes = [];
846
819
  return Promise.all(promises);
847
820
  }
848
821
  stopNotes(velocity, force, scheduleTime) {
@@ -863,9 +836,6 @@ class MidyGM2 {
863
836
  if (!this.isPlaying)
864
837
  return;
865
838
  this.isStopping = true;
866
- for (let i = 0; i < this.channels.length; i++) {
867
- this.resetAllStates(i);
868
- }
869
839
  }
870
840
  pause() {
871
841
  if (!this.isPlaying || this.isPaused)
@@ -900,35 +870,31 @@ class MidyGM2 {
900
870
  return this.resumeTime + now - this.startTime - this.startDelay;
901
871
  }
902
872
  processScheduledNotes(channel, callback) {
903
- channel.scheduledNotes.forEach((noteList) => {
904
- for (let i = 0; i < noteList.length; i++) {
905
- const note = noteList[i];
906
- if (!note)
907
- continue;
908
- callback(note);
909
- }
910
- });
911
- }
912
- getActiveNotes(channel, scheduleTime) {
913
- const activeNotes = new SparseMap(128);
914
- channel.scheduledNotes.forEach((noteList) => {
915
- const activeNote = this.getActiveNote(noteList, scheduleTime);
916
- if (activeNote) {
917
- activeNotes.set(activeNote.noteNumber, activeNote);
918
- }
919
- });
920
- return activeNotes;
873
+ const scheduledNotes = channel.scheduledNotes;
874
+ for (let i = 0; i < scheduledNotes.length; i++) {
875
+ const note = scheduledNotes[i];
876
+ if (!note)
877
+ continue;
878
+ if (note.ending)
879
+ continue;
880
+ callback(note);
881
+ }
921
882
  }
922
- getActiveNote(noteList, scheduleTime) {
923
- for (let i = noteList.length - 1; i >= 0; i--) {
924
- const note = noteList[i];
883
+ processActiveNotes(channel, scheduleTime, callback) {
884
+ const scheduledNotes = channel.scheduledNotes;
885
+ for (let i = 0; i < scheduledNotes.length; i++) {
886
+ const note = scheduledNotes[i];
925
887
  if (!note)
926
- return;
888
+ continue;
889
+ if (note.ending)
890
+ continue;
891
+ const noteOffEvent = note.noteOffEvent;
892
+ if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
893
+ continue;
927
894
  if (scheduleTime < note.startTime)
928
895
  continue;
929
- return (note.ending) ? null : note;
896
+ callback(note);
930
897
  }
931
- return noteList[0];
932
898
  }
933
899
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
934
900
  const sampleRate = audioContext.sampleRate;
@@ -1093,25 +1059,95 @@ class MidyGM2 {
1093
1059
  }
1094
1060
  updateDetune(channel, note, scheduleTime) {
1095
1061
  const noteDetune = this.calcNoteDetune(channel, note);
1096
- const detune = channel.detune + noteDetune;
1097
- note.bufferSource.detune
1098
- .cancelScheduledValues(scheduleTime)
1099
- .setValueAtTime(detune, scheduleTime);
1100
- }
1101
- getPortamentoTime(channel) {
1102
- const factor = 5 * Math.log(10) * 127;
1103
- return channel.state.portamentoTime * factor;
1104
- }
1105
- setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
1062
+ const pitchControl = this.getPitchControl(channel, note);
1063
+ const detune = channel.detune + noteDetune + pitchControl;
1064
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1065
+ const startTime = note.startTime;
1066
+ const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1067
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1068
+ note.bufferSource.detune
1069
+ .cancelScheduledValues(scheduleTime)
1070
+ .setValueAtTime(detune - deltaCent, scheduleTime)
1071
+ .linearRampToValueAtTime(detune, portamentoTime);
1072
+ }
1073
+ else {
1074
+ note.bufferSource.detune
1075
+ .cancelScheduledValues(scheduleTime)
1076
+ .setValueAtTime(detune, scheduleTime);
1077
+ }
1078
+ }
1079
+ getPortamentoTime(channel, note) {
1080
+ const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
1081
+ const value = Math.ceil(channel.state.portamentoTime * 127);
1082
+ return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
1083
+ }
1084
+ getPitchIncrementSpeed(value) {
1085
+ const points = [
1086
+ [0, 1000],
1087
+ [6, 100],
1088
+ [16, 20],
1089
+ [32, 10],
1090
+ [48, 5],
1091
+ [64, 2.5],
1092
+ [80, 1],
1093
+ [96, 0.4],
1094
+ [112, 0.15],
1095
+ [127, 0.01],
1096
+ ];
1097
+ const logPoints = new Array(points.length);
1098
+ for (let i = 0; i < points.length; i++) {
1099
+ const [x, y] = points[i];
1100
+ if (value === x)
1101
+ return y;
1102
+ logPoints[i] = [x, Math.log(y)];
1103
+ }
1104
+ let startIndex = 0;
1105
+ for (let i = 1; i < logPoints.length; i++) {
1106
+ if (value <= logPoints[i][0]) {
1107
+ startIndex = i - 1;
1108
+ break;
1109
+ }
1110
+ }
1111
+ const [x0, y0] = logPoints[startIndex];
1112
+ const [x1, y1] = logPoints[startIndex + 1];
1113
+ const h = x1 - x0;
1114
+ const t = (value - x0) / h;
1115
+ let m0, m1;
1116
+ if (startIndex === 0) {
1117
+ m0 = (y1 - y0) / h;
1118
+ }
1119
+ else {
1120
+ const [xPrev, yPrev] = logPoints[startIndex - 1];
1121
+ m0 = (y1 - yPrev) / (x1 - xPrev);
1122
+ }
1123
+ if (startIndex === logPoints.length - 2) {
1124
+ m1 = (y1 - y0) / h;
1125
+ }
1126
+ else {
1127
+ const [xNext, yNext] = logPoints[startIndex + 2];
1128
+ m1 = (yNext - y0) / (xNext - x0);
1129
+ }
1130
+ // Cubic Hermite Spline
1131
+ const t2 = t * t;
1132
+ const t3 = t2 * t;
1133
+ const h00 = 2 * t3 - 3 * t2 + 1;
1134
+ const h10 = t3 - 2 * t2 + t;
1135
+ const h01 = -2 * t3 + 3 * t2;
1136
+ const h11 = t3 - t2;
1137
+ const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
1138
+ return Math.exp(y);
1139
+ }
1140
+ setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
1106
1141
  const { voiceParams, startTime } = note;
1107
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
1142
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1143
+ (1 + this.getAmplitudeControl(channel));
1108
1144
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1109
1145
  const volDelay = startTime + voiceParams.volDelay;
1110
- const portamentoTime = volDelay + this.getPortamentoTime(channel);
1146
+ const volAttack = volDelay + voiceParams.volAttack;
1147
+ const volHold = volAttack + voiceParams.volHold;
1111
1148
  note.volumeEnvelopeNode.gain
1112
1149
  .cancelScheduledValues(scheduleTime)
1113
- .setValueAtTime(0, volDelay)
1114
- .linearRampToValueAtTime(sustainVolume, portamentoTime);
1150
+ .setValueAtTime(sustainVolume, volHold);
1115
1151
  }
1116
1152
  setVolumeEnvelope(channel, note, scheduleTime) {
1117
1153
  const { voiceParams, startTime } = note;
@@ -1130,6 +1166,12 @@ class MidyGM2 {
1130
1166
  .setValueAtTime(attackVolume, volHold)
1131
1167
  .linearRampToValueAtTime(sustainVolume, volDecay);
1132
1168
  }
1169
+ setPortamentoPitchEnvelope(note, scheduleTime) {
1170
+ const baseRate = note.voiceParams.playbackRate;
1171
+ note.bufferSource.playbackRate
1172
+ .cancelScheduledValues(scheduleTime)
1173
+ .setValueAtTime(baseRate, scheduleTime);
1174
+ }
1133
1175
  setPitchEnvelope(note, scheduleTime) {
1134
1176
  const { voiceParams } = note;
1135
1177
  const baseRate = voiceParams.playbackRate;
@@ -1157,19 +1199,20 @@ class MidyGM2 {
1157
1199
  const maxFrequency = 20000; // max Hz of initialFilterFc
1158
1200
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
1159
1201
  }
1160
- setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
1202
+ setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1161
1203
  const state = channel.state;
1162
1204
  const { voiceParams, noteNumber, startTime } = note;
1163
1205
  const softPedalFactor = 1 -
1164
1206
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1165
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1166
- softPedalFactor;
1207
+ const baseCent = voiceParams.initialFilterFc +
1208
+ this.getFilterCutoffControl(channel);
1209
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1167
1210
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1168
1211
  const sustainFreq = baseFreq +
1169
1212
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1170
1213
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1171
1214
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1172
- const portamentoTime = startTime + this.getPortamentoTime(channel);
1215
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1173
1216
  const modDelay = startTime + voiceParams.modDelay;
1174
1217
  note.filterNode.frequency
1175
1218
  .cancelScheduledValues(scheduleTime)
@@ -1254,7 +1297,7 @@ class MidyGM2 {
1254
1297
  return audioBuffer;
1255
1298
  }
1256
1299
  }
1257
- async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1300
+ async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1258
1301
  const now = this.audioContext.currentTime;
1259
1302
  const state = channel.state;
1260
1303
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
@@ -1270,20 +1313,24 @@ class MidyGM2 {
1270
1313
  type: "lowpass",
1271
1314
  Q: voiceParams.initialFilterQ / 10, // dB
1272
1315
  });
1273
- if (portamento) {
1274
- note.portamento = true;
1275
- this.setPortamentoStartVolumeEnvelope(channel, note, now);
1276
- this.setPortamentoStartFilterEnvelope(channel, note, now);
1316
+ const prevNote = channel.scheduledNotes.at(-1);
1317
+ if (prevNote && prevNote.noteNumber !== noteNumber) {
1318
+ note.portamentoNoteNumber = prevNote.noteNumber;
1319
+ }
1320
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1321
+ this.setPortamentoVolumeEnvelope(channel, note, now);
1322
+ this.setPortamentoFilterEnvelope(channel, note, now);
1323
+ this.setPortamentoPitchEnvelope(note, now);
1277
1324
  }
1278
1325
  else {
1279
- note.portamento = false;
1280
1326
  this.setVolumeEnvelope(channel, note, now);
1281
1327
  this.setFilterEnvelope(channel, note, now);
1328
+ this.setPitchEnvelope(note, now);
1282
1329
  }
1330
+ this.updateDetune(channel, note, now);
1283
1331
  if (0 < state.vibratoDepth) {
1284
1332
  this.startVibrato(channel, note, now);
1285
1333
  }
1286
- this.setPitchEnvelope(note, now);
1287
1334
  if (0 < state.modulationDepth) {
1288
1335
  this.startModulation(channel, note, now);
1289
1336
  }
@@ -1329,9 +1376,8 @@ class MidyGM2 {
1329
1376
  if (prev) {
1330
1377
  const [prevNote, prevChannelNumber] = prev;
1331
1378
  if (prevNote && !prevNote.ending) {
1332
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1333
- startTime, true, // force
1334
- undefined);
1379
+ this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1380
+ startTime, true);
1335
1381
  }
1336
1382
  }
1337
1383
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
@@ -1350,9 +1396,8 @@ class MidyGM2 {
1350
1396
  channelNumber;
1351
1397
  const prevNote = this.drumExclusiveClassNotes[index];
1352
1398
  if (prevNote && !prevNote.ending) {
1353
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1354
- startTime, true, // force
1355
- undefined);
1399
+ this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1400
+ startTime, true);
1356
1401
  }
1357
1402
  this.drumExclusiveClassNotes[index] = note;
1358
1403
  }
@@ -1360,10 +1405,10 @@ class MidyGM2 {
1360
1405
  if (!channel.isDrum)
1361
1406
  return false;
1362
1407
  const programNumber = channel.programNumber;
1363
- return (programNumber === 48 && noteNumber === 88) ||
1364
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
1408
+ return !((programNumber === 48 && noteNumber === 88) ||
1409
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1365
1410
  }
1366
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1411
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1367
1412
  const channel = this.channels[channelNumber];
1368
1413
  const bankNumber = this.calcBank(channel, channelNumber);
1369
1414
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -1374,7 +1419,8 @@ class MidyGM2 {
1374
1419
  if (!voice)
1375
1420
  return;
1376
1421
  const isSF3 = soundFont.parsed.info.version.major === 3;
1377
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1422
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1423
+ note.noteOffEvent = noteOffEvent;
1378
1424
  note.gainL.connect(channel.gainL);
1379
1425
  note.gainR.connect(channel.gainR);
1380
1426
  if (0.5 <= channel.state.sustainPedal) {
@@ -1383,33 +1429,39 @@ class MidyGM2 {
1383
1429
  this.handleExclusiveClass(note, channelNumber, startTime);
1384
1430
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
1385
1431
  const scheduledNotes = channel.scheduledNotes;
1386
- let notes = scheduledNotes.get(noteNumber);
1387
- if (notes) {
1388
- notes.push(note);
1389
- }
1390
- else {
1391
- notes = [note];
1392
- scheduledNotes.set(noteNumber, notes);
1393
- }
1432
+ note.index = scheduledNotes.length;
1433
+ scheduledNotes.push(note);
1394
1434
  if (this.isDrumNoteOffException(channel, noteNumber)) {
1395
1435
  const stopTime = startTime + note.bufferSource.buffer.duration;
1396
- const index = notes.length - 1;
1397
1436
  const promise = new Promise((resolve) => {
1398
1437
  note.bufferSource.onended = () => {
1399
- this.disconnectNote(note, scheduledNotes, index);
1438
+ scheduledNotes[note.index] = undefined;
1439
+ this.disconnectNote(note);
1400
1440
  resolve();
1401
1441
  };
1402
1442
  note.bufferSource.stop(stopTime);
1403
1443
  });
1404
1444
  this.notePromises.push(promise);
1405
1445
  }
1446
+ else if (noteOffEvent) {
1447
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1448
+ const portamentoTime = this.getPortamentoTime(channel, note);
1449
+ const portamentoEndTime = startTime + portamentoTime;
1450
+ const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
1451
+ Math.max(noteOffEvent.startTime, portamentoEndTime), false);
1452
+ this.notePromises.push(notePromise);
1453
+ }
1454
+ else {
1455
+ const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
1456
+ this.notePromises.push(notePromise);
1457
+ }
1458
+ }
1406
1459
  }
1407
1460
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1408
1461
  scheduleTime ??= this.audioContext.currentTime;
1409
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1462
+ return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
1410
1463
  }
1411
- disconnectNote(note, scheduledNotes, index) {
1412
- scheduledNotes[index] = null;
1464
+ disconnectNote(note) {
1413
1465
  note.bufferSource.disconnect();
1414
1466
  note.filterNode.disconnect();
1415
1467
  note.volumeEnvelopeNode.disconnect();
@@ -1432,8 +1484,7 @@ class MidyGM2 {
1432
1484
  note.chorusEffectsSend.disconnect();
1433
1485
  }
1434
1486
  }
1435
- stopNote(endTime, stopTime, scheduledNotes, index) {
1436
- const note = scheduledNotes[index];
1487
+ stopNote(channel, note, endTime, stopTime) {
1437
1488
  note.volumeEnvelopeNode.gain
1438
1489
  .cancelScheduledValues(endTime)
1439
1490
  .linearRampToValueAtTime(0, stopTime);
@@ -1443,64 +1494,57 @@ class MidyGM2 {
1443
1494
  }, stopTime);
1444
1495
  return new Promise((resolve) => {
1445
1496
  note.bufferSource.onended = () => {
1446
- this.disconnectNote(note, scheduledNotes, index);
1497
+ channel.scheduledNotes[note.index] = undefined;
1498
+ this.disconnectNote(note);
1447
1499
  resolve();
1448
1500
  };
1449
1501
  note.bufferSource.stop(stopTime);
1450
1502
  });
1451
1503
  }
1452
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1504
+ scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1453
1505
  const channel = this.channels[channelNumber];
1454
- if (this.isDrumNoteOffException(channel, noteNumber))
1506
+ if (this.isDrumNoteOffException(channel, note.noteNumber))
1455
1507
  return;
1456
1508
  const state = channel.state;
1457
1509
  if (!force) {
1458
1510
  if (0.5 <= state.sustainPedal)
1459
1511
  return;
1460
- if (channel.sostenutoNotes.has(noteNumber))
1512
+ if (0.5 <= channel.state.sostenutoPedal)
1461
1513
  return;
1462
1514
  }
1463
- if (!channel.scheduledNotes.has(noteNumber))
1464
- return;
1465
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
1515
+ const volRelease = endTime + note.voiceParams.volRelease;
1516
+ const modRelease = endTime + note.voiceParams.modRelease;
1517
+ note.filterNode.frequency
1518
+ .cancelScheduledValues(endTime)
1519
+ .linearRampToValueAtTime(0, modRelease);
1520
+ const stopTime = Math.min(volRelease, modRelease);
1521
+ return this.stopNote(channel, note, endTime, stopTime);
1522
+ }
1523
+ findNoteOffTarget(channel, noteNumber) {
1524
+ const scheduledNotes = channel.scheduledNotes;
1466
1525
  for (let i = 0; i < scheduledNotes.length; i++) {
1467
1526
  const note = scheduledNotes[i];
1468
1527
  if (!note)
1469
1528
  continue;
1470
1529
  if (note.ending)
1471
1530
  continue;
1472
- if (portamentoNoteNumber === undefined) {
1473
- const volRelease = endTime + note.voiceParams.volRelease;
1474
- const modRelease = endTime + note.voiceParams.modRelease;
1475
- note.filterNode.frequency
1476
- .cancelScheduledValues(endTime)
1477
- .linearRampToValueAtTime(0, modRelease);
1478
- const stopTime = Math.min(volRelease, modRelease);
1479
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
1480
- }
1481
- else {
1482
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1483
- const deltaNote = portamentoNoteNumber - noteNumber;
1484
- const baseRate = note.voiceParams.playbackRate;
1485
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1486
- note.bufferSource.playbackRate
1487
- .cancelScheduledValues(endTime)
1488
- .linearRampToValueAtTime(targetRate, portamentoTime);
1489
- return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1490
- }
1531
+ if (note.noteNumber !== noteNumber)
1532
+ continue;
1533
+ return note;
1491
1534
  }
1492
1535
  }
1493
1536
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1494
1537
  scheduleTime ??= this.audioContext.currentTime;
1495
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
1496
- undefined);
1538
+ const channel = this.channels[channelNumber];
1539
+ const note = this.findNoteOffTarget(channel, noteNumber);
1540
+ return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1497
1541
  }
1498
1542
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1499
1543
  const velocity = halfVelocity * 2;
1500
1544
  const channel = this.channels[channelNumber];
1501
1545
  const promises = [];
1502
1546
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1503
- const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1547
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1504
1548
  promises.push(promise);
1505
1549
  }
1506
1550
  channel.sustainNotes = [];
@@ -1510,12 +1554,14 @@ class MidyGM2 {
1510
1554
  const velocity = halfVelocity * 2;
1511
1555
  const channel = this.channels[channelNumber];
1512
1556
  const promises = [];
1557
+ const sostenutoNotes = channel.sostenutoNotes;
1513
1558
  channel.state.sostenutoPedal = 0;
1514
- channel.sostenutoNotes.forEach((note) => {
1515
- const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1559
+ for (let i = 0; i < sostenutoNotes.length; i++) {
1560
+ const note = sostenutoNotes[i];
1561
+ const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
1516
1562
  promises.push(promise);
1517
- });
1518
- channel.sostenutoNotes.clear();
1563
+ }
1564
+ channel.sostenutoNotes = [];
1519
1565
  return promises;
1520
1566
  }
1521
1567
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -1565,10 +1611,10 @@ class MidyGM2 {
1565
1611
  channel.detune += pressureDepth * (next - prev);
1566
1612
  }
1567
1613
  const table = channel.channelPressureTable;
1568
- this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1614
+ this.processActiveNotes(channel, scheduleTime, (note) => {
1569
1615
  this.setControllerParameters(channel, note, table);
1570
1616
  });
1571
- // this.applyVoiceParams(channel, 13);
1617
+ this.applyVoiceParams(channel, 13);
1572
1618
  }
1573
1619
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1574
1620
  const pitchBend = msb * 128 + lsb;
@@ -1745,6 +1791,7 @@ class MidyGM2 {
1745
1791
  state.set(channel.state.array);
1746
1792
  state[2] = velocity / 127;
1747
1793
  state[3] = noteNumber / 127;
1794
+ state[13] = state.channelPressure / 127;
1748
1795
  return state;
1749
1796
  }
1750
1797
  applyVoiceParams(channel, controllerType, scheduleTime) {
@@ -1771,8 +1818,8 @@ class MidyGM2 {
1771
1818
  if (key in voiceParams)
1772
1819
  noteVoiceParams[key] = voiceParams[key];
1773
1820
  }
1774
- if (note.portamento) {
1775
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1821
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1822
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1776
1823
  }
1777
1824
  else {
1778
1825
  this.setFilterEnvelope(channel, note, scheduleTime);
@@ -1795,32 +1842,32 @@ class MidyGM2 {
1795
1842
  });
1796
1843
  }
1797
1844
  createControlChangeHandlers() {
1798
- return {
1799
- 0: this.setBankMSB,
1800
- 1: this.setModulationDepth,
1801
- 5: this.setPortamentoTime,
1802
- 6: this.dataEntryMSB,
1803
- 7: this.setVolume,
1804
- 10: this.setPan,
1805
- 11: this.setExpression,
1806
- 32: this.setBankLSB,
1807
- 38: this.dataEntryLSB,
1808
- 64: this.setSustainPedal,
1809
- 65: this.setPortamento,
1810
- 66: this.setSostenutoPedal,
1811
- 67: this.setSoftPedal,
1812
- 91: this.setReverbSendLevel,
1813
- 93: this.setChorusSendLevel,
1814
- 100: this.setRPNLSB,
1815
- 101: this.setRPNMSB,
1816
- 120: this.allSoundOff,
1817
- 121: this.resetAllControllers,
1818
- 123: this.allNotesOff,
1819
- 124: this.omniOff,
1820
- 125: this.omniOn,
1821
- 126: this.monoOn,
1822
- 127: this.polyOn,
1823
- };
1845
+ const handlers = new Array(128);
1846
+ handlers[0] = this.setBankMSB;
1847
+ handlers[1] = this.setModulationDepth;
1848
+ handlers[5] = this.setPortamentoTime;
1849
+ handlers[6] = this.dataEntryMSB;
1850
+ handlers[7] = this.setVolume;
1851
+ handlers[10] = this.setPan;
1852
+ handlers[11] = this.setExpression;
1853
+ handlers[32] = this.setBankLSB;
1854
+ handlers[38] = this.dataEntryLSB;
1855
+ handlers[64] = this.setSustainPedal;
1856
+ handlers[65] = this.setPortamento;
1857
+ handlers[66] = this.setSostenutoPedal;
1858
+ handlers[67] = this.setSoftPedal;
1859
+ handlers[91] = this.setReverbSendLevel;
1860
+ handlers[93] = this.setChorusSendLevel;
1861
+ handlers[100] = this.setRPNLSB;
1862
+ handlers[101] = this.setRPNMSB;
1863
+ handlers[120] = this.allSoundOff;
1864
+ handlers[121] = this.resetAllControllers;
1865
+ handlers[123] = this.allNotesOff;
1866
+ handlers[124] = this.omniOff;
1867
+ handlers[125] = this.omniOn;
1868
+ handlers[126] = this.monoOn;
1869
+ handlers[127] = this.polyOn;
1870
+ return handlers;
1824
1871
  }
1825
1872
  handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1826
1873
  const handler = this.controlChangeHandlers[controllerType];
@@ -1857,9 +1904,33 @@ class MidyGM2 {
1857
1904
  channel.state.modulationDepth = modulation / 127;
1858
1905
  this.updateModulation(channel, scheduleTime);
1859
1906
  }
1860
- setPortamentoTime(channelNumber, portamentoTime) {
1907
+ updatePortamento(channel, scheduleTime) {
1908
+ this.processScheduledNotes(channel, (note) => {
1909
+ if (0.5 <= channel.state.portamento) {
1910
+ if (0 <= note.portamentoNoteNumber) {
1911
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
1912
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1913
+ this.setPortamentoPitchEnvelope(note, scheduleTime);
1914
+ this.updateDetune(channel, note, scheduleTime);
1915
+ }
1916
+ }
1917
+ else {
1918
+ if (0 <= note.portamentoNoteNumber) {
1919
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1920
+ this.setFilterEnvelope(channel, note, scheduleTime);
1921
+ this.setPitchEnvelope(note, scheduleTime);
1922
+ this.updateDetune(channel, note, scheduleTime);
1923
+ }
1924
+ }
1925
+ });
1926
+ }
1927
+ setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
1861
1928
  const channel = this.channels[channelNumber];
1929
+ scheduleTime ??= this.audioContext.currentTime;
1862
1930
  channel.state.portamentoTime = portamentoTime / 127;
1931
+ if (channel.isDrum)
1932
+ return;
1933
+ this.updatePortamento(channel, scheduleTime);
1863
1934
  }
1864
1935
  setKeyBasedVolume(channel, scheduleTime) {
1865
1936
  this.processScheduledNotes(channel, (note) => {
@@ -1945,11 +2016,13 @@ class MidyGM2 {
1945
2016
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1946
2017
  }
1947
2018
  }
1948
- setPortamento(channelNumber, value) {
2019
+ setPortamento(channelNumber, value, scheduleTime) {
1949
2020
  const channel = this.channels[channelNumber];
1950
2021
  if (channel.isDrum)
1951
2022
  return;
2023
+ scheduleTime ??= this.audioContext.currentTime;
1952
2024
  channel.state.portamento = value / 127;
2025
+ this.updatePortamento(channel, scheduleTime);
1953
2026
  }
1954
2027
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1955
2028
  const channel = this.channels[channelNumber];
@@ -1958,7 +2031,11 @@ class MidyGM2 {
1958
2031
  scheduleTime ??= this.audioContext.currentTime;
1959
2032
  channel.state.sostenutoPedal = value / 127;
1960
2033
  if (64 <= value) {
1961
- channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
2034
+ const sostenutoNotes = [];
2035
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2036
+ sostenutoNotes.push(note);
2037
+ });
2038
+ channel.sostenutoNotes = sostenutoNotes;
1962
2039
  }
1963
2040
  else {
1964
2041
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
@@ -1968,12 +2045,13 @@ class MidyGM2 {
1968
2045
  const channel = this.channels[channelNumber];
1969
2046
  if (channel.isDrum)
1970
2047
  return;
2048
+ const state = channel.state;
1971
2049
  scheduleTime ??= this.audioContext.currentTime;
1972
- channel.state.softPedal = softPedal / 127;
2050
+ state.softPedal = softPedal / 127;
1973
2051
  this.processScheduledNotes(channel, (note) => {
1974
- if (note.portamento) {
1975
- this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1976
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
2052
+ if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2053
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2054
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1977
2055
  }
1978
2056
  else {
1979
2057
  this.setVolumeEnvelope(channel, note, scheduleTime);
@@ -2170,24 +2248,32 @@ class MidyGM2 {
2170
2248
  }
2171
2249
  allSoundOff(channelNumber, _value, scheduleTime) {
2172
2250
  scheduleTime ??= this.audioContext.currentTime;
2173
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
2251
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2174
2252
  }
2175
2253
  resetAllStates(channelNumber) {
2254
+ const scheduleTime = this.audioContext.currentTime;
2176
2255
  const channel = this.channels[channelNumber];
2177
2256
  const state = channel.state;
2178
- for (const type of Object.keys(defaultControllerState)) {
2179
- state[type] = defaultControllerState[type].defaultValue;
2257
+ const entries = Object.entries(defaultControllerState);
2258
+ for (const [key, { type, defaultValue }] of entries) {
2259
+ if (128 <= type) {
2260
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2261
+ }
2262
+ else {
2263
+ state[key] = defaultValue;
2264
+ }
2180
2265
  }
2181
- for (const type of Object.keys(this.constructor.channelSettings)) {
2182
- channel[type] = this.constructor.channelSettings[type];
2266
+ for (const key of Object.keys(this.constructor.channelSettings)) {
2267
+ channel[key] = this.constructor.channelSettings[key];
2183
2268
  }
2269
+ this.resetChannelTable(channel);
2184
2270
  this.mode = "GM2";
2185
2271
  this.masterFineTuning = 0; // cb
2186
2272
  this.masterCoarseTuning = 0; // cb
2187
2273
  }
2188
2274
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2189
- resetAllControllers(channelNumber) {
2190
- const stateTypes = [
2275
+ resetAllControllers(channelNumber, _value, scheduleTime) {
2276
+ const keys = [
2191
2277
  "channelPressure",
2192
2278
  "pitchWheel",
2193
2279
  "expression",
@@ -2199,10 +2285,17 @@ class MidyGM2 {
2199
2285
  ];
2200
2286
  const channel = this.channels[channelNumber];
2201
2287
  const state = channel.state;
2202
- for (let i = 0; i < stateTypes.length; i++) {
2203
- const type = stateTypes[i];
2204
- state[type] = defaultControllerState[type].defaultValue;
2288
+ for (let i = 0; i < keys.length; i++) {
2289
+ const key = keys[i];
2290
+ const { type, defaultValue } = defaultControllerState[key];
2291
+ if (128 <= type) {
2292
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2293
+ }
2294
+ else {
2295
+ state[key] = defaultValue;
2296
+ }
2205
2297
  }
2298
+ this.setPitchBend(channelNumber, 8192, scheduleTime);
2206
2299
  const settingTypes = [
2207
2300
  "rpnMSB",
2208
2301
  "rpnLSB",
@@ -2214,7 +2307,7 @@ class MidyGM2 {
2214
2307
  }
2215
2308
  allNotesOff(channelNumber, _value, scheduleTime) {
2216
2309
  scheduleTime ??= this.audioContext.currentTime;
2217
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2310
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2218
2311
  }
2219
2312
  omniOff(channelNumber, value, scheduleTime) {
2220
2313
  this.allNotesOff(channelNumber, value, scheduleTime);
@@ -2431,7 +2524,7 @@ class MidyGM2 {
2431
2524
  this.reverbEffect = options.reverbAlgorithm(audioContext);
2432
2525
  }
2433
2526
  getReverbTime(value) {
2434
- return Math.pow(Math.E, (value - 40) * 0.025);
2527
+ return Math.exp((value - 40) * 0.025);
2435
2528
  }
2436
2529
  // mean free path equation
2437
2530
  // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
@@ -2588,6 +2681,8 @@ class MidyGM2 {
2588
2681
  if (!channelBitmap[i])
2589
2682
  continue;
2590
2683
  const channel = this.channels[i];
2684
+ if (channel.isDrum)
2685
+ continue;
2591
2686
  for (let j = 0; j < 12; j++) {
2592
2687
  const centValue = data[j + 7] - 64;
2593
2688
  channel.scaleOctaveTuningTable[j] = centValue;
@@ -2624,7 +2719,13 @@ class MidyGM2 {
2624
2719
  setControllerParameters(channel, note, table) {
2625
2720
  if (table[0] !== 64)
2626
2721
  this.updateDetune(channel, note);
2627
- if (!note.portamento) {
2722
+ if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2723
+ if (table[1] !== 64)
2724
+ this.setPortamentoFilterEnvelope(channel, note);
2725
+ if (table[2] !== 64)
2726
+ this.setPortamentoVolumeEnvelope(channel, note);
2727
+ }
2728
+ else {
2628
2729
  if (table[1] !== 64)
2629
2730
  this.setFilterEnvelope(channel, note);
2630
2731
  if (table[2] !== 64)
@@ -2639,7 +2740,10 @@ class MidyGM2 {
2639
2740
  }
2640
2741
  handlePressureSysEx(data, tableName) {
2641
2742
  const channelNumber = data[4];
2642
- const table = this.channels[channelNumber][tableName];
2743
+ const channel = this.channels[channelNumber];
2744
+ if (channel.isDrum)
2745
+ return;
2746
+ const table = channel[tableName];
2643
2747
  for (let i = 5; i < data.length - 1; i += 2) {
2644
2748
  const pp = data[i];
2645
2749
  const rr = data[i + 1];
@@ -2649,8 +2753,13 @@ class MidyGM2 {
2649
2753
  initControlTable() {
2650
2754
  const channelCount = 128;
2651
2755
  const slotSize = 6;
2652
- const defaultValues = [64, 64, 64, 0, 0, 0];
2653
2756
  const table = new Uint8Array(channelCount * slotSize);
2757
+ return this.resetControlTable(table);
2758
+ }
2759
+ resetControlTable(table) {
2760
+ const channelCount = 128;
2761
+ const slotSize = 6;
2762
+ const defaultValues = [64, 64, 64, 0, 0, 0];
2654
2763
  for (let ch = 0; ch < channelCount; ch++) {
2655
2764
  const offset = ch * slotSize;
2656
2765
  table.set(defaultValues, offset);
@@ -2667,8 +2776,11 @@ class MidyGM2 {
2667
2776
  }
2668
2777
  handleControlChangeSysEx(data) {
2669
2778
  const channelNumber = data[4];
2779
+ const channel = this.channels[channelNumber];
2780
+ if (channel.isDrum)
2781
+ return;
2670
2782
  const controllerType = data[5];
2671
- const table = this.channels[channelNumber].controlTable[controllerType];
2783
+ const table = channel.controlTable[controllerType];
2672
2784
  for (let i = 6; i < data.length - 1; i += 2) {
2673
2785
  const pp = data[i];
2674
2786
  const rr = data[i + 1];
@@ -2682,8 +2794,11 @@ class MidyGM2 {
2682
2794
  }
2683
2795
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2684
2796
  const channelNumber = data[4];
2797
+ const channel = this.channels[channelNumber];
2798
+ if (channel.isDrum)
2799
+ return;
2685
2800
  const keyNumber = data[5];
2686
- const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
2801
+ const table = channel.keyBasedInstrumentControlTable;
2687
2802
  for (let i = 6; i < data.length - 1; i += 2) {
2688
2803
  const controllerType = data[i];
2689
2804
  const value = data[i + 1];