@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.
package/esm/midy-GM2.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, "noteOffEvent", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
57
17
  Object.defineProperty(this, "bufferSource", {
58
18
  enumerable: true,
59
19
  configurable: true,
@@ -138,11 +98,11 @@ class Note {
138
98
  writable: true,
139
99
  value: void 0
140
100
  });
141
- Object.defineProperty(this, "portamento", {
101
+ Object.defineProperty(this, "portamentoNoteNumber", {
142
102
  enumerable: true,
143
103
  configurable: true,
144
104
  writable: true,
145
- value: void 0
105
+ value: -1
146
106
  });
147
107
  this.noteNumber = noteNumber;
148
108
  this.velocity = velocity;
@@ -197,7 +157,7 @@ const defaultControllerState = {
197
157
  portamentoTime: { type: 128 + 5, defaultValue: 0 },
198
158
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
199
159
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
200
- pan: { type: 128 + 10, defaultValue: 0.5 },
160
+ pan: { type: 128 + 10, defaultValue: 64 / 127 },
201
161
  expression: { type: 128 + 11, defaultValue: 1 },
202
162
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
203
163
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
@@ -205,14 +165,6 @@ const defaultControllerState = {
205
165
  portamento: { type: 128 + 65, defaultValue: 0 },
206
166
  sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
207
167
  softPedal: { type: 128 + 67, defaultValue: 0 },
208
- filterResonance: { type: 128 + 71, defaultValue: 0.5 },
209
- releaseTime: { type: 128 + 72, defaultValue: 0.5 },
210
- attackTime: { type: 128 + 73, defaultValue: 0.5 },
211
- brightness: { type: 128 + 74, defaultValue: 0.5 },
212
- decayTime: { type: 128 + 75, defaultValue: 0.5 },
213
- vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
214
- vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
215
- vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
216
168
  reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
217
169
  chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
218
170
  // dataIncrement: { type: 128 + 96, defaultValue: 0 },
@@ -487,7 +439,7 @@ export class MidyGM2 {
487
439
  initSoundFontTable() {
488
440
  const table = new Array(128);
489
441
  for (let i = 0; i < 128; i++) {
490
- table[i] = new SparseMap(128);
442
+ table[i] = new Map();
491
443
  }
492
444
  return table;
493
445
  }
@@ -534,18 +486,24 @@ export class MidyGM2 {
534
486
  merger,
535
487
  };
536
488
  }
489
+ resetChannelTable(channel) {
490
+ this.resetControlTable(channel.controlTable);
491
+ channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
492
+ channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
493
+ channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
494
+ }
537
495
  createChannels(audioContext) {
538
496
  const channels = Array.from({ length: this.numChannels }, () => {
539
497
  return {
540
498
  currentBufferSource: null,
541
499
  isDrum: false,
542
- ...this.constructor.channelSettings,
543
500
  state: new ControllerState(),
544
- controlTable: this.initControlTable(),
501
+ ...this.constructor.channelSettings,
545
502
  ...this.setChannelAudioNodes(audioContext),
546
- scheduledNotes: new SparseMap(128),
503
+ scheduledNotes: [],
547
504
  sustainNotes: [],
548
- sostenutoNotes: new SparseMap(128),
505
+ sostenutoNotes: [],
506
+ controlTable: this.initControlTable(),
549
507
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
550
508
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
551
509
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
@@ -592,46 +550,20 @@ export class MidyGM2 {
592
550
  }
593
551
  return bufferSource;
594
552
  }
595
- findPortamentoTarget(queueIndex) {
596
- const endEvent = this.timeline[queueIndex];
597
- if (!this.channels[endEvent.channel].portamento)
598
- return;
599
- const endTime = endEvent.startTime;
600
- let target;
601
- while (++queueIndex < this.timeline.length) {
602
- const event = this.timeline[queueIndex];
603
- if (endTime !== event.startTime)
604
- break;
605
- if (event.type !== "noteOn")
606
- continue;
607
- if (!target || event.noteNumber < target.noteNumber) {
608
- target = event;
609
- }
610
- }
611
- return target;
612
- }
613
- async scheduleTimelineEvents(t, offset, queueIndex) {
553
+ async scheduleTimelineEvents(t, resumeTime, queueIndex) {
614
554
  while (queueIndex < this.timeline.length) {
615
555
  const event = this.timeline[queueIndex];
616
556
  if (event.startTime > t + this.lookAhead)
617
557
  break;
618
- const startTime = event.startTime + this.startDelay - offset;
558
+ const delay = this.startDelay - resumeTime;
559
+ const startTime = event.startTime + delay;
619
560
  switch (event.type) {
620
- case "noteOn":
621
- if (event.velocity !== 0) {
622
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
623
- break;
624
- }
625
- /* falls through */
626
- case "noteOff": {
627
- const portamentoTarget = this.findPortamentoTarget(queueIndex);
628
- if (portamentoTarget)
629
- portamentoTarget.portamento = true;
630
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
631
- portamentoTarget?.noteNumber);
632
- if (notePromise) {
633
- this.notePromises.push(notePromise);
634
- }
561
+ case "noteOn": {
562
+ const noteOffEvent = {
563
+ ...event.noteOffEvent,
564
+ startTime: event.noteOffEvent.startTime + delay,
565
+ };
566
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
635
567
  break;
636
568
  }
637
569
  case "controller":
@@ -667,7 +599,7 @@ export class MidyGM2 {
667
599
  this.isPaused = false;
668
600
  this.startTime = this.audioContext.currentTime;
669
601
  let queueIndex = this.getQueueIndex(this.resumeTime);
670
- let offset = this.resumeTime - this.startTime;
602
+ let resumeTime = this.resumeTime - this.startTime;
671
603
  this.notePromises = [];
672
604
  const schedulePlayback = async () => {
673
605
  if (queueIndex >= this.timeline.length) {
@@ -676,18 +608,21 @@ export class MidyGM2 {
676
608
  this.exclusiveClassNotes.fill(undefined);
677
609
  this.drumExclusiveClassNotes.fill(undefined);
678
610
  this.audioBufferCache.clear();
611
+ for (let i = 0; i < this.channels.length; i++) {
612
+ this.resetAllStates(i);
613
+ }
679
614
  resolve();
680
615
  return;
681
616
  }
682
617
  const now = this.audioContext.currentTime;
683
- const t = now + offset;
684
- queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
618
+ const t = now + resumeTime;
619
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
685
620
  if (this.isPausing) {
686
621
  await this.stopNotes(0, true, now);
687
622
  this.notePromises = [];
688
- resolve();
689
623
  this.isPausing = false;
690
624
  this.isPaused = true;
625
+ resolve();
691
626
  return;
692
627
  }
693
628
  else if (this.isStopping) {
@@ -696,9 +631,12 @@ export class MidyGM2 {
696
631
  this.exclusiveClassNotes.fill(undefined);
697
632
  this.drumExclusiveClassNotes.fill(undefined);
698
633
  this.audioBufferCache.clear();
699
- resolve();
634
+ for (let i = 0; i < this.channels.length; i++) {
635
+ this.resetAllStates(i);
636
+ }
700
637
  this.isStopping = false;
701
638
  this.isPaused = false;
639
+ resolve();
702
640
  return;
703
641
  }
704
642
  else if (this.isSeeking) {
@@ -707,7 +645,7 @@ export class MidyGM2 {
707
645
  this.drumExclusiveClassNotes.fill(undefined);
708
646
  this.startTime = this.audioContext.currentTime;
709
647
  queueIndex = this.getQueueIndex(this.resumeTime);
710
- offset = this.resumeTime - this.startTime;
648
+ resumeTime = this.resumeTime - this.startTime;
711
649
  this.isSeeking = false;
712
650
  await schedulePlayback();
713
651
  }
@@ -829,17 +767,52 @@ export class MidyGM2 {
829
767
  prevTempoTicks = event.ticks;
830
768
  }
831
769
  }
770
+ const activeNotes = new Array(this.channels.length * 128);
771
+ for (let i = 0; i < activeNotes.length; i++) {
772
+ activeNotes[i] = [];
773
+ }
774
+ for (let i = 0; i < timeline.length; i++) {
775
+ const event = timeline[i];
776
+ switch (event.type) {
777
+ case "noteOn": {
778
+ const index = event.channel * 128 + event.noteNumber;
779
+ activeNotes[index].push(event);
780
+ break;
781
+ }
782
+ case "noteOff": {
783
+ const index = event.channel * 128 + event.noteNumber;
784
+ const noteOn = activeNotes[index].pop();
785
+ if (noteOn) {
786
+ noteOn.noteOffEvent = event;
787
+ }
788
+ else {
789
+ const eventString = JSON.stringify(event, null, 2);
790
+ console.warn(`noteOff without matching noteOn: ${eventString}`);
791
+ }
792
+ }
793
+ }
794
+ }
832
795
  return { instruments, timeline };
833
796
  }
797
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
798
+ const channel = this.channels[channelNumber];
799
+ const promises = [];
800
+ this.processActiveNotes(channel, scheduleTime, (note) => {
801
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
802
+ this.notePromises.push(promise);
803
+ promises.push(promise);
804
+ });
805
+ return Promise.all(promises);
806
+ }
834
807
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
835
808
  const channel = this.channels[channelNumber];
836
809
  const promises = [];
837
810
  this.processScheduledNotes(channel, (note) => {
838
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
811
+ const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
839
812
  this.notePromises.push(promise);
840
813
  promises.push(promise);
841
814
  });
842
- channel.scheduledNotes.clear();
815
+ channel.scheduledNotes = [];
843
816
  return Promise.all(promises);
844
817
  }
845
818
  stopNotes(velocity, force, scheduleTime) {
@@ -860,9 +833,6 @@ export class MidyGM2 {
860
833
  if (!this.isPlaying)
861
834
  return;
862
835
  this.isStopping = true;
863
- for (let i = 0; i < this.channels.length; i++) {
864
- this.resetAllStates(i);
865
- }
866
836
  }
867
837
  pause() {
868
838
  if (!this.isPlaying || this.isPaused)
@@ -897,35 +867,31 @@ export class MidyGM2 {
897
867
  return this.resumeTime + now - this.startTime - this.startDelay;
898
868
  }
899
869
  processScheduledNotes(channel, callback) {
900
- channel.scheduledNotes.forEach((noteList) => {
901
- for (let i = 0; i < noteList.length; i++) {
902
- const note = noteList[i];
903
- if (!note)
904
- continue;
905
- callback(note);
906
- }
907
- });
908
- }
909
- getActiveNotes(channel, scheduleTime) {
910
- const activeNotes = new SparseMap(128);
911
- channel.scheduledNotes.forEach((noteList) => {
912
- const activeNote = this.getActiveNote(noteList, scheduleTime);
913
- if (activeNote) {
914
- activeNotes.set(activeNote.noteNumber, activeNote);
915
- }
916
- });
917
- return activeNotes;
870
+ const scheduledNotes = channel.scheduledNotes;
871
+ for (let i = 0; i < scheduledNotes.length; i++) {
872
+ const note = scheduledNotes[i];
873
+ if (!note)
874
+ continue;
875
+ if (note.ending)
876
+ continue;
877
+ callback(note);
878
+ }
918
879
  }
919
- getActiveNote(noteList, scheduleTime) {
920
- for (let i = noteList.length - 1; i >= 0; i--) {
921
- const note = noteList[i];
880
+ processActiveNotes(channel, scheduleTime, callback) {
881
+ const scheduledNotes = channel.scheduledNotes;
882
+ for (let i = 0; i < scheduledNotes.length; i++) {
883
+ const note = scheduledNotes[i];
922
884
  if (!note)
923
- return;
885
+ continue;
886
+ if (note.ending)
887
+ continue;
888
+ const noteOffEvent = note.noteOffEvent;
889
+ if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
890
+ continue;
924
891
  if (scheduleTime < note.startTime)
925
892
  continue;
926
- return (note.ending) ? null : note;
893
+ callback(note);
927
894
  }
928
- return noteList[0];
929
895
  }
930
896
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
931
897
  const sampleRate = audioContext.sampleRate;
@@ -1090,25 +1056,95 @@ export class MidyGM2 {
1090
1056
  }
1091
1057
  updateDetune(channel, note, scheduleTime) {
1092
1058
  const noteDetune = this.calcNoteDetune(channel, note);
1093
- const detune = channel.detune + noteDetune;
1094
- note.bufferSource.detune
1095
- .cancelScheduledValues(scheduleTime)
1096
- .setValueAtTime(detune, scheduleTime);
1097
- }
1098
- getPortamentoTime(channel) {
1099
- const factor = 5 * Math.log(10) * 127;
1100
- return channel.state.portamentoTime * factor;
1101
- }
1102
- setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
1059
+ const pitchControl = this.getPitchControl(channel, note);
1060
+ const detune = channel.detune + noteDetune + pitchControl;
1061
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1062
+ const startTime = note.startTime;
1063
+ const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1064
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1065
+ note.bufferSource.detune
1066
+ .cancelScheduledValues(scheduleTime)
1067
+ .setValueAtTime(detune - deltaCent, scheduleTime)
1068
+ .linearRampToValueAtTime(detune, portamentoTime);
1069
+ }
1070
+ else {
1071
+ note.bufferSource.detune
1072
+ .cancelScheduledValues(scheduleTime)
1073
+ .setValueAtTime(detune, scheduleTime);
1074
+ }
1075
+ }
1076
+ getPortamentoTime(channel, note) {
1077
+ const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
1078
+ const value = Math.ceil(channel.state.portamentoTime * 127);
1079
+ return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
1080
+ }
1081
+ getPitchIncrementSpeed(value) {
1082
+ const points = [
1083
+ [0, 1000],
1084
+ [6, 100],
1085
+ [16, 20],
1086
+ [32, 10],
1087
+ [48, 5],
1088
+ [64, 2.5],
1089
+ [80, 1],
1090
+ [96, 0.4],
1091
+ [112, 0.15],
1092
+ [127, 0.01],
1093
+ ];
1094
+ const logPoints = new Array(points.length);
1095
+ for (let i = 0; i < points.length; i++) {
1096
+ const [x, y] = points[i];
1097
+ if (value === x)
1098
+ return y;
1099
+ logPoints[i] = [x, Math.log(y)];
1100
+ }
1101
+ let startIndex = 0;
1102
+ for (let i = 1; i < logPoints.length; i++) {
1103
+ if (value <= logPoints[i][0]) {
1104
+ startIndex = i - 1;
1105
+ break;
1106
+ }
1107
+ }
1108
+ const [x0, y0] = logPoints[startIndex];
1109
+ const [x1, y1] = logPoints[startIndex + 1];
1110
+ const h = x1 - x0;
1111
+ const t = (value - x0) / h;
1112
+ let m0, m1;
1113
+ if (startIndex === 0) {
1114
+ m0 = (y1 - y0) / h;
1115
+ }
1116
+ else {
1117
+ const [xPrev, yPrev] = logPoints[startIndex - 1];
1118
+ m0 = (y1 - yPrev) / (x1 - xPrev);
1119
+ }
1120
+ if (startIndex === logPoints.length - 2) {
1121
+ m1 = (y1 - y0) / h;
1122
+ }
1123
+ else {
1124
+ const [xNext, yNext] = logPoints[startIndex + 2];
1125
+ m1 = (yNext - y0) / (xNext - x0);
1126
+ }
1127
+ // Cubic Hermite Spline
1128
+ const t2 = t * t;
1129
+ const t3 = t2 * t;
1130
+ const h00 = 2 * t3 - 3 * t2 + 1;
1131
+ const h10 = t3 - 2 * t2 + t;
1132
+ const h01 = -2 * t3 + 3 * t2;
1133
+ const h11 = t3 - t2;
1134
+ const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
1135
+ return Math.exp(y);
1136
+ }
1137
+ setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
1103
1138
  const { voiceParams, startTime } = note;
1104
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
1139
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1140
+ (1 + this.getAmplitudeControl(channel));
1105
1141
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1106
1142
  const volDelay = startTime + voiceParams.volDelay;
1107
- const portamentoTime = volDelay + this.getPortamentoTime(channel);
1143
+ const volAttack = volDelay + voiceParams.volAttack;
1144
+ const volHold = volAttack + voiceParams.volHold;
1108
1145
  note.volumeEnvelopeNode.gain
1109
1146
  .cancelScheduledValues(scheduleTime)
1110
- .setValueAtTime(0, volDelay)
1111
- .linearRampToValueAtTime(sustainVolume, portamentoTime);
1147
+ .setValueAtTime(sustainVolume, volHold);
1112
1148
  }
1113
1149
  setVolumeEnvelope(channel, note, scheduleTime) {
1114
1150
  const { voiceParams, startTime } = note;
@@ -1127,6 +1163,12 @@ export class MidyGM2 {
1127
1163
  .setValueAtTime(attackVolume, volHold)
1128
1164
  .linearRampToValueAtTime(sustainVolume, volDecay);
1129
1165
  }
1166
+ setPortamentoPitchEnvelope(note, scheduleTime) {
1167
+ const baseRate = note.voiceParams.playbackRate;
1168
+ note.bufferSource.playbackRate
1169
+ .cancelScheduledValues(scheduleTime)
1170
+ .setValueAtTime(baseRate, scheduleTime);
1171
+ }
1130
1172
  setPitchEnvelope(note, scheduleTime) {
1131
1173
  const { voiceParams } = note;
1132
1174
  const baseRate = voiceParams.playbackRate;
@@ -1154,19 +1196,20 @@ export class MidyGM2 {
1154
1196
  const maxFrequency = 20000; // max Hz of initialFilterFc
1155
1197
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
1156
1198
  }
1157
- setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
1199
+ setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1158
1200
  const state = channel.state;
1159
1201
  const { voiceParams, noteNumber, startTime } = note;
1160
1202
  const softPedalFactor = 1 -
1161
1203
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1162
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1163
- softPedalFactor;
1204
+ const baseCent = voiceParams.initialFilterFc +
1205
+ this.getFilterCutoffControl(channel);
1206
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1164
1207
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1165
1208
  const sustainFreq = baseFreq +
1166
1209
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1167
1210
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1168
1211
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1169
- const portamentoTime = startTime + this.getPortamentoTime(channel);
1212
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1170
1213
  const modDelay = startTime + voiceParams.modDelay;
1171
1214
  note.filterNode.frequency
1172
1215
  .cancelScheduledValues(scheduleTime)
@@ -1251,7 +1294,7 @@ export class MidyGM2 {
1251
1294
  return audioBuffer;
1252
1295
  }
1253
1296
  }
1254
- async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1297
+ async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1255
1298
  const now = this.audioContext.currentTime;
1256
1299
  const state = channel.state;
1257
1300
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
@@ -1267,20 +1310,24 @@ export class MidyGM2 {
1267
1310
  type: "lowpass",
1268
1311
  Q: voiceParams.initialFilterQ / 10, // dB
1269
1312
  });
1270
- if (portamento) {
1271
- note.portamento = true;
1272
- this.setPortamentoStartVolumeEnvelope(channel, note, now);
1273
- this.setPortamentoStartFilterEnvelope(channel, note, now);
1313
+ const prevNote = channel.scheduledNotes.at(-1);
1314
+ if (prevNote && prevNote.noteNumber !== noteNumber) {
1315
+ note.portamentoNoteNumber = prevNote.noteNumber;
1316
+ }
1317
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1318
+ this.setPortamentoVolumeEnvelope(channel, note, now);
1319
+ this.setPortamentoFilterEnvelope(channel, note, now);
1320
+ this.setPortamentoPitchEnvelope(note, now);
1274
1321
  }
1275
1322
  else {
1276
- note.portamento = false;
1277
1323
  this.setVolumeEnvelope(channel, note, now);
1278
1324
  this.setFilterEnvelope(channel, note, now);
1325
+ this.setPitchEnvelope(note, now);
1279
1326
  }
1327
+ this.updateDetune(channel, note, now);
1280
1328
  if (0 < state.vibratoDepth) {
1281
1329
  this.startVibrato(channel, note, now);
1282
1330
  }
1283
- this.setPitchEnvelope(note, now);
1284
1331
  if (0 < state.modulationDepth) {
1285
1332
  this.startModulation(channel, note, now);
1286
1333
  }
@@ -1326,9 +1373,8 @@ export class MidyGM2 {
1326
1373
  if (prev) {
1327
1374
  const [prevNote, prevChannelNumber] = prev;
1328
1375
  if (prevNote && !prevNote.ending) {
1329
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1330
- startTime, true, // force
1331
- undefined);
1376
+ this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1377
+ startTime, true);
1332
1378
  }
1333
1379
  }
1334
1380
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
@@ -1347,9 +1393,8 @@ export class MidyGM2 {
1347
1393
  channelNumber;
1348
1394
  const prevNote = this.drumExclusiveClassNotes[index];
1349
1395
  if (prevNote && !prevNote.ending) {
1350
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1351
- startTime, true, // force
1352
- undefined);
1396
+ this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1397
+ startTime, true);
1353
1398
  }
1354
1399
  this.drumExclusiveClassNotes[index] = note;
1355
1400
  }
@@ -1357,10 +1402,10 @@ export class MidyGM2 {
1357
1402
  if (!channel.isDrum)
1358
1403
  return false;
1359
1404
  const programNumber = channel.programNumber;
1360
- return (programNumber === 48 && noteNumber === 88) ||
1361
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
1405
+ return !((programNumber === 48 && noteNumber === 88) ||
1406
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1362
1407
  }
1363
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1408
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1364
1409
  const channel = this.channels[channelNumber];
1365
1410
  const bankNumber = this.calcBank(channel, channelNumber);
1366
1411
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -1371,7 +1416,8 @@ export class MidyGM2 {
1371
1416
  if (!voice)
1372
1417
  return;
1373
1418
  const isSF3 = soundFont.parsed.info.version.major === 3;
1374
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1419
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1420
+ note.noteOffEvent = noteOffEvent;
1375
1421
  note.gainL.connect(channel.gainL);
1376
1422
  note.gainR.connect(channel.gainR);
1377
1423
  if (0.5 <= channel.state.sustainPedal) {
@@ -1380,33 +1426,39 @@ export class MidyGM2 {
1380
1426
  this.handleExclusiveClass(note, channelNumber, startTime);
1381
1427
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
1382
1428
  const scheduledNotes = channel.scheduledNotes;
1383
- let notes = scheduledNotes.get(noteNumber);
1384
- if (notes) {
1385
- notes.push(note);
1386
- }
1387
- else {
1388
- notes = [note];
1389
- scheduledNotes.set(noteNumber, notes);
1390
- }
1429
+ note.index = scheduledNotes.length;
1430
+ scheduledNotes.push(note);
1391
1431
  if (this.isDrumNoteOffException(channel, noteNumber)) {
1392
1432
  const stopTime = startTime + note.bufferSource.buffer.duration;
1393
- const index = notes.length - 1;
1394
1433
  const promise = new Promise((resolve) => {
1395
1434
  note.bufferSource.onended = () => {
1396
- this.disconnectNote(note, scheduledNotes, index);
1435
+ scheduledNotes[note.index] = undefined;
1436
+ this.disconnectNote(note);
1397
1437
  resolve();
1398
1438
  };
1399
1439
  note.bufferSource.stop(stopTime);
1400
1440
  });
1401
1441
  this.notePromises.push(promise);
1402
1442
  }
1443
+ else if (noteOffEvent) {
1444
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1445
+ const portamentoTime = this.getPortamentoTime(channel, note);
1446
+ const portamentoEndTime = startTime + portamentoTime;
1447
+ const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
1448
+ Math.max(noteOffEvent.startTime, portamentoEndTime), false);
1449
+ this.notePromises.push(notePromise);
1450
+ }
1451
+ else {
1452
+ const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
1453
+ this.notePromises.push(notePromise);
1454
+ }
1455
+ }
1403
1456
  }
1404
1457
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1405
1458
  scheduleTime ??= this.audioContext.currentTime;
1406
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1459
+ return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
1407
1460
  }
1408
- disconnectNote(note, scheduledNotes, index) {
1409
- scheduledNotes[index] = null;
1461
+ disconnectNote(note) {
1410
1462
  note.bufferSource.disconnect();
1411
1463
  note.filterNode.disconnect();
1412
1464
  note.volumeEnvelopeNode.disconnect();
@@ -1429,8 +1481,7 @@ export class MidyGM2 {
1429
1481
  note.chorusEffectsSend.disconnect();
1430
1482
  }
1431
1483
  }
1432
- stopNote(endTime, stopTime, scheduledNotes, index) {
1433
- const note = scheduledNotes[index];
1484
+ stopNote(channel, note, endTime, stopTime) {
1434
1485
  note.volumeEnvelopeNode.gain
1435
1486
  .cancelScheduledValues(endTime)
1436
1487
  .linearRampToValueAtTime(0, stopTime);
@@ -1440,64 +1491,57 @@ export class MidyGM2 {
1440
1491
  }, stopTime);
1441
1492
  return new Promise((resolve) => {
1442
1493
  note.bufferSource.onended = () => {
1443
- this.disconnectNote(note, scheduledNotes, index);
1494
+ channel.scheduledNotes[note.index] = undefined;
1495
+ this.disconnectNote(note);
1444
1496
  resolve();
1445
1497
  };
1446
1498
  note.bufferSource.stop(stopTime);
1447
1499
  });
1448
1500
  }
1449
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1501
+ scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1450
1502
  const channel = this.channels[channelNumber];
1451
- if (this.isDrumNoteOffException(channel, noteNumber))
1503
+ if (this.isDrumNoteOffException(channel, note.noteNumber))
1452
1504
  return;
1453
1505
  const state = channel.state;
1454
1506
  if (!force) {
1455
1507
  if (0.5 <= state.sustainPedal)
1456
1508
  return;
1457
- if (channel.sostenutoNotes.has(noteNumber))
1509
+ if (0.5 <= channel.state.sostenutoPedal)
1458
1510
  return;
1459
1511
  }
1460
- if (!channel.scheduledNotes.has(noteNumber))
1461
- return;
1462
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
1512
+ const volRelease = endTime + note.voiceParams.volRelease;
1513
+ const modRelease = endTime + note.voiceParams.modRelease;
1514
+ note.filterNode.frequency
1515
+ .cancelScheduledValues(endTime)
1516
+ .linearRampToValueAtTime(0, modRelease);
1517
+ const stopTime = Math.min(volRelease, modRelease);
1518
+ return this.stopNote(channel, note, endTime, stopTime);
1519
+ }
1520
+ findNoteOffTarget(channel, noteNumber) {
1521
+ const scheduledNotes = channel.scheduledNotes;
1463
1522
  for (let i = 0; i < scheduledNotes.length; i++) {
1464
1523
  const note = scheduledNotes[i];
1465
1524
  if (!note)
1466
1525
  continue;
1467
1526
  if (note.ending)
1468
1527
  continue;
1469
- if (portamentoNoteNumber === undefined) {
1470
- const volRelease = endTime + note.voiceParams.volRelease;
1471
- const modRelease = endTime + note.voiceParams.modRelease;
1472
- note.filterNode.frequency
1473
- .cancelScheduledValues(endTime)
1474
- .linearRampToValueAtTime(0, modRelease);
1475
- const stopTime = Math.min(volRelease, modRelease);
1476
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
1477
- }
1478
- else {
1479
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1480
- const deltaNote = portamentoNoteNumber - noteNumber;
1481
- const baseRate = note.voiceParams.playbackRate;
1482
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1483
- note.bufferSource.playbackRate
1484
- .cancelScheduledValues(endTime)
1485
- .linearRampToValueAtTime(targetRate, portamentoTime);
1486
- return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1487
- }
1528
+ if (note.noteNumber !== noteNumber)
1529
+ continue;
1530
+ return note;
1488
1531
  }
1489
1532
  }
1490
1533
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1491
1534
  scheduleTime ??= this.audioContext.currentTime;
1492
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
1493
- undefined);
1535
+ const channel = this.channels[channelNumber];
1536
+ const note = this.findNoteOffTarget(channel, noteNumber);
1537
+ return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1494
1538
  }
1495
1539
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1496
1540
  const velocity = halfVelocity * 2;
1497
1541
  const channel = this.channels[channelNumber];
1498
1542
  const promises = [];
1499
1543
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1500
- const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1544
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1501
1545
  promises.push(promise);
1502
1546
  }
1503
1547
  channel.sustainNotes = [];
@@ -1507,12 +1551,14 @@ export class MidyGM2 {
1507
1551
  const velocity = halfVelocity * 2;
1508
1552
  const channel = this.channels[channelNumber];
1509
1553
  const promises = [];
1554
+ const sostenutoNotes = channel.sostenutoNotes;
1510
1555
  channel.state.sostenutoPedal = 0;
1511
- channel.sostenutoNotes.forEach((note) => {
1512
- const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1556
+ for (let i = 0; i < sostenutoNotes.length; i++) {
1557
+ const note = sostenutoNotes[i];
1558
+ const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
1513
1559
  promises.push(promise);
1514
- });
1515
- channel.sostenutoNotes.clear();
1560
+ }
1561
+ channel.sostenutoNotes = [];
1516
1562
  return promises;
1517
1563
  }
1518
1564
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -1562,10 +1608,10 @@ export class MidyGM2 {
1562
1608
  channel.detune += pressureDepth * (next - prev);
1563
1609
  }
1564
1610
  const table = channel.channelPressureTable;
1565
- this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1611
+ this.processActiveNotes(channel, scheduleTime, (note) => {
1566
1612
  this.setControllerParameters(channel, note, table);
1567
1613
  });
1568
- // this.applyVoiceParams(channel, 13);
1614
+ this.applyVoiceParams(channel, 13);
1569
1615
  }
1570
1616
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1571
1617
  const pitchBend = msb * 128 + lsb;
@@ -1742,6 +1788,7 @@ export class MidyGM2 {
1742
1788
  state.set(channel.state.array);
1743
1789
  state[2] = velocity / 127;
1744
1790
  state[3] = noteNumber / 127;
1791
+ state[13] = state.channelPressure / 127;
1745
1792
  return state;
1746
1793
  }
1747
1794
  applyVoiceParams(channel, controllerType, scheduleTime) {
@@ -1768,8 +1815,8 @@ export class MidyGM2 {
1768
1815
  if (key in voiceParams)
1769
1816
  noteVoiceParams[key] = voiceParams[key];
1770
1817
  }
1771
- if (note.portamento) {
1772
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1818
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1819
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1773
1820
  }
1774
1821
  else {
1775
1822
  this.setFilterEnvelope(channel, note, scheduleTime);
@@ -1792,32 +1839,32 @@ export class MidyGM2 {
1792
1839
  });
1793
1840
  }
1794
1841
  createControlChangeHandlers() {
1795
- return {
1796
- 0: this.setBankMSB,
1797
- 1: this.setModulationDepth,
1798
- 5: this.setPortamentoTime,
1799
- 6: this.dataEntryMSB,
1800
- 7: this.setVolume,
1801
- 10: this.setPan,
1802
- 11: this.setExpression,
1803
- 32: this.setBankLSB,
1804
- 38: this.dataEntryLSB,
1805
- 64: this.setSustainPedal,
1806
- 65: this.setPortamento,
1807
- 66: this.setSostenutoPedal,
1808
- 67: this.setSoftPedal,
1809
- 91: this.setReverbSendLevel,
1810
- 93: this.setChorusSendLevel,
1811
- 100: this.setRPNLSB,
1812
- 101: this.setRPNMSB,
1813
- 120: this.allSoundOff,
1814
- 121: this.resetAllControllers,
1815
- 123: this.allNotesOff,
1816
- 124: this.omniOff,
1817
- 125: this.omniOn,
1818
- 126: this.monoOn,
1819
- 127: this.polyOn,
1820
- };
1842
+ const handlers = new Array(128);
1843
+ handlers[0] = this.setBankMSB;
1844
+ handlers[1] = this.setModulationDepth;
1845
+ handlers[5] = this.setPortamentoTime;
1846
+ handlers[6] = this.dataEntryMSB;
1847
+ handlers[7] = this.setVolume;
1848
+ handlers[10] = this.setPan;
1849
+ handlers[11] = this.setExpression;
1850
+ handlers[32] = this.setBankLSB;
1851
+ handlers[38] = this.dataEntryLSB;
1852
+ handlers[64] = this.setSustainPedal;
1853
+ handlers[65] = this.setPortamento;
1854
+ handlers[66] = this.setSostenutoPedal;
1855
+ handlers[67] = this.setSoftPedal;
1856
+ handlers[91] = this.setReverbSendLevel;
1857
+ handlers[93] = this.setChorusSendLevel;
1858
+ handlers[100] = this.setRPNLSB;
1859
+ handlers[101] = this.setRPNMSB;
1860
+ handlers[120] = this.allSoundOff;
1861
+ handlers[121] = this.resetAllControllers;
1862
+ handlers[123] = this.allNotesOff;
1863
+ handlers[124] = this.omniOff;
1864
+ handlers[125] = this.omniOn;
1865
+ handlers[126] = this.monoOn;
1866
+ handlers[127] = this.polyOn;
1867
+ return handlers;
1821
1868
  }
1822
1869
  handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1823
1870
  const handler = this.controlChangeHandlers[controllerType];
@@ -1854,9 +1901,33 @@ export class MidyGM2 {
1854
1901
  channel.state.modulationDepth = modulation / 127;
1855
1902
  this.updateModulation(channel, scheduleTime);
1856
1903
  }
1857
- setPortamentoTime(channelNumber, portamentoTime) {
1904
+ updatePortamento(channel, scheduleTime) {
1905
+ this.processScheduledNotes(channel, (note) => {
1906
+ if (0.5 <= channel.state.portamento) {
1907
+ if (0 <= note.portamentoNoteNumber) {
1908
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
1909
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1910
+ this.setPortamentoPitchEnvelope(note, scheduleTime);
1911
+ this.updateDetune(channel, note, scheduleTime);
1912
+ }
1913
+ }
1914
+ else {
1915
+ if (0 <= note.portamentoNoteNumber) {
1916
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1917
+ this.setFilterEnvelope(channel, note, scheduleTime);
1918
+ this.setPitchEnvelope(note, scheduleTime);
1919
+ this.updateDetune(channel, note, scheduleTime);
1920
+ }
1921
+ }
1922
+ });
1923
+ }
1924
+ setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
1858
1925
  const channel = this.channels[channelNumber];
1926
+ scheduleTime ??= this.audioContext.currentTime;
1859
1927
  channel.state.portamentoTime = portamentoTime / 127;
1928
+ if (channel.isDrum)
1929
+ return;
1930
+ this.updatePortamento(channel, scheduleTime);
1860
1931
  }
1861
1932
  setKeyBasedVolume(channel, scheduleTime) {
1862
1933
  this.processScheduledNotes(channel, (note) => {
@@ -1942,11 +2013,13 @@ export class MidyGM2 {
1942
2013
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1943
2014
  }
1944
2015
  }
1945
- setPortamento(channelNumber, value) {
2016
+ setPortamento(channelNumber, value, scheduleTime) {
1946
2017
  const channel = this.channels[channelNumber];
1947
2018
  if (channel.isDrum)
1948
2019
  return;
2020
+ scheduleTime ??= this.audioContext.currentTime;
1949
2021
  channel.state.portamento = value / 127;
2022
+ this.updatePortamento(channel, scheduleTime);
1950
2023
  }
1951
2024
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1952
2025
  const channel = this.channels[channelNumber];
@@ -1955,7 +2028,11 @@ export class MidyGM2 {
1955
2028
  scheduleTime ??= this.audioContext.currentTime;
1956
2029
  channel.state.sostenutoPedal = value / 127;
1957
2030
  if (64 <= value) {
1958
- channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
2031
+ const sostenutoNotes = [];
2032
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2033
+ sostenutoNotes.push(note);
2034
+ });
2035
+ channel.sostenutoNotes = sostenutoNotes;
1959
2036
  }
1960
2037
  else {
1961
2038
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
@@ -1965,12 +2042,13 @@ export class MidyGM2 {
1965
2042
  const channel = this.channels[channelNumber];
1966
2043
  if (channel.isDrum)
1967
2044
  return;
2045
+ const state = channel.state;
1968
2046
  scheduleTime ??= this.audioContext.currentTime;
1969
- channel.state.softPedal = softPedal / 127;
2047
+ state.softPedal = softPedal / 127;
1970
2048
  this.processScheduledNotes(channel, (note) => {
1971
- if (note.portamento) {
1972
- this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1973
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
2049
+ if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2050
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2051
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1974
2052
  }
1975
2053
  else {
1976
2054
  this.setVolumeEnvelope(channel, note, scheduleTime);
@@ -2167,24 +2245,32 @@ export class MidyGM2 {
2167
2245
  }
2168
2246
  allSoundOff(channelNumber, _value, scheduleTime) {
2169
2247
  scheduleTime ??= this.audioContext.currentTime;
2170
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
2248
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2171
2249
  }
2172
2250
  resetAllStates(channelNumber) {
2251
+ const scheduleTime = this.audioContext.currentTime;
2173
2252
  const channel = this.channels[channelNumber];
2174
2253
  const state = channel.state;
2175
- for (const type of Object.keys(defaultControllerState)) {
2176
- state[type] = defaultControllerState[type].defaultValue;
2254
+ const entries = Object.entries(defaultControllerState);
2255
+ for (const [key, { type, defaultValue }] of entries) {
2256
+ if (128 <= type) {
2257
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2258
+ }
2259
+ else {
2260
+ state[key] = defaultValue;
2261
+ }
2177
2262
  }
2178
- for (const type of Object.keys(this.constructor.channelSettings)) {
2179
- channel[type] = this.constructor.channelSettings[type];
2263
+ for (const key of Object.keys(this.constructor.channelSettings)) {
2264
+ channel[key] = this.constructor.channelSettings[key];
2180
2265
  }
2266
+ this.resetChannelTable(channel);
2181
2267
  this.mode = "GM2";
2182
2268
  this.masterFineTuning = 0; // cb
2183
2269
  this.masterCoarseTuning = 0; // cb
2184
2270
  }
2185
2271
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2186
- resetAllControllers(channelNumber) {
2187
- const stateTypes = [
2272
+ resetAllControllers(channelNumber, _value, scheduleTime) {
2273
+ const keys = [
2188
2274
  "channelPressure",
2189
2275
  "pitchWheel",
2190
2276
  "expression",
@@ -2196,10 +2282,17 @@ export class MidyGM2 {
2196
2282
  ];
2197
2283
  const channel = this.channels[channelNumber];
2198
2284
  const state = channel.state;
2199
- for (let i = 0; i < stateTypes.length; i++) {
2200
- const type = stateTypes[i];
2201
- state[type] = defaultControllerState[type].defaultValue;
2285
+ for (let i = 0; i < keys.length; i++) {
2286
+ const key = keys[i];
2287
+ const { type, defaultValue } = defaultControllerState[key];
2288
+ if (128 <= type) {
2289
+ this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2290
+ }
2291
+ else {
2292
+ state[key] = defaultValue;
2293
+ }
2202
2294
  }
2295
+ this.setPitchBend(channelNumber, 8192, scheduleTime);
2203
2296
  const settingTypes = [
2204
2297
  "rpnMSB",
2205
2298
  "rpnLSB",
@@ -2211,7 +2304,7 @@ export class MidyGM2 {
2211
2304
  }
2212
2305
  allNotesOff(channelNumber, _value, scheduleTime) {
2213
2306
  scheduleTime ??= this.audioContext.currentTime;
2214
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2307
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2215
2308
  }
2216
2309
  omniOff(channelNumber, value, scheduleTime) {
2217
2310
  this.allNotesOff(channelNumber, value, scheduleTime);
@@ -2428,7 +2521,7 @@ export class MidyGM2 {
2428
2521
  this.reverbEffect = options.reverbAlgorithm(audioContext);
2429
2522
  }
2430
2523
  getReverbTime(value) {
2431
- return Math.pow(Math.E, (value - 40) * 0.025);
2524
+ return Math.exp((value - 40) * 0.025);
2432
2525
  }
2433
2526
  // mean free path equation
2434
2527
  // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
@@ -2585,6 +2678,8 @@ export class MidyGM2 {
2585
2678
  if (!channelBitmap[i])
2586
2679
  continue;
2587
2680
  const channel = this.channels[i];
2681
+ if (channel.isDrum)
2682
+ continue;
2588
2683
  for (let j = 0; j < 12; j++) {
2589
2684
  const centValue = data[j + 7] - 64;
2590
2685
  channel.scaleOctaveTuningTable[j] = centValue;
@@ -2621,7 +2716,13 @@ export class MidyGM2 {
2621
2716
  setControllerParameters(channel, note, table) {
2622
2717
  if (table[0] !== 64)
2623
2718
  this.updateDetune(channel, note);
2624
- if (!note.portamento) {
2719
+ if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2720
+ if (table[1] !== 64)
2721
+ this.setPortamentoFilterEnvelope(channel, note);
2722
+ if (table[2] !== 64)
2723
+ this.setPortamentoVolumeEnvelope(channel, note);
2724
+ }
2725
+ else {
2625
2726
  if (table[1] !== 64)
2626
2727
  this.setFilterEnvelope(channel, note);
2627
2728
  if (table[2] !== 64)
@@ -2636,7 +2737,10 @@ export class MidyGM2 {
2636
2737
  }
2637
2738
  handlePressureSysEx(data, tableName) {
2638
2739
  const channelNumber = data[4];
2639
- const table = this.channels[channelNumber][tableName];
2740
+ const channel = this.channels[channelNumber];
2741
+ if (channel.isDrum)
2742
+ return;
2743
+ const table = channel[tableName];
2640
2744
  for (let i = 5; i < data.length - 1; i += 2) {
2641
2745
  const pp = data[i];
2642
2746
  const rr = data[i + 1];
@@ -2646,8 +2750,13 @@ export class MidyGM2 {
2646
2750
  initControlTable() {
2647
2751
  const channelCount = 128;
2648
2752
  const slotSize = 6;
2649
- const defaultValues = [64, 64, 64, 0, 0, 0];
2650
2753
  const table = new Uint8Array(channelCount * slotSize);
2754
+ return this.resetControlTable(table);
2755
+ }
2756
+ resetControlTable(table) {
2757
+ const channelCount = 128;
2758
+ const slotSize = 6;
2759
+ const defaultValues = [64, 64, 64, 0, 0, 0];
2651
2760
  for (let ch = 0; ch < channelCount; ch++) {
2652
2761
  const offset = ch * slotSize;
2653
2762
  table.set(defaultValues, offset);
@@ -2664,8 +2773,11 @@ export class MidyGM2 {
2664
2773
  }
2665
2774
  handleControlChangeSysEx(data) {
2666
2775
  const channelNumber = data[4];
2776
+ const channel = this.channels[channelNumber];
2777
+ if (channel.isDrum)
2778
+ return;
2667
2779
  const controllerType = data[5];
2668
- const table = this.channels[channelNumber].controlTable[controllerType];
2780
+ const table = channel.controlTable[controllerType];
2669
2781
  for (let i = 6; i < data.length - 1; i += 2) {
2670
2782
  const pp = data[i];
2671
2783
  const rr = data[i + 1];
@@ -2679,8 +2791,11 @@ export class MidyGM2 {
2679
2791
  }
2680
2792
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2681
2793
  const channelNumber = data[4];
2794
+ const channel = this.channels[channelNumber];
2795
+ if (channel.isDrum)
2796
+ return;
2682
2797
  const keyNumber = data[5];
2683
- const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
2798
+ const table = channel.keyBasedInstrumentControlTable;
2684
2799
  for (let i = 6; i < data.length - 1; i += 2) {
2685
2800
  const controllerType = data[i];
2686
2801
  const value = data[i + 1];