@marmooo/midy 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/midy.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
3
  class Note {
4
- constructor(noteNumber, velocity, startTime, instrumentKey) {
4
+ constructor(noteNumber, velocity, startTime, voice, voiceParams) {
5
5
  Object.defineProperty(this, "bufferSource", {
6
6
  enumerable: true,
7
7
  configurable: true,
@@ -62,12 +62,106 @@ class Note {
62
62
  writable: true,
63
63
  value: void 0
64
64
  });
65
+ Object.defineProperty(this, "portamento", {
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true,
69
+ value: void 0
70
+ });
65
71
  this.noteNumber = noteNumber;
66
72
  this.velocity = velocity;
67
73
  this.startTime = startTime;
68
- this.instrumentKey = instrumentKey;
74
+ this.voice = voice;
75
+ this.voiceParams = voiceParams;
69
76
  }
70
77
  }
78
+ // normalized to 0-1 for use with the SF2 modulator model
79
+ const defaultControllerState = {
80
+ noteOnVelocity: { type: 2, defaultValue: 0 },
81
+ noteOnKeyNumber: { type: 3, defaultValue: 0 },
82
+ polyPressure: { type: 10, defaultValue: 0 },
83
+ channelPressure: { type: 13, defaultValue: 0 },
84
+ pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
85
+ pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
86
+ link: { type: 127, defaultValue: 0 },
87
+ // bankMSB: { type: 128 + 0, defaultValue: 121, },
88
+ modulationDepth: { type: 128 + 1, defaultValue: 0 },
89
+ portamentoTime: { type: 128 + 5, defaultValue: 0 },
90
+ // dataMSB: { type: 128 + 6, defaultValue: 0, },
91
+ volume: { type: 128 + 7, defaultValue: 100 / 127 },
92
+ pan: { type: 128 + 10, defaultValue: 0.5 },
93
+ expression: { type: 128 + 11, defaultValue: 1 },
94
+ // bankLSB: { type: 128 + 32, defaultValue: 0, },
95
+ // dataLSB: { type: 128 + 38, defaultValue: 0, },
96
+ sustainPedal: { type: 128 + 64, defaultValue: 0 },
97
+ portamento: { type: 128 + 65, defaultValue: 0 },
98
+ sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
99
+ softPedal: { type: 128 + 67, defaultValue: 0 },
100
+ filterResonance: { type: 128 + 71, defaultValue: 0.5 },
101
+ releaseTime: { type: 128 + 72, defaultValue: 0.5 },
102
+ attackTime: { type: 128 + 73, defaultValue: 0.5 },
103
+ brightness: { type: 128 + 74, defaultValue: 0.5 },
104
+ decayTime: { type: 128 + 75, defaultValue: 0.5 },
105
+ vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
106
+ vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
107
+ vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
108
+ reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
109
+ chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
110
+ // dataIncrement: { type: 128 + 96, defaultValue: 0 },
111
+ // dataDecrement: { type: 128 + 97, defaultValue: 0 },
112
+ // rpnLSB: { type: 128 + 100, defaultValue: 127 },
113
+ // rpnMSB: { type: 128 + 101, defaultValue: 127 },
114
+ // allSoundOff: { type: 128 + 120, defaultValue: 0 },
115
+ // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
116
+ // allNotesOff: { type: 128 + 123, defaultValue: 0 },
117
+ // omniOff: { type: 128 + 124, defaultValue: 0 },
118
+ // omniOn: { type: 128 + 125, defaultValue: 0 },
119
+ // monoOn: { type: 128 + 126, defaultValue: 0 },
120
+ // polyOn: { type: 128 + 127, defaultValue: 0 },
121
+ };
122
+ class ControllerState {
123
+ constructor() {
124
+ Object.defineProperty(this, "array", {
125
+ enumerable: true,
126
+ configurable: true,
127
+ writable: true,
128
+ value: new Float32Array(256)
129
+ });
130
+ const entries = Object.entries(defaultControllerState);
131
+ for (const [name, { type, defaultValue }] of entries) {
132
+ this.array[type] = defaultValue;
133
+ Object.defineProperty(this, name, {
134
+ get: () => this.array[type],
135
+ set: (value) => this.array[type] = value,
136
+ enumerable: true,
137
+ configurable: true,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ const filterEnvelopeKeys = [
143
+ "modEnvToPitch",
144
+ "initialFilterFc",
145
+ "modEnvToFilterFc",
146
+ "modDelay",
147
+ "modAttack",
148
+ "modHold",
149
+ "modDecay",
150
+ "modSustain",
151
+ "modRelease",
152
+ "playbackRate",
153
+ ];
154
+ const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
155
+ const volumeEnvelopeKeys = [
156
+ "volDelay",
157
+ "volAttack",
158
+ "volHold",
159
+ "volDecay",
160
+ "volSustain",
161
+ "volRelease",
162
+ "initialAttenuation",
163
+ ];
164
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
71
165
  export class Midy {
72
166
  constructor(audioContext, options = this.defaultOptions) {
73
167
  Object.defineProperty(this, "ticksPerBeat", {
@@ -248,6 +342,7 @@ export class Midy {
248
342
  this.audioContext = audioContext;
249
343
  this.options = { ...this.defaultOptions, ...options };
250
344
  this.masterGain = new GainNode(audioContext);
345
+ this.voiceParamsHandlers = this.createVoiceParamsHandlers();
251
346
  this.controlChangeHandlers = this.createControlChangeHandlers();
252
347
  this.channels = this.createChannels(audioContext);
253
348
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
@@ -294,7 +389,7 @@ export class Midy {
294
389
  this.totalTime = this.calcTotalTime();
295
390
  }
296
391
  setChannelAudioNodes(audioContext) {
297
- const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
392
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
298
393
  const gainL = new GainNode(audioContext, { gain: gainLeft });
299
394
  const gainR = new GainNode(audioContext, { gain: gainRight });
300
395
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -311,7 +406,7 @@ export class Midy {
311
406
  const channels = Array.from({ length: 16 }, () => {
312
407
  return {
313
408
  ...this.constructor.channelSettings,
314
- ...this.constructor.effectSettings,
409
+ state: new ControllerState(),
315
410
  ...this.setChannelAudioNodes(audioContext),
316
411
  scheduledNotes: new Map(),
317
412
  sostenutoNotes: new Map(),
@@ -325,11 +420,11 @@ export class Midy {
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 @@ export class Midy {
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 @@ export class Midy {
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
  }
@@ -419,7 +513,7 @@ export class Midy {
419
513
  this.handleChannelPressure(event.channel, event.amount);
420
514
  break;
421
515
  case "pitchBend":
422
- this.setPitchBend(event.channel, event.value);
516
+ this.setPitchBend(event.channel, event.value + 8192);
423
517
  break;
424
518
  case "sysEx":
425
519
  this.handleSysEx(event.data);
@@ -816,53 +910,63 @@ export class Midy {
816
910
  calcSemitoneOffset(channel) {
817
911
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
818
912
  const channelTuning = channel.coarseTuning + channel.fineTuning;
819
- const tuning = masterTuning + channelTuning;
820
- return channel.pitchBend * channel.pitchBendRange + tuning;
821
- }
822
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
823
- return instrumentKey.playbackRate(noteNumber) *
824
- Math.pow(2, semitoneOffset / 12);
913
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
914
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
915
+ const pitch = pitchWheel * pitchWheelSensitivity;
916
+ return masterTuning + channelTuning + pitch;
825
917
  }
826
918
  setPortamentoStartVolumeEnvelope(channel, note) {
827
- const { instrumentKey, startTime } = note;
828
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
829
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
830
- const volDelay = startTime + instrumentKey.volDelay;
831
- const portamentoTime = volDelay + channel.portamentoTime;
919
+ const now = this.audioContext.currentTime;
920
+ const { voiceParams, startTime } = note;
921
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
922
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
923
+ const volDelay = startTime + voiceParams.volDelay;
924
+ const portamentoTime = volDelay + channel.state.portamentoTime;
832
925
  note.volumeNode.gain
833
- .cancelScheduledValues(startTime)
926
+ .cancelScheduledValues(now)
834
927
  .setValueAtTime(0, volDelay)
835
928
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
836
929
  }
837
930
  setVolumeEnvelope(channel, note) {
838
- const { instrumentKey, startTime } = note;
839
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
840
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
841
- const volDelay = startTime + instrumentKey.volDelay;
842
- const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
843
- const volHold = volAttack + instrumentKey.volHold;
844
- const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
931
+ const now = this.audioContext.currentTime;
932
+ const state = channel.state;
933
+ const { voiceParams, startTime } = note;
934
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
935
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
936
+ const volDelay = startTime + voiceParams.volDelay;
937
+ const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
938
+ const volHold = volAttack + voiceParams.volHold;
939
+ const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
845
940
  note.volumeNode.gain
846
- .cancelScheduledValues(startTime)
941
+ .cancelScheduledValues(now)
847
942
  .setValueAtTime(0, startTime)
848
943
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
849
944
  .exponentialRampToValueAtTime(attackVolume, volAttack)
850
945
  .setValueAtTime(attackVolume, volHold)
851
946
  .linearRampToValueAtTime(sustainVolume, volDecay);
852
947
  }
853
- setPitch(note, semitoneOffset) {
854
- const { instrumentKey, noteNumber, startTime } = note;
855
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
856
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
948
+ setPlaybackRate(note) {
949
+ const now = this.audioContext.currentTime;
950
+ note.bufferSource.playbackRate
951
+ .cancelScheduledValues(now)
952
+ .setValueAtTime(note.voiceParams.playbackRate, now);
953
+ }
954
+ setPitch(channel, note) {
955
+ const now = this.audioContext.currentTime;
956
+ const { startTime } = note;
957
+ const basePitch = this.calcSemitoneOffset(channel) * 100;
958
+ note.bufferSource.detune
959
+ .cancelScheduledValues(now)
960
+ .setValueAtTime(basePitch, startTime);
961
+ const modEnvToPitch = note.voiceParams.modEnvToPitch;
857
962
  if (modEnvToPitch === 0)
858
963
  return;
859
- const basePitch = note.bufferSource.playbackRate.value;
860
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
861
- const modDelay = startTime + instrumentKey.modDelay;
862
- const modAttack = modDelay + instrumentKey.modAttack;
863
- const modHold = modAttack + instrumentKey.modHold;
864
- const modDecay = modHold + instrumentKey.modDecay;
865
- note.bufferSource.playbackRate.value
964
+ const peekPitch = basePitch + modEnvToPitch;
965
+ const modDelay = startTime + voiceParams.modDelay;
966
+ const modAttack = modDelay + voiceParams.modAttack;
967
+ const modHold = modAttack + voiceParams.modHold;
968
+ const modDecay = modHold + voiceParams.modDecay;
969
+ note.bufferSource.detune
866
970
  .setValueAtTime(basePitch, modDelay)
867
971
  .exponentialRampToValueAtTime(peekPitch, modAttack)
868
972
  .setValueAtTime(peekPitch, modHold)
@@ -874,42 +978,46 @@ export class Midy {
874
978
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
875
979
  }
876
980
  setPortamentoStartFilterEnvelope(channel, note) {
877
- const { instrumentKey, noteNumber, startTime } = note;
981
+ const now = this.audioContext.currentTime;
982
+ const state = channel.state;
983
+ const { voiceParams, noteNumber, startTime } = note;
878
984
  const softPedalFactor = 1 -
879
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
880
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
881
- softPedalFactor * channel.brightness;
882
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
985
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
986
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
987
+ softPedalFactor * state.brightness * 2;
988
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
883
989
  const sustainFreq = baseFreq +
884
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
990
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
885
991
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
886
992
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
887
- const portamentoTime = startTime + channel.portamentoTime;
888
- const modDelay = startTime + instrumentKey.modDelay;
993
+ const portamentoTime = startTime + channel.state.portamentoTime;
994
+ const modDelay = startTime + voiceParams.modDelay;
889
995
  note.filterNode.frequency
890
- .cancelScheduledValues(startTime)
996
+ .cancelScheduledValues(now)
891
997
  .setValueAtTime(adjustedBaseFreq, startTime)
892
998
  .setValueAtTime(adjustedBaseFreq, modDelay)
893
999
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
894
1000
  }
895
1001
  setFilterEnvelope(channel, note) {
896
- const { instrumentKey, noteNumber, startTime } = note;
1002
+ const now = this.audioContext.currentTime;
1003
+ const state = channel.state;
1004
+ const { voiceParams, noteNumber, startTime } = note;
897
1005
  const softPedalFactor = 1 -
898
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
899
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
900
- softPedalFactor * channel.brightness;
901
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
1006
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1007
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1008
+ softPedalFactor * state.brightness * 2;
1009
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
902
1010
  const sustainFreq = baseFreq +
903
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1011
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
904
1012
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
905
1013
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
906
1014
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
907
- const modDelay = startTime + instrumentKey.modDelay;
908
- const modAttack = modDelay + instrumentKey.modAttack;
909
- const modHold = modAttack + instrumentKey.modHold;
910
- const modDecay = modHold + instrumentKey.modDecay;
1015
+ const modDelay = startTime + voiceParams.modDelay;
1016
+ const modAttack = modDelay + voiceParams.modAttack;
1017
+ const modHold = modAttack + voiceParams.modHold;
1018
+ const modDecay = modHold + voiceParams.modDecay;
911
1019
  note.filterNode.frequency
912
- .cancelScheduledValues(startTime)
1020
+ .cancelScheduledValues(now)
913
1021
  .setValueAtTime(adjustedBaseFreq, startTime)
914
1022
  .setValueAtTime(adjustedBaseFreq, modDelay)
915
1023
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -917,25 +1025,18 @@ export class Midy {
917
1025
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
918
1026
  }
919
1027
  startModulation(channel, note, startTime) {
920
- const { instrumentKey } = note;
921
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1028
+ const { voiceParams } = note;
922
1029
  note.modulationLFO = new OscillatorNode(this.audioContext, {
923
- frequency: this.centToHz(instrumentKey.freqModLFO),
1030
+ frequency: this.centToHz(voiceParams.freqModLFO),
924
1031
  });
925
1032
  note.filterDepth = new GainNode(this.audioContext, {
926
- gain: instrumentKey.modLfoToFilterFc,
927
- });
928
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
929
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
930
- note.modulationDepth = new GainNode(this.audioContext, {
931
- gain: modulationDepth * modulationDepthSign,
932
- });
933
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
934
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
935
- note.volumeDepth = new GainNode(this.audioContext, {
936
- gain: volumeDepth * volumeDepthSign,
1033
+ gain: voiceParams.modLfoToFilterFc,
937
1034
  });
938
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1035
+ note.modulationDepth = new GainNode(this.audioContext);
1036
+ this.setModLfoToPitch(channel, note);
1037
+ note.volumeDepth = new GainNode(this.audioContext);
1038
+ this.setModLfoToVolume(note);
1039
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
939
1040
  note.modulationLFO.connect(note.filterDepth);
940
1041
  note.filterDepth.connect(note.filterNode.frequency);
941
1042
  note.modulationLFO.connect(note.modulationDepth);
@@ -944,67 +1045,58 @@ export class Midy {
944
1045
  note.volumeDepth.connect(note.volumeNode.gain);
945
1046
  }
946
1047
  startVibrato(channel, note, startTime) {
947
- const { instrumentKey } = note;
948
- const { vibLfoToPitch } = instrumentKey;
1048
+ const { voiceParams } = note;
1049
+ const state = channel.state;
949
1050
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
950
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
951
- channel.vibratoRate,
952
- });
953
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
954
- const vibratoDepthSign = 0 < vibLfoToPitch;
955
- note.vibratoDepth = new GainNode(this.audioContext, {
956
- gain: vibratoDepth * vibratoDepthSign,
1051
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1052
+ state.vibratoRate,
957
1053
  });
958
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1054
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1055
+ note.vibratoDepth = new GainNode(this.audioContext);
1056
+ this.setVibLfoToPitch(channel, note);
959
1057
  note.vibratoLFO.connect(note.vibratoDepth);
960
1058
  note.vibratoDepth.connect(note.bufferSource.detune);
961
1059
  }
962
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
963
- const semitoneOffset = this.calcSemitoneOffset(channel);
964
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
965
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1060
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1061
+ const state = channel.state;
1062
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1063
+ const voiceParams = voice.getAllParams(controllerState);
1064
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1065
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
966
1066
  note.volumeNode = new GainNode(this.audioContext);
967
1067
  note.filterNode = new BiquadFilterNode(this.audioContext, {
968
1068
  type: "lowpass",
969
- Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
1069
+ Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
970
1070
  });
971
1071
  if (portamento) {
1072
+ note.portamento = true;
972
1073
  this.setPortamentoStartVolumeEnvelope(channel, note);
973
1074
  this.setPortamentoStartFilterEnvelope(channel, note);
974
1075
  }
975
1076
  else {
1077
+ note.portamento = false;
976
1078
  this.setVolumeEnvelope(channel, note);
977
1079
  this.setFilterEnvelope(channel, note);
978
1080
  }
979
- if (0 < channel.vibratoDepth) {
1081
+ if (0 < state.vibratoDepth) {
980
1082
  this.startVibrato(channel, note, startTime);
981
1083
  }
982
- if (0 < channel.modulationDepth) {
983
- this.setPitch(note, semitoneOffset);
1084
+ this.setPlaybackRate(note);
1085
+ if (0 < state.modulationDepth) {
1086
+ this.setPitch(channel, note);
984
1087
  this.startModulation(channel, note, startTime);
985
1088
  }
986
- else {
987
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
988
- }
989
1089
  if (this.mono && channel.currentBufferSource) {
990
1090
  channel.currentBufferSource.stop(startTime);
991
1091
  channel.currentBufferSource = note.bufferSource;
992
1092
  }
993
1093
  note.bufferSource.connect(note.filterNode);
994
1094
  note.filterNode.connect(note.volumeNode);
995
- if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
996
- note.reverbEffectsSend = new GainNode(this.audioContext, {
997
- gain: instrumentKey.reverbEffectsSend,
998
- });
999
- note.volumeNode.connect(note.reverbEffectsSend);
1000
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1095
+ if (0 < channel.chorusSendLevel) {
1096
+ this.setChorusEffectsSend(channel, note, 0);
1001
1097
  }
1002
- if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
1003
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1004
- gain: instrumentKey.chorusEffectsSend,
1005
- });
1006
- note.volumeNode.connect(note.chorusEffectsSend);
1007
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1098
+ if (0 < channel.reverbSendLevel) {
1099
+ this.setReverbEffectsSend(channel, note, 0);
1008
1100
  }
1009
1101
  note.bufferSource.start(startTime);
1010
1102
  return note;
@@ -1026,16 +1118,16 @@ export class Midy {
1026
1118
  return;
1027
1119
  const soundFont = this.soundFonts[soundFontIndex];
1028
1120
  const isSF3 = soundFont.parsed.info.version.major === 3;
1029
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
1030
- if (!instrumentKey)
1121
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1122
+ if (!voice)
1031
1123
  return;
1032
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1124
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1033
1125
  note.volumeNode.connect(channel.gainL);
1034
1126
  note.volumeNode.connect(channel.gainR);
1035
- if (channel.sostenutoPedal) {
1127
+ if (channel.state.sostenutoPedal) {
1036
1128
  channel.sostenutoNotes.set(noteNumber, note);
1037
1129
  }
1038
- const exclusiveClass = instrumentKey.exclusiveClass;
1130
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1039
1131
  if (exclusiveClass !== 0) {
1040
1132
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1041
1133
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
@@ -1097,8 +1189,9 @@ export class Midy {
1097
1189
  }
1098
1190
  scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1099
1191
  const channel = this.channels[channelNumber];
1192
+ const state = channel.state;
1100
1193
  if (!force) {
1101
- if (channel.sustainPedal)
1194
+ if (0.5 < state.sustainPedal)
1102
1195
  return;
1103
1196
  if (channel.sostenutoNotes.has(noteNumber))
1104
1197
  return;
@@ -1114,8 +1207,8 @@ export class Midy {
1114
1207
  continue;
1115
1208
  if (portamentoNoteNumber === undefined) {
1116
1209
  const volRelease = endTime +
1117
- note.instrumentKey.volRelease * channel.releaseTime;
1118
- const modRelease = endTime + note.instrumentKey.modRelease;
1210
+ note.voiceParams.volRelease * state.releaseTime * 2;
1211
+ const modRelease = endTime + note.voiceParams.modRelease;
1119
1212
  note.filterNode.frequency
1120
1213
  .cancelScheduledValues(endTime)
1121
1214
  .linearRampToValueAtTime(0, modRelease);
@@ -1123,7 +1216,7 @@ export class Midy {
1123
1216
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1124
1217
  }
1125
1218
  else {
1126
- const portamentoTime = endTime + channel.portamentoTime;
1219
+ const portamentoTime = endTime + state.portamentoTime;
1127
1220
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1128
1221
  const detune = note.bufferSource.detune.value + detuneChange;
1129
1222
  note.bufferSource.detune
@@ -1141,7 +1234,7 @@ export class Midy {
1141
1234
  const velocity = halfVelocity * 2;
1142
1235
  const channel = this.channels[channelNumber];
1143
1236
  const promises = [];
1144
- channel.sustainPedal = false;
1237
+ channel.state.sustainPedal = halfVelocity;
1145
1238
  channel.scheduledNotes.forEach((noteList) => {
1146
1239
  for (let i = 0; i < noteList.length; i++) {
1147
1240
  const note = noteList[i];
@@ -1158,7 +1251,7 @@ export class Midy {
1158
1251
  const velocity = halfVelocity * 2;
1159
1252
  const channel = this.channels[channelNumber];
1160
1253
  const promises = [];
1161
- channel.sostenutoPedal = false;
1254
+ channel.state.sostenutoPedal = 0;
1162
1255
  channel.sostenutoNotes.forEach((activeNote) => {
1163
1256
  const { noteNumber } = activeNote;
1164
1257
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1203,6 +1296,7 @@ export class Midy {
1203
1296
  .setValueAtTime(gain * pressure, now);
1204
1297
  }
1205
1298
  }
1299
+ // this.applyVoiceParams(channel, 10);
1206
1300
  }
1207
1301
  handleProgramChange(channelNumber, program) {
1208
1302
  const channel = this.channels[channelNumber];
@@ -1223,18 +1317,232 @@ export class Midy {
1223
1317
  .setValueAtTime(gain * pressure, now);
1224
1318
  });
1225
1319
  }
1320
+ // this.applyVoiceParams(channel, 13);
1226
1321
  }
1227
1322
  handlePitchBendMessage(channelNumber, lsb, msb) {
1228
- const pitchBend = msb * 128 + lsb - 8192;
1323
+ const pitchBend = msb * 128 + lsb;
1229
1324
  this.setPitchBend(channelNumber, pitchBend);
1230
1325
  }
1231
- setPitchBend(channelNumber, pitchBend) {
1326
+ setPitchBend(channelNumber, value) {
1232
1327
  const channel = this.channels[channelNumber];
1233
- const prevPitchBend = channel.pitchBend;
1234
- channel.pitchBend = pitchBend / 8192;
1235
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1236
- channel.pitchBendRange * 100;
1328
+ const state = channel.state;
1329
+ state.pitchWheel = value / 16383;
1330
+ const pitchWheel = (value - 8192) / 8192;
1331
+ const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1237
1332
  this.updateDetune(channel, detuneChange);
1333
+ this.applyVoiceParams(channel, 14);
1334
+ }
1335
+ setModLfoToPitch(channel, note) {
1336
+ const now = this.audioContext.currentTime;
1337
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1338
+ const modulationDepth = Math.abs(modLfoToPitch) +
1339
+ channel.state.modulationDepth;
1340
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1341
+ note.modulationDepth.gain
1342
+ .cancelScheduledValues(now)
1343
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1344
+ }
1345
+ setModLfoToVolume(note) {
1346
+ const now = this.audioContext.currentTime;
1347
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1348
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1349
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1350
+ note.volumeDepth.gain
1351
+ .cancelScheduledValues(now)
1352
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1353
+ }
1354
+ setChorusEffectsSend(note, prevValue) {
1355
+ if (0 < prevValue) {
1356
+ if (0 < note.voiceParams.chorusEffectsSend) {
1357
+ const now = this.audioContext.currentTime;
1358
+ const value = note.voiceParams.chorusEffectsSend;
1359
+ note.chorusEffectsSend.gain
1360
+ .cancelScheduledValues(now)
1361
+ .setValueAtTime(value, now);
1362
+ }
1363
+ else {
1364
+ note.chorusEffectsSend.disconnect();
1365
+ }
1366
+ }
1367
+ else {
1368
+ if (0 < note.voiceParams.chorusEffectsSend) {
1369
+ if (!note.chorusEffectsSend) {
1370
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1371
+ gain: note.voiceParams.chorusEffectsSend,
1372
+ });
1373
+ note.volumeNode.connect(note.chorusEffectsSend);
1374
+ }
1375
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1376
+ }
1377
+ }
1378
+ }
1379
+ setReverbEffectsSend(note, prevValue) {
1380
+ if (0 < prevValue) {
1381
+ if (0 < note.voiceParams.reverbEffectsSend) {
1382
+ const now = this.audioContext.currentTime;
1383
+ const value = note.voiceParams.reverbEffectsSend;
1384
+ note.reverbEffectsSend.gain
1385
+ .cancelScheduledValues(now)
1386
+ .setValueAtTime(value, now);
1387
+ }
1388
+ else {
1389
+ note.reverbEffectsSend.disconnect();
1390
+ }
1391
+ }
1392
+ else {
1393
+ if (0 < note.voiceParams.reverbEffectsSend) {
1394
+ if (!note.reverbEffectsSend) {
1395
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1396
+ gain: note.voiceParams.reverbEffectsSend,
1397
+ });
1398
+ note.volumeNode.connect(note.reverbEffectsSend);
1399
+ }
1400
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1401
+ }
1402
+ }
1403
+ }
1404
+ setVibLfoToPitch(channel, note) {
1405
+ const now = this.audioContext.currentTime;
1406
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1407
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1408
+ 2;
1409
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1410
+ note.vibratoDepth.gain
1411
+ .cancelScheduledValues(now)
1412
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1413
+ }
1414
+ setModLfoToFilterFc(note) {
1415
+ const now = this.audioContext.currentTime;
1416
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1417
+ note.filterDepth.gain
1418
+ .cancelScheduledValues(now)
1419
+ .setValueAtTime(modLfoToFilterFc, now);
1420
+ }
1421
+ setDelayModLFO(note) {
1422
+ const now = this.audioContext.currentTime;
1423
+ const startTime = note.startTime;
1424
+ if (startTime < now)
1425
+ return;
1426
+ note.modulationLFO.stop(now);
1427
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1428
+ note.modulationLFO.connect(note.filterDepth);
1429
+ }
1430
+ setFreqModLFO(note) {
1431
+ const now = this.audioContext.currentTime;
1432
+ const freqModLFO = note.voiceParams.freqModLFO;
1433
+ note.modulationLFO.frequency
1434
+ .cancelScheduledValues(now)
1435
+ .setValueAtTime(freqModLFO, now);
1436
+ }
1437
+ createVoiceParamsHandlers() {
1438
+ return {
1439
+ modLfoToPitch: (channel, note, _prevValue) => {
1440
+ if (0 < channel.state.modulationDepth) {
1441
+ this.setModLfoToPitch(channel, note);
1442
+ }
1443
+ },
1444
+ vibLfoToPitch: (channel, note, _prevValue) => {
1445
+ if (0 < channel.state.vibratoDepth) {
1446
+ this.setVibLfoToPitch(channel, note);
1447
+ }
1448
+ },
1449
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1450
+ if (0 < channel.state.modulationDepth)
1451
+ this.setModLfoToFilterFc(note);
1452
+ },
1453
+ modLfoToVolume: (channel, note) => {
1454
+ if (0 < channel.state.modulationDepth)
1455
+ this.setModLfoToVolume(note);
1456
+ },
1457
+ chorusEffectsSend: (_channel, note, prevValue) => {
1458
+ this.setChorusEffectsSend(note, prevValue);
1459
+ },
1460
+ reverbEffectsSend: (_channel, note, prevValue) => {
1461
+ this.setReverbEffectsSend(note, prevValue);
1462
+ },
1463
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1464
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1465
+ delayVibLFO: (channel, note, prevValue) => {
1466
+ if (0 < channel.state.vibratoDepth) {
1467
+ const now = this.audioContext.currentTime;
1468
+ const prevStartTime = note.startTime +
1469
+ prevValue * channel.state.vibratoDelay * 2;
1470
+ if (now < prevStartTime)
1471
+ return;
1472
+ const startTime = note.startTime +
1473
+ value * channel.state.vibratoDelay * 2;
1474
+ note.vibratoLFO.stop(now);
1475
+ note.vibratoLFO.start(startTime);
1476
+ }
1477
+ },
1478
+ freqVibLFO: (channel, note, _prevValue) => {
1479
+ if (0 < channel.state.vibratoDepth) {
1480
+ const now = this.audioContext.currentTime;
1481
+ note.vibratoLFO.frequency
1482
+ .cancelScheduledValues(now)
1483
+ .setValueAtTime(value * sate.vibratoRate, now);
1484
+ }
1485
+ },
1486
+ };
1487
+ }
1488
+ getControllerState(channel, noteNumber, velocity) {
1489
+ const state = new Float32Array(channel.state.array.length);
1490
+ state.set(channel.state.array);
1491
+ state[2] = velocity / 127;
1492
+ state[3] = noteNumber / 127;
1493
+ return state;
1494
+ }
1495
+ applyVoiceParams(channel, controllerType) {
1496
+ channel.scheduledNotes.forEach((noteList) => {
1497
+ for (let i = 0; i < noteList.length; i++) {
1498
+ const note = noteList[i];
1499
+ if (!note)
1500
+ continue;
1501
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1502
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1503
+ let appliedFilterEnvelope = false;
1504
+ let appliedVolumeEnvelope = false;
1505
+ for (const [key, value] of Object.entries(voiceParams)) {
1506
+ const prevValue = note.voiceParams[key];
1507
+ if (value === prevValue)
1508
+ continue;
1509
+ note.voiceParams[key] = value;
1510
+ if (key in this.voiceParamsHandlers) {
1511
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1512
+ }
1513
+ else if (filterEnvelopeKeySet.has(key)) {
1514
+ if (appliedFilterEnvelope)
1515
+ continue;
1516
+ appliedFilterEnvelope = true;
1517
+ const noteVoiceParams = note.voiceParams;
1518
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1519
+ const key = filterEnvelopeKeys[i];
1520
+ if (key in voiceParams)
1521
+ noteVoiceParams[key] = voiceParams[key];
1522
+ }
1523
+ if (note.portamento) {
1524
+ this.setPortamentoStartFilterEnvelope(channel, note);
1525
+ }
1526
+ else {
1527
+ this.setFilterEnvelope(channel, note);
1528
+ }
1529
+ this.setPitch(channel, note);
1530
+ }
1531
+ else if (volumeEnvelopeKeySet.has(key)) {
1532
+ if (appliedVolumeEnvelope)
1533
+ continue;
1534
+ appliedVolumeEnvelope = true;
1535
+ const noteVoiceParams = note.voiceParams;
1536
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1537
+ const key = volumeEnvelopeKeys[i];
1538
+ if (key in voiceParams)
1539
+ noteVoiceParams[key] = voiceParams[key];
1540
+ }
1541
+ this.setVolumeEnvelope(channel, note);
1542
+ }
1543
+ }
1544
+ }
1545
+ });
1238
1546
  }
1239
1547
  createControlChangeHandlers() {
1240
1548
  return {
@@ -1274,13 +1582,16 @@ export class Midy {
1274
1582
  127: this.polyOn,
1275
1583
  };
1276
1584
  }
1277
- handleControlChange(channelNumber, controller, value) {
1278
- const handler = this.controlChangeHandlers[controller];
1585
+ handleControlChange(channelNumber, controllerType, value) {
1586
+ const handler = this.controlChangeHandlers[controllerType];
1279
1587
  if (handler) {
1280
1588
  handler.call(this, channelNumber, value);
1589
+ const channel = this.channels[channelNumber];
1590
+ const controller = 128 + controllerType;
1591
+ this.applyVoiceParams(channel, controller);
1281
1592
  }
1282
1593
  else {
1283
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1594
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1284
1595
  }
1285
1596
  }
1286
1597
  setBankMSB(channelNumber, msb) {
@@ -1294,11 +1605,10 @@ export class Midy {
1294
1605
  if (!note)
1295
1606
  continue;
1296
1607
  if (note.modulationDepth) {
1297
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1608
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1298
1609
  }
1299
1610
  else {
1300
- const semitoneOffset = this.calcSemitoneOffset(channel);
1301
- this.setPitch(note, semitoneOffset);
1611
+ this.setPitch(channel, note);
1302
1612
  this.startModulation(channel, note, now);
1303
1613
  }
1304
1614
  }
@@ -1306,21 +1616,22 @@ export class Midy {
1306
1616
  }
1307
1617
  setModulationDepth(channelNumber, modulation) {
1308
1618
  const channel = this.channels[channelNumber];
1309
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1619
+ channel.state.modulationDepth = (modulation / 127) *
1620
+ channel.modulationDepthRange;
1310
1621
  this.updateModulation(channel);
1311
1622
  }
1312
1623
  setPortamentoTime(channelNumber, portamentoTime) {
1313
1624
  const channel = this.channels[channelNumber];
1314
1625
  const factor = 5 * Math.log(10) / 127;
1315
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1626
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1316
1627
  }
1317
1628
  setVolume(channelNumber, volume) {
1318
1629
  const channel = this.channels[channelNumber];
1319
- channel.volume = volume / 127;
1630
+ channel.state.volume = volume / 127;
1320
1631
  this.updateChannelVolume(channel);
1321
1632
  }
1322
1633
  panToGain(pan) {
1323
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1634
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1324
1635
  return {
1325
1636
  gainLeft: Math.cos(theta),
1326
1637
  gainRight: Math.sin(theta),
@@ -1328,12 +1639,12 @@ export class Midy {
1328
1639
  }
1329
1640
  setPan(channelNumber, pan) {
1330
1641
  const channel = this.channels[channelNumber];
1331
- channel.pan = pan;
1642
+ channel.state.pan = pan / 127;
1332
1643
  this.updateChannelVolume(channel);
1333
1644
  }
1334
1645
  setExpression(channelNumber, expression) {
1335
1646
  const channel = this.channels[channelNumber];
1336
- channel.expression = expression / 127;
1647
+ channel.state.expression = expression / 127;
1337
1648
  this.updateChannelVolume(channel);
1338
1649
  }
1339
1650
  setBankLSB(channelNumber, lsb) {
@@ -1345,8 +1656,9 @@ export class Midy {
1345
1656
  }
1346
1657
  updateChannelVolume(channel) {
1347
1658
  const now = this.audioContext.currentTime;
1348
- const volume = channel.volume * channel.expression;
1349
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1659
+ const state = channel.state;
1660
+ const volume = state.volume * state.expression;
1661
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1350
1662
  channel.gainL.gain
1351
1663
  .cancelScheduledValues(now)
1352
1664
  .setValueAtTime(volume * gainLeft, now);
@@ -1355,24 +1667,24 @@ export class Midy {
1355
1667
  .setValueAtTime(volume * gainRight, now);
1356
1668
  }
1357
1669
  setSustainPedal(channelNumber, value) {
1358
- const isOn = value >= 64;
1359
- this.channels[channelNumber].sustainPedal = isOn;
1360
- if (!isOn) {
1670
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1671
+ if (value < 64) {
1361
1672
  this.releaseSustainPedal(channelNumber, value);
1362
1673
  }
1363
1674
  }
1364
1675
  setPortamento(channelNumber, value) {
1365
- this.channels[channelNumber].portamento = value >= 64;
1676
+ this.channels[channelNumber].state.portamento = value / 127;
1366
1677
  }
1367
1678
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1368
1679
  const channel = this.channels[channelNumber];
1680
+ const state = channel.state;
1369
1681
  const reverbEffect = this.reverbEffect;
1370
- if (0 < channel.reverbSendLevel) {
1682
+ if (0 < state.reverbSendLevel) {
1371
1683
  if (0 < reverbSendLevel) {
1372
1684
  const now = this.audioContext.currentTime;
1373
- channel.reverbSendLevel = reverbSendLevel / 127;
1685
+ state.reverbSendLevel = reverbSendLevel / 127;
1374
1686
  reverbEffect.input.gain.cancelScheduledValues(now);
1375
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1687
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1376
1688
  }
1377
1689
  else {
1378
1690
  channel.scheduledNotes.forEach((noteList) => {
@@ -1380,7 +1692,7 @@ export class Midy {
1380
1692
  const note = noteList[i];
1381
1693
  if (!note)
1382
1694
  continue;
1383
- if (note.instrumentKey.reverbEffectsSend <= 0)
1695
+ if (note.voiceParams.reverbEffectsSend <= 0)
1384
1696
  continue;
1385
1697
  note.reverbEffectsSend.disconnect();
1386
1698
  }
@@ -1395,32 +1707,25 @@ export class Midy {
1395
1707
  const note = noteList[i];
1396
1708
  if (!note)
1397
1709
  continue;
1398
- if (note.instrumentKey.reverbEffectsSend <= 0)
1399
- continue;
1400
- if (!note.reverbEffectsSend) {
1401
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1402
- gain: note.instrumentKey.reverbEffectsSend,
1403
- });
1404
- note.volumeNode.connect(note.reverbEffectsSend);
1405
- }
1406
- note.reverbEffectsSend.connect(reverbEffect.input);
1710
+ this.setReverbEffectsSend(note, 0);
1407
1711
  }
1408
1712
  });
1409
- channel.reverbSendLevel = reverbSendLevel / 127;
1713
+ state.reverbSendLevel = reverbSendLevel / 127;
1410
1714
  reverbEffect.input.gain.cancelScheduledValues(now);
1411
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1715
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1412
1716
  }
1413
1717
  }
1414
1718
  }
1415
1719
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1416
1720
  const channel = this.channels[channelNumber];
1721
+ const state = channel.state;
1417
1722
  const chorusEffect = this.chorusEffect;
1418
- if (0 < channel.chorusSendLevel) {
1723
+ if (0 < state.chorusSendLevel) {
1419
1724
  if (0 < chorusSendLevel) {
1420
1725
  const now = this.audioContext.currentTime;
1421
- channel.chorusSendLevel = chorusSendLevel / 127;
1726
+ state.chorusSendLevel = chorusSendLevel / 127;
1422
1727
  chorusEffect.input.gain.cancelScheduledValues(now);
1423
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1728
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1424
1729
  }
1425
1730
  else {
1426
1731
  channel.scheduledNotes.forEach((noteList) => {
@@ -1428,7 +1733,7 @@ export class Midy {
1428
1733
  const note = noteList[i];
1429
1734
  if (!note)
1430
1735
  continue;
1431
- if (note.instrumentKey.chorusEffectsSend <= 0)
1736
+ if (note.voiceParams.chorusEffectsSend <= 0)
1432
1737
  continue;
1433
1738
  note.chorusEffectsSend.disconnect();
1434
1739
  }
@@ -1443,28 +1748,19 @@ export class Midy {
1443
1748
  const note = noteList[i];
1444
1749
  if (!note)
1445
1750
  continue;
1446
- if (note.instrumentKey.chorusEffectsSend <= 0)
1447
- continue;
1448
- if (!note.chorusEffectsSend) {
1449
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1450
- gain: note.instrumentKey.chorusEffectsSend,
1451
- });
1452
- note.volumeNode.connect(note.chorusEffectsSend);
1453
- }
1454
- note.chorusEffectsSend.connect(chorusEffect.input);
1751
+ this.setChorusEffectsSend(note, 0);
1455
1752
  }
1456
1753
  });
1457
- channel.chorusSendLevel = chorusSendLevel / 127;
1754
+ state.chorusSendLevel = chorusSendLevel / 127;
1458
1755
  chorusEffect.input.gain.cancelScheduledValues(now);
1459
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1756
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1460
1757
  }
1461
1758
  }
1462
1759
  }
1463
1760
  setSostenutoPedal(channelNumber, value) {
1464
- const isOn = value >= 64;
1465
1761
  const channel = this.channels[channelNumber];
1466
- channel.sostenutoPedal = isOn;
1467
- if (isOn) {
1762
+ channel.state.sostenutoPedal = value / 127;
1763
+ if (64 <= value) {
1468
1764
  const now = this.audioContext.currentTime;
1469
1765
  const activeNotes = this.getActiveNotes(channel, now);
1470
1766
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1475,31 +1771,31 @@ export class Midy {
1475
1771
  }
1476
1772
  setSoftPedal(channelNumber, softPedal) {
1477
1773
  const channel = this.channels[channelNumber];
1478
- channel.softPedal = softPedal / 127;
1774
+ channel.state.softPedal = softPedal / 127;
1479
1775
  }
1480
1776
  setFilterResonance(channelNumber, filterResonance) {
1481
1777
  const now = this.audioContext.currentTime;
1482
1778
  const channel = this.channels[channelNumber];
1483
- channel.filterResonance = filterResonance / 64;
1779
+ const state = channel.state;
1780
+ state.filterResonance = filterResonance / 64;
1484
1781
  channel.scheduledNotes.forEach((noteList) => {
1485
1782
  for (let i = 0; i < noteList.length; i++) {
1486
1783
  const note = noteList[i];
1487
1784
  if (!note)
1488
1785
  continue;
1489
- const Q = note.instrumentKey.initialFilterQ / 10 *
1490
- channel.filterResonance;
1786
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1491
1787
  note.filterNode.Q.setValueAtTime(Q, now);
1492
1788
  }
1493
1789
  });
1494
1790
  }
1495
1791
  setReleaseTime(channelNumber, releaseTime) {
1496
1792
  const channel = this.channels[channelNumber];
1497
- channel.releaseTime = releaseTime / 64;
1793
+ channel.state.releaseTime = releaseTime / 64;
1498
1794
  }
1499
1795
  setAttackTime(channelNumber, attackTime) {
1500
1796
  const now = this.audioContext.currentTime;
1501
1797
  const channel = this.channels[channelNumber];
1502
- channel.attackTime = attackTime / 64;
1798
+ channel.state.attackTime = attackTime / 64;
1503
1799
  channel.scheduledNotes.forEach((noteList) => {
1504
1800
  for (let i = 0; i < noteList.length; i++) {
1505
1801
  const note = noteList[i];
@@ -1513,7 +1809,7 @@ export class Midy {
1513
1809
  }
1514
1810
  setBrightness(channelNumber, brightness) {
1515
1811
  const channel = this.channels[channelNumber];
1516
- channel.brightness = brightness / 64;
1812
+ channel.state.brightness = brightness / 64;
1517
1813
  channel.scheduledNotes.forEach((noteList) => {
1518
1814
  for (let i = 0; i < noteList.length; i++) {
1519
1815
  const note = noteList[i];
@@ -1525,7 +1821,7 @@ export class Midy {
1525
1821
  }
1526
1822
  setDecayTime(channelNumber, dacayTime) {
1527
1823
  const channel = this.channels[channelNumber];
1528
- channel.decayTime = dacayTime / 64;
1824
+ channel.state.decayTime = dacayTime / 64;
1529
1825
  channel.scheduledNotes.forEach((noteList) => {
1530
1826
  for (let i = 0; i < noteList.length; i++) {
1531
1827
  const note = noteList[i];
@@ -1537,7 +1833,7 @@ export class Midy {
1537
1833
  }
1538
1834
  setVibratoRate(channelNumber, vibratoRate) {
1539
1835
  const channel = this.channels[channelNumber];
1540
- channel.vibratoRate = vibratoRate / 64;
1836
+ channel.state.vibratoRate = vibratoRate / 64;
1541
1837
  if (channel.vibratoDepth <= 0)
1542
1838
  return;
1543
1839
  const now = this.audioContext.currentTime;
@@ -1545,16 +1841,16 @@ export class Midy {
1545
1841
  activeNotes.forEach((activeNote) => {
1546
1842
  activeNote.vibratoLFO.frequency
1547
1843
  .cancelScheduledValues(now)
1548
- .setValueAtTime(channel.vibratoRate, now);
1844
+ .setValueAtTime(channel.state.vibratoRate, now);
1549
1845
  });
1550
1846
  }
1551
1847
  setVibratoDepth(channelNumber, vibratoDepth) {
1552
1848
  const channel = this.channels[channelNumber];
1553
- channel.vibratoDepth = vibratoDepth / 64;
1849
+ channel.state.vibratoDepth = vibratoDepth / 64;
1554
1850
  }
1555
1851
  setVibratoDelay(channelNumber, vibratoDelay) {
1556
1852
  const channel = this.channels[channelNumber];
1557
- channel.vibratoDelay = vibratoDelay / 64;
1853
+ channel.state.vibratoDelay = vibratoDelay / 64;
1558
1854
  }
1559
1855
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1560
1856
  if (maxLSB < channel.dataLSB) {
@@ -1624,7 +1920,7 @@ export class Midy {
1624
1920
  this.channels[channelNumber].dataMSB = value;
1625
1921
  this.handleRPN(channelNumber, 0);
1626
1922
  }
1627
- updateDetune(channel, detuneChange) {
1923
+ updateDetune(channel, detune) {
1628
1924
  const now = this.audioContext.currentTime;
1629
1925
  channel.scheduledNotes.forEach((noteList) => {
1630
1926
  for (let i = 0; i < noteList.length; i++) {
@@ -1632,7 +1928,6 @@ export class Midy {
1632
1928
  if (!note)
1633
1929
  continue;
1634
1930
  const { bufferSource } = note;
1635
- const detune = bufferSource.detune.value + detuneChange;
1636
1931
  bufferSource.detune
1637
1932
  .cancelScheduledValues(now)
1638
1933
  .setValueAtTime(detune, now);
@@ -1645,13 +1940,13 @@ export class Midy {
1645
1940
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1646
1941
  this.setPitchBendRange(channelNumber, pitchBendRange);
1647
1942
  }
1648
- setPitchBendRange(channelNumber, pitchBendRange) {
1943
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1649
1944
  const channel = this.channels[channelNumber];
1650
- const prevPitchBendRange = channel.pitchBendRange;
1651
- channel.pitchBendRange = pitchBendRange;
1652
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1653
- channel.pitchBend * 100;
1654
- this.updateDetune(channel, detuneChange);
1945
+ const state = channel.state;
1946
+ state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1947
+ const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1948
+ this.updateDetune(channel, detune);
1949
+ this.applyVoiceParams(channel, 16);
1655
1950
  }
1656
1951
  handleFineTuningRPN(channelNumber) {
1657
1952
  const channel = this.channels[channelNumber];
@@ -1695,7 +1990,30 @@ export class Midy {
1695
1990
  return this.stopChannelNotes(channelNumber, 0, true);
1696
1991
  }
1697
1992
  resetAllControllers(channelNumber) {
1698
- Object.assign(this.channels[channelNumber], this.effectSettings);
1993
+ const stateTypes = [
1994
+ "expression",
1995
+ "modulationDepth",
1996
+ "sustainPedal",
1997
+ "portamento",
1998
+ "sostenutoPedal",
1999
+ "softPedal",
2000
+ "channelPressure",
2001
+ "pitchWheelSensitivity",
2002
+ ];
2003
+ const channel = this.channels[channelNumber];
2004
+ const state = channel.state;
2005
+ for (let i = 0; i < stateTypes.length; i++) {
2006
+ const type = stateTypes[i];
2007
+ state[type] = defaultControllerState[type];
2008
+ }
2009
+ const settingTypes = [
2010
+ "rpnMSB",
2011
+ "rpnLSB",
2012
+ ];
2013
+ for (let i = 0; i < settingTypes.length; i++) {
2014
+ const type = settingTypes[i];
2015
+ channel[type] = this.constructor.channelSettings[type];
2016
+ }
1699
2017
  }
1700
2018
  allNotesOff(channelNumber) {
1701
2019
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -2057,48 +2375,19 @@ Object.defineProperty(Midy, "channelSettings", {
2057
2375
  writable: true,
2058
2376
  value: {
2059
2377
  currentBufferSource: null,
2060
- volume: 100 / 127,
2061
- pan: 64,
2062
- portamentoTime: 1, // sec
2063
- filterResonance: 1,
2064
- releaseTime: 1,
2065
- attackTime: 1,
2066
- brightness: 1,
2067
- decayTime: 1,
2068
- reverbSendLevel: 0,
2069
- chorusSendLevel: 0,
2070
- vibratoRate: 1,
2071
- vibratoDepth: 1,
2072
- vibratoDelay: 1,
2378
+ program: 0,
2073
2379
  bank: 121 * 128,
2074
2380
  bankMSB: 121,
2075
2381
  bankLSB: 0,
2076
2382
  dataMSB: 0,
2077
2383
  dataLSB: 0,
2078
- program: 0,
2079
- pitchBend: 0,
2384
+ rpnMSB: 127,
2385
+ rpnLSB: 127,
2080
2386
  fineTuning: 0, // cb
2081
2387
  coarseTuning: 0, // cb
2082
2388
  modulationDepthRange: 50, // cent
2083
2389
  }
2084
2390
  });
2085
- Object.defineProperty(Midy, "effectSettings", {
2086
- enumerable: true,
2087
- configurable: true,
2088
- writable: true,
2089
- value: {
2090
- expression: 1,
2091
- modulationDepth: 0,
2092
- sustainPedal: false,
2093
- portamento: false,
2094
- sostenutoPedal: false,
2095
- softPedal: 0,
2096
- rpnMSB: 127,
2097
- rpnLSB: 127,
2098
- channelPressure: 0,
2099
- pitchBendRange: 2,
2100
- }
2101
- });
2102
2391
  Object.defineProperty(Midy, "controllerDestinationSettings", {
2103
2392
  enumerable: true,
2104
2393
  configurable: true,