@marmooo/midy 0.3.1 → 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 (0 < event.velocity) {
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,13 +770,37 @@ 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
  }
837
800
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
838
801
  const channel = this.channels[channelNumber];
839
802
  const promises = [];
840
- const activeNotes = this.getActiveNotes(channel, scheduleTime);
841
- activeNotes.forEach((note) => {
803
+ this.processActiveNotes(channel, scheduleTime, (note) => {
842
804
  const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
843
805
  this.notePromises.push(promise);
844
806
  promises.push(promise);
@@ -849,11 +811,11 @@ class MidyGM2 {
849
811
  const channel = this.channels[channelNumber];
850
812
  const promises = [];
851
813
  this.processScheduledNotes(channel, (note) => {
852
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
814
+ const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
853
815
  this.notePromises.push(promise);
854
816
  promises.push(promise);
855
817
  });
856
- channel.scheduledNotes.clear();
818
+ channel.scheduledNotes = [];
857
819
  return Promise.all(promises);
858
820
  }
859
821
  stopNotes(velocity, force, scheduleTime) {
@@ -874,9 +836,6 @@ class MidyGM2 {
874
836
  if (!this.isPlaying)
875
837
  return;
876
838
  this.isStopping = true;
877
- for (let i = 0; i < this.channels.length; i++) {
878
- this.resetAllStates(i);
879
- }
880
839
  }
881
840
  pause() {
882
841
  if (!this.isPlaying || this.isPaused)
@@ -911,37 +870,31 @@ class MidyGM2 {
911
870
  return this.resumeTime + now - this.startTime - this.startDelay;
912
871
  }
913
872
  processScheduledNotes(channel, callback) {
914
- channel.scheduledNotes.forEach((noteList) => {
915
- for (let i = 0; i < noteList.length; i++) {
916
- const note = noteList[i];
917
- if (!note)
918
- continue;
919
- if (note.ending)
920
- continue;
921
- callback(note);
922
- }
923
- });
924
- }
925
- getActiveNotes(channel, scheduleTime) {
926
- const activeNotes = new SparseMap(128);
927
- channel.scheduledNotes.forEach((noteList) => {
928
- const activeNote = this.getActiveNote(noteList, scheduleTime);
929
- if (activeNote) {
930
- activeNotes.set(activeNote.noteNumber, activeNote);
931
- }
932
- });
933
- 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
+ }
934
882
  }
935
- getActiveNote(noteList, scheduleTime) {
936
- for (let i = noteList.length - 1; i >= 0; i--) {
937
- 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];
938
887
  if (!note)
939
- return;
888
+ continue;
889
+ if (note.ending)
890
+ continue;
891
+ const noteOffEvent = note.noteOffEvent;
892
+ if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
893
+ continue;
940
894
  if (scheduleTime < note.startTime)
941
895
  continue;
942
- return (note.ending) ? null : note;
896
+ callback(note);
943
897
  }
944
- return noteList[0];
945
898
  }
946
899
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
947
900
  const sampleRate = audioContext.sampleRate;
@@ -1106,25 +1059,95 @@ class MidyGM2 {
1106
1059
  }
1107
1060
  updateDetune(channel, note, scheduleTime) {
1108
1061
  const noteDetune = this.calcNoteDetune(channel, note);
1109
- const detune = channel.detune + noteDetune;
1110
- note.bufferSource.detune
1111
- .cancelScheduledValues(scheduleTime)
1112
- .setValueAtTime(detune, scheduleTime);
1113
- }
1114
- getPortamentoTime(channel) {
1115
- const factor = 5 * Math.log(10) * 127;
1116
- return channel.state.portamentoTime * factor;
1117
- }
1118
- 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) {
1119
1141
  const { voiceParams, startTime } = note;
1120
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
1142
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1143
+ (1 + this.getAmplitudeControl(channel));
1121
1144
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1122
1145
  const volDelay = startTime + voiceParams.volDelay;
1123
- const portamentoTime = volDelay + this.getPortamentoTime(channel);
1146
+ const volAttack = volDelay + voiceParams.volAttack;
1147
+ const volHold = volAttack + voiceParams.volHold;
1124
1148
  note.volumeEnvelopeNode.gain
1125
1149
  .cancelScheduledValues(scheduleTime)
1126
- .setValueAtTime(0, volDelay)
1127
- .linearRampToValueAtTime(sustainVolume, portamentoTime);
1150
+ .setValueAtTime(sustainVolume, volHold);
1128
1151
  }
1129
1152
  setVolumeEnvelope(channel, note, scheduleTime) {
1130
1153
  const { voiceParams, startTime } = note;
@@ -1143,6 +1166,12 @@ class MidyGM2 {
1143
1166
  .setValueAtTime(attackVolume, volHold)
1144
1167
  .linearRampToValueAtTime(sustainVolume, volDecay);
1145
1168
  }
1169
+ setPortamentoPitchEnvelope(note, scheduleTime) {
1170
+ const baseRate = note.voiceParams.playbackRate;
1171
+ note.bufferSource.playbackRate
1172
+ .cancelScheduledValues(scheduleTime)
1173
+ .setValueAtTime(baseRate, scheduleTime);
1174
+ }
1146
1175
  setPitchEnvelope(note, scheduleTime) {
1147
1176
  const { voiceParams } = note;
1148
1177
  const baseRate = voiceParams.playbackRate;
@@ -1170,19 +1199,20 @@ class MidyGM2 {
1170
1199
  const maxFrequency = 20000; // max Hz of initialFilterFc
1171
1200
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
1172
1201
  }
1173
- setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
1202
+ setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1174
1203
  const state = channel.state;
1175
1204
  const { voiceParams, noteNumber, startTime } = note;
1176
1205
  const softPedalFactor = 1 -
1177
1206
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1178
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1179
- softPedalFactor;
1207
+ const baseCent = voiceParams.initialFilterFc +
1208
+ this.getFilterCutoffControl(channel);
1209
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1180
1210
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1181
1211
  const sustainFreq = baseFreq +
1182
1212
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1183
1213
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1184
1214
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1185
- const portamentoTime = startTime + this.getPortamentoTime(channel);
1215
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1186
1216
  const modDelay = startTime + voiceParams.modDelay;
1187
1217
  note.filterNode.frequency
1188
1218
  .cancelScheduledValues(scheduleTime)
@@ -1267,7 +1297,7 @@ class MidyGM2 {
1267
1297
  return audioBuffer;
1268
1298
  }
1269
1299
  }
1270
- async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1300
+ async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1271
1301
  const now = this.audioContext.currentTime;
1272
1302
  const state = channel.state;
1273
1303
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
@@ -1283,20 +1313,24 @@ class MidyGM2 {
1283
1313
  type: "lowpass",
1284
1314
  Q: voiceParams.initialFilterQ / 10, // dB
1285
1315
  });
1286
- if (0.5 <= state.portamento && portamento) {
1287
- note.portamento = true;
1288
- this.setPortamentoStartVolumeEnvelope(channel, note, now);
1289
- 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);
1290
1324
  }
1291
1325
  else {
1292
- note.portamento = false;
1293
1326
  this.setVolumeEnvelope(channel, note, now);
1294
1327
  this.setFilterEnvelope(channel, note, now);
1328
+ this.setPitchEnvelope(note, now);
1295
1329
  }
1330
+ this.updateDetune(channel, note, now);
1296
1331
  if (0 < state.vibratoDepth) {
1297
1332
  this.startVibrato(channel, note, now);
1298
1333
  }
1299
- this.setPitchEnvelope(note, now);
1300
1334
  if (0 < state.modulationDepth) {
1301
1335
  this.startModulation(channel, note, now);
1302
1336
  }
@@ -1342,9 +1376,8 @@ class MidyGM2 {
1342
1376
  if (prev) {
1343
1377
  const [prevNote, prevChannelNumber] = prev;
1344
1378
  if (prevNote && !prevNote.ending) {
1345
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1346
- startTime, true, // force
1347
- undefined);
1379
+ this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1380
+ startTime, true);
1348
1381
  }
1349
1382
  }
1350
1383
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
@@ -1363,9 +1396,8 @@ class MidyGM2 {
1363
1396
  channelNumber;
1364
1397
  const prevNote = this.drumExclusiveClassNotes[index];
1365
1398
  if (prevNote && !prevNote.ending) {
1366
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1367
- startTime, true, // force
1368
- undefined);
1399
+ this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1400
+ startTime, true);
1369
1401
  }
