@marmooo/midy 0.1.7 → 0.2.0

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,7 +1,7 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
3
  class Note {
4
- constructor(noteNumber, velocity, startTime, instrumentKey) {
4
+ constructor(noteNumber, velocity, startTime, voice, voiceParams) {
5
5
  Object.defineProperty(this, "bufferSource", {
6
6
  enumerable: true,
7
7
  configurable: true,
@@ -62,12 +62,106 @@ class Note {
62
62
  writable: true,
63
63
  value: void 0
64
64
  });
65
+ Object.defineProperty(this, "portamento", {
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true,
69
+ value: void 0
70
+ });
65
71
  this.noteNumber = noteNumber;
66
72
  this.velocity = velocity;
67
73
  this.startTime = startTime;
68
- this.instrumentKey = instrumentKey;
74
+ this.voice = voice;
75
+ this.voiceParams = voiceParams;
69
76
  }
70
77
  }
78
+ // normalized to 0-1 for use with the SF2 modulator model
79
+ const defaultControllerState = {
80
+ noteOnVelocity: { type: 2, defaultValue: 0 },
81
+ noteOnKeyNumber: { type: 3, defaultValue: 0 },
82
+ polyPressure: { type: 10, defaultValue: 0 },
83
+ channelPressure: { type: 13, defaultValue: 0 },
84
+ pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
85
+ pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
86
+ link: { type: 127, defaultValue: 0 },
87
+ // bankMSB: { type: 128 + 0, defaultValue: 121, },
88
+ modulationDepth: { type: 128 + 1, defaultValue: 0 },
89
+ portamentoTime: { type: 128 + 5, defaultValue: 0 },
90
+ // dataMSB: { type: 128 + 6, defaultValue: 0, },
91
+ volume: { type: 128 + 7, defaultValue: 100 / 127 },
92
+ pan: { type: 128 + 10, defaultValue: 0.5 },
93
+ expression: { type: 128 + 11, defaultValue: 1 },
94
+ // bankLSB: { type: 128 + 32, defaultValue: 0, },
95
+ // dataLSB: { type: 128 + 38, defaultValue: 0, },
96
+ sustainPedal: { type: 128 + 64, defaultValue: 0 },
97
+ portamento: { type: 128 + 65, defaultValue: 0 },
98
+ sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
99
+ softPedal: { type: 128 + 67, defaultValue: 0 },
100
+ filterResonance: { type: 128 + 71, defaultValue: 0.5 },
101
+ releaseTime: { type: 128 + 72, defaultValue: 0.5 },
102
+ attackTime: { type: 128 + 73, defaultValue: 0.5 },
103
+ brightness: { type: 128 + 74, defaultValue: 0.5 },
104
+ decayTime: { type: 128 + 75, defaultValue: 0.5 },
105
+ vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
106
+ vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
107
+ vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
108
+ reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
109
+ chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
110
+ // dataIncrement: { type: 128 + 96, defaultValue: 0 },
111
+ // dataDecrement: { type: 128 + 97, defaultValue: 0 },
112
+ // rpnLSB: { type: 128 + 100, defaultValue: 127 },
113
+ // rpnMSB: { type: 128 + 101, defaultValue: 127 },
114
+ // allSoundOff: { type: 128 + 120, defaultValue: 0 },
115
+ // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
116
+ // allNotesOff: { type: 128 + 123, defaultValue: 0 },
117
+ // omniOff: { type: 128 + 124, defaultValue: 0 },
118
+ // omniOn: { type: 128 + 125, defaultValue: 0 },
119
+ // monoOn: { type: 128 + 126, defaultValue: 0 },
120
+ // polyOn: { type: 128 + 127, defaultValue: 0 },
121
+ };
122
+ class ControllerState {
123
+ constructor() {
124
+ Object.defineProperty(this, "array", {
125
+ enumerable: true,
126
+ configurable: true,
127
+ writable: true,
128
+ value: new Float32Array(256)
129
+ });
130
+ const entries = Object.entries(defaultControllerState);
131
+ for (const [name, { type, defaultValue }] of entries) {
132
+ this.array[type] = defaultValue;
133
+ Object.defineProperty(this, name, {
134
+ get: () => this.array[type],
135
+ set: (value) => this.array[type] = value,
136
+ enumerable: true,
137
+ configurable: true,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ const filterEnvelopeKeys = [
143
+ "modEnvToPitch",
144
+ "initialFilterFc",
145
+ "modEnvToFilterFc",
146
+ "modDelay",
147
+ "modAttack",
148
+ "modHold",
149
+ "modDecay",
150
+ "modSustain",
151
+ "modRelease",
152
+ "playbackRate",
153
+ ];
154
+ const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
155
+ const volumeEnvelopeKeys = [
156
+ "volDelay",
157
+ "volAttack",
158
+ "volHold",
159
+ "volDecay",
160
+ "volSustain",
161
+ "volRelease",
162
+ "initialAttenuation",
163
+ ];
164
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
71
165
  export class MidyGM2 {
72
166
  constructor(audioContext, options = this.defaultOptions) {
73
167
  Object.defineProperty(this, "ticksPerBeat", {
@@ -248,6 +342,7 @@ export class MidyGM2 {
248
342
  this.audioContext = audioContext;
249
343
  this.options = { ...this.defaultOptions, ...options };
250
344
  this.masterGain = new GainNode(audioContext);
345
+ this.voiceParamsHandlers = this.createVoiceParamsHandlers();
251
346
  this.controlChangeHandlers = this.createControlChangeHandlers();
252
347
  this.channels = this.createChannels(audioContext);
253
348
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
@@ -294,7 +389,7 @@ export class MidyGM2 {
294
389
  this.totalTime = this.calcTotalTime();
295
390
  }
296
391
  setChannelAudioNodes(audioContext) {
297
- const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
392
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
298
393
  const gainL = new GainNode(audioContext, { gain: gainLeft });
299
394
  const gainR = new GainNode(audioContext, { gain: gainRight });
300
395
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -311,7 +406,7 @@ export class MidyGM2 {
311
406
  const channels = Array.from({ length: 16 }, () => {
312
407
  return {
313
408
  ...this.constructor.channelSettings,
314
- ...this.constructor.effectSettings,
409
+ state: new ControllerState(),
315
410
  ...this.setChannelAudioNodes(audioContext),
316
411
  scheduledNotes: new Map(),
317
412
  sostenutoNotes: new Map(),
@@ -322,11 +417,11 @@ export class MidyGM2 {
322
417
  });
323
418
  return channels;
324
419
  }
325
- async createNoteBuffer(instrumentKey, isSF3) {
326
- const sampleStart = instrumentKey.start;
327
- const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
420
+ async createNoteBuffer(voiceParams, isSF3) {
421
+ const sampleStart = voiceParams.start;
422
+ const sampleEnd = voiceParams.sample.length + voiceParams.end;
328
423
  if (isSF3) {
329
- const sample = instrumentKey.sample;
424
+ const sample = voiceParams.sample;
330
425
  const start = sample.byteOffset + sampleStart;
331
426
  const end = sample.byteOffset + sampleEnd;
332
427
  const buffer = sample.buffer.slice(start, end);
@@ -334,14 +429,14 @@ export class MidyGM2 {
334
429
  return audioBuffer;
335
430
  }
336
431
  else {
337
- const sample = instrumentKey.sample;
432
+ const sample = voiceParams.sample;
338
433
  const start = sample.byteOffset + sampleStart;
339
434
  const end = sample.byteOffset + sampleEnd;
340
435
  const buffer = sample.buffer.slice(start, end);
341
436
  const audioBuffer = new AudioBuffer({
342
437
  numberOfChannels: 1,
343
438
  length: sample.length,
344
- sampleRate: instrumentKey.sampleRate,
439
+ sampleRate: voiceParams.sampleRate,
345
440
  });
346
441
  const channelData = audioBuffer.getChannelData(0);
347
442
  const int16Array = new Int16Array(buffer);
@@ -351,15 +446,14 @@ export class MidyGM2 {
351
446
  return audioBuffer;
352
447
  }
353
448
  }
354
- async createNoteBufferNode(instrumentKey, isSF3) {
449
+ async createNoteBufferNode(voiceParams, isSF3) {
355
450
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
356
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
451
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
357
452
  bufferSource.buffer = audioBuffer;
358
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
453
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
359
454
  if (bufferSource.loop) {
360
- bufferSource.loopStart = instrumentKey.loopStart /
361
- instrumentKey.sampleRate;
362
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
455
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
456
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
363
457
  }
364
458
  return bufferSource;
365
459
  }
@@ -413,7 +507,7 @@ export class MidyGM2 {
413
507
  this.handleChannelPressure(event.channel, event.amount);
414
508
  break;
415
509
  case "pitchBend":
416
- this.setPitchBend(event.channel, event.value);
510
+ this.setPitchBend(event.channel, event.value + 8192);
417
511
  break;
418
512
  case "sysEx":
419
513
  this.handleSysEx(event.data);
@@ -810,53 +904,62 @@ export class MidyGM2 {
810
904
  calcSemitoneOffset(channel) {
811
905
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
812
906
  const channelTuning = channel.coarseTuning + channel.fineTuning;
813
- const tuning = masterTuning + channelTuning;
814
- return channel.pitchBend * channel.pitchBendRange + tuning;
815
- }
816
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
817
- return instrumentKey.playbackRate(noteNumber) *
818
- Math.pow(2, semitoneOffset / 12);
907
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
908
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
909
+ const pitch = pitchWheel * pitchWheelSensitivity;
910
+ return masterTuning + channelTuning + pitch;
819
911
  }
820
912
  setPortamentoStartVolumeEnvelope(channel, note) {
821
- const { instrumentKey, startTime } = note;
822
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
823
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
824
- const volDelay = startTime + instrumentKey.volDelay;
825
- const portamentoTime = volDelay + channel.portamentoTime;
913
+ const now = this.audioContext.currentTime;
914
+ const { voiceParams, startTime } = note;
915
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
916
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
917
+ const volDelay = startTime + voiceParams.volDelay;
918
+ const portamentoTime = volDelay + channel.state.portamentoTime;
826
919
  note.volumeNode.gain
827
- .cancelScheduledValues(startTime)
920
+ .cancelScheduledValues(now)
828
921
  .setValueAtTime(0, volDelay)
829
922
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
830
923
  }
831
924
  setVolumeEnvelope(note) {
832
- const { instrumentKey, startTime } = note;
833
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
834
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
835
- const volDelay = startTime + instrumentKey.volDelay;
836
- const volAttack = volDelay + instrumentKey.volAttack;
837
- const volHold = volAttack + instrumentKey.volHold;
838
- const volDecay = volHold + instrumentKey.volDecay;
925
+ const now = this.audioContext.currentTime;
926
+ const { voiceParams, startTime } = note;
927
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
928
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
929
+ const volDelay = startTime + voiceParams.volDelay;
930
+ const volAttack = volDelay + voiceParams.volAttack;
931
+ const volHold = volAttack + voiceParams.volHold;
932
+ const volDecay = volHold + voiceParams.volDecay;
839
933
  note.volumeNode.gain
840
- .cancelScheduledValues(startTime)
934
+ .cancelScheduledValues(now)
841
935
  .setValueAtTime(0, startTime)
842
936
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
843
937
  .exponentialRampToValueAtTime(attackVolume, volAttack)
844
938
  .setValueAtTime(attackVolume, volHold)
845
939
  .linearRampToValueAtTime(sustainVolume, volDecay);
846
940
  }
847
- setPitch(note, semitoneOffset) {
848
- const { instrumentKey, noteNumber, startTime } = note;
849
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
850
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
941
+ setPlaybackRate(note) {
942
+ const now = this.audioContext.currentTime;
943
+ note.bufferSource.playbackRate
944
+ .cancelScheduledValues(now)
945
+ .setValueAtTime(note.voiceParams.playbackRate, now);
946
+ }
947
+ setPitch(channel, note) {
948
+ const now = this.audioContext.currentTime;
949
+ const { startTime } = note;
950
+ const basePitch = this.calcSemitoneOffset(channel) * 100;
951
+ note.bufferSource.detune
952
+ .cancelScheduledValues(now)
953
+ .setValueAtTime(basePitch, startTime);
954
+ const modEnvToPitch = note.voiceParams.modEnvToPitch;
851
955
  if (modEnvToPitch === 0)
852
956
  return;
853
- const basePitch = note.bufferSource.playbackRate.value;
854
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
855
- const modDelay = startTime + instrumentKey.modDelay;
856
- const modAttack = modDelay + instrumentKey.modAttack;
857
- const modHold = modAttack + instrumentKey.modHold;
858
- const modDecay = modHold + instrumentKey.modDecay;
859
- note.bufferSource.playbackRate.value
957
+ const peekPitch = basePitch + modEnvToPitch;
958
+ const modDelay = startTime + voiceParams.modDelay;
959
+ const modAttack = modDelay + voiceParams.modAttack;
960
+ const modHold = modAttack + voiceParams.modHold;
961
+ const modDecay = modHold + voiceParams.modDecay;
962
+ note.bufferSource.detune
860
963
  .setValueAtTime(basePitch, modDelay)
861
964
  .exponentialRampToValueAtTime(peekPitch, modAttack)
862
965
  .setValueAtTime(peekPitch, modHold)
@@ -868,42 +971,46 @@ export class MidyGM2 {
868
971
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
869
972
  }
870
973
  setPortamentoStartFilterEnvelope(channel, note) {
871
- const { instrumentKey, noteNumber, startTime } = note;
974
+ const now = this.audioContext.currentTime;
975
+ const state = channel.state;
976
+ const { voiceParams, noteNumber, startTime } = note;
872
977
  const softPedalFactor = 1 -
873
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
874
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
978
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
979
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
875
980
  softPedalFactor;
876
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
981
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
877
982
  const sustainFreq = baseFreq +
878
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
983
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
879
984
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
880
985
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
881
- const portamentoTime = startTime + channel.portamentoTime;
882
- const modDelay = startTime + instrumentKey.modDelay;
986
+ const portamentoTime = startTime + channel.state.portamentoTime;
987
+ const modDelay = startTime + voiceParams.modDelay;
883
988
  note.filterNode.frequency
884
- .cancelScheduledValues(startTime)
989
+ .cancelScheduledValues(now)
885
990
  .setValueAtTime(adjustedBaseFreq, startTime)
886
991
  .setValueAtTime(adjustedBaseFreq, modDelay)
887
992
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
888
993
  }
889
994
  setFilterEnvelope(channel, note) {
890
- const { instrumentKey, noteNumber, startTime } = note;
995
+ const now = this.audioContext.currentTime;
996
+ const state = channel.state;
997
+ const { voiceParams, noteNumber, startTime } = note;
891
998
  const softPedalFactor = 1 -
892
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
893
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
999
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1000
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
894
1001
  softPedalFactor;
895
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
1002
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
896
1003
  const sustainFreq = baseFreq +
897
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1004
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
898
1005
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
899
1006
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
900
1007
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
901
- const modDelay = startTime + instrumentKey.modDelay;
902
- const modAttack = modDelay + instrumentKey.modAttack;
903
- const modHold = modAttack + instrumentKey.modHold;
904
- const modDecay = modHold + instrumentKey.modDecay;
1008
+ const modDelay = startTime + voiceParams.modDelay;
1009
+ const modAttack = modDelay + voiceParams.modAttack;
1010
+ const modHold = modAttack + voiceParams.modHold;
1011
+ const modDecay = modHold + voiceParams.modDecay;
905
1012
  note.filterNode.frequency
906
- .cancelScheduledValues(startTime)
1013
+ .cancelScheduledValues(now)
907
1014
  .setValueAtTime(adjustedBaseFreq, startTime)
908
1015
  .setValueAtTime(adjustedBaseFreq, modDelay)
909
1016
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -911,25 +1018,18 @@ export class MidyGM2 {
911
1018
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
912
1019
  }
913
1020
  startModulation(channel, note, startTime) {
914
- const { instrumentKey } = note;
915
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1021
+ const { voiceParams } = note;
916
1022
  note.modulationLFO = new OscillatorNode(this.audioContext, {
917
- frequency: this.centToHz(instrumentKey.freqModLFO),
1023
+ frequency: this.centToHz(voiceParams.freqModLFO),
918
1024
  });
919
1025
  note.filterDepth = new GainNode(this.audioContext, {
920
- gain: instrumentKey.modLfoToFilterFc,
921
- });
922
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
923
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
924
- note.modulationDepth = new GainNode(this.audioContext, {
925
- gain: modulationDepth * modulationDepthSign,
926
- });
927
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
928
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
929
- note.volumeDepth = new GainNode(this.audioContext, {
930
- gain: volumeDepth * volumeDepthSign,
1026
+ gain: voiceParams.modLfoToFilterFc,
931
1027
  });
932
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1028
+ note.modulationDepth = new GainNode(this.audioContext);
1029
+ this.setModLfoToPitch(channel, note);
1030
+ note.volumeDepth = new GainNode(this.audioContext);
1031
+ this.setModLfoToVolume(note);
1032
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
933
1033
  note.modulationLFO.connect(note.filterDepth);
934
1034
  note.filterDepth.connect(note.filterNode.frequency);
935
1035
  note.modulationLFO.connect(note.modulationDepth);
@@ -938,67 +1038,58 @@ export class MidyGM2 {
938
1038
  note.volumeDepth.connect(note.volumeNode.gain);
939
1039
  }
940
1040
  startVibrato(channel, note, startTime) {
941
- const { instrumentKey } = note;
942
- const { vibLfoToPitch } = instrumentKey;
1041
+ const { voiceParams } = note;
1042
+ const state = channel.state;
943
1043
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
944
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
945
- channel.vibratoRate,
946
- });
947
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
948
- const vibratoDepthSign = 0 < vibLfoToPitch;
949
- note.vibratoDepth = new GainNode(this.audioContext, {
950
- gain: vibratoDepth * vibratoDepthSign,
1044
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1045
+ state.vibratoRate,
951
1046
  });
952
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1047
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1048
+ note.vibratoDepth = new GainNode(this.audioContext);
1049
+ this.setVibLfoToPitch(channel, note);
953
1050
  note.vibratoLFO.connect(note.vibratoDepth);
954
1051
  note.vibratoDepth.connect(note.bufferSource.detune);
955
1052
  }
956
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
957
- const semitoneOffset = this.calcSemitoneOffset(channel);
958
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
959
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1053
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1054
+ const state = channel.state;
1055
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1056
+ const voiceParams = voice.getAllParams(controllerState);
1057
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1058
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
960
1059
  note.volumeNode = new GainNode(this.audioContext);
961
1060
  note.filterNode = new BiquadFilterNode(this.audioContext, {
962
1061
  type: "lowpass",
963
- Q: instrumentKey.initialFilterQ / 10, // dB
1062
+ Q: voiceParams.initialFilterQ / 10, // dB
964
1063
  });
965
1064
  if (portamento) {
1065
+ note.portamento = true;
966
1066
  this.setPortamentoStartVolumeEnvelope(channel, note);
967
1067
  this.setPortamentoStartFilterEnvelope(channel, note);
968
1068
  }
969
1069
  else {
970
- this.setVolumeEnvelope(note);
1070
+ note.portamento = false;
1071
+ this.setVolumeEnvelope(channel, note);
971
1072
  this.setFilterEnvelope(channel, note);
972
1073
  }
973
- if (0 < channel.vibratoDepth) {
1074
+ if (0 < state.vibratoDepth) {
974
1075
  this.startVibrato(channel, note, startTime);
975
1076
  }
976
- if (0 < channel.modulationDepth) {
977
- this.setPitch(note, semitoneOffset);
1077
+ this.setPlaybackRate(note);
1078
+ if (0 < state.modulationDepth) {
1079
+ this.setPitch(channel, note);
978
1080
  this.startModulation(channel, note, startTime);
979
1081
  }
980
- else {
981
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
982
- }
983
1082
  if (this.mono && channel.currentBufferSource) {
984
1083
  channel.currentBufferSource.stop(startTime);
985
1084
  channel.currentBufferSource = note.bufferSource;
986
1085
  }
987
1086
  note.bufferSource.connect(note.filterNode);
988
1087
  note.filterNode.connect(note.volumeNode);
989
- if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
990
- note.reverbEffectsSend = new GainNode(this.audioContext, {
991
- gain: instrumentKey.reverbEffectsSend,
992
- });
993
- note.volumeNode.connect(note.reverbEffectsSend);
994
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1088
+ if (0 < channel.chorusSendLevel) {
1089
+ this.setChorusEffectsSend(channel, note, 0);
995
1090
  }
996
- if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
997
- note.chorusEffectsSend = new GainNode(this.audioContext, {
998
- gain: instrumentKey.chorusEffectsSend,
999
- });
1000
- note.volumeNode.connect(note.chorusEffectsSend);
1001
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1091
+ if (0 < channel.reverbSendLevel) {
1092
+ this.setReverbEffectsSend(channel, note, 0);
1002
1093
  }
1003
1094
  note.bufferSource.start(startTime);
1004
1095
  return note;
@@ -1020,16 +1111,16 @@ export class MidyGM2 {
1020
1111
  return;
1021
1112
  const soundFont = this.soundFonts[soundFontIndex];
1022
1113
  const isSF3 = soundFont.parsed.info.version.major === 3;
1023
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
1024
- if (!instrumentKey)
1114
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1115
+ if (!voice)
1025
1116
  return;
1026
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1117
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1027
1118
  note.volumeNode.connect(channel.gainL);
1028
1119
  note.volumeNode.connect(channel.gainR);
1029
- if (channel.sostenutoPedal) {
1120
+ if (channel.state.sostenutoPedal) {
1030
1121
  channel.sostenutoNotes.set(noteNumber, note);
1031
1122
  }
1032
- const exclusiveClass = instrumentKey.exclusiveClass;
1123
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1033
1124
  if (exclusiveClass !== 0) {
1034
1125
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1035
1126
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
@@ -1091,8 +1182,9 @@ export class MidyGM2 {
1091
1182
  }
1092
1183
  scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1093
1184
  const channel = this.channels[channelNumber];
1185
+ const state = channel.state;
1094
1186
  if (!force) {
1095
- if (channel.sustainPedal)
1187
+ if (0.5 < state.sustainPedal)
1096
1188
  return;
1097
1189
  if (channel.sostenutoNotes.has(noteNumber))
1098
1190
  return;
@@ -1107,8 +1199,8 @@ export class MidyGM2 {
1107
1199
  if (note.ending)
1108
1200
  continue;
1109
1201
  if (portamentoNoteNumber === undefined) {
1110
- const volRelease = endTime + note.instrumentKey.volRelease;
1111
- const modRelease = endTime + note.instrumentKey.modRelease;
1202
+ const volRelease = endTime + note.voiceParams.volRelease;
1203
+ const modRelease = endTime + note.voiceParams.modRelease;
1112
1204
  note.filterNode.frequency
1113
1205
  .cancelScheduledValues(endTime)
1114
1206
  .linearRampToValueAtTime(0, modRelease);
@@ -1116,7 +1208,7 @@ export class MidyGM2 {
1116
1208
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1117
1209
  }
1118
1210
  else {
1119
- const portamentoTime = endTime + channel.portamentoTime;
1211
+ const portamentoTime = endTime + state.portamentoTime;
1120
1212
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1121
1213
  const detune = note.bufferSource.detune.value + detuneChange;
1122
1214
  note.bufferSource.detune
@@ -1134,7 +1226,7 @@ export class MidyGM2 {
1134
1226
  const velocity = halfVelocity * 2;
1135
1227
  const channel = this.channels[channelNumber];
1136
1228
  const promises = [];
1137
- channel.sustainPedal = false;
1229
+ channel.state.sustainPedal = halfVelocity;
1138
1230
  channel.scheduledNotes.forEach((noteList) => {
1139
1231
  for (let i = 0; i < noteList.length; i++) {
1140
1232
  const note = noteList[i];
@@ -1151,7 +1243,7 @@ export class MidyGM2 {
1151
1243
  const velocity = halfVelocity * 2;
1152
1244
  const channel = this.channels[channelNumber];
1153
1245
  const promises = [];
1154
- channel.sostenutoPedal = false;
1246
+ channel.state.sostenutoPedal = 0;
1155
1247
  channel.sostenutoNotes.forEach((activeNote) => {
1156
1248
  const { noteNumber } = activeNote;
1157
1249
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1199,18 +1291,232 @@ export class MidyGM2 {
1199
1291
  .setValueAtTime(gain * pressure, now);
1200
1292
  });
1201
1293
  }
1294
+ // this.applyVoiceParams(channel, 13);
1202
1295
  }
1203
1296
  handlePitchBendMessage(channelNumber, lsb, msb) {
1204
- const pitchBend = msb * 128 + lsb - 8192;
1297
+ const pitchBend = msb * 128 + lsb;
1205
1298
  this.setPitchBend(channelNumber, pitchBend);
1206
1299
  }
1207
- setPitchBend(channelNumber, pitchBend) {
1300
+ setPitchBend(channelNumber, value) {
1208
1301
  const channel = this.channels[channelNumber];
1209
- const prevPitchBend = channel.pitchBend;
1210
- channel.pitchBend = pitchBend / 8192;
1211
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1212
- channel.pitchBendRange * 100;
1302
+ const state = channel.state;
1303
+ state.pitchWheel = value / 16383;
1304
+ const pitchWheel = (value - 8192) / 8192;
1305
+ const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1213
1306
  this.updateDetune(channel, detuneChange);
1307
+ this.applyVoiceParams(channel, 14);
1308
+ }
1309
+ setModLfoToPitch(channel, note) {
1310
+ const now = this.audioContext.currentTime;
1311
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1312
+ const modulationDepth = Math.abs(modLfoToPitch) +
1313
+ channel.state.modulationDepth;
1314
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1315
+ note.modulationDepth.gain
1316
+ .cancelScheduledValues(now)
1317
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1318
+ }
1319
+ setModLfoToVolume(note) {
1320
+ const now = this.audioContext.currentTime;
1321
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1322
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1323
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1324
+ note.volumeDepth.gain
1325
+ .cancelScheduledValues(now)
1326
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1327
+ }
1328
+ setChorusEffectsSend(note, prevValue) {
1329
+ if (0 < prevValue) {
1330
+ if (0 < note.voiceParams.chorusEffectsSend) {
1331
+ const now = this.audioContext.currentTime;
1332
+ const value = note.voiceParams.chorusEffectsSend;
1333
+ note.chorusEffectsSend.gain
1334
+ .cancelScheduledValues(now)
1335
+ .setValueAtTime(value, now);
1336
+ }
1337
+ else {
1338
+ note.chorusEffectsSend.disconnect();
1339
+ }
1340
+ }
1341
+ else {
1342
+ if (0 < note.voiceParams.chorusEffectsSend) {
1343
+ if (!note.chorusEffectsSend) {
1344
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1345
+ gain: note.voiceParams.chorusEffectsSend,
1346
+ });
1347
+ note.volumeNode.connect(note.chorusEffectsSend);
1348
+ }
1349
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1350
+ }
1351
+ }
1352
+ }
1353
+ setReverbEffectsSend(note, prevValue) {
1354
+ if (0 < prevValue) {
1355
+ if (0 < note.voiceParams.reverbEffectsSend) {
1356
+ const now = this.audioContext.currentTime;
1357
+ const value = note.voiceParams.reverbEffectsSend;
1358
+ note.reverbEffectsSend.gain
1359
+ .cancelScheduledValues(now)
1360
+ .setValueAtTime(value, now);
1361
+ }
1362
+ else {
1363
+ note.reverbEffectsSend.disconnect();
1364
+ }
1365
+ }
1366
+ else {
1367
+ if (0 < note.voiceParams.reverbEffectsSend) {
1368
+ if (!note.reverbEffectsSend) {
1369
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1370
+ gain: note.voiceParams.reverbEffectsSend,
1371
+ });
1372
+ note.volumeNode.connect(note.reverbEffectsSend);
1373
+ }
1374
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1375
+ }
1376
+ }
1377
+ }
1378
+ setVibLfoToPitch(channel, note) {
1379
+ const now = this.audioContext.currentTime;
1380
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1381
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1382
+ 2;
1383
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1384
+ note.vibratoDepth.gain
1385
+ .cancelScheduledValues(now)
1386
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1387
+ }
1388
+ setModLfoToFilterFc(note) {
1389
+ const now = this.audioContext.currentTime;
1390
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1391
+ note.filterDepth.gain
1392
+ .cancelScheduledValues(now)
1393
+ .setValueAtTime(modLfoToFilterFc, now);
1394
+ }
1395
+ setDelayModLFO(note) {
1396
+ const now = this.audioContext.currentTime;
1397
+ const startTime = note.startTime;
1398
+ if (startTime < now)
1399
+ return;
1400
+ note.modulationLFO.stop(now);
1401
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1402
+ note.modulationLFO.connect(note.filterDepth);
1403
+ }
1404
+ setFreqModLFO(note) {
1405
+ const now = this.audioContext.currentTime;
1406
+ const freqModLFO = note.voiceParams.freqModLFO;
1407
+ note.modulationLFO.frequency
1408
+ .cancelScheduledValues(now)
1409
+ .setValueAtTime(freqModLFO, now);
1410
+ }
1411
+ createVoiceParamsHandlers() {
1412
+ return {
1413
+ modLfoToPitch: (channel, note, _prevValue) => {
1414
+ if (0 < channel.state.modulationDepth) {
1415
+ this.setModLfoToPitch(channel, note);
1416
+ }
1417
+ },
1418
+ vibLfoToPitch: (channel, note, _prevValue) => {
1419
+ if (0 < channel.state.vibratoDepth) {
1420
+ this.setVibLfoToPitch(channel, note);
1421
+ }
1422
+ },
1423
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1424
+ if (0 < channel.state.modulationDepth)
1425
+ this.setModLfoToFilterFc(note);
1426
+ },
1427
+ modLfoToVolume: (channel, note) => {
1428
+ if (0 < channel.state.modulationDepth)
1429
+ this.setModLfoToVolume(note);
1430
+ },
1431
+ chorusEffectsSend: (_channel, note, prevValue) => {
1432
+ this.setChorusEffectsSend(note, prevValue);
1433
+ },
1434
+ reverbEffectsSend: (_channel, note, prevValue) => {
1435
+ this.setReverbEffectsSend(note, prevValue);
1436
+ },
1437
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1438
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1439
+ delayVibLFO: (channel, note, prevValue) => {
1440
+ if (0 < channel.state.vibratoDepth) {
1441
+ const now = this.audioContext.currentTime;
1442
+ const prevStartTime = note.startTime +
1443
+ prevValue * channel.state.vibratoDelay * 2;
1444
+ if (now < prevStartTime)
1445
+ return;
1446
+ const startTime = note.startTime +
1447
+ value * channel.state.vibratoDelay * 2;
1448
+ note.vibratoLFO.stop(now);
1449
+ note.vibratoLFO.start(startTime);
1450
+ }
1451
+ },
1452
+ freqVibLFO: (channel, note, _prevValue) => {
1453
+ if (0 < channel.state.vibratoDepth) {
1454
+ const now = this.audioContext.currentTime;
1455
+ note.vibratoLFO.frequency
1456
+ .cancelScheduledValues(now)
1457
+ .setValueAtTime(value * sate.vibratoRate, now);
1458
+ }
1459
+ },
1460
+ };
1461
+ }
1462
+ getControllerState(channel, noteNumber, velocity) {
1463
+ const state = new Float32Array(channel.state.array.length);
1464
+ state.set(channel.state.array);
1465
+ state[2] = velocity / 127;
1466
+ state[3] = noteNumber / 127;
1467
+ return state;
1468
+ }
1469
+ applyVoiceParams(channel, controllerType) {
1470
+ channel.scheduledNotes.forEach((noteList) => {
1471
+ for (let i = 0; i < noteList.length; i++) {
1472
+ const note = noteList[i];
1473
+ if (!note)
1474
+ continue;
1475
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1476
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1477
+ let appliedFilterEnvelope = false;
1478
+ let appliedVolumeEnvelope = false;
1479
+ for (const [key, value] of Object.entries(voiceParams)) {
1480
+ const prevValue = note.voiceParams[key];
1481
+ if (value === prevValue)
1482
+ continue;
1483
+ note.voiceParams[key] = value;
1484
+ if (key in this.voiceParamsHandlers) {
1485
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1486
+ }
1487
+ else if (filterEnvelopeKeySet.has(key)) {
1488
+ if (appliedFilterEnvelope)
1489
+ continue;
1490
+ appliedFilterEnvelope = true;
1491
+ const noteVoiceParams = note.voiceParams;
1492
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1493
+ const key = filterEnvelopeKeys[i];
1494
+ if (key in voiceParams)
1495
+ noteVoiceParams[key] = voiceParams[key];
1496
+ }
1497
+ if (note.portamento) {
1498
+ this.setPortamentoStartFilterEnvelope(channel, note);
1499
+ }
1500
+ else {
1501
+ this.setFilterEnvelope(channel, note);
1502
+ }
1503
+ this.setPitch(channel, note);
1504
+ }
1505
+ else if (volumeEnvelopeKeySet.has(key)) {
1506
+ if (appliedVolumeEnvelope)
1507
+ continue;
1508
+ appliedVolumeEnvelope = true;
1509
+ const noteVoiceParams = note.voiceParams;
1510
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1511
+ const key = volumeEnvelopeKeys[i];
1512
+ if (key in voiceParams)
1513
+ noteVoiceParams[key] = voiceParams[key];
1514
+ }
1515
+ this.setVolumeEnvelope(channel, note);
1516
+ }
1517
+ }
1518
+ }
1519
+ });
1214
1520
  }
1215
1521
  createControlChangeHandlers() {
1216
1522
  return {
@@ -1240,13 +1546,16 @@ export class MidyGM2 {
1240
1546
  127: this.polyOn,
1241
1547
  };
1242
1548
  }
1243
- handleControlChange(channelNumber, controller, value) {
1244
- const handler = this.controlChangeHandlers[controller];
1549
+ handleControlChange(channelNumber, controllerType, value) {
1550
+ const handler = this.controlChangeHandlers[controllerType];
1245
1551
  if (handler) {
1246
1552
  handler.call(this, channelNumber, value);
1553
+ const channel = this.channels[channelNumber];
1554
+ const controller = 128 + controllerType;
1555
+ this.applyVoiceParams(channel, controller);
1247
1556
  }
1248
1557
  else {
1249
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1558
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1250
1559
  }
1251
1560
  }
1252
1561
  setBankMSB(channelNumber, msb) {
@@ -1260,11 +1569,10 @@ export class MidyGM2 {
1260
1569
  if (!note)
1261
1570
  continue;
1262
1571
  if (note.modulationDepth) {
1263
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1572
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1264
1573
  }
1265
1574
  else {
1266
- const semitoneOffset = this.calcSemitoneOffset(channel);
1267
- this.setPitch(note, semitoneOffset);
1575
+ this.setPitch(channel, note);
1268
1576
  this.startModulation(channel, note, now);
1269
1577
  }
1270
1578
  }
@@ -1272,21 +1580,22 @@ export class MidyGM2 {
1272
1580
  }
1273
1581
  setModulationDepth(channelNumber, modulation) {
1274
1582
  const channel = this.channels[channelNumber];
1275
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1583
+ channel.state.modulationDepth = (modulation / 127) *
1584
+ channel.modulationDepthRange;
1276
1585
  this.updateModulation(channel);
1277
1586
  }
1278
1587
  setPortamentoTime(channelNumber, portamentoTime) {
1279
1588
  const channel = this.channels[channelNumber];
1280
1589
  const factor = 5 * Math.log(10) / 127;
1281
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1590
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1282
1591
  }
1283
1592
  setVolume(channelNumber, volume) {
1284
1593
  const channel = this.channels[channelNumber];
1285
- channel.volume = volume / 127;
1594
+ channel.state.volume = volume / 127;
1286
1595
  this.updateChannelVolume(channel);
1287
1596
  }
1288
1597
  panToGain(pan) {
1289
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1598
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1290
1599
  return {
1291
1600
  gainLeft: Math.cos(theta),
1292
1601
  gainRight: Math.sin(theta),
@@ -1294,12 +1603,12 @@ export class MidyGM2 {
1294
1603
  }
1295
1604
  setPan(channelNumber, pan) {
1296
1605
  const channel = this.channels[channelNumber];
1297
- channel.pan = pan;
1606
+ channel.state.pan = pan / 127;
1298
1607
  this.updateChannelVolume(channel);
1299
1608
  }
1300
1609
  setExpression(channelNumber, expression) {
1301
1610
  const channel = this.channels[channelNumber];
1302
- channel.expression = expression / 127;
1611
+ channel.state.expression = expression / 127;
1303
1612
  this.updateChannelVolume(channel);
1304
1613
  }
1305
1614
  setBankLSB(channelNumber, lsb) {
@@ -1311,8 +1620,9 @@ export class MidyGM2 {
1311
1620
  }
1312
1621
  updateChannelVolume(channel) {
1313
1622
  const now = this.audioContext.currentTime;
1314
- const volume = channel.volume * channel.expression;
1315
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1623
+ const state = channel.state;
1624
+ const volume = state.volume * state.expression;
1625
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1316
1626
  channel.gainL.gain
1317
1627
  .cancelScheduledValues(now)
1318
1628
  .setValueAtTime(volume * gainLeft, now);
@@ -1321,24 +1631,24 @@ export class MidyGM2 {
1321
1631
  .setValueAtTime(volume * gainRight, now);
1322
1632
  }
1323
1633
  setSustainPedal(channelNumber, value) {
1324
- const isOn = value >= 64;
1325
- this.channels[channelNumber].sustainPedal = isOn;
1326
- if (!isOn) {
1634
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1635
+ if (value < 64) {
1327
1636
  this.releaseSustainPedal(channelNumber, value);
1328
1637
  }
1329
1638
  }
1330
1639
  setPortamento(channelNumber, value) {
1331
- this.channels[channelNumber].portamento = value >= 64;
1640
+ this.channels[channelNumber].state.portamento = value / 127;
1332
1641
  }
1333
1642
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1334
1643
  const channel = this.channels[channelNumber];
1644
+ const state = channel.state;
1335
1645
  const reverbEffect = this.reverbEffect;
1336
- if (0 < channel.reverbSendLevel) {
1646
+ if (0 < state.reverbSendLevel) {
1337
1647
  if (0 < reverbSendLevel) {
1338
1648
  const now = this.audioContext.currentTime;
1339
- channel.reverbSendLevel = reverbSendLevel / 127;
1649
+ state.reverbSendLevel = reverbSendLevel / 127;
1340
1650
  reverbEffect.input.gain.cancelScheduledValues(now);
1341
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1651
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1342
1652
  }
1343
1653
  else {
1344
1654
  channel.scheduledNotes.forEach((noteList) => {
@@ -1346,7 +1656,7 @@ export class MidyGM2 {
1346
1656
  const note = noteList[i];
1347
1657
  if (!note)
1348
1658
  continue;
1349
- if (note.instrumentKey.reverbEffectsSend <= 0)
1659
+ if (note.voiceParams.reverbEffectsSend <= 0)
1350
1660
  continue;
1351
1661
  note.reverbEffectsSend.disconnect();
1352
1662
  }
@@ -1361,32 +1671,25 @@ export class MidyGM2 {
1361
1671
  const note = noteList[i];
1362
1672
  if (!note)
1363
1673
  continue;
1364
- if (note.instrumentKey.reverbEffectsSend <= 0)
1365
- continue;
1366
- if (!note.reverbEffectsSend) {
1367
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1368
- gain: note.instrumentKey.reverbEffectsSend,
1369
- });
1370
- note.volumeNode.connect(note.reverbEffectsSend);
1371
- }
1372
- note.reverbEffectsSend.connect(reverbEffect.input);
1674
+ this.setReverbEffectsSend(note, 0);
1373
1675
  }
1374
1676
  });
1375
- channel.reverbSendLevel = reverbSendLevel / 127;
1677
+ state.reverbSendLevel = reverbSendLevel / 127;
1376
1678
  reverbEffect.input.gain.cancelScheduledValues(now);
1377
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1679
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1378
1680
  }
1379
1681
  }
1380
1682
  }
1381
1683
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1382
1684
  const channel = this.channels[channelNumber];
1685
+ const state = channel.state;
1383
1686
  const chorusEffect = this.chorusEffect;
1384
- if (0 < channel.chorusSendLevel) {
1687
+ if (0 < state.chorusSendLevel) {
1385
1688
  if (0 < chorusSendLevel) {
1386
1689
  const now = this.audioContext.currentTime;
1387
- channel.chorusSendLevel = chorusSendLevel / 127;
1690
+ state.chorusSendLevel = chorusSendLevel / 127;
1388
1691
  chorusEffect.input.gain.cancelScheduledValues(now);
1389
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1692
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1390
1693
  }
1391
1694
  else {
1392
1695
  channel.scheduledNotes.forEach((noteList) => {
@@ -1394,7 +1697,7 @@ export class MidyGM2 {
1394
1697
  const note = noteList[i];
1395
1698
  if (!note)
1396
1699
  continue;
1397
- if (note.instrumentKey.chorusEffectsSend <= 0)
1700
+ if (note.voiceParams.chorusEffectsSend <= 0)
1398
1701
  continue;
1399
1702
  note.chorusEffectsSend.disconnect();
1400
1703
  }
@@ -1409,28 +1712,19 @@ export class MidyGM2 {
1409
1712
  const note = noteList[i];
1410
1713
  if (!note)
1411
1714
  continue;
1412
- if (note.instrumentKey.chorusEffectsSend <= 0)
1413
- continue;
1414
- if (!note.chorusEffectsSend) {
1415
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1416
- gain: note.instrumentKey.chorusEffectsSend,
1417
- });
1418
- note.volumeNode.connect(note.chorusEffectsSend);
1419
- }
1420
- note.chorusEffectsSend.connect(chorusEffect.input);
1715
+ this.setChorusEffectsSend(note, 0);
1421
1716
  }
1422
1717
  });
1423
- channel.chorusSendLevel = chorusSendLevel / 127;
1718
+ state.chorusSendLevel = chorusSendLevel / 127;
1424
1719
  chorusEffect.input.gain.cancelScheduledValues(now);
1425
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1720
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1426
1721
  }
1427
1722
  }
1428
1723
  }
1429
1724
  setSostenutoPedal(channelNumber, value) {
1430
- const isOn = value >= 64;
1431
1725
  const channel = this.channels[channelNumber];
1432
- channel.sostenutoPedal = isOn;
1433
- if (isOn) {
1726
+ channel.state.sostenutoPedal = value / 127;
1727
+ if (64 <= value) {
1434
1728
  const now = this.audioContext.currentTime;
1435
1729
  const activeNotes = this.getActiveNotes(channel, now);
1436
1730
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1441,7 +1735,7 @@ export class MidyGM2 {
1441
1735
  }
1442
1736
  setSoftPedal(channelNumber, softPedal) {
1443
1737
  const channel = this.channels[channelNumber];
1444
- channel.softPedal = softPedal / 127;
1738
+ channel.state.softPedal = softPedal / 127;
1445
1739
  }
1446
1740
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1447
1741
  if (maxLSB < channel.dataLSB) {
@@ -1499,7 +1793,7 @@ export class MidyGM2 {
1499
1793
  this.channels[channelNumber].dataMSB = value;
1500
1794
  this.handleRPN(channelNumber);
1501
1795
  }
1502
- updateDetune(channel, detuneChange) {
1796
+ updateDetune(channel, detune) {
1503
1797
  const now = this.audioContext.currentTime;
1504
1798
  channel.scheduledNotes.forEach((noteList) => {
1505
1799
  for (let i = 0; i < noteList.length; i++) {
@@ -1507,7 +1801,6 @@ export class MidyGM2 {
1507
1801
  if (!note)
1508
1802
  continue;
1509
1803
  const { bufferSource } = note;
1510
- const detune = bufferSource.detune.value + detuneChange;
1511
1804
  bufferSource.detune
1512
1805
  .cancelScheduledValues(now)
1513
1806
  .setValueAtTime(detune, now);
@@ -1520,13 +1813,13 @@ export class MidyGM2 {
1520
1813
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1521
1814
  this.setPitchBendRange(channelNumber, pitchBendRange);
1522
1815
  }
1523
- setPitchBendRange(channelNumber, pitchBendRange) {
1816
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1524
1817
  const channel = this.channels[channelNumber];
1525
- const prevPitchBendRange = channel.pitchBendRange;
1526
- channel.pitchBendRange = pitchBendRange;
1527
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1528
- channel.pitchBend * 100;
1529
- this.updateDetune(channel, detuneChange);
1818
+ const state = channel.state;
1819
+ state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1820
+ const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1821
+ this.updateDetune(channel, detune);
1822
+ this.applyVoiceParams(channel, 16);
1530
1823
  }
1531
1824
  handleFineTuningRPN(channelNumber) {
1532
1825
  const channel = this.channels[channelNumber];
@@ -1570,7 +1863,30 @@ export class MidyGM2 {
1570
1863
  return this.stopChannelNotes(channelNumber, 0, true);
1571
1864
  }
1572
1865
  resetAllControllers(channelNumber) {
1573
- Object.assign(this.channels[channelNumber], this.effectSettings);
1866
+ const stateTypes = [
1867
+ "expression",
1868
+ "modulationDepth",
1869
+ "sustainPedal",
1870
+ "portamento",
1871
+ "sostenutoPedal",
1872
+ "softPedal",
1873
+ "channelPressure",
1874
+ "pitchWheelSensitivity",
1875
+ ];
1876
+ const channel = this.channels[channelNumber];
1877
+ const state = channel.state;
1878
+ for (let i = 0; i < stateTypes.length; i++) {
1879
+ const type = stateTypes[i];
1880
+ state[type] = defaultControllerState[type];
1881
+ }
1882
+ const settingTypes = [
1883
+ "rpnMSB",
1884
+ "rpnLSB",
1885
+ ];
1886
+ for (let i = 0; i < settingTypes.length; i++) {
1887
+ const type = settingTypes[i];
1888
+ channel[type] = this.constructor.channelSettings[type];
1889
+ }
1574
1890
  }
1575
1891
  allNotesOff(channelNumber) {
1576
1892
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1932,43 +2248,19 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1932
2248
  writable: true,
1933
2249
  value: {
1934
2250
  currentBufferSource: null,
1935
- volume: 100 / 127,
1936
- pan: 64,
1937
- portamentoTime: 1, // sec
1938
- reverbSendLevel: 0,
1939
- chorusSendLevel: 0,
1940
- vibratoRate: 1,
1941
- vibratoDepth: 1,
1942
- vibratoDelay: 1,
2251
+ program: 0,
1943
2252
  bank: 121 * 128,
1944
2253
  bankMSB: 121,
1945
2254
  bankLSB: 0,
1946
2255
  dataMSB: 0,
1947
2256
  dataLSB: 0,
1948
- program: 0,
1949
- pitchBend: 0,
2257
+ rpnMSB: 127,
2258
+ rpnLSB: 127,
1950
2259
  fineTuning: 0, // cb
1951
2260
  coarseTuning: 0, // cb
1952
2261
  modulationDepthRange: 50, // cent
1953
2262
  }
1954
2263
  });
1955
- Object.defineProperty(MidyGM2, "effectSettings", {
1956
- enumerable: true,
1957
- configurable: true,
1958
- writable: true,
1959
- value: {
1960
- expression: 1,
1961
- modulationDepth: 0,
1962
- sustainPedal: false,
1963
- portamento: false,
1964
- sostenutoPedal: false,
1965
- softPedal: 0,
1966
- rpnMSB: 127,
1967
- rpnLSB: 127,
1968
- channelPressure: 0,
1969
- pitchBendRange: 2,
1970
- }
1971
- });
1972
2264
  Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
1973
2265
  enumerable: true,
1974
2266
  configurable: true,