@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/script/midy.js CHANGED
@@ -4,7 +4,7 @@ exports.Midy = 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 Midy {
75
169
  constructor(audioContext, options = this.defaultOptions) {
76
170
  Object.defineProperty(this, "ticksPerBeat", {
@@ -251,6 +345,7 @@ class Midy {
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 Midy {
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 Midy {
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(),
@@ -328,11 +423,11 @@ class Midy {
328
423
  });
329
424
  return channels;
330
425
  }
331
- async createNoteBuffer(instrumentKey, isSF3) {
332
- const sampleStart = instrumentKey.start;
333
- const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
426
+ async createNoteBuffer(voiceParams, isSF3) {
427
+ const sampleStart = voiceParams.start;
428
+ const sampleEnd = voiceParams.sample.length + voiceParams.end;
334
429
  if (isSF3) {
335
- const sample = instrumentKey.sample;
430
+ const sample = voiceParams.sample;
336
431
  const start = sample.byteOffset + sampleStart;
337
432
  const end = sample.byteOffset + sampleEnd;
338
433
  const buffer = sample.buffer.slice(start, end);
@@ -340,14 +435,14 @@ class Midy {
340
435
  return audioBuffer;
341
436
  }
342
437
  else {
343
- const sample = instrumentKey.sample;
438
+ const sample = voiceParams.sample;
344
439
  const start = sample.byteOffset + sampleStart;
345
440
  const end = sample.byteOffset + sampleEnd;
346
441
  const buffer = sample.buffer.slice(start, end);
347
442
  const audioBuffer = new AudioBuffer({
348
443
  numberOfChannels: 1,
349
444
  length: sample.length,
350
- sampleRate: instrumentKey.sampleRate,
445
+ sampleRate: voiceParams.sampleRate,
351
446
  });
352
447
  const channelData = audioBuffer.getChannelData(0);
353
448
  const int16Array = new Int16Array(buffer);
@@ -357,15 +452,14 @@ class Midy {
357
452
  return audioBuffer;
358
453
  }
359
454
  }
360
- async createNoteBufferNode(instrumentKey, isSF3) {
455
+ async createNoteBufferNode(voiceParams, isSF3) {
361
456
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
362
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
457
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
363
458
  bufferSource.buffer = audioBuffer;
364
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
459
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
365
460
  if (bufferSource.loop) {
366
- bufferSource.loopStart = instrumentKey.loopStart /
367
- instrumentKey.sampleRate;
368
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
461
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
462
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
369
463
  }
370
464
  return bufferSource;
371
465
  }
@@ -422,7 +516,7 @@ class Midy {
422
516
  this.handleChannelPressure(event.channel, event.amount);
423
517
  break;
424
518
  case "pitchBend":
425
- this.setPitchBend(event.channel, event.value);
519
+ this.setPitchBend(event.channel, event.value + 8192);
426
520
  break;
427
521
  case "sysEx":
428
522
  this.handleSysEx(event.data);
@@ -819,53 +913,63 @@ class Midy {
819
913
  calcSemitoneOffset(channel) {
820
914
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
821
915
  const channelTuning = channel.coarseTuning + channel.fineTuning;
822
- const tuning = masterTuning + channelTuning;
823
- return channel.pitchBend * channel.pitchBendRange + tuning;
824
- }
825
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
826
- return instrumentKey.playbackRate(noteNumber) *
827
- Math.pow(2, semitoneOffset / 12);
916
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
917
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
918
+ const pitch = pitchWheel * pitchWheelSensitivity;
919
+ return masterTuning + channelTuning + pitch;
828
920
  }
829
921
  setPortamentoStartVolumeEnvelope(channel, note) {
830
- const { instrumentKey, startTime } = note;
831
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
832
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
833
- const volDelay = startTime + instrumentKey.volDelay;
834
- const portamentoTime = volDelay + channel.portamentoTime;
922
+ const now = this.audioContext.currentTime;
923
+ const { voiceParams, startTime } = note;
924
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
925
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
926
+ const volDelay = startTime + voiceParams.volDelay;
927
+ const portamentoTime = volDelay + channel.state.portamentoTime;
835
928
  note.volumeNode.gain
836
- .cancelScheduledValues(startTime)
929
+ .cancelScheduledValues(now)
837
930
  .setValueAtTime(0, volDelay)
838
931
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
839
932
  }
840
933
  setVolumeEnvelope(channel, note) {
841
- const { instrumentKey, startTime } = note;
842
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
843
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
844
- const volDelay = startTime + instrumentKey.volDelay;
845
- const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
846
- const volHold = volAttack + instrumentKey.volHold;
847
- const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
934
+ const now = this.audioContext.currentTime;
935
+ const state = channel.state;
936
+ const { voiceParams, startTime } = note;
937
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
938
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
939
+ const volDelay = startTime + voiceParams.volDelay;
940
+ const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
941
+ const volHold = volAttack + voiceParams.volHold;
942
+ const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
848
943
  note.volumeNode.gain
849
- .cancelScheduledValues(startTime)
944
+ .cancelScheduledValues(now)
850
945
  .setValueAtTime(0, startTime)
851
946
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
852
947
  .exponentialRampToValueAtTime(attackVolume, volAttack)
853
948
  .setValueAtTime(attackVolume, volHold)
854
949
  .linearRampToValueAtTime(sustainVolume, volDecay);
855
950
  }
856
- setPitch(note, semitoneOffset) {
857
- const { instrumentKey, noteNumber, startTime } = note;
858
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
859
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
951
+ setPlaybackRate(note) {
952
+ const now = this.audioContext.currentTime;
953
+ note.bufferSource.playbackRate
954
+ .cancelScheduledValues(now)
955
+ .setValueAtTime(note.voiceParams.playbackRate, now);
956
+ }
957
+ setPitch(channel, note) {
958
+ const now = this.audioContext.currentTime;
959
+ const { startTime } = note;
960
+ const basePitch = this.calcSemitoneOffset(channel) * 100;
961
+ note.bufferSource.detune
962
+ .cancelScheduledValues(now)
963
+ .setValueAtTime(basePitch, startTime);
964
+ const modEnvToPitch = note.voiceParams.modEnvToPitch;
860
965
  if (modEnvToPitch === 0)
861
966
  return;
862
- const basePitch = note.bufferSource.playbackRate.value;
863
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
864
- const modDelay = startTime + instrumentKey.modDelay;
865
- const modAttack = modDelay + instrumentKey.modAttack;
866
- const modHold = modAttack + instrumentKey.modHold;
867
- const modDecay = modHold + instrumentKey.modDecay;
868
- note.bufferSource.playbackRate.value
967
+ const peekPitch = basePitch + modEnvToPitch;
968
+ const modDelay = startTime + voiceParams.modDelay;
969
+ const modAttack = modDelay + voiceParams.modAttack;
970
+ const modHold = modAttack + voiceParams.modHold;
971
+ const modDecay = modHold + voiceParams.modDecay;
972
+ note.bufferSource.detune
869
973
  .setValueAtTime(basePitch, modDelay)
870
974
  .exponentialRampToValueAtTime(peekPitch, modAttack)
871
975
  .setValueAtTime(peekPitch, modHold)
@@ -877,42 +981,46 @@ class Midy {
877
981
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
878
982
  }
879
983
  setPortamentoStartFilterEnvelope(channel, note) {
880
- const { instrumentKey, noteNumber, startTime } = note;
984
+ const now = this.audioContext.currentTime;
985
+ const state = channel.state;
986
+ const { voiceParams, noteNumber, startTime } = note;
881
987
  const softPedalFactor = 1 -
882
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
883
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
884
- softPedalFactor * channel.brightness;
885
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
988
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
989
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
990
+ softPedalFactor * state.brightness * 2;
991
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
886
992
  const sustainFreq = baseFreq +
887
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
993
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
888
994
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
889
995
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
890
- const portamentoTime = startTime + channel.portamentoTime;
891
- const modDelay = startTime + instrumentKey.modDelay;
996
+ const portamentoTime = startTime + channel.state.portamentoTime;
997
+ const modDelay = startTime + voiceParams.modDelay;
892
998
  note.filterNode.frequency
893
- .cancelScheduledValues(startTime)
999
+ .cancelScheduledValues(now)
894
1000
  .setValueAtTime(adjustedBaseFreq, startTime)
895
1001
  .setValueAtTime(adjustedBaseFreq, modDelay)
896
1002
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
897
1003
  }
898
1004
  setFilterEnvelope(channel, note) {
899
- const { instrumentKey, noteNumber, startTime } = note;
1005
+ const now = this.audioContext.currentTime;
1006
+ const state = channel.state;
1007
+ const { voiceParams, noteNumber, startTime } = note;
900
1008
  const softPedalFactor = 1 -
901
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
902
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
903
- softPedalFactor * channel.brightness;
904
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
1009
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1010
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1011
+ softPedalFactor * state.brightness * 2;
1012
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
905
1013
  const sustainFreq = baseFreq +
906
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1014
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
907
1015
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
908
1016
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
909
1017
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
910
- const modDelay = startTime + instrumentKey.modDelay;
911
- const modAttack = modDelay + instrumentKey.modAttack;
912
- const modHold = modAttack + instrumentKey.modHold;
913
- const modDecay = modHold + instrumentKey.modDecay;
1018
+ const modDelay = startTime + voiceParams.modDelay;
1019
+ const modAttack = modDelay + voiceParams.modAttack;
1020
+ const modHold = modAttack + voiceParams.modHold;
1021
+ const modDecay = modHold + voiceParams.modDecay;
914
1022
  note.filterNode.frequency
915
- .cancelScheduledValues(startTime)
1023
+ .cancelScheduledValues(now)
916
1024
  .setValueAtTime(adjustedBaseFreq, startTime)
917
1025
  .setValueAtTime(adjustedBaseFreq, modDelay)
918
1026
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -920,25 +1028,18 @@ class Midy {
920
1028
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
921
1029
  }
922
1030
  startModulation(channel, note, startTime) {
923
- const { instrumentKey } = note;
924
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1031
+ const { voiceParams } = note;
925
1032
  note.modulationLFO = new OscillatorNode(this.audioContext, {
926
- frequency: this.centToHz(instrumentKey.freqModLFO),
1033
+ frequency: this.centToHz(voiceParams.freqModLFO),
927
1034
  });
928
1035
  note.filterDepth = new GainNode(this.audioContext, {
929
- gain: instrumentKey.modLfoToFilterFc,
930
- });
931
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
932
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
933
- note.modulationDepth = new GainNode(this.audioContext, {
934
- gain: modulationDepth * modulationDepthSign,
935
- });
936
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
937
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
938
- note.volumeDepth = new GainNode(this.audioContext, {
939
- gain: volumeDepth * volumeDepthSign,
1036
+ gain: voiceParams.modLfoToFilterFc,
940
1037
  });
941
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1038
+ note.modulationDepth = new GainNode(this.audioContext);
1039
+ this.setModLfoToPitch(channel, note);
1040
+ note.volumeDepth = new GainNode(this.audioContext);
1041
+ this.setModLfoToVolume(note);
1042
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
942
1043
  note.modulationLFO.connect(note.filterDepth);
943
1044
  note.filterDepth.connect(note.filterNode.frequency);
944
1045
  note.modulationLFO.connect(note.modulationDepth);
@@ -947,67 +1048,58 @@ class Midy {
947
1048
  note.volumeDepth.connect(note.volumeNode.gain);
948
1049
  }
949
1050
  startVibrato(channel, note, startTime) {
950
- const { instrumentKey } = note;
951
- const { vibLfoToPitch } = instrumentKey;
1051
+ const { voiceParams } = note;
1052
+ const state = channel.state;
952
1053
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
953
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
954
- channel.vibratoRate,
955
- });
956
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
957
- const vibratoDepthSign = 0 < vibLfoToPitch;
958
- note.vibratoDepth = new GainNode(this.audioContext, {
959
- gain: vibratoDepth * vibratoDepthSign,
1054
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1055
+ state.vibratoRate,
960
1056
  });
961
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1057
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1058
+ note.vibratoDepth = new GainNode(this.audioContext);
1059
+ this.setVibLfoToPitch(channel, note);
962
1060
  note.vibratoLFO.connect(note.vibratoDepth);
963
1061
  note.vibratoDepth.connect(note.bufferSource.detune);
964
1062
  }
965
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
966
- const semitoneOffset = this.calcSemitoneOffset(channel);
967
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
968
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1063
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1064
+ const state = channel.state;
1065
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1066
+ const voiceParams = voice.getAllParams(controllerState);
1067
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1068
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
969
1069
  note.volumeNode = new GainNode(this.audioContext);
970
1070
  note.filterNode = new BiquadFilterNode(this.audioContext, {
971
1071
  type: "lowpass",
972
- Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
1072
+ Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
973
1073
  });
974
1074
  if (portamento) {
1075
+ note.portamento = true;
975
1076
  this.setPortamentoStartVolumeEnvelope(channel, note);
976
1077
  this.setPortamentoStartFilterEnvelope(channel, note);
977
1078
  }
978
1079
  else {
1080
+ note.portamento = false;
979
1081
  this.setVolumeEnvelope(channel, note);
980
1082
  this.setFilterEnvelope(channel, note);
981
1083
  }
982
- if (0 < channel.vibratoDepth) {
1084
+ if (0 < state.vibratoDepth) {
983
1085
  this.startVibrato(channel, note, startTime);
984
1086
  }
985
- if (0 < channel.modulationDepth) {
986
- this.setPitch(note, semitoneOffset);
1087
+ this.setPlaybackRate(note);
1088
+ if (0 < state.modulationDepth) {
1089
+ this.setPitch(channel, note);
987
1090
  this.startModulation(channel, note, startTime);
988
1091
  }
989
- else {
990
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
991
- }
992
1092
  if (this.mono && channel.currentBufferSource) {
993
1093
  channel.currentBufferSource.stop(startTime);
994
1094
  channel.currentBufferSource = note.bufferSource;
995
1095
  }
996
1096
  note.bufferSource.connect(note.filterNode);
997
1097
  note.filterNode.connect(note.volumeNode);
998
- if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
999
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1000
- gain: instrumentKey.reverbEffectsSend,
1001
- });
1002
- note.volumeNode.connect(note.reverbEffectsSend);
1003
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1098
+ if (0 < channel.chorusSendLevel) {
1099
+ this.setChorusEffectsSend(channel, note, 0);
1004
1100
  }
1005
- if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
1006
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1007
- gain: instrumentKey.chorusEffectsSend,
1008
- });
1009
- note.volumeNode.connect(note.chorusEffectsSend);
1010
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1101
+ if (0 < channel.reverbSendLevel) {
1102
+ this.setReverbEffectsSend(channel, note, 0);
1011
1103
  }
1012
1104
  note.bufferSource.start(startTime);
1013
1105
  return note;
@@ -1029,16 +1121,16 @@ class Midy {
1029
1121
  return;
1030
1122
  const soundFont = this.soundFonts[soundFontIndex];
1031
1123
  const isSF3 = soundFont.parsed.info.version.major === 3;
1032
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
1033
- if (!instrumentKey)
1124
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1125
+ if (!voice)
1034
1126
  return;
1035
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1127
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1036
1128
  note.volumeNode.connect(channel.gainL);
1037
1129
  note.volumeNode.connect(channel.gainR);
1038
- if (channel.sostenutoPedal) {
1130
+ if (channel.state.sostenutoPedal) {
1039
1131
  channel.sostenutoNotes.set(noteNumber, note);
1040
1132
  }
1041
- const exclusiveClass = instrumentKey.exclusiveClass;
1133
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1042
1134
  if (exclusiveClass !== 0) {
1043
1135
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1044
1136
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
@@ -1100,8 +1192,9 @@ class Midy {
1100
1192
  }
1101
1193
  scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1102
1194
  const channel = this.channels[channelNumber];
1195
+ const state = channel.state;
1103
1196
  if (!force) {
1104
- if (channel.sustainPedal)
1197
+ if (0.5 < state.sustainPedal)
1105
1198
  return;
1106
1199
  if (channel.sostenutoNotes.has(noteNumber))
1107
1200
  return;
@@ -1117,8 +1210,8 @@ class Midy {
1117
1210
  continue;
1118
1211
  if (portamentoNoteNumber === undefined) {
1119
1212
  const volRelease = endTime +
1120
- note.instrumentKey.volRelease * channel.releaseTime;
1121
- const modRelease = endTime + note.instrumentKey.modRelease;
1213
+ note.voiceParams.volRelease * state.releaseTime * 2;
1214
+ const modRelease = endTime + note.voiceParams.modRelease;
1122
1215
  note.filterNode.frequency
1123
1216
  .cancelScheduledValues(endTime)
1124
1217
  .linearRampToValueAtTime(0, modRelease);
@@ -1126,7 +1219,7 @@ class Midy {
1126
1219
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1127
1220
  }
1128
1221
  else {
1129
- const portamentoTime = endTime + channel.portamentoTime;
1222
+ const portamentoTime = endTime + state.portamentoTime;
1130
1223
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1131
1224
  const detune = note.bufferSource.detune.value + detuneChange;
1132
1225
  note.bufferSource.detune
@@ -1144,7 +1237,7 @@ class Midy {
1144
1237
  const velocity = halfVelocity * 2;
1145
1238
  const channel = this.channels[channelNumber];
1146
1239
  const promises = [];
1147
- channel.sustainPedal = false;
1240
+ channel.state.sustainPedal = halfVelocity;
1148
1241
  channel.scheduledNotes.forEach((noteList) => {
1149
1242
  for (let i = 0; i < noteList.length; i++) {
1150
1243
  const note = noteList[i];
@@ -1161,7 +1254,7 @@ class Midy {
1161
1254
  const velocity = halfVelocity * 2;
1162
1255
  const channel = this.channels[channelNumber];
1163
1256
  const promises = [];
1164
- channel.sostenutoPedal = false;
1257
+ channel.state.sostenutoPedal = 0;
1165
1258
  channel.sostenutoNotes.forEach((activeNote) => {
1166
1259
  const { noteNumber } = activeNote;
1167
1260
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1206,6 +1299,7 @@ class Midy {
1206
1299
  .setValueAtTime(gain * pressure, now);
1207
1300
  }
1208
1301
  }
1302
+ // this.applyVoiceParams(channel, 10);
1209
1303
  }
1210
1304
  handleProgramChange(channelNumber, program) {
1211
1305
  const channel = this.channels[channelNumber];
@@ -1226,18 +1320,232 @@ class Midy {
1226
1320
  .setValueAtTime(gain * pressure, now);
1227
1321
  });
1228
1322
  }
1323
+ // this.applyVoiceParams(channel, 13);
1229
1324
  }
1230
1325
  handlePitchBendMessage(channelNumber, lsb, msb) {
1231
- const pitchBend = msb * 128 + lsb - 8192;
1326
+ const pitchBend = msb * 128 + lsb;
1232
1327
  this.setPitchBend(channelNumber, pitchBend);
1233
1328
  }
1234
- setPitchBend(channelNumber, pitchBend) {
1329
+ setPitchBend(channelNumber, value) {
1235
1330
  const channel = this.channels[channelNumber];
1236
- const prevPitchBend = channel.pitchBend;
1237
- channel.pitchBend = pitchBend / 8192;
1238
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1239
- channel.pitchBendRange * 100;
1331
+ const state = channel.state;
1332
+ state.pitchWheel = value / 16383;
1333
+ const pitchWheel = (value - 8192) / 8192;
1334
+ const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1240
1335
  this.updateDetune(channel, detuneChange);
1336
+ this.applyVoiceParams(channel, 14);
1337
+ }
1338
+ setModLfoToPitch(channel, note) {
1339
+ const now = this.audioContext.currentTime;
1340
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1341
+ const modulationDepth = Math.abs(modLfoToPitch) +
1342
+ channel.state.modulationDepth;
1343
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1344
+ note.modulationDepth.gain
1345
+ .cancelScheduledValues(now)
1346
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1347
+ }
1348
+ setModLfoToVolume(note) {
1349
+ const now = this.audioContext.currentTime;
1350
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1351
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1352
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1353
+ note.volumeDepth.gain
1354
+ .cancelScheduledValues(now)
1355
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1356
+ }
1357
+ setChorusEffectsSend(note, prevValue) {
1358
+ if (0 < prevValue) {
1359
+ if (0 < note.voiceParams.chorusEffectsSend) {
1360
+ const now = this.audioContext.currentTime;
1361
+ const value = note.voiceParams.chorusEffectsSend;
1362
+ note.chorusEffectsSend.gain
1363
+ .cancelScheduledValues(now)
1364
+ .setValueAtTime(value, now);
1365
+ }
1366
+ else {
1367
+ note.chorusEffectsSend.disconnect();
1368
+ }
1369
+ }
1370
+ else {
1371
+ if (0 < note.voiceParams.chorusEffectsSend) {
1372
+ if (!note.chorusEffectsSend) {
1373
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1374
+ gain: note.voiceParams.chorusEffectsSend,
1375
+ });
1376
+ note.volumeNode.connect(note.chorusEffectsSend);
1377
+ }
1378
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1379
+ }
1380
+ }
1381
+ }
1382
+ setReverbEffectsSend(note, prevValue) {
1383
+ if (0 < prevValue) {
1384
+ if (0 < note.voiceParams.reverbEffectsSend) {
1385
+ const now = this.audioContext.currentTime;
1386
+ const value = note.voiceParams.reverbEffectsSend;
1387
+ note.reverbEffectsSend.gain
1388
+ .cancelScheduledValues(now)
1389
+ .setValueAtTime(value, now);
1390
+ }
1391
+ else {
1392
+ note.reverbEffectsSend.disconnect();
1393
+ }
1394
+ }
1395
+ else {
1396
+ if (0 < note.voiceParams.reverbEffectsSend) {
1397
+ if (!note.reverbEffectsSend) {
1398
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1399
+ gain: note.voiceParams.reverbEffectsSend,
1400
+ });
1401
+ note.volumeNode.connect(note.reverbEffectsSend);
1402
+ }
1403
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1404
+ }
1405
+ }
1406
+ }
1407
+ setVibLfoToPitch(channel, note) {
1408
+ const now = this.audioContext.currentTime;
1409
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1410
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1411
+ 2;
1412
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1413
+ note.vibratoDepth.gain
1414
+ .cancelScheduledValues(now)
1415
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1416
+ }
1417
+ setModLfoToFilterFc(note) {
1418
+ const now = this.audioContext.currentTime;
1419
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1420
+ note.filterDepth.gain
1421
+ .cancelScheduledValues(now)
1422
+ .setValueAtTime(modLfoToFilterFc, now);
1423
+ }
1424
+ setDelayModLFO(note) {
1425
+ const now = this.audioContext.currentTime;
1426
+ const startTime = note.startTime;
1427
+ if (startTime < now)
1428
+ return;
1429
+ note.modulationLFO.stop(now);
1430
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1431
+ note.modulationLFO.connect(note.filterDepth);
1432
+ }
1433
+ setFreqModLFO(note) {
1434
+ const now = this.audioContext.currentTime;
1435
+ const freqModLFO = note.voiceParams.freqModLFO;
1436
+ note.modulationLFO.frequency
1437
+ .cancelScheduledValues(now)
1438
+ .setValueAtTime(freqModLFO, now);
1439
+ }
1440
+ createVoiceParamsHandlers() {
1441
+ return {
1442
+ modLfoToPitch: (channel, note, _prevValue) => {
1443
+ if (0 < channel.state.modulationDepth) {
1444
+ this.setModLfoToPitch(channel, note);
1445
+ }
1446
+ },
1447
+ vibLfoToPitch: (channel, note, _prevValue) => {
1448
+ if (0 < channel.state.vibratoDepth) {
1449
+ this.setVibLfoToPitch(channel, note);
1450
+ }
1451
+ },
1452
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1453
+ if (0 < channel.state.modulationDepth)
1454
+ this.setModLfoToFilterFc(note);
1455
+ },
1456
+ modLfoToVolume: (channel, note) => {
1457
+ if (0 < channel.state.modulationDepth)
1458
+ this.setModLfoToVolume(note);
1459
+ },
1460
+ chorusEffectsSend: (_channel, note, prevValue) => {
1461
+ this.setChorusEffectsSend(note, prevValue);
1462
+ },
1463
+ reverbEffectsSend: (_channel, note, prevValue) => {
1464
+ this.setReverbEffectsSend(note, prevValue);
1465
+ },
1466
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1467
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1468
+ delayVibLFO: (channel, note, prevValue) => {
1469
+ if (0 < channel.state.vibratoDepth) {
1470
+ const now = this.audioContext.currentTime;
1471
+ const prevStartTime = note.startTime +
1472
+ prevValue * channel.state.vibratoDelay * 2;
1473
+ if (now < prevStartTime)
1474
+ return;
1475
+ const startTime = note.startTime +
1476
+ value * channel.state.vibratoDelay * 2;
1477
+ note.vibratoLFO.stop(now);
1478
+ note.vibratoLFO.start(startTime);
1479
+ }
1480
+ },
1481
+ freqVibLFO: (channel, note, _prevValue) => {
1482
+ if (0 < channel.state.vibratoDepth) {
1483
+ const now = this.audioContext.currentTime;
1484
+ note.vibratoLFO.frequency
1485
+ .cancelScheduledValues(now)
1486
+ .setValueAtTime(value * sate.vibratoRate, now);
1487
+ }
1488
+ },
1489
+ };
1490
+ }
1491
+ getControllerState(channel, noteNumber, velocity) {
1492
+ const state = new Float32Array(channel.state.array.length);
1493
+ state.set(channel.state.array);
1494
+ state[2] = velocity / 127;
1495
+ state[3] = noteNumber / 127;
1496
+ return state;
1497
+ }
1498
+ applyVoiceParams(channel, controllerType) {
1499
+ channel.scheduledNotes.forEach((noteList) => {
1500
+ for (let i = 0; i < noteList.length; i++) {
1501
+ const note = noteList[i];
1502
+ if (!note)
1503
+ continue;
1504
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1505
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1506
+ let appliedFilterEnvelope = false;
1507
+ let appliedVolumeEnvelope = false;
1508
+ for (const [key, value] of Object.entries(voiceParams)) {
1509
+ const prevValue = note.voiceParams[key];
1510
+ if (value === prevValue)
1511
+ continue;
1512
+ note.voiceParams[key] = value;
1513
+ if (key in this.voiceParamsHandlers) {
1514
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1515
+ }
1516
+ else if (filterEnvelopeKeySet.has(key)) {
1517
+ if (appliedFilterEnvelope)
1518
+ continue;
1519
+ appliedFilterEnvelope = true;
1520
+ const noteVoiceParams = note.voiceParams;
1521
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1522
+ const key = filterEnvelopeKeys[i];
1523
+ if (key in voiceParams)
1524
+ noteVoiceParams[key] = voiceParams[key];
1525
+ }
1526
+ if (note.portamento) {
1527
+ this.setPortamentoStartFilterEnvelope(channel, note);
1528
+ }
1529
+ else {
1530
+ this.setFilterEnvelope(channel, note);
1531
+ }
1532
+ this.setPitch(channel, note);
1533
+ }
1534
+ else if (volumeEnvelopeKeySet.has(key)) {
1535
+ if (appliedVolumeEnvelope)
1536
+ continue;
1537
+ appliedVolumeEnvelope = true;
1538
+ const noteVoiceParams = note.voiceParams;
1539
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1540
+ const key = volumeEnvelopeKeys[i];
1541
+ if (key in voiceParams)
1542
+ noteVoiceParams[key] = voiceParams[key];
1543
+ }
1544
+ this.setVolumeEnvelope(channel, note);
1545
+ }
1546
+ }
1547
+ }
1548
+ });
1241
1549
  }
1242
1550
  createControlChangeHandlers() {
1243
1551
  return {
@@ -1277,13 +1585,16 @@ class Midy {
1277
1585
  127: this.polyOn,
1278
1586
  };
1279
1587
  }
1280
- handleControlChange(channelNumber, controller, value) {
1281
- const handler = this.controlChangeHandlers[controller];
1588
+ handleControlChange(channelNumber, controllerType, value) {
1589
+ const handler = this.controlChangeHandlers[controllerType];
1282
1590
  if (handler) {
1283
1591
  handler.call(this, channelNumber, value);
1592
+ const channel = this.channels[channelNumber];
1593
+ const controller = 128 + controllerType;
1594
+ this.applyVoiceParams(channel, controller);
1284
1595
  }
1285
1596
  else {
1286
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1597
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1287
1598
  }
1288
1599
  }
1289
1600
  setBankMSB(channelNumber, msb) {
@@ -1297,11 +1608,10 @@ class Midy {
1297
1608
  if (!note)
1298
1609
  continue;
1299
1610
  if (note.modulationDepth) {
1300
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1611
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1301
1612
  }
1302
1613
  else {
1303
- const semitoneOffset = this.calcSemitoneOffset(channel);
1304
- this.setPitch(note, semitoneOffset);
1614
+ this.setPitch(channel, note);
1305
1615
  this.startModulation(channel, note, now);
1306
1616
  }
1307
1617
  }
@@ -1309,21 +1619,22 @@ class Midy {
1309
1619
  }
1310
1620
  setModulationDepth(channelNumber, modulation) {
1311
1621
  const channel = this.channels[channelNumber];
1312
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1622
+ channel.state.modulationDepth = (modulation / 127) *
1623
+ channel.modulationDepthRange;
1313
1624
  this.updateModulation(channel);
1314
1625
  }
1315
1626
  setPortamentoTime(channelNumber, portamentoTime) {
1316
1627
  const channel = this.channels[channelNumber];
1317
1628
  const factor = 5 * Math.log(10) / 127;
1318
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1629
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1319
1630
  }
1320
1631
  setVolume(channelNumber, volume) {
1321
1632
  const channel = this.channels[channelNumber];
1322
- channel.volume = volume / 127;
1633
+ channel.state.volume = volume / 127;
1323
1634
  this.updateChannelVolume(channel);
1324
1635
  }
1325
1636
  panToGain(pan) {
1326
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1637
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1327
1638
  return {
1328
1639
  gainLeft: Math.cos(theta),
1329
1640
  gainRight: Math.sin(theta),
@@ -1331,12 +1642,12 @@ class Midy {
1331
1642
  }
1332
1643
  setPan(channelNumber, pan) {
1333
1644
  const channel = this.channels[channelNumber];
1334
- channel.pan = pan;
1645
+ channel.state.pan = pan / 127;
1335
1646
  this.updateChannelVolume(channel);
1336
1647
  }
1337
1648
  setExpression(channelNumber, expression) {
1338
1649
  const channel = this.channels[channelNumber];
1339
- channel.expression = expression / 127;
1650
+ channel.state.expression = expression / 127;
1340
1651
  this.updateChannelVolume(channel);
1341
1652
  }
1342
1653
  setBankLSB(channelNumber, lsb) {
@@ -1348,8 +1659,9 @@ class Midy {
1348
1659
  }
1349
1660
  updateChannelVolume(channel) {
1350
1661
  const now = this.audioContext.currentTime;
1351
- const volume = channel.volume * channel.expression;
1352
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1662
+ const state = channel.state;
1663
+ const volume = state.volume * state.expression;
1664
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1353
1665
  channel.gainL.gain
1354
1666
  .cancelScheduledValues(now)
1355
1667
  .setValueAtTime(volume * gainLeft, now);
@@ -1358,24 +1670,24 @@ class Midy {
1358
1670
  .setValueAtTime(volume * gainRight, now);
1359
1671
  }
1360
1672
  setSustainPedal(channelNumber, value) {
1361
- const isOn = value >= 64;
1362
- this.channels[channelNumber].sustainPedal = isOn;
1363
- if (!isOn) {
1673
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1674
+ if (value < 64) {
1364
1675
  this.releaseSustainPedal(channelNumber, value);
1365
1676
  }
1366
1677
  }
1367
1678
  setPortamento(channelNumber, value) {
1368
- this.channels[channelNumber].portamento = value >= 64;
1679
+ this.channels[channelNumber].state.portamento = value / 127;
1369
1680
  }
1370
1681
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1371
1682
  const channel = this.channels[channelNumber];
1683
+ const state = channel.state;
1372
1684
  const reverbEffect = this.reverbEffect;
1373
- if (0 < channel.reverbSendLevel) {
1685
+ if (0 < state.reverbSendLevel) {
1374
1686
  if (0 < reverbSendLevel) {
1375
1687
  const now = this.audioContext.currentTime;
1376
- channel.reverbSendLevel = reverbSendLevel / 127;
1688
+ state.reverbSendLevel = reverbSendLevel / 127;
1377
1689
  reverbEffect.input.gain.cancelScheduledValues(now);
1378
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1690
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1379
1691
  }
1380
1692
  else {
1381
1693
  channel.scheduledNotes.forEach((noteList) => {
@@ -1383,7 +1695,7 @@ class Midy {
1383
1695
  const note = noteList[i];
1384
1696
  if (!note)
1385
1697
  continue;
1386
- if (note.instrumentKey.reverbEffectsSend <= 0)
1698
+ if (note.voiceParams.reverbEffectsSend <= 0)
1387
1699
  continue;
1388
1700
  note.reverbEffectsSend.disconnect();
1389
1701
  }
@@ -1398,32 +1710,25 @@ class Midy {
1398
1710
  const note = noteList[i];
1399
1711
  if (!note)
1400
1712
  continue;
1401
- if (note.instrumentKey.reverbEffectsSend <= 0)
1402
- continue;
1403
- if (!note.reverbEffectsSend) {
1404
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1405
- gain: note.instrumentKey.reverbEffectsSend,
1406
- });
1407
- note.volumeNode.connect(note.reverbEffectsSend);
1408
- }
1409
- note.reverbEffectsSend.connect(reverbEffect.input);
1713
+ this.setReverbEffectsSend(note, 0);
1410
1714
  }
1411
1715
  });
1412
- channel.reverbSendLevel = reverbSendLevel / 127;
1716
+ state.reverbSendLevel = reverbSendLevel / 127;
1413
1717
  reverbEffect.input.gain.cancelScheduledValues(now);
1414
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1718
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1415
1719
  }
1416
1720
  }
1417
1721
  }
1418
1722
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1419
1723
  const channel = this.channels[channelNumber];
1724
+ const state = channel.state;
1420
1725
  const chorusEffect = this.chorusEffect;
1421
- if (0 < channel.chorusSendLevel) {
1726
+ if (0 < state.chorusSendLevel) {
1422
1727
  if (0 < chorusSendLevel) {
1423
1728
  const now = this.audioContext.currentTime;
1424
- channel.chorusSendLevel = chorusSendLevel / 127;
1729
+ state.chorusSendLevel = chorusSendLevel / 127;
1425
1730
  chorusEffect.input.gain.cancelScheduledValues(now);
1426
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1731
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1427
1732
  }
1428
1733
  else {
1429
1734
  channel.scheduledNotes.forEach((noteList) => {
@@ -1431,7 +1736,7 @@ class Midy {
1431
1736
  const note = noteList[i];
1432
1737
  if (!note)
1433
1738
  continue;
1434
- if (note.instrumentKey.chorusEffectsSend <= 0)
1739
+ if (note.voiceParams.chorusEffectsSend <= 0)
1435
1740
  continue;
1436
1741
  note.chorusEffectsSend.disconnect();
1437
1742
  }
@@ -1446,28 +1751,19 @@ class Midy {
1446
1751
  const note = noteList[i];
1447
1752
  if (!note)
1448
1753
  continue;
1449
- if (note.instrumentKey.chorusEffectsSend <= 0)
1450
- continue;
1451
- if (!note.chorusEffectsSend) {
1452
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1453
- gain: note.instrumentKey.chorusEffectsSend,
1454
- });
1455
- note.volumeNode.connect(note.chorusEffectsSend);
1456
- }
1457
- note.chorusEffectsSend.connect(chorusEffect.input);
1754
+ this.setChorusEffectsSend(note, 0);
1458
1755
  }
1459
1756
  });
1460
- channel.chorusSendLevel = chorusSendLevel / 127;
1757
+ state.chorusSendLevel = chorusSendLevel / 127;
1461
1758
  chorusEffect.input.gain.cancelScheduledValues(now);
1462
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1759
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1463
1760
  }
1464
1761
  }
1465
1762
  }
1466
1763
  setSostenutoPedal(channelNumber, value) {
1467
- const isOn = value >= 64;
1468
1764
  const channel = this.channels[channelNumber];
1469
- channel.sostenutoPedal = isOn;
1470
- if (isOn) {
1765
+ channel.state.sostenutoPedal = value / 127;
1766
+ if (64 <= value) {
1471
1767
  const now = this.audioContext.currentTime;
1472
1768
  const activeNotes = this.getActiveNotes(channel, now);
1473
1769
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1478,31 +1774,31 @@ class Midy {
1478
1774
  }
1479
1775
  setSoftPedal(channelNumber, softPedal) {
1480
1776
  const channel = this.channels[channelNumber];
1481
- channel.softPedal = softPedal / 127;
1777
+ channel.state.softPedal = softPedal / 127;
1482
1778
  }
1483
1779
  setFilterResonance(channelNumber, filterResonance) {
1484
1780
  const now = this.audioContext.currentTime;
1485
1781
  const channel = this.channels[channelNumber];
1486
- channel.filterResonance = filterResonance / 64;
1782
+ const state = channel.state;
1783
+ state.filterResonance = filterResonance / 64;
1487
1784
  channel.scheduledNotes.forEach((noteList) => {
1488
1785
  for (let i = 0; i < noteList.length; i++) {
1489
1786
  const note = noteList[i];
1490
1787
  if (!note)
1491
1788
  continue;
1492
- const Q = note.instrumentKey.initialFilterQ / 10 *
1493
- channel.filterResonance;
1789
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1494
1790
  note.filterNode.Q.setValueAtTime(Q, now);
1495
1791
  }
1496
1792
  });
1497
1793
  }
1498
1794
  setReleaseTime(channelNumber, releaseTime) {
1499
1795
  const channel = this.channels[channelNumber];
1500
- channel.releaseTime = releaseTime / 64;
1796
+ channel.state.releaseTime = releaseTime / 64;
1501
1797
  }
1502
1798
  setAttackTime(channelNumber, attackTime) {
1503
1799
  const now = this.audioContext.currentTime;
1504
1800
  const channel = this.channels[channelNumber];
1505
- channel.attackTime = attackTime / 64;
1801
+ channel.state.attackTime = attackTime / 64;
1506
1802
  channel.scheduledNotes.forEach((noteList) => {
1507
1803
  for (let i = 0; i < noteList.length; i++) {
1508
1804
  const note = noteList[i];
@@ -1516,7 +1812,7 @@ class Midy {
1516
1812
  }
1517
1813
  setBrightness(channelNumber, brightness) {
1518
1814
  const channel = this.channels[channelNumber];
1519
- channel.brightness = brightness / 64;
1815
+ channel.state.brightness = brightness / 64;
1520
1816
  channel.scheduledNotes.forEach((noteList) => {
1521
1817
  for (let i = 0; i < noteList.length; i++) {
1522
1818
  const note = noteList[i];
@@ -1528,7 +1824,7 @@ class Midy {
1528
1824
  }
1529
1825
  setDecayTime(channelNumber, dacayTime) {
1530
1826
  const channel = this.channels[channelNumber];
1531
- channel.decayTime = dacayTime / 64;
1827
+ channel.state.decayTime = dacayTime / 64;
1532
1828
  channel.scheduledNotes.forEach((noteList) => {
1533
1829
  for (let i = 0; i < noteList.length; i++) {
1534
1830
  const note = noteList[i];
@@ -1540,7 +1836,7 @@ class Midy {
1540
1836
  }
1541
1837
  setVibratoRate(channelNumber, vibratoRate) {
1542
1838
  const channel = this.channels[channelNumber];
1543
- channel.vibratoRate = vibratoRate / 64;
1839
+ channel.state.vibratoRate = vibratoRate / 64;
1544
1840
  if (channel.vibratoDepth <= 0)
1545
1841
  return;
1546
1842
  const now = this.audioContext.currentTime;
@@ -1548,16 +1844,16 @@ class Midy {
1548
1844
  activeNotes.forEach((activeNote) => {
1549
1845
  activeNote.vibratoLFO.frequency
1550
1846
  .cancelScheduledValues(now)
1551
- .setValueAtTime(channel.vibratoRate, now);
1847
+ .setValueAtTime(channel.state.vibratoRate, now);
1552
1848
  });
1553
1849
  }
1554
1850
  setVibratoDepth(channelNumber, vibratoDepth) {
1555
1851
  const channel = this.channels[channelNumber];
1556
- channel.vibratoDepth = vibratoDepth / 64;
1852
+ channel.state.vibratoDepth = vibratoDepth / 64;
1557
1853
  }
1558
1854
  setVibratoDelay(channelNumber, vibratoDelay) {
1559
1855
  const channel = this.channels[channelNumber];
1560
- channel.vibratoDelay = vibratoDelay / 64;
1856
+ channel.state.vibratoDelay = vibratoDelay / 64;
1561
1857
  }
1562
1858
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1563
1859
  if (maxLSB < channel.dataLSB) {
@@ -1627,7 +1923,7 @@ class Midy {
1627
1923
  this.channels[channelNumber].dataMSB = value;
1628
1924
  this.handleRPN(channelNumber, 0);
1629
1925
  }
1630
- updateDetune(channel, detuneChange) {
1926
+ updateDetune(channel, detune) {
1631
1927
  const now = this.audioContext.currentTime;
1632
1928
  channel.scheduledNotes.forEach((noteList) => {
1633
1929
  for (let i = 0; i < noteList.length; i++) {
@@ -1635,7 +1931,6 @@ class Midy {
1635
1931
  if (!note)
1636
1932
  continue;
1637
1933
  const { bufferSource } = note;
1638
- const detune = bufferSource.detune.value + detuneChange;
1639
1934
  bufferSource.detune
1640
1935
  .cancelScheduledValues(now)
1641
1936
  .setValueAtTime(detune, now);
@@ -1648,13 +1943,13 @@ class Midy {
1648
1943
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1649
1944
  this.setPitchBendRange(channelNumber, pitchBendRange);
1650
1945
  }
1651
- setPitchBendRange(channelNumber, pitchBendRange) {
1946
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1652
1947
  const channel = this.channels[channelNumber];
1653
- const prevPitchBendRange = channel.pitchBendRange;
1654
- channel.pitchBendRange = pitchBendRange;
1655
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1656
- channel.pitchBend * 100;
1657
- this.updateDetune(channel, detuneChange);
1948
+ const state = channel.state;
1949
+ state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1950
+ const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1951
+ this.updateDetune(channel, detune);
1952
+ this.applyVoiceParams(channel, 16);
1658
1953
  }
1659
1954
  handleFineTuningRPN(channelNumber) {
1660
1955
  const channel = this.channels[channelNumber];
@@ -1698,7 +1993,30 @@ class Midy {
1698
1993
  return this.stopChannelNotes(channelNumber, 0, true);
1699
1994
  }
1700
1995
  resetAllControllers(channelNumber) {
1701
- Object.assign(this.channels[channelNumber], this.effectSettings);
1996
+ const stateTypes = [
1997
+ "expression",
1998
+ "modulationDepth",
1999
+ "sustainPedal",
2000
+ "portamento",
2001
+ "sostenutoPedal",
2002
+ "softPedal",
2003
+ "channelPressure",
2004
+ "pitchWheelSensitivity",
2005
+ ];
2006
+ const channel = this.channels[channelNumber];
2007
+ const state = channel.state;
2008
+ for (let i = 0; i < stateTypes.length; i++) {
2009
+ const type = stateTypes[i];
2010
+ state[type] = defaultControllerState[type];
2011
+ }
2012
+ const settingTypes = [
2013
+ "rpnMSB",
2014
+ "rpnLSB",
2015
+ ];
2016
+ for (let i = 0; i < settingTypes.length; i++) {
2017
+ const type = settingTypes[i];
2018
+ channel[type] = this.constructor.channelSettings[type];
2019
+ }
1702
2020
  }
1703
2021
  allNotesOff(channelNumber) {
1704
2022
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -2061,48 +2379,19 @@ Object.defineProperty(Midy, "channelSettings", {
2061
2379
  writable: true,
2062
2380
  value: {
2063
2381
  currentBufferSource: null,
2064
- volume: 100 / 127,
2065
- pan: 64,
2066
- portamentoTime: 1, // sec
2067
- filterResonance: 1,
2068
- releaseTime: 1,
2069
- attackTime: 1,
2070
- brightness: 1,
2071
- decayTime: 1,
2072
- reverbSendLevel: 0,
2073
- chorusSendLevel: 0,
2074
- vibratoRate: 1,
2075
- vibratoDepth: 1,
2076
- vibratoDelay: 1,
2382
+ program: 0,
2077
2383
  bank: 121 * 128,
2078
2384
  bankMSB: 121,
2079
2385
  bankLSB: 0,
2080
2386
  dataMSB: 0,
2081
2387
  dataLSB: 0,
2082
- program: 0,
2083
- pitchBend: 0,
2388
+ rpnMSB: 127,
2389
+ rpnLSB: 127,
2084
2390
  fineTuning: 0, // cb
2085
2391
  coarseTuning: 0, // cb
2086
2392
  modulationDepthRange: 50, // cent
2087
2393
  }
2088
2394
  });
2089
- Object.defineProperty(Midy, "effectSettings", {
2090
- enumerable: true,
2091
- configurable: true,
2092
- writable: true,
2093
- value: {
2094
- expression: 1,
2095
- modulationDepth: 0,
2096
- sustainPedal: false,
2097
- portamento: false,
2098
- sostenutoPedal: false,
2099
- softPedal: 0,
2100
- rpnMSB: 127,
2101
- rpnLSB: 127,
2102
- channelPressure: 0,
2103
- pitchBendRange: 2,
2104
- }
2105
- });
2106
2395
  Object.defineProperty(Midy, "controllerDestinationSettings", {
2107
2396
  enumerable: true,
2108
2397
  configurable: true,