1370
1402
  this.drumExclusiveClassNotes[index] = note;
1371
1403
  }
@@ -1376,7 +1408,7 @@ class MidyGM2 {
1376
1408
  return !((programNumber === 48 && noteNumber === 88) ||
1377
1409
  (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1378
1410
  }
1379
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1411
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1380
1412
  const channel = this.channels[channelNumber];
1381
1413
  const bankNumber = this.calcBank(channel, channelNumber);
1382
1414
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -1387,7 +1419,8 @@ class MidyGM2 {
1387
1419
  if (!voice)
1388
1420
  return;
1389
1421
  const isSF3 = soundFont.parsed.info.version.major === 3;
1390
- 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;
1391
1424
  note.gainL.connect(channel.gainL);
1392
1425
  note.gainR.connect(channel.gainR);
1393
1426
  if (0.5 <= channel.state.sustainPedal) {
@@ -1396,20 +1429,13 @@ class MidyGM2 {
1396
1429
  this.handleExclusiveClass(note, channelNumber, startTime);
1397
1430
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
1398
1431
  const scheduledNotes = channel.scheduledNotes;
1399
- let noteList = scheduledNotes.get(noteNumber);
1400
- if (noteList) {
1401
- noteList.push(note);
1402
- }
1403
- else {
1404
- noteList = [note];
1405
- scheduledNotes.set(noteNumber, noteList);
1406
- }
1432
+ note.index = scheduledNotes.length;
1433
+ scheduledNotes.push(note);
1407
1434
  if (this.isDrumNoteOffException(channel, noteNumber)) {
1408
1435
  const stopTime = startTime + note.bufferSource.buffer.duration;
1409
- const index = noteList.length - 1;
1410
1436
  const promise = new Promise((resolve) => {
1411
1437
  note.bufferSource.onended = () => {
1412
- noteList[index] = undefined;
1438
+ scheduledNotes[note.index] = undefined;
1413
1439
  this.disconnectNote(note);
1414
1440
  resolve();
1415
1441
  };
@@ -1417,10 +1443,23 @@ class MidyGM2 {
1417
1443
  });
1418
1444
  this.notePromises.push(promise);
1419
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
+ }
1420
1459
  }
1421
1460
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1422
1461
  scheduleTime ??= this.audioContext.currentTime;
1423
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1462
+ return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
1424
1463
  }
1425
1464
  disconnectNote(note) {
1426
1465
  note.bufferSource.disconnect();
@@ -1445,8 +1484,7 @@ class MidyGM2 {
1445
1484
  note.chorusEffectsSend.disconnect();
1446
1485
  }
1447
1486
  }
1448
- stopNote(endTime, stopTime, noteList, index) {
1449
- const note = noteList[index];
1487
+ stopNote(channel, note, endTime, stopTime) {
1450
1488
  note.volumeEnvelopeNode.gain
1451
1489
  .cancelScheduledValues(endTime)
1452
1490
  .linearRampToValueAtTime(0, stopTime);
@@ -1456,73 +1494,57 @@ class MidyGM2 {
1456
1494
  }, stopTime);
1457
1495
  return new Promise((resolve) => {
1458
1496
  note.bufferSource.onended = () => {
1459
- noteList[index] = undefined;
1497
+ channel.scheduledNotes[note.index] = undefined;
1460
1498
  this.disconnectNote(note);
1461
1499
  resolve();
1462
1500
  };
1463
1501
  note.bufferSource.stop(stopTime);
1464
1502
  });
1465
1503
  }
1466
- findNoteOffTarget(noteList) {
1467
- for (let i = 0; i < noteList.length; i++) {
1468
- const note = noteList[i];
1469
- if (!note)
1470
- continue;
1471
- if (note.ending)
1472
- continue;
1473
- return [note, i];
1474
- }
1475
- }
1476
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1504
+ scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1477
1505
  const channel = this.channels[channelNumber];
1478
- if (this.isDrumNoteOffException(channel, noteNumber))
1506
+ if (this.isDrumNoteOffException(channel, note.noteNumber))
1479
1507
  return;
1480
1508
  const state = channel.state;
1481
1509
  if (!force) {
1482
1510
  if (0.5 <= state.sustainPedal)
1483
1511
  return;
1484
- if (channel.sostenutoNotes.has(noteNumber))
1512
+ if (0.5 <= channel.state.sostenutoPedal)
1485
1513
  return;
1486
1514
  }
1487
- const noteList = channel.scheduledNotes.get(noteNumber);
1488
- if (!noteList)
1489
- return; // be careful with drum channel
1490
- const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1491
- if (!noteOffTarget)
1492
- return;
1493
- const [note, i] = noteOffTarget;
1494
- if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
1495
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1496
- const deltaNote = portamentoNoteNumber - noteNumber;
1497
- const baseRate = note.voiceParams.playbackRate;
1498
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1499
- note.bufferSource.playbackRate
1500
- .cancelScheduledValues(endTime)
1501
- .linearRampToValueAtTime(targetRate, portamentoTime);
1502
- return this.stopNote(endTime, portamentoTime, noteList, i);
1503
- }
1504
- else {
1505
- const volRelease = endTime +
1506
- note.voiceParams.volRelease * state.releaseTime * 2;
1507
- const modRelease = endTime + note.voiceParams.modRelease;
1508
- note.filterNode.frequency
1509
- .cancelScheduledValues(endTime)
1510
- .linearRampToValueAtTime(0, modRelease);
1511
- const stopTime = Math.min(volRelease, modRelease);
1512
- return this.stopNote(endTime, stopTime, noteList, i);
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;
1525
+ for (let i = 0; i < scheduledNotes.length; i++) {
1526
+ const note = scheduledNotes[i];
1527
+ if (!note)
1528
+ continue;
1529
+ if (note.ending)
1530
+ continue;
1531
+ if (note.noteNumber !== noteNumber)
1532
+ continue;
1533
+ return note;
1513
1534
  }
1514
1535
  }
1515
1536
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1516
1537
  scheduleTime ??= this.audioContext.currentTime;
1517
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
1518
- undefined);
1538
+ const channel = this.channels[channelNumber];
1539
+ const note = this.findNoteOffTarget(channel, noteNumber);
1540
+ return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1519
1541
  }
1520
1542
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1521
1543
  const velocity = halfVelocity * 2;
1522
1544
  const channel = this.channels[channelNumber];
1523
1545
  const promises = [];
1524
1546
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1525
- const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1547
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1526
1548
  promises.push(promise);
1527
1549
  }
1528
1550
  channel.sustainNotes = [];
@@ -1532,12 +1554,14 @@ class MidyGM2 {
1532
1554
  const velocity = halfVelocity * 2;
1533
1555
  const channel = this.channels[channelNumber];
1534
1556
  const promises = [];
1557
+ const sostenutoNotes = channel.sostenutoNotes;
1535
1558
  channel.state.sostenutoPedal = 0;
1536
- channel.sostenutoNotes.forEach((note) => {
1537
- 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);
1538
1562
  promises.push(promise);
1539
- });
1540
- channel.sostenutoNotes.clear();
1563
+ }
1564
+ channel.sostenutoNotes = [];
1541
1565
  return promises;
1542
1566
  }
1543
1567
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -1587,7 +1611,7 @@ class MidyGM2 {
1587
1611
  channel.detune += pressureDepth * (next - prev);
1588
1612
  }
1589
1613
  const table = channel.channelPressureTable;
1590
- this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1614
+ this.processActiveNotes(channel, scheduleTime, (note) => {
1591
1615
  this.setControllerParameters(channel, note, table);
1592
1616
  });
1593
1617
  this.applyVoiceParams(channel, 13);
@@ -1794,8 +1818,8 @@ class MidyGM2 {
1794
1818
  if (key in voiceParams)
1795
1819
  noteVoiceParams[key] = voiceParams[key];
1796
1820
  }
