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