@marmooo/midy 0.1.6 → 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.
Files changed (37) hide show
  1. package/esm/midy-GM1.d.ts +53 -27
  2. package/esm/midy-GM1.d.ts.map +1 -1
  3. package/esm/midy-GM1.js +398 -146
  4. package/esm/midy-GM2.d.ts +55 -35
  5. package/esm/midy-GM2.d.ts.map +1 -1
  6. package/esm/midy-GM2.js +646 -244
  7. package/esm/midy-GMLite.d.ts +51 -26
  8. package/esm/midy-GMLite.d.ts.map +1 -1
  9. package/esm/midy-GMLite.js +379 -148
  10. package/esm/midy.d.ts +55 -40
  11. package/esm/midy.d.ts.map +1 -1
  12. package/esm/midy.js +662 -263
  13. package/package.json +5 -1
  14. package/script/midy-GM1.d.ts +53 -27
  15. package/script/midy-GM1.d.ts.map +1 -1
  16. package/script/midy-GM1.js +401 -149
  17. package/script/midy-GM2.d.ts +55 -35
  18. package/script/midy-GM2.d.ts.map +1 -1
  19. package/script/midy-GM2.js +649 -247
  20. package/script/midy-GMLite.d.ts +51 -26
  21. package/script/midy-GMLite.d.ts.map +1 -1
  22. package/script/midy-GMLite.js +382 -151
  23. package/script/midy.d.ts +55 -40
  24. package/script/midy.d.ts.map +1 -1
  25. package/script/midy.js +665 -266
  26. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
  27. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
  28. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -180
  29. package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
  30. package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
  31. package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -216
  32. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
  33. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
  34. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -190
  35. package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
  36. package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
  37. package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -221
package/esm/midy.js CHANGED
@@ -1,7 +1,7 @@
1
- import { parseMidi } from "./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js";
2
- import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js";
1
+ import { parseMidi } from "midi-file";
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,
@@ -50,12 +50,118 @@ class Note {
50
50
  writable: true,
51
51
  value: void 0
52
52
  });
53
+ Object.defineProperty(this, "reverbEffectsSend", {
54
+ enumerable: true,
55
+ configurable: true,
56
+ writable: true,
57
+ value: void 0
58
+ });
59
+ Object.defineProperty(this, "chorusEffectsSend", {
60
+ enumerable: true,
61
+ configurable: true,
62
+ writable: true,
63
+ value: void 0
64
+ });
65
+ Object.defineProperty(this, "portamento", {
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true,
69
+ value: void 0
70
+ });
53
71
  this.noteNumber = noteNumber;
54
72
  this.velocity = velocity;
55
73
  this.startTime = startTime;
56
- this.instrumentKey = instrumentKey;
74
+ this.voice = voice;
75
+ this.voiceParams = voiceParams;
76
+ }
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
+ }
57
140
  }
58
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);
59
165
  export class Midy {
60
166
  constructor(audioContext, options = this.defaultOptions) {
61
167
  Object.defineProperty(this, "ticksPerBeat", {
@@ -205,6 +311,12 @@ export class Midy {
205
311
  writable: true,
206
312
  value: []
207
313
  });
314
+ Object.defineProperty(this, "exclusiveClassMap", {
315
+ enumerable: true,
316
+ configurable: true,
317
+ writable: true,
318
+ value: new Map()
319
+ });
208
320
  Object.defineProperty(this, "defaultOptions", {
209
321
  enumerable: true,
210
322
  configurable: true,
@@ -230,6 +342,7 @@ export class Midy {
230
342
  this.audioContext = audioContext;
231
343
  this.options = { ...this.defaultOptions, ...options };
232
344
  this.masterGain = new GainNode(audioContext);
345
+ this.voiceParamsHandlers = this.createVoiceParamsHandlers();
233
346
  this.controlChangeHandlers = this.createControlChangeHandlers();
234
347
  this.channels = this.createChannels(audioContext);
235
348
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
@@ -276,7 +389,7 @@ export class Midy {
276
389
  this.totalTime = this.calcTotalTime();
277
390
  }
278
391
  setChannelAudioNodes(audioContext) {
279
- const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
392
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
280
393
  const gainL = new GainNode(audioContext, { gain: gainLeft });
281
394
  const gainR = new GainNode(audioContext, { gain: gainRight });
282
395
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -293,7 +406,7 @@ export class Midy {
293
406
  const channels = Array.from({ length: 16 }, () => {
294
407
  return {
295
408
  ...this.constructor.channelSettings,
296
- ...this.constructor.effectSettings,
409
+ state: new ControllerState(),
297
410
  ...this.setChannelAudioNodes(audioContext),
298
411
  scheduledNotes: new Map(),
299
412
  sostenutoNotes: new Map(),
@@ -307,38 +420,43 @@ export class Midy {
307
420
  });
308
421
  return channels;
309
422
  }
310
- async createNoteBuffer(instrumentKey, isSF3) {
311
- const sampleStart = instrumentKey.start;
312
- 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;
313
426
  if (isSF3) {
314
- const sample = instrumentKey.sample.slice(sampleStart, sampleEnd);
315
- const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
427
+ const sample = voiceParams.sample;
428
+ const start = sample.byteOffset + sampleStart;
429
+ const end = sample.byteOffset + sampleEnd;
430
+ const buffer = sample.buffer.slice(start, end);
431
+ const audioBuffer = await this.audioContext.decodeAudioData(buffer);
316
432
  return audioBuffer;
317
433
  }
318
434
  else {
319
- const sample = instrumentKey.sample.subarray(sampleStart, sampleEnd);
435
+ const sample = voiceParams.sample;
436
+ const start = sample.byteOffset + sampleStart;
437
+ const end = sample.byteOffset + sampleEnd;
438
+ const buffer = sample.buffer.slice(start, end);
320
439
  const audioBuffer = new AudioBuffer({
321
440
  numberOfChannels: 1,
322
441
  length: sample.length,
323
- sampleRate: instrumentKey.sampleRate,
442
+ sampleRate: voiceParams.sampleRate,
324
443
  });
325
444
  const channelData = audioBuffer.getChannelData(0);
326
- const int16Array = new Int16Array(sample.buffer);
445
+ const int16Array = new Int16Array(buffer);
327
446
  for (let i = 0; i < int16Array.length; i++) {
328
447
  channelData[i] = int16Array[i] / 32768;
329
448
  }
330
449
  return audioBuffer;
331
450
  }
332
451
  }
333
- async createNoteBufferNode(instrumentKey, isSF3) {
452
+ async createNoteBufferNode(voiceParams, isSF3) {
334
453
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
335
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
454
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
336
455
  bufferSource.buffer = audioBuffer;
337
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
456
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
338
457
  if (bufferSource.loop) {
339
- bufferSource.loopStart = instrumentKey.loopStart /
340
- instrumentKey.sampleRate;
341
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
458
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
459
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
342
460
  }
343
461
  return bufferSource;
344
462
  }
@@ -395,7 +513,7 @@ export class Midy {
395
513
  this.handleChannelPressure(event.channel, event.amount);
396
514
  break;
397
515
  case "pitchBend":
398
- this.setPitchBend(event.channel, event.value);
516
+ this.setPitchBend(event.channel, event.value + 8192);
399
517
  break;
400
518
  case "sysEx":
401
519
  this.handleSysEx(event.data);
@@ -424,6 +542,7 @@ export class Midy {
424
542
  if (queueIndex >= this.timeline.length) {
425
543
  await Promise.all(this.notePromises);
426
544
  this.notePromises = [];
545
+ this.exclusiveClassMap.clear();
427
546
  resolve();
428
547
  return;
429
548
  }
@@ -439,6 +558,7 @@ export class Midy {
439
558
  }
440
559
  else if (this.isStopping) {
441
560
  await this.stopNotes(0, true);
561
+ this.exclusiveClassMap.clear();
442
562
  this.notePromises = [];
443
563
  resolve();
444
564
  this.isStopping = false;
@@ -447,6 +567,7 @@ export class Midy {
447
567
  }
448
568
  else if (this.isSeeking) {
449
569
  this.stopNotes(0, true);
570
+ this.exclusiveClassMap.clear();
450
571
  this.startTime = this.audioContext.currentTime;
451
572
  queueIndex = this.getQueueIndex(this.resumeTime);
452
573
  offset = this.resumeTime - this.startTime;
@@ -675,14 +796,14 @@ export class Midy {
675
796
  return impulse;
676
797
  }
677
798
  createConvolutionReverb(audioContext, impulse) {
678
- const output = new GainNode(audioContext);
799
+ const input = new GainNode(audioContext);
679
800
  const convolverNode = new ConvolverNode(audioContext, {
680
801
  buffer: impulse,
681
802
  });
682
- convolverNode.connect(output);
803
+ input.connect(convolverNode);
683
804
  return {
684
- input: convolverNode,
685
- output,
805
+ input,
806
+ output: convolverNode,
686
807
  convolverNode,
687
808
  };
688
809
  }
@@ -724,7 +845,6 @@ export class Midy {
724
845
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
725
846
  createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
726
847
  const input = new GainNode(audioContext);
727
- const output = new GainNode(audioContext);
728
848
  const mergerGain = new GainNode(audioContext);
729
849
  for (let i = 0; i < combDelays.length; i++) {
730
850
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
@@ -735,7 +855,7 @@ export class Midy {
735
855
  const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
736
856
  allpasses.push(allpass);
737
857
  }
738
- allpasses.at(-1).connect(output);
858
+ const output = allpasses.at(-1);
739
859
  return { input, output };
740
860
  }
741
861
  createChorusEffect(audioContext) {
@@ -790,53 +910,63 @@ export class Midy {
790
910
  calcSemitoneOffset(channel) {
791
911
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
792
912
  const channelTuning = channel.coarseTuning + channel.fineTuning;
793
- const tuning = masterTuning + channelTuning;
794
- return channel.pitchBend * channel.pitchBendRange + tuning;
795
- }
796
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
797
- return instrumentKey.playbackRate(noteNumber) *
798
- 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;
799
917
  }
800
918
  setPortamentoStartVolumeEnvelope(channel, note) {
801
- const { instrumentKey, startTime } = note;
802
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
803
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
804
- const volDelay = startTime + instrumentKey.volDelay;
805
- 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;
806
925
  note.volumeNode.gain
807
- .cancelScheduledValues(startTime)
926
+ .cancelScheduledValues(now)
808
927
  .setValueAtTime(0, volDelay)
809
928
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
810
929
  }
811
930
  setVolumeEnvelope(channel, note) {
812
- const { instrumentKey, startTime } = note;
813
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
814
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
815
- const volDelay = startTime + instrumentKey.volDelay;
816
- const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
817
- const volHold = volAttack + instrumentKey.volHold;
818
- 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;
819
940
  note.volumeNode.gain
820
- .cancelScheduledValues(startTime)
941
+ .cancelScheduledValues(now)
821
942
  .setValueAtTime(0, startTime)
822
943
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
823
944
  .exponentialRampToValueAtTime(attackVolume, volAttack)
824
945
  .setValueAtTime(attackVolume, volHold)
825
946
  .linearRampToValueAtTime(sustainVolume, volDecay);
826
947
  }
827
- setPitch(note, semitoneOffset) {
828
- const { instrumentKey, noteNumber, startTime } = note;
829
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
830
- 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;
831
962
  if (modEnvToPitch === 0)
832
963
  return;
833
- const basePitch = note.bufferSource.playbackRate.value;
834
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
835
- const modDelay = startTime + instrumentKey.modDelay;
836
- const modAttack = modDelay + instrumentKey.modAttack;
837
- const modHold = modAttack + instrumentKey.modHold;
838
- const modDecay = modHold + instrumentKey.modDecay;
839
- 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
840
970
  .setValueAtTime(basePitch, modDelay)
841
971
  .exponentialRampToValueAtTime(peekPitch, modAttack)
842
972
  .setValueAtTime(peekPitch, modHold)
@@ -848,42 +978,46 @@ export class Midy {
848
978
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
849
979
  }
850
980
  setPortamentoStartFilterEnvelope(channel, note) {
851
- const { instrumentKey, noteNumber, startTime } = note;
981
+ const now = this.audioContext.currentTime;
982
+ const state = channel.state;
983
+ const { voiceParams, noteNumber, startTime } = note;
852
984
  const softPedalFactor = 1 -
853
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
854
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
855
- softPedalFactor * channel.brightness;
856
- 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;
857
989
  const sustainFreq = baseFreq +
858
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
990
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
859
991
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
860
992
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
861
- const portamentoTime = startTime + channel.portamentoTime;
862
- const modDelay = startTime + instrumentKey.modDelay;
993
+ const portamentoTime = startTime + channel.state.portamentoTime;
994
+ const modDelay = startTime + voiceParams.modDelay;
863
995
  note.filterNode.frequency
864
- .cancelScheduledValues(startTime)
996
+ .cancelScheduledValues(now)
865
997
  .setValueAtTime(adjustedBaseFreq, startTime)
866
998
  .setValueAtTime(adjustedBaseFreq, modDelay)
867
999
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
868
1000
  }
869
1001
  setFilterEnvelope(channel, note) {
870
- const { instrumentKey, noteNumber, startTime } = note;
1002
+ const now = this.audioContext.currentTime;
1003
+ const state = channel.state;
1004
+ const { voiceParams, noteNumber, startTime } = note;
871
1005
  const softPedalFactor = 1 -
872
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
873
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
874
- softPedalFactor * channel.brightness;
875
- 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;
876
1010
  const sustainFreq = baseFreq +
877
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1011
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
878
1012
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
879
1013
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
880
1014
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
881
- const modDelay = startTime + instrumentKey.modDelay;
882
- const modAttack = modDelay + instrumentKey.modAttack;
883
- const modHold = modAttack + instrumentKey.modHold;
884
- 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;
885
1019
  note.filterNode.frequency
886
- .cancelScheduledValues(startTime)
1020
+ .cancelScheduledValues(now)
887
1021
  .setValueAtTime(adjustedBaseFreq, startTime)
888
1022
  .setValueAtTime(adjustedBaseFreq, modDelay)
889
1023
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -891,25 +1025,18 @@ export class Midy {
891
1025
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
892
1026
  }
893
1027
  startModulation(channel, note, startTime) {
894
- const { instrumentKey } = note;
895
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1028
+ const { voiceParams } = note;
896
1029
  note.modulationLFO = new OscillatorNode(this.audioContext, {
897
- frequency: this.centToHz(instrumentKey.freqModLFO),
1030
+ frequency: this.centToHz(voiceParams.freqModLFO),
898
1031
  });
899
1032
  note.filterDepth = new GainNode(this.audioContext, {
900
- gain: instrumentKey.modLfoToFilterFc,
1033
+ gain: voiceParams.modLfoToFilterFc,
901
1034
  });
902
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
903
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
904
- note.modulationDepth = new GainNode(this.audioContext, {
905
- gain: modulationDepth * modulationDepthSign,
906
- });
907
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
908
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
909
- note.volumeDepth = new GainNode(this.audioContext, {
910
- gain: volumeDepth * volumeDepthSign,
911
- });
912
- 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);
913
1040
  note.modulationLFO.connect(note.filterDepth);
914
1041
  note.filterDepth.connect(note.filterNode.frequency);
915
1042
  note.modulationLFO.connect(note.modulationDepth);
@@ -918,54 +1045,59 @@ export class Midy {
918
1045
  note.volumeDepth.connect(note.volumeNode.gain);
919
1046
  }
920
1047
  startVibrato(channel, note, startTime) {
921
- const { instrumentKey } = note;
922
- const { vibLfoToPitch } = instrumentKey;
1048
+ const { voiceParams } = note;
1049
+ const state = channel.state;
923
1050
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
924
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
925
- channel.vibratoRate,
926
- });
927
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
928
- const vibratoDepthSign = 0 < vibLfoToPitch;
929
- note.vibratoDepth = new GainNode(this.audioContext, {
930
- gain: vibratoDepth * vibratoDepthSign,
1051
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1052
+ state.vibratoRate,
931
1053
  });
932
- 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);
933
1057
  note.vibratoLFO.connect(note.vibratoDepth);
934
1058
  note.vibratoDepth.connect(note.bufferSource.detune);
935
1059
  }
936
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
937
- const semitoneOffset = this.calcSemitoneOffset(channel);
938
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
939
- 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);
940
1066
  note.volumeNode = new GainNode(this.audioContext);
941
1067
  note.filterNode = new BiquadFilterNode(this.audioContext, {
942
1068
  type: "lowpass",
943
- Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
1069
+ Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
944
1070
  });
945
1071
  if (portamento) {
1072
+ note.portamento = true;
946
1073
  this.setPortamentoStartVolumeEnvelope(channel, note);
947
1074
  this.setPortamentoStartFilterEnvelope(channel, note);
948
1075
  }
949
1076
  else {
1077
+ note.portamento = false;
950
1078
  this.setVolumeEnvelope(channel, note);
951
1079
  this.setFilterEnvelope(channel, note);
952
1080
  }
953
- if (0 < channel.vibratoDepth) {
1081
+ if (0 < state.vibratoDepth) {
954
1082
  this.startVibrato(channel, note, startTime);
955
1083
  }
956
- if (0 < channel.modulationDepth) {
957
- this.setPitch(note, semitoneOffset);
1084
+ this.setPlaybackRate(note);
1085
+ if (0 < state.modulationDepth) {
1086
+ this.setPitch(channel, note);
958
1087
  this.startModulation(channel, note, startTime);
959
1088
  }
960
- else {
961
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
962
- }
963
1089
  if (this.mono && channel.currentBufferSource) {
964
1090
  channel.currentBufferSource.stop(startTime);
965
1091
  channel.currentBufferSource = note.bufferSource;
966
1092
  }
967
1093
  note.bufferSource.connect(note.filterNode);
968
1094
  note.filterNode.connect(note.volumeNode);
1095
+ if (0 < channel.chorusSendLevel) {
1096
+ this.setChorusEffectsSend(channel, note, 0);
1097
+ }
1098
+ if (0 < channel.reverbSendLevel) {
1099
+ this.setReverbEffectsSend(channel, note, 0);
1100
+ }
969
1101
  note.bufferSource.start(startTime);
970
1102
  return note;
971
1103
  }
@@ -986,15 +1118,28 @@ export class Midy {
986
1118
  return;
987
1119
  const soundFont = this.soundFonts[soundFontIndex];
988
1120
  const isSF3 = soundFont.parsed.info.version.major === 3;
989
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
990
- if (!instrumentKey)
1121
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1122
+ if (!voice)
991
1123
  return;
992
- 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);
993
1125
  note.volumeNode.connect(channel.gainL);
994
1126
  note.volumeNode.connect(channel.gainR);
995
- if (channel.sostenutoPedal) {
1127
+ if (channel.state.sostenutoPedal) {
996
1128
  channel.sostenutoNotes.set(noteNumber, note);
997
1129
  }
1130
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1131
+ if (exclusiveClass !== 0) {
1132
+ if (this.exclusiveClassMap.has(exclusiveClass)) {
1133
+ const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1134
+ const [prevNote, prevChannelNumber] = prevEntry;
1135
+ if (!prevNote.ending) {
1136
+ this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1137
+ startTime, undefined, // portamentoNoteNumber
1138
+ true);
1139
+ }
1140
+ }
1141
+ this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
1142
+ }
998
1143
  const scheduledNotes = channel.scheduledNotes;
999
1144
  if (scheduledNotes.has(noteNumber)) {
1000
1145
  scheduledNotes.get(noteNumber).push(note);
@@ -1007,15 +1152,15 @@ export class Midy {
1007
1152
  const now = this.audioContext.currentTime;
1008
1153
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
1009
1154
  }
1010
- stopNote(stopTime, endTime, scheduledNotes, index) {
1155
+ stopNote(endTime, stopTime, scheduledNotes, index) {
1011
1156
  const note = scheduledNotes[index];
1012
1157
  note.volumeNode.gain
1013
- .cancelScheduledValues(stopTime)
1014
- .linearRampToValueAtTime(0, endTime);
1158
+ .cancelScheduledValues(endTime)
1159
+ .linearRampToValueAtTime(0, stopTime);
1015
1160
  note.ending = true;
1016
1161
  this.scheduleTask(() => {
1017
1162
  note.bufferSource.loop = false;
1018
- }, endTime);
1163
+ }, stopTime);
1019
1164
  return new Promise((resolve) => {
1020
1165
  note.bufferSource.onended = () => {
1021
1166
  scheduledNotes[index] = null;
@@ -1031,15 +1176,22 @@ export class Midy {
1031
1176
  note.vibratoDepth.disconnect();
1032
1177
  note.vibratoLFO.stop();
1033
1178
  }
1179
+ if (note.reverbEffectsSend) {
1180
+ note.reverbEffectsSend.disconnect();
1181
+ }
1182
+ if (note.chorusEffectsSend) {
1183
+ note.chorusEffectsSend.disconnect();
1184
+ }
1034
1185
  resolve();
1035
1186
  };
1036
- note.bufferSource.stop(endTime);
1187
+ note.bufferSource.stop(stopTime);
1037
1188
  });
1038
1189
  }
1039
- scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
1190
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1040
1191
  const channel = this.channels[channelNumber];
1192
+ const state = channel.state;
1041
1193
  if (!force) {
1042
- if (channel.sustainPedal)
1194
+ if (0.5 < state.sustainPedal)
1043
1195
  return;
1044
1196
  if (channel.sostenutoNotes.has(noteNumber))
1045
1197
  return;
@@ -1054,22 +1206,23 @@ export class Midy {
1054
1206
  if (note.ending)
1055
1207
  continue;
1056
1208
  if (portamentoNoteNumber === undefined) {
1057
- const volEndTime = stopTime +
1058
- note.instrumentKey.volRelease * channel.releaseTime;
1059
- const modRelease = stopTime + note.instrumentKey.modRelease;
1209
+ const volRelease = endTime +
1210
+ note.voiceParams.volRelease * state.releaseTime * 2;
1211
+ const modRelease = endTime + note.voiceParams.modRelease;
1060
1212
  note.filterNode.frequency
1061
- .cancelScheduledValues(stopTime)
1213
+ .cancelScheduledValues(endTime)
1062
1214
  .linearRampToValueAtTime(0, modRelease);
1063
- return this.stopNote(stopTime, volEndTime, scheduledNotes, i);
1215
+ const stopTime = Math.min(volRelease, modRelease);
1216
+ return this.stopNote(endTime, stopTime, scheduledNotes, i);
1064
1217
  }
1065
1218
  else {
1066
- const portamentoTime = stopTime + channel.portamentoTime;
1219
+ const portamentoTime = endTime + state.portamentoTime;
1067
1220
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1068
1221
  const detune = note.bufferSource.detune.value + detuneChange;
1069
1222
  note.bufferSource.detune
1070
- .cancelScheduledValues(stopTime)
1223
+ .cancelScheduledValues(endTime)
1071
1224
  .linearRampToValueAtTime(detune, portamentoTime);
1072
- return this.stopNote(stopTime, portamentoTime, scheduledNotes, i);
1225
+ return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1073
1226
  }
1074
1227
  }
1075
1228
  }
@@ -1081,7 +1234,7 @@ export class Midy {
1081
1234
  const velocity = halfVelocity * 2;
1082
1235
  const channel = this.channels[channelNumber];
1083
1236
  const promises = [];
1084
- channel.sustainPedal = false;
1237
+ channel.state.sustainPedal = halfVelocity;
1085
1238
  channel.scheduledNotes.forEach((noteList) => {
1086
1239
  for (let i = 0; i < noteList.length; i++) {
1087
1240
  const note = noteList[i];
@@ -1098,7 +1251,7 @@ export class Midy {
1098
1251
  const velocity = halfVelocity * 2;
1099
1252
  const channel = this.channels[channelNumber];
1100
1253
  const promises = [];
1101
- channel.sostenutoPedal = false;
1254
+ channel.state.sostenutoPedal = 0;
1102
1255
  channel.sostenutoNotes.forEach((activeNote) => {
1103
1256
  const { noteNumber } = activeNote;
1104
1257
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1116,7 +1269,7 @@ export class Midy {
1116
1269
  case 0x90:
1117
1270
  return this.noteOn(channelNumber, data1, data2);
1118
1271
  case 0xA0:
1119
- return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
1272
+ return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
1120
1273
  case 0xB0:
1121
1274
  return this.handleControlChange(channelNumber, data1, data2);
1122
1275
  case 0xC0:
@@ -1143,6 +1296,7 @@ export class Midy {
1143
1296
  .setValueAtTime(gain * pressure, now);
1144
1297
  }
1145
1298
  }
1299
+ // this.applyVoiceParams(channel, 10);
1146
1300
  }
1147
1301
  handleProgramChange(channelNumber, program) {
1148
1302
  const channel = this.channels[channelNumber];
@@ -1163,18 +1317,232 @@ export class Midy {
1163
1317
  .setValueAtTime(gain * pressure, now);
1164
1318
  });
1165
1319
  }
1320
+ // this.applyVoiceParams(channel, 13);
1166
1321
  }
1167
1322
  handlePitchBendMessage(channelNumber, lsb, msb) {
1168
- const pitchBend = msb * 128 + lsb - 8192;
1323
+ const pitchBend = msb * 128 + lsb;
1169
1324
  this.setPitchBend(channelNumber, pitchBend);
1170
1325
  }
1171
- setPitchBend(channelNumber, pitchBend) {
1326
+ setPitchBend(channelNumber, value) {
1172
1327
  const channel = this.channels[channelNumber];
1173
- const prevPitchBend = channel.pitchBend;
1174
- channel.pitchBend = pitchBend / 8192;
1175
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1176
- 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;
1177
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
+ });
1178
1546
  }
1179
1547
  createControlChangeHandlers() {
1180
1548
  return {
@@ -1214,13 +1582,16 @@ export class Midy {
1214
1582
  127: this.polyOn,
1215
1583
  };
1216
1584
  }
1217
- handleControlChange(channelNumber, controller, value) {
1218
- const handler = this.controlChangeHandlers[controller];
1585
+ handleControlChange(channelNumber, controllerType, value) {
1586
+ const handler = this.controlChangeHandlers[controllerType];
1219
1587
  if (handler) {
1220
1588
  handler.call(this, channelNumber, value);
1589
+ const channel = this.channels[channelNumber];
1590
+ const controller = 128 + controllerType;
1591
+ this.applyVoiceParams(channel, controller);
1221
1592
  }
1222
1593
  else {
1223
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1594
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1224
1595
  }
1225
1596
  }
1226
1597
  setBankMSB(channelNumber, msb) {
@@ -1234,11 +1605,10 @@ export class Midy {
1234
1605
  if (!note)
1235
1606
  continue;
1236
1607
  if (note.modulationDepth) {
1237
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1608
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1238
1609
  }
1239
1610
  else {
1240
- const semitoneOffset = this.calcSemitoneOffset(channel);
1241
- this.setPitch(note, semitoneOffset);
1611
+ this.setPitch(channel, note);
1242
1612
  this.startModulation(channel, note, now);
1243
1613
  }
1244
1614
  }
@@ -1246,21 +1616,22 @@ export class Midy {
1246
1616
  }
1247
1617
  setModulationDepth(channelNumber, modulation) {
1248
1618
  const channel = this.channels[channelNumber];
1249
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1619
+ channel.state.modulationDepth = (modulation / 127) *
1620
+ channel.modulationDepthRange;
1250
1621
  this.updateModulation(channel);
1251
1622
  }
1252
1623
  setPortamentoTime(channelNumber, portamentoTime) {
1253
1624
  const channel = this.channels[channelNumber];
1254
1625
  const factor = 5 * Math.log(10) / 127;
1255
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1626
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1256
1627
  }
1257
1628
  setVolume(channelNumber, volume) {
1258
1629
  const channel = this.channels[channelNumber];
1259
- channel.volume = volume / 127;
1630
+ channel.state.volume = volume / 127;
1260
1631
  this.updateChannelVolume(channel);
1261
1632
  }
1262
1633
  panToGain(pan) {
1263
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1634
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1264
1635
  return {
1265
1636
  gainLeft: Math.cos(theta),
1266
1637
  gainRight: Math.sin(theta),
@@ -1268,12 +1639,12 @@ export class Midy {
1268
1639
  }
1269
1640
  setPan(channelNumber, pan) {
1270
1641
  const channel = this.channels[channelNumber];
1271
- channel.pan = pan;
1642
+ channel.state.pan = pan / 127;
1272
1643
  this.updateChannelVolume(channel);
1273
1644
  }
1274
1645
  setExpression(channelNumber, expression) {
1275
1646
  const channel = this.channels[channelNumber];
1276
- channel.expression = expression / 127;
1647
+ channel.state.expression = expression / 127;
1277
1648
  this.updateChannelVolume(channel);
1278
1649
  }
1279
1650
  setBankLSB(channelNumber, lsb) {
@@ -1285,8 +1656,9 @@ export class Midy {
1285
1656
  }
1286
1657
  updateChannelVolume(channel) {
1287
1658
  const now = this.audioContext.currentTime;
1288
- const volume = channel.volume * channel.expression;
1289
- 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);
1290
1662
  channel.gainL.gain
1291
1663
  .cancelScheduledValues(now)
1292
1664
  .setValueAtTime(volume * gainLeft, now);
@@ -1295,68 +1667,100 @@ export class Midy {
1295
1667
  .setValueAtTime(volume * gainRight, now);
1296
1668
  }
1297
1669
  setSustainPedal(channelNumber, value) {
1298
- const isOn = value >= 64;
1299
- this.channels[channelNumber].sustainPedal = isOn;
1300
- if (!isOn) {
1670
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1671
+ if (value < 64) {
1301
1672
  this.releaseSustainPedal(channelNumber, value);
1302
1673
  }
1303
1674
  }
1304
1675
  setPortamento(channelNumber, value) {
1305
- this.channels[channelNumber].portamento = value >= 64;
1676
+ this.channels[channelNumber].state.portamento = value / 127;
1306
1677
  }
1307
1678
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1308
1679
  const channel = this.channels[channelNumber];
1680
+ const state = channel.state;
1309
1681
  const reverbEffect = this.reverbEffect;
1310
- if (0 < channel.reverbSendLevel) {
1682
+ if (0 < state.reverbSendLevel) {
1311
1683
  if (0 < reverbSendLevel) {
1312
1684
  const now = this.audioContext.currentTime;
1313
- channel.reverbSendLevel = reverbSendLevel / 127;
1314
- reverbEffect.output.gain.cancelScheduledValues(now);
1315
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1685
+ state.reverbSendLevel = reverbSendLevel / 127;
1686
+ reverbEffect.input.gain.cancelScheduledValues(now);
1687
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1316
1688
  }
1317
1689
  else {
1318
- channel.merger.disconnect(reverbEffect.input);
1690
+ channel.scheduledNotes.forEach((noteList) => {
1691
+ for (let i = 0; i < noteList.length; i++) {
1692
+ const note = noteList[i];
1693
+ if (!note)
1694
+ continue;
1695
+ if (note.voiceParams.reverbEffectsSend <= 0)
1696
+ continue;
1697
+ note.reverbEffectsSend.disconnect();
1698
+ }
1699
+ });
1319
1700
  }
1320
1701
  }
1321
1702
  else {
1322
1703
  if (0 < reverbSendLevel) {
1323
- channel.merger.connect(reverbEffect.input);
1324
1704
  const now = this.audioContext.currentTime;
1325
- channel.reverbSendLevel = reverbSendLevel / 127;
1326
- reverbEffect.output.gain.cancelScheduledValues(now);
1327
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1705
+ channel.scheduledNotes.forEach((noteList) => {
1706
+ for (let i = 0; i < noteList.length; i++) {
1707
+ const note = noteList[i];
1708
+ if (!note)
1709
+ continue;
1710
+ this.setReverbEffectsSend(note, 0);
1711
+ }
1712
+ });
1713
+ state.reverbSendLevel = reverbSendLevel / 127;
1714
+ reverbEffect.input.gain.cancelScheduledValues(now);
1715
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1328
1716
  }
1329
1717
  }
1330
1718
  }
1331
1719
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1332
1720
  const channel = this.channels[channelNumber];
1721
+ const state = channel.state;
1333
1722
  const chorusEffect = this.chorusEffect;
1334
- if (0 < channel.chorusSendLevel) {
1723
+ if (0 < state.chorusSendLevel) {
1335
1724
  if (0 < chorusSendLevel) {
1336
1725
  const now = this.audioContext.currentTime;
1337
- channel.chorusSendLevel = chorusSendLevel / 127;
1338
- chorusEffect.output.gain.cancelScheduledValues(now);
1339
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1726
+ state.chorusSendLevel = chorusSendLevel / 127;
1727
+ chorusEffect.input.gain.cancelScheduledValues(now);
1728
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1340
1729
  }
1341
1730
  else {
1342
- channel.merger.disconnect(chorusEffect.input);
1731
+ channel.scheduledNotes.forEach((noteList) => {
1732
+ for (let i = 0; i < noteList.length; i++) {
1733
+ const note = noteList[i];
1734
+ if (!note)
1735
+ continue;
1736
+ if (note.voiceParams.chorusEffectsSend <= 0)
1737
+ continue;
1738
+ note.chorusEffectsSend.disconnect();
1739
+ }
1740
+ });
1343
1741
  }
1344
1742
  }
1345
1743
  else {
1346
1744
  if (0 < chorusSendLevel) {
1347
- channel.merger.connect(chorusEffect.input);
1348
1745
  const now = this.audioContext.currentTime;
1349
- channel.chorusSendLevel = chorusSendLevel / 127;
1350
- chorusEffect.output.gain.cancelScheduledValues(now);
1351
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1746
+ channel.scheduledNotes.forEach((noteList) => {
1747
+ for (let i = 0; i < noteList.length; i++) {
1748
+ const note = noteList[i];
1749
+ if (!note)
1750
+ continue;
1751
+ this.setChorusEffectsSend(note, 0);
1752
+ }
1753
+ });
1754
+ state.chorusSendLevel = chorusSendLevel / 127;
1755
+ chorusEffect.input.gain.cancelScheduledValues(now);
1756
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1352
1757
  }
1353
1758
  }
1354
1759
  }
1355
1760
  setSostenutoPedal(channelNumber, value) {
1356
- const isOn = value >= 64;
1357
1761
  const channel = this.channels[channelNumber];
1358
- channel.sostenutoPedal = isOn;
1359
- if (isOn) {
1762
+ channel.state.sostenutoPedal = value / 127;
1763
+ if (64 <= value) {
1360
1764
  const now = this.audioContext.currentTime;
1361
1765
  const activeNotes = this.getActiveNotes(channel, now);
1362
1766
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1367,31 +1771,31 @@ export class Midy {
1367
1771
  }
1368
1772
  setSoftPedal(channelNumber, softPedal) {
1369
1773
  const channel = this.channels[channelNumber];
1370
- channel.softPedal = softPedal / 127;
1774
+ channel.state.softPedal = softPedal / 127;
1371
1775
  }
1372
1776
  setFilterResonance(channelNumber, filterResonance) {
1373
1777
  const now = this.audioContext.currentTime;
1374
1778
  const channel = this.channels[channelNumber];
1375
- channel.filterResonance = filterResonance / 64;
1779
+ const state = channel.state;
1780
+ state.filterResonance = filterResonance / 64;
1376
1781
  channel.scheduledNotes.forEach((noteList) => {
1377
1782
  for (let i = 0; i < noteList.length; i++) {
1378
1783
  const note = noteList[i];
1379
1784
  if (!note)
1380
1785
  continue;
1381
- const Q = note.instrumentKey.initialFilterQ / 10 *
1382
- channel.filterResonance;
1786
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1383
1787
  note.filterNode.Q.setValueAtTime(Q, now);
1384
1788
  }
1385
1789
  });
1386
1790
  }
1387
1791
  setReleaseTime(channelNumber, releaseTime) {
1388
1792
  const channel = this.channels[channelNumber];
1389
- channel.releaseTime = releaseTime / 64;
1793
+ channel.state.releaseTime = releaseTime / 64;
1390
1794
  }
1391
1795
  setAttackTime(channelNumber, attackTime) {
1392
1796
  const now = this.audioContext.currentTime;
1393
1797
  const channel = this.channels[channelNumber];
1394
- channel.attackTime = attackTime / 64;
1798
+ channel.state.attackTime = attackTime / 64;
1395
1799
  channel.scheduledNotes.forEach((noteList) => {
1396
1800
  for (let i = 0; i < noteList.length; i++) {
1397
1801
  const note = noteList[i];
@@ -1405,7 +1809,7 @@ export class Midy {
1405
1809
  }
1406
1810
  setBrightness(channelNumber, brightness) {
1407
1811
  const channel = this.channels[channelNumber];
1408
- channel.brightness = brightness / 64;
1812
+ channel.state.brightness = brightness / 64;
1409
1813
  channel.scheduledNotes.forEach((noteList) => {
1410
1814
  for (let i = 0; i < noteList.length; i++) {
1411
1815
  const note = noteList[i];
@@ -1417,7 +1821,7 @@ export class Midy {
1417
1821
  }
1418
1822
  setDecayTime(channelNumber, dacayTime) {
1419
1823
  const channel = this.channels[channelNumber];
1420
- channel.decayTime = dacayTime / 64;
1824
+ channel.state.decayTime = dacayTime / 64;
1421
1825
  channel.scheduledNotes.forEach((noteList) => {
1422
1826
  for (let i = 0; i < noteList.length; i++) {
1423
1827
  const note = noteList[i];
@@ -1429,7 +1833,7 @@ export class Midy {
1429
1833
  }
1430
1834
  setVibratoRate(channelNumber, vibratoRate) {
1431
1835
  const channel = this.channels[channelNumber];
1432
- channel.vibratoRate = vibratoRate / 64;
1836
+ channel.state.vibratoRate = vibratoRate / 64;
1433
1837
  if (channel.vibratoDepth <= 0)
1434
1838
  return;
1435
1839
  const now = this.audioContext.currentTime;
@@ -1437,16 +1841,16 @@ export class Midy {
1437
1841
  activeNotes.forEach((activeNote) => {
1438
1842
  activeNote.vibratoLFO.frequency
1439
1843
  .cancelScheduledValues(now)
1440
- .setValueAtTime(channel.vibratoRate, now);
1844
+ .setValueAtTime(channel.state.vibratoRate, now);
1441
1845
  });
1442
1846
  }
1443
1847
  setVibratoDepth(channelNumber, vibratoDepth) {
1444
1848
  const channel = this.channels[channelNumber];
1445
- channel.vibratoDepth = vibratoDepth / 64;
1849
+ channel.state.vibratoDepth = vibratoDepth / 64;
1446
1850
  }
1447
1851
  setVibratoDelay(channelNumber, vibratoDelay) {
1448
1852
  const channel = this.channels[channelNumber];
1449
- channel.vibratoDelay = vibratoDelay / 64;
1853
+ channel.state.vibratoDelay = vibratoDelay / 64;
1450
1854
  }
1451
1855
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1452
1856
  if (maxLSB < channel.dataLSB) {
@@ -1516,7 +1920,7 @@ export class Midy {
1516
1920
  this.channels[channelNumber].dataMSB = value;
1517
1921
  this.handleRPN(channelNumber, 0);
1518
1922
  }
1519
- updateDetune(channel, detuneChange) {
1923
+ updateDetune(channel, detune) {
1520
1924
  const now = this.audioContext.currentTime;
1521
1925
  channel.scheduledNotes.forEach((noteList) => {
1522
1926
  for (let i = 0; i < noteList.length; i++) {
@@ -1524,7 +1928,6 @@ export class Midy {
1524
1928
  if (!note)
1525
1929
  continue;
1526
1930
  const { bufferSource } = note;
1527
- const detune = bufferSource.detune.value + detuneChange;
1528
1931
  bufferSource.detune
1529
1932
  .cancelScheduledValues(now)
1530
1933
  .setValueAtTime(detune, now);
@@ -1537,13 +1940,13 @@ export class Midy {
1537
1940
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1538
1941
  this.setPitchBendRange(channelNumber, pitchBendRange);
1539
1942
  }
1540
- setPitchBendRange(channelNumber, pitchBendRange) {
1943
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1541
1944
  const channel = this.channels[channelNumber];
1542
- const prevPitchBendRange = channel.pitchBendRange;
1543
- channel.pitchBendRange = pitchBendRange;
1544
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1545
- channel.pitchBend * 100;
1546
- 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);
1547
1950
  }
1548
1951
  handleFineTuningRPN(channelNumber) {
1549
1952
  const channel = this.channels[channelNumber];
@@ -1587,7 +1990,30 @@ export class Midy {
1587
1990
  return this.stopChannelNotes(channelNumber, 0, true);
1588
1991
  }
1589
1992
  resetAllControllers(channelNumber) {
1590
- 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
+ }
1591
2017
  }
1592
2018
  allNotesOff(channelNumber) {
1593
2019
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1781,10 +2207,8 @@ export class Midy {
1781
2207
  }
1782
2208
  setReverbTime(value) {
1783
2209
  this.reverb.time = this.getReverbTime(value);
1784
- const { audioContext, channels, options } = this;
1785
- for (let i = 0; i < channels.length; i++) {
1786
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1787
- }
2210
+ const { audioContext, options } = this;
2211
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1788
2212
  }
1789
2213
  getReverbTime(value) {
1790
2214
  return Math.pow(Math.E, (value - 40) * 0.025);
@@ -1861,10 +2285,7 @@ export class Midy {
1861
2285
  const now = this.audioContext.currentTime;
1862
2286
  const modRate = this.getChorusModRate(value);
1863
2287
  this.chorus.modRate = modRate;
1864
- for (let i = 0; i < this.channels.length; i++) {
1865
- const lfo = this.channels[i].chorusEffect.lfo;
1866
- lfo.frequency.setValueAtTime(modRate, now);
1867
- }
2288
+ this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
1868
2289
  }
1869
2290
  getChorusModRate(value) {
1870
2291
  return value * 0.122; // Hz
@@ -1873,12 +2294,9 @@ export class Midy {
1873
2294
  const now = this.audioContext.currentTime;
1874
2295
  const modDepth = this.getChorusModDepth(value);
1875
2296
  this.chorus.modDepth = modDepth;
1876
- for (let i = 0; i < this.channels.length; i++) {
1877
- const chorusEffect = this.channels[i].chorusEffect;
1878
- chorusEffect.lfoGain.gain
1879
- .cancelScheduledValues(now)
1880
- .setValueAtTime(modDepth / 2, now);
1881
- }
2297
+ this.chorusEffect.lfoGain.gain
2298
+ .cancelScheduledValues(now)
2299
+ .setValueAtTime(modDepth / 2, now);
1882
2300
  }
1883
2301
  getChorusModDepth(value) {
1884
2302
  return (value + 1) / 3200; // second
@@ -1887,14 +2305,11 @@ export class Midy {
1887
2305
  const now = this.audioContext.currentTime;
1888
2306
  const feedback = this.getChorusFeedback(value);
1889
2307
  this.chorus.feedback = feedback;
1890
- for (let i = 0; i < this.channels.length; i++) {
1891
- const chorusEffect = this.channels[i].chorusEffect;
1892
- for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1893
- const feedbackGain = chorusEffect.feedbackGains[j];
1894
- feedbackGain.gain
1895
- .cancelScheduledValues(now)
1896
- .setValueAtTime(feedback, now);
1897
- }
2308
+ const chorusEffect = this.chorusEffect;
2309
+ for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
2310
+ chorusEffect.feedbackGains[i].gain
2311
+ .cancelScheduledValues(now)
2312
+ .setValueAtTime(feedback, now);
1898
2313
  }
1899
2314
  }
1900
2315
  getChorusFeedback(value) {
@@ -1902,15 +2317,28 @@ export class Midy {
1902
2317
  }
1903
2318
  setChorusSendToReverb(value) {
1904
2319
  const sendToReverb = this.getChorusSendToReverb(value);
1905
- if (0 < sendToReverb) {
1906
- const now = this.audioContext.currentTime;
2320
+ const sendGain = this.chorusEffect.sendGain;
2321
+ if (0 < this.chorus.sendToReverb) {
1907
2322
  this.chorus.sendToReverb = sendToReverb;
1908
- this.chorusEffect.sendGain.gain
1909
- .cancelScheduledValues(now)
1910
- .setValueAtTime(sendToReverb, now);
2323
+ if (0 < sendToReverb) {
2324
+ const now = this.audioContext.currentTime;
2325
+ sendGain.gain
2326
+ .cancelScheduledValues(now)
2327
+ .setValueAtTime(sendToReverb, now);
2328
+ }
2329
+ else {
2330
+ sendGain.disconnect();
2331
+ }
1911
2332
  }
1912
- else if (this.chorus.sendToReverb !== 0) {
1913
- this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
2333
+ else {
2334
+ this.chorus.sendToReverb = sendToReverb;
2335
+ if (0 < sendToReverb) {
2336
+ const now = this.audioContext.currentTime;
2337
+ sendGain.connect(this.reverbEffect.input);
2338
+ sendGain.gain
2339
+ .cancelScheduledValues(now)
2340
+ .setValueAtTime(sendToReverb, now);
2341
+ }
1914
2342
  }
1915
2343
  }
1916
2344
  getChorusSendToReverb(value) {
@@ -1947,48 +2375,19 @@ Object.defineProperty(Midy, "channelSettings", {
1947
2375
  writable: true,
1948
2376
  value: {
1949
2377
  currentBufferSource: null,
1950
- volume: 100 / 127,
1951
- pan: 64,
1952
- portamentoTime: 1, // sec
1953
- filterResonance: 1,
1954
- releaseTime: 1,
1955
- attackTime: 1,
1956
- brightness: 1,
1957
- decayTime: 1,
1958
- reverbSendLevel: 0,
1959
- chorusSendLevel: 0,
1960
- vibratoRate: 1,
1961
- vibratoDepth: 1,
1962
- vibratoDelay: 1,
2378
+ program: 0,
1963
2379
  bank: 121 * 128,
1964
2380
  bankMSB: 121,
1965
2381
  bankLSB: 0,
1966
2382
  dataMSB: 0,
1967
2383
  dataLSB: 0,
1968
- program: 0,
1969
- pitchBend: 0,
2384
+ rpnMSB: 127,
2385
+ rpnLSB: 127,
1970
2386
  fineTuning: 0, // cb
1971
2387
  coarseTuning: 0, // cb
1972
2388
  modulationDepthRange: 50, // cent
1973
2389
  }
1974
2390
  });
1975
- Object.defineProperty(Midy, "effectSettings", {
1976
- enumerable: true,
1977
- configurable: true,
1978
- writable: true,
1979
- value: {
1980
- expression: 1,
1981
- modulationDepth: 0,
1982
- sustainPedal: false,
1983
- portamento: false,
1984
- sostenutoPedal: false,
1985
- softPedal: 0,
1986
- rpnMSB: 127,
1987
- rpnLSB: 127,
1988
- channelPressure: 0,
1989
- pitchBendRange: 2,
1990
- }
1991
- });
1992
2391
  Object.defineProperty(Midy, "controllerDestinationSettings", {
1993
2392
  enumerable: true,
1994
2393
  configurable: true,