1797
- if (0.5 <= channel.state.portamento && note.portamento) {
1798
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1821
+ if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1822
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1799
1823
  }
1800
1824
  else {
1801
1825
  this.setFilterEnvelope(channel, note, scheduleTime);
@@ -1818,32 +1842,32 @@ class MidyGM2 {
1818
1842
  });
1819
1843
  }
1820
1844
  createControlChangeHandlers() {
1821
- return {
1822
- 0: this.setBankMSB,
1823
- 1: this.setModulationDepth,
1824
- 5: this.setPortamentoTime,
1825
- 6: this.dataEntryMSB,
1826
- 7: this.setVolume,
1827
- 10: this.setPan,
1828
- 11: this.setExpression,
1829
- 32: this.setBankLSB,
1830
- 38: this.dataEntryLSB,
1831
- 64: this.setSustainPedal,
1832
- 65: this.setPortamento,
1833
- 66: this.setSostenutoPedal,
1834
- 67: this.setSoftPedal,
1835
- 91: this.setReverbSendLevel,
1836
- 93: this.setChorusSendLevel,
1837
- 100: this.setRPNLSB,
1838
- 101: this.setRPNMSB,
1839
- 120: this.allSoundOff,
1840
- 121: this.resetAllControllers,
1841
- 123: this.allNotesOff,
1842
- 124: this.omniOff,
1843
- 125: this.omniOn,
1844
- 126: this.monoOn,
1845
- 127: this.polyOn,
1846
- };
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;
1847
1871
  }
1848
1872
  handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1849
1873
  const handler = this.controlChangeHandlers[controllerType];
@@ -1880,9 +1904,33 @@ class MidyGM2 {
1880
1904
  channel.state.modulationDepth = modulation / 127;
1881
1905
  this.updateModulation(channel, scheduleTime);
1882
1906
  }
1883
- 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) {
1884
1928
  const channel = this.channels[channelNumber];
1929
+ scheduleTime ??= this.audioContext.currentTime;
1885
1930
  channel.state.portamentoTime = portamentoTime / 127;
1931
+ if (channel.isDrum)
1932
+ return;
1933
+ this.updatePortamento(channel, scheduleTime);
1886
1934
  }
1887
1935
  setKeyBasedVolume(channel, scheduleTime) {
1888
1936
  this.processScheduledNotes(channel, (note) => {
@@ -1968,11 +2016,13 @@ class MidyGM2 {
1968
2016
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1969
2017
  }
1970
2018
  }
1971
- setPortamento(channelNumber, value) {
2019
+ setPortamento(channelNumber, value, scheduleTime) {
1972
2020
  const channel = this.channels[channelNumber];
1973
2021
  if (channel.isDrum)
1974
2022
  return;
2023
+ scheduleTime ??= this.audioContext.currentTime;
1975
2024
  channel.state.portamento = value / 127;
2025
+ this.updatePortamento(channel, scheduleTime);
1976
2026
  }
1977
2027
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1978
2028
  const channel = this.channels[channelNumber];
@@ -1981,7 +2031,11 @@ class MidyGM2 {
1981
2031
  scheduleTime ??= this.audioContext.currentTime;
1982
2032
  channel.state.sostenutoPedal = value / 127;
1983
2033
  if (64 <= value) {
1984
- channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
2034
+ const sostenutoNotes = [];
2035
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2036
+ sostenutoNotes.push(note);
2037
+ });
2038
+ channel.sostenutoNotes = sostenutoNotes;
1985
2039
  }
1986
2040
  else {
1987
2041
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
@@ -1995,9 +2049,9 @@ class MidyGM2 {
1995
2049
  scheduleTime ??= this.audioContext.currentTime;
1996
2050
  state.softPedal = softPedal / 127;
1997
2051
  this.processScheduledNotes(channel, (note) => {
1998
- if (0.5 <= state.portamento && note.portamento) {
1999
- this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
2000
- 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);
2001
2055
  }
2002
2056
  else {
2003
2057
  this.setVolumeEnvelope(channel, note, scheduleTime);
@@ -2197,21 +2251,29 @@ class MidyGM2 {
2197
2251
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2198
2252
  }
2199
2253
  resetAllStates(channelNumber) {
2254
+ const scheduleTime = this.audioContext.currentTime;
2200
2255
  const channel = this.channels[channelNumber];
2201
2256
  const state = channel.state;
2202
- for (const type of Object.keys(defaultControllerState)) {
2203
- 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
+ }
2204
2265
  }
2205
- for (const type of Object.keys(this.constructor.channelSettings)) {
2206
- channel[type] = this.constructor.channelSettings[type];
2266
+ for (const key of Object.keys(this.constructor.channelSettings)) {
2267
+ channel[key] = this.constructor.channelSettings[key];
2207
2268
  }
2269
+ this.resetChannelTable(channel);
2208
2270
  this.mode = "GM2";
2209
2271
  this.masterFineTuning = 0; // cb
2210
2272
  this.masterCoarseTuning = 0; // cb
2211
2273
  }
2212
2274
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2213
- resetAllControllers(channelNumber) {
2214
- const stateTypes = [
2275
+ resetAllControllers(channelNumber, _value, scheduleTime) {
2276
+ const keys = [
2215
2277
  "channelPressure",
2216
2278
  "pitchWheel",
2217
2279
  "expression",
@@ -2223,10 +2285,17 @@ class MidyGM2 {
2223
2285
  ];
2224
2286
  const channel = this.channels[channelNumber];
2225
2287
  const state = channel.state;
2226
- for (let i = 0; i < stateTypes.length; i++) {
2227
- const type = stateTypes[i];
2228
- 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
+ }
2229
2297
  }
2298
+ this.setPitchBend(channelNumber, 8192, scheduleTime);
2230
2299
  const settingTypes = [
2231
2300
  "rpnMSB",
2232
2301
  "rpnLSB",
@@ -2455,7 +2524,7 @@ class MidyGM2 {
2455
2524
  this.reverbEffect = options.reverbAlgorithm(audioContext);
2456
2525
  }
2457
2526
  getReverbTime(value) {
2458
- return Math.pow(Math.E, (value - 40) * 0.025);
2527
+ return Math.exp((value - 40) * 0.025);
2459
2528
  }
2460
2529
  // mean free path equation
2461
2530
  // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
@@ -2612,6 +2681,8 @@ class MidyGM2 {
2612
2681
  if (!channelBitmap[i])
2613
2682
  continue;
2614
2683
  const channel = this.channels[i];
2684
+ if (channel.isDrum)
2685
+ continue;
2615
2686
  for (let j = 0; j < 12; j++) {
2616
2687
  const centValue = data[j + 7] - 64;
2617
2688
  channel.scaleOctaveTuningTable[j] = centValue;
@@ -2648,7 +2719,13 @@ class MidyGM2 {
2648
2719
  setControllerParameters(channel, note, table) {
2649
2720
  if (table[0] !== 64)
2650
2721
  this.updateDetune(channel, note);
2651
- 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 {
2652
2729
  if (table[1] !== 64)
2653
2730
  this.setFilterEnvelope(channel, note);
2654
2731
  if (table[2] !== 64)
@@ -2663,7 +2740,10 @@ class MidyGM2 {
2663
2740
  }
2664
2741
  handlePressureSysEx(data, tableName) {
2665
2742
  const channelNumber = data[4];
2666
- const table = this.channels[channelNumber][tableName];
2743
+ const channel = this.channels[channelNumber];
2744
+ if (channel.isDrum)
2745
+ return;
2746
+ const table = channel[tableName];
2667
2747
  for (let i = 5; i < data.length - 1; i += 2) {
2668
2748
  const pp = data[i];
2669
2749
  const rr = data[i + 1];
@@ -2673,8 +2753,13 @@ class MidyGM2 {
2673
2753
  initControlTable() {
2674
2754
  const channelCount = 128;
2675
2755
  const slotSize = 6;
2676
- const defaultValues = [64, 64, 64, 0, 0, 0];
2677
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];
2678
2763
  for (let ch = 0; ch < channelCount; ch++) {
2679
2764
  const offset = ch * slotSize;
2680
2765
  table.set(defaultValues, offset);
@@ -2691,8 +2776,11 @@ class MidyGM2 {
2691
2776
  }
2692
2777
  handleControlChangeSysEx(data) {
2693
2778
  const channelNumber = data[4];
2779
+ const channel = this.channels[channelNumber];
2780
+ if (channel.isDrum)
2781
+ return;
2694
2782
  const controllerType = data[5];
2695
- const table = this.channels[channelNumber].controlTable[controllerType];
2783
+ const table = channel.controlTable[controllerType];
2696
2784
  for (let i = 6; i < data.length - 1; i += 2) {
2697
2785
  const pp = data[i];
2698
2786
  const rr = data[i + 1];
@@ -2706,8 +2794,11 @@ class MidyGM2 {
2706
2794
  }
2707
2795
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2708
2796
  const channelNumber = data[4];
2797
+ const channel = this.channels[channelNumber];
2798
+ if (channel.isDrum)
2799
+ return;
2709
2800
  const keyNumber = data[5];
2710
- const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
2801
+ const table = channel.keyBasedInstrumentControlTable;
2711
2802
  for (let i = 6; i < data.length - 1; i += 2) {
2712
2803
  const controllerType = data[i];
2713
2804
  const value = data[i + 1];