@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/script/midy.js CHANGED
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Midy = void 0;
4
- const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
5
- const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js");
4
+ const midi_file_1 = require("midi-file");
5
+ const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
6
  class Note {
7
- constructor(noteNumber, velocity, startTime, instrumentKey) {
7
+ constructor(noteNumber, velocity, startTime, voice, voiceParams) {
8
8
  Object.defineProperty(this, "bufferSource", {
9
9
  enumerable: true,
10
10
  configurable: true,
@@ -53,12 +53,118 @@ class Note {
53
53
  writable: true,
54
54
  value: void 0
55
55
  });
56
+ Object.defineProperty(this, "reverbEffectsSend", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: void 0
61
+ });
62
+ Object.defineProperty(this, "chorusEffectsSend", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: void 0
67
+ });
68
+ Object.defineProperty(this, "portamento", {
69
+ enumerable: true,
70
+ configurable: true,
71
+ writable: true,
72
+ value: void 0
73
+ });
56
74
  this.noteNumber = noteNumber;
57
75
  this.velocity = velocity;
58
76
  this.startTime = startTime;
59
- this.instrumentKey = instrumentKey;
77
+ this.voice = voice;
78
+ this.voiceParams = voiceParams;
79
+ }
80
+ }
81
+ // normalized to 0-1 for use with the SF2 modulator model
82
+ const defaultControllerState = {
83
+ noteOnVelocity: { type: 2, defaultValue: 0 },
84
+ noteOnKeyNumber: { type: 3, defaultValue: 0 },
85
+ polyPressure: { type: 10, defaultValue: 0 },
86
+ channelPressure: { type: 13, defaultValue: 0 },
87
+ pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
88
+ pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
89
+ link: { type: 127, defaultValue: 0 },
90
+ // bankMSB: { type: 128 + 0, defaultValue: 121, },
91
+ modulationDepth: { type: 128 + 1, defaultValue: 0 },
92
+ portamentoTime: { type: 128 + 5, defaultValue: 0 },
93
+ // dataMSB: { type: 128 + 6, defaultValue: 0, },
94
+ volume: { type: 128 + 7, defaultValue: 100 / 127 },
95
+ pan: { type: 128 + 10, defaultValue: 0.5 },
96
+ expression: { type: 128 + 11, defaultValue: 1 },
97
+ // bankLSB: { type: 128 + 32, defaultValue: 0, },
98
+ // dataLSB: { type: 128 + 38, defaultValue: 0, },
99
+ sustainPedal: { type: 128 + 64, defaultValue: 0 },
100
+ portamento: { type: 128 + 65, defaultValue: 0 },
101
+ sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
102
+ softPedal: { type: 128 + 67, defaultValue: 0 },
103
+ filterResonance: { type: 128 + 71, defaultValue: 0.5 },
104
+ releaseTime: { type: 128 + 72, defaultValue: 0.5 },
105
+ attackTime: { type: 128 + 73, defaultValue: 0.5 },
106
+ brightness: { type: 128 + 74, defaultValue: 0.5 },
107
+ decayTime: { type: 128 + 75, defaultValue: 0.5 },
108
+ vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
109
+ vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
110
+ vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
111
+ reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
112
+ chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
113
+ // dataIncrement: { type: 128 + 96, defaultValue: 0 },
114
+ // dataDecrement: { type: 128 + 97, defaultValue: 0 },
115
+ // rpnLSB: { type: 128 + 100, defaultValue: 127 },
116
+ // rpnMSB: { type: 128 + 101, defaultValue: 127 },
117
+ // allSoundOff: { type: 128 + 120, defaultValue: 0 },
118
+ // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
119
+ // allNotesOff: { type: 128 + 123, defaultValue: 0 },
120
+ // omniOff: { type: 128 + 124, defaultValue: 0 },
121
+ // omniOn: { type: 128 + 125, defaultValue: 0 },
122
+ // monoOn: { type: 128 + 126, defaultValue: 0 },
123
+ // polyOn: { type: 128 + 127, defaultValue: 0 },
124
+ };
125
+ class ControllerState {
126
+ constructor() {
127
+ Object.defineProperty(this, "array", {
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true,
131
+ value: new Float32Array(256)
132
+ });
133
+ const entries = Object.entries(defaultControllerState);
134
+ for (const [name, { type, defaultValue }] of entries) {
135
+ this.array[type] = defaultValue;
136
+ Object.defineProperty(this, name, {
137
+ get: () => this.array[type],
138
+ set: (value) => this.array[type] = value,
139
+ enumerable: true,
140
+ configurable: true,
141
+ });
142
+ }
60
143
  }
61
144
  }
145
+ const filterEnvelopeKeys = [
146
+ "modEnvToPitch",
147
+ "initialFilterFc",
148
+ "modEnvToFilterFc",
149
+ "modDelay",
150
+ "modAttack",
151
+ "modHold",
152
+ "modDecay",
153
+ "modSustain",
154
+ "modRelease",
155
+ "playbackRate",
156
+ ];
157
+ const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
158
+ const volumeEnvelopeKeys = [
159
+ "volDelay",
160
+ "volAttack",
161
+ "volHold",
162
+ "volDecay",
163
+ "volSustain",
164
+ "volRelease",
165
+ "initialAttenuation",
166
+ ];
167
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
62
168
  class Midy {
63
169
  constructor(audioContext, options = this.defaultOptions) {
64
170
  Object.defineProperty(this, "ticksPerBeat", {
@@ -208,6 +314,12 @@ class Midy {
208
314
  writable: true,
209
315
  value: []
210
316
  });
317
+ Object.defineProperty(this, "exclusiveClassMap", {
318
+ enumerable: true,
319
+ configurable: true,
320
+ writable: true,
321
+ value: new Map()
322
+ });
211
323
  Object.defineProperty(this, "defaultOptions", {
212
324
  enumerable: true,
213
325
  configurable: true,
@@ -233,6 +345,7 @@ class Midy {
233
345
  this.audioContext = audioContext;
234
346
  this.options = { ...this.defaultOptions, ...options };
235
347
  this.masterGain = new GainNode(audioContext);
348
+ this.voiceParamsHandlers = this.createVoiceParamsHandlers();
236
349
  this.controlChangeHandlers = this.createControlChangeHandlers();
237
350
  this.channels = this.createChannels(audioContext);
238
351
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
@@ -264,14 +377,14 @@ class Midy {
264
377
  async loadSoundFont(soundFontUrl) {
265
378
  const response = await fetch(soundFontUrl);
266
379
  const arrayBuffer = await response.arrayBuffer();
267
- const parsed = (0, _esm_js_2.parse)(new Uint8Array(arrayBuffer));
268
- const soundFont = new _esm_js_2.SoundFont(parsed);
380
+ const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
381
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
269
382
  this.addSoundFont(soundFont);
270
383
  }
271
384
  async loadMIDI(midiUrl) {
272
385
  const response = await fetch(midiUrl);
273
386
  const arrayBuffer = await response.arrayBuffer();
274
- const midi = (0, _esm_js_1.parseMidi)(new Uint8Array(arrayBuffer));
387
+ const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
275
388
  this.ticksPerBeat = midi.header.ticksPerBeat;
276
389
  const midiData = this.extractMidiData(midi);
277
390
  this.instruments = midiData.instruments;
@@ -279,7 +392,7 @@ class Midy {
279
392
  this.totalTime = this.calcTotalTime();
280
393
  }
281
394
  setChannelAudioNodes(audioContext) {
282
- const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
395
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
283
396
  const gainL = new GainNode(audioContext, { gain: gainLeft });
284
397
  const gainR = new GainNode(audioContext, { gain: gainRight });
285
398
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -296,7 +409,7 @@ class Midy {
296
409
  const channels = Array.from({ length: 16 }, () => {
297
410
  return {
298
411
  ...this.constructor.channelSettings,
299
- ...this.constructor.effectSettings,
412
+ state: new ControllerState(),
300
413
  ...this.setChannelAudioNodes(audioContext),
301
414
  scheduledNotes: new Map(),
302
415
  sostenutoNotes: new Map(),
@@ -310,38 +423,43 @@ class Midy {
310
423
  });
311
424
  return channels;
312
425
  }
313
- async createNoteBuffer(instrumentKey, isSF3) {
314
- const sampleStart = instrumentKey.start;
315
- const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
426
+ async createNoteBuffer(voiceParams, isSF3) {
427
+ const sampleStart = voiceParams.start;
428
+ const sampleEnd = voiceParams.sample.length + voiceParams.end;
316
429
  if (isSF3) {
317
- const sample = instrumentKey.sample.slice(sampleStart, sampleEnd);
318
- const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
430
+ const sample = voiceParams.sample;
431
+ const start = sample.byteOffset + sampleStart;
432
+ const end = sample.byteOffset + sampleEnd;
433
+ const buffer = sample.buffer.slice(start, end);
434
+ const audioBuffer = await this.audioContext.decodeAudioData(buffer);
319
435
  return audioBuffer;
320
436
  }
321
437
  else {
322
- const sample = instrumentKey.sample.subarray(sampleStart, sampleEnd);
438
+ const sample = voiceParams.sample;
439
+ const start = sample.byteOffset + sampleStart;
440
+ const end = sample.byteOffset + sampleEnd;
441
+ const buffer = sample.buffer.slice(start, end);
323
442
  const audioBuffer = new AudioBuffer({
324
443
  numberOfChannels: 1,
325
444
  length: sample.length,
326
- sampleRate: instrumentKey.sampleRate,
445
+ sampleRate: voiceParams.sampleRate,
327
446
  });
328
447
  const channelData = audioBuffer.getChannelData(0);
329
- const int16Array = new Int16Array(sample.buffer);
448
+ const int16Array = new Int16Array(buffer);
330
449
  for (let i = 0; i < int16Array.length; i++) {
331
450
  channelData[i] = int16Array[i] / 32768;
332
451
  }
333
452
  return audioBuffer;
334
453
  }
335
454
  }
336
- async createNoteBufferNode(instrumentKey, isSF3) {
455
+ async createNoteBufferNode(voiceParams, isSF3) {
337
456
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
338
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
457
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
339
458
  bufferSource.buffer = audioBuffer;
340
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
459
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
341
460
  if (bufferSource.loop) {
342
- bufferSource.loopStart = instrumentKey.loopStart /
343
- instrumentKey.sampleRate;
344
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
461
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
462
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
345
463
  }
346
464
  return bufferSource;
347
465
  }
@@ -398,7 +516,7 @@ class Midy {
398
516
  this.handleChannelPressure(event.channel, event.amount);
399
517
  break;
400
518
  case "pitchBend":
401
- this.setPitchBend(event.channel, event.value);
519
+ this.setPitchBend(event.channel, event.value + 8192);
402
520
  break;
403
521
  case "sysEx":
404
522
  this.handleSysEx(event.data);
@@ -427,6 +545,7 @@ class Midy {
427
545
  if (queueIndex >= this.timeline.length) {
428
546
  await Promise.all(this.notePromises);
429
547
  this.notePromises = [];
548
+ this.exclusiveClassMap.clear();
430
549
  resolve();
431
550
  return;
432
551
  }
@@ -442,6 +561,7 @@ class Midy {
442
561
  }
443
562
  else if (this.isStopping) {
444
563
  await this.stopNotes(0, true);
564
+ this.exclusiveClassMap.clear();
445
565
  this.notePromises = [];
446
566
  resolve();
447
567
  this.isStopping = false;
@@ -450,6 +570,7 @@ class Midy {
450
570
  }
451
571
  else if (this.isSeeking) {
452
572
  this.stopNotes(0, true);
573
+ this.exclusiveClassMap.clear();
453
574
  this.startTime = this.audioContext.currentTime;
454
575
  queueIndex = this.getQueueIndex(this.resumeTime);
455
576
  offset = this.resumeTime - this.startTime;
@@ -678,14 +799,14 @@ class Midy {
678
799
  return impulse;
679
800
  }
680
801
  createConvolutionReverb(audioContext, impulse) {
681
- const output = new GainNode(audioContext);
802
+ const input = new GainNode(audioContext);
682
803
  const convolverNode = new ConvolverNode(audioContext, {
683
804
  buffer: impulse,
684
805
  });
685
- convolverNode.connect(output);
806
+ input.connect(convolverNode);
686
807
  return {
687
- input: convolverNode,
688
- output,
808
+ input,
809
+ output: convolverNode,
689
810
  convolverNode,
690
811
  };
691
812
  }
@@ -727,7 +848,6 @@ class Midy {
727
848
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
728
849
  createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
729
850
  const input = new GainNode(audioContext);
730
- const output = new GainNode(audioContext);
731
851
  const mergerGain = new GainNode(audioContext);
732
852
  for (let i = 0; i < combDelays.length; i++) {
733
853
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
@@ -738,7 +858,7 @@ class Midy {
738
858
  const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
739
859
  allpasses.push(allpass);
740
860
  }
741
- allpasses.at(-1).connect(output);
861
+ const output = allpasses.at(-1);
742
862
  return { input, output };
743
863
  }
744
864
  createChorusEffect(audioContext) {
@@ -793,53 +913,63 @@ class Midy {
793
913
  calcSemitoneOffset(channel) {
794
914
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
795
915
  const channelTuning = channel.coarseTuning + channel.fineTuning;
796
- const tuning = masterTuning + channelTuning;
797
- return channel.pitchBend * channel.pitchBendRange + tuning;
798
- }
799
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
800
- return instrumentKey.playbackRate(noteNumber) *
801
- Math.pow(2, semitoneOffset / 12);
916
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
917
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
918
+ const pitch = pitchWheel * pitchWheelSensitivity;
919
+ return masterTuning + channelTuning + pitch;
802
920
  }
803
921
  setPortamentoStartVolumeEnvelope(channel, note) {
804
- const { instrumentKey, startTime } = note;
805
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
806
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
807
- const volDelay = startTime + instrumentKey.volDelay;
808
- const portamentoTime = volDelay + channel.portamentoTime;
922
+ const now = this.audioContext.currentTime;
923
+ const { voiceParams, startTime } = note;
924
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
925
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
926
+ const volDelay = startTime + voiceParams.volDelay;
927
+ const portamentoTime = volDelay + channel.state.portamentoTime;
809
928
  note.volumeNode.gain
810
- .cancelScheduledValues(startTime)
929
+ .cancelScheduledValues(now)
811
930
  .setValueAtTime(0, volDelay)
812
931
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
813
932
  }
814
933
  setVolumeEnvelope(channel, note) {
815
- const { instrumentKey, startTime } = note;
816
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
817
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
818
- const volDelay = startTime + instrumentKey.volDelay;
819
- const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
820
- const volHold = volAttack + instrumentKey.volHold;
821
- const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
934
+ const now = this.audioContext.currentTime;
935
+ const state = channel.state;
936
+ const { voiceParams, startTime } = note;
937
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
938
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
939
+ const volDelay = startTime + voiceParams.volDelay;
940
+ const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
941
+ const volHold = volAttack + voiceParams.volHold;
942
+ const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
822
943
  note.volumeNode.gain
823
- .cancelScheduledValues(startTime)
944
+ .cancelScheduledValues(now)
824
945
  .setValueAtTime(0, startTime)
825
946
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
826
947
  .exponentialRampToValueAtTime(attackVolume, volAttack)
827
948
  .setValueAtTime(attackVolume, volHold)
828
949
  .linearRampToValueAtTime(sustainVolume, volDecay);
829
950
  }
830
- setPitch(note, semitoneOffset) {
831
- const { instrumentKey, noteNumber, startTime } = note;
832
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
833
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
951
+ setPlaybackRate(note) {
952
+ const now = this.audioContext.currentTime;
953
+ note.bufferSource.playbackRate
954
+ .cancelScheduledValues(now)
955
+ .setValueAtTime(note.voiceParams.playbackRate, now);
956
+ }
957
+ setPitch(channel, note) {
958
+ const now = this.audioContext.currentTime;
959
+ const { startTime } = note;
960
+ const basePitch = this.calcSemitoneOffset(channel) * 100;
961
+ note.bufferSource.detune
962
+ .cancelScheduledValues(now)
963
+ .setValueAtTime(basePitch, startTime);
964
+ const modEnvToPitch = note.voiceParams.modEnvToPitch;
834
965
  if (modEnvToPitch === 0)
835
966
  return;
836
- const basePitch = note.bufferSource.playbackRate.value;
837
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
838
- const modDelay = startTime + instrumentKey.modDelay;
839
- const modAttack = modDelay + instrumentKey.modAttack;
840
- const modHold = modAttack + instrumentKey.modHold;
841
- const modDecay = modHold + instrumentKey.modDecay;
842
- note.bufferSource.playbackRate.value
967
+ const peekPitch = basePitch + modEnvToPitch;
968
+ const modDelay = startTime + voiceParams.modDelay;
969
+ const modAttack = modDelay + voiceParams.modAttack;
970
+ const modHold = modAttack + voiceParams.modHold;
971
+ const modDecay = modHold + voiceParams.modDecay;
972
+ note.bufferSource.detune
843
973
  .setValueAtTime(basePitch, modDelay)
844
974
  .exponentialRampToValueAtTime(peekPitch, modAttack)
845
975
  .setValueAtTime(peekPitch, modHold)
@@ -851,42 +981,46 @@ class Midy {
851
981
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
852
982
  }
853
983
  setPortamentoStartFilterEnvelope(channel, note) {
854
- const { instrumentKey, noteNumber, startTime } = note;
984
+ const now = this.audioContext.currentTime;
985
+ const state = channel.state;
986
+ const { voiceParams, noteNumber, startTime } = note;
855
987
  const softPedalFactor = 1 -
856
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
857
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
858
- softPedalFactor * channel.brightness;
859
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
988
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
989
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
990
+ softPedalFactor * state.brightness * 2;
991
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
860
992
  const sustainFreq = baseFreq +
861
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
993
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
862
994
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
863
995
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
864
- const portamentoTime = startTime + channel.portamentoTime;
865
- const modDelay = startTime + instrumentKey.modDelay;
996
+ const portamentoTime = startTime + channel.state.portamentoTime;
997
+ const modDelay = startTime + voiceParams.modDelay;
866
998
  note.filterNode.frequency
867
- .cancelScheduledValues(startTime)
999
+ .cancelScheduledValues(now)
868
1000
  .setValueAtTime(adjustedBaseFreq, startTime)
869
1001
  .setValueAtTime(adjustedBaseFreq, modDelay)
870
1002
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
871
1003
  }
872
1004
  setFilterEnvelope(channel, note) {
873
- const { instrumentKey, noteNumber, startTime } = note;
1005
+ const now = this.audioContext.currentTime;
1006
+ const state = channel.state;
1007
+ const { voiceParams, noteNumber, startTime } = note;
874
1008
  const softPedalFactor = 1 -
875
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
876
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
877
- softPedalFactor * channel.brightness;
878
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
1009
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1010
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1011
+ softPedalFactor * state.brightness * 2;
1012
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
879
1013
  const sustainFreq = baseFreq +
880
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1014
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
881
1015
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
882
1016
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
883
1017
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
884
- const modDelay = startTime + instrumentKey.modDelay;
885
- const modAttack = modDelay + instrumentKey.modAttack;
886
- const modHold = modAttack + instrumentKey.modHold;
887
- const modDecay = modHold + instrumentKey.modDecay;
1018
+ const modDelay = startTime + voiceParams.modDelay;
1019
+ const modAttack = modDelay + voiceParams.modAttack;
1020
+ const modHold = modAttack + voiceParams.modHold;
1021
+ const modDecay = modHold + voiceParams.modDecay;
888
1022
  note.filterNode.frequency
889
- .cancelScheduledValues(startTime)
1023
+ .cancelScheduledValues(now)
890
1024
  .setValueAtTime(adjustedBaseFreq, startTime)
891
1025
  .setValueAtTime(adjustedBaseFreq, modDelay)
892
1026
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -894,25 +1028,18 @@ class Midy {
894
1028
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
895
1029
  }
896
1030
  startModulation(channel, note, startTime) {
897
- const { instrumentKey } = note;
898
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1031
+ const { voiceParams } = note;
899
1032
  note.modulationLFO = new OscillatorNode(this.audioContext, {
900
- frequency: this.centToHz(instrumentKey.freqModLFO),
1033
+ frequency: this.centToHz(voiceParams.freqModLFO),
901
1034
  });
902
1035
  note.filterDepth = new GainNode(this.audioContext, {
903
- gain: instrumentKey.modLfoToFilterFc,
1036
+ gain: voiceParams.modLfoToFilterFc,
904
1037
  });
905
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
906
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
907
- note.modulationDepth = new GainNode(this.audioContext, {
908
- gain: modulationDepth * modulationDepthSign,
909
- });
910
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
911
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
912
- note.volumeDepth = new GainNode(this.audioContext, {
913
- gain: volumeDepth * volumeDepthSign,
914
- });
915
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1038
+ note.modulationDepth = new GainNode(this.audioContext);
1039
+ this.setModLfoToPitch(channel, note);
1040
+ note.volumeDepth = new GainNode(this.audioContext);
1041
+ this.setModLfoToVolume(note);
1042
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
916
1043
  note.modulationLFO.connect(note.filterDepth);
917
1044
  note.filterDepth.connect(note.filterNode.frequency);
918
1045
  note.modulationLFO.connect(note.modulationDepth);
@@ -921,54 +1048,59 @@ class Midy {
921
1048
  note.volumeDepth.connect(note.volumeNode.gain);
922
1049
  }
923
1050
  startVibrato(channel, note, startTime) {
924
- const { instrumentKey } = note;
925
- const { vibLfoToPitch } = instrumentKey;
1051
+ const { voiceParams } = note;
1052
+ const state = channel.state;
926
1053
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
927
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
928
- channel.vibratoRate,
929
- });
930
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
931
- const vibratoDepthSign = 0 < vibLfoToPitch;
932
- note.vibratoDepth = new GainNode(this.audioContext, {
933
- gain: vibratoDepth * vibratoDepthSign,
1054
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1055
+ state.vibratoRate,
934
1056
  });
935
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1057
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1058
+ note.vibratoDepth = new GainNode(this.audioContext);
1059
+ this.setVibLfoToPitch(channel, note);
936
1060
  note.vibratoLFO.connect(note.vibratoDepth);
937
1061
  note.vibratoDepth.connect(note.bufferSource.detune);
938
1062
  }
939
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
940
- const semitoneOffset = this.calcSemitoneOffset(channel);
941
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
942
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1063
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1064
+ const state = channel.state;
1065
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1066
+ const voiceParams = voice.getAllParams(controllerState);
1067
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1068
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
943
1069
  note.volumeNode = new GainNode(this.audioContext);
944
1070
  note.filterNode = new BiquadFilterNode(this.audioContext, {
945
1071
  type: "lowpass",
946
- Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
1072
+ Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
947
1073
  });
948
1074
  if (portamento) {
1075
+ note.portamento = true;
949
1076
  this.setPortamentoStartVolumeEnvelope(channel, note);
950
1077
  this.setPortamentoStartFilterEnvelope(channel, note);
951
1078
  }
952
1079
  else {
1080
+ note.portamento = false;
953
1081
  this.setVolumeEnvelope(channel, note);
954
1082
  this.setFilterEnvelope(channel, note);
955
1083
  }
956
- if (0 < channel.vibratoDepth) {
1084
+ if (0 < state.vibratoDepth) {
957
1085
  this.startVibrato(channel, note, startTime);
958
1086
  }
959
- if (0 < channel.modulationDepth) {
960
- this.setPitch(note, semitoneOffset);
1087
+ this.setPlaybackRate(note);
1088
+ if (0 < state.modulationDepth) {
1089
+ this.setPitch(channel, note);
961
1090
  this.startModulation(channel, note, startTime);
962
1091
  }
963
- else {
964
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
965
- }
966
1092
  if (this.mono && channel.currentBufferSource) {
967
1093
  channel.currentBufferSource.stop(startTime);
968
1094
  channel.currentBufferSource = note.bufferSource;
969
1095
  }
970
1096
  note.bufferSource.connect(note.filterNode);
971
1097
  note.filterNode.connect(note.volumeNode);
1098
+ if (0 < channel.chorusSendLevel) {
1099
+ this.setChorusEffectsSend(channel, note, 0);
1100
+ }
1101
+ if (0 < channel.reverbSendLevel) {
1102
+ this.setReverbEffectsSend(channel, note, 0);
1103
+ }
972
1104
  note.bufferSource.start(startTime);
973
1105
  return note;
974
1106
  }
@@ -989,15 +1121,28 @@ class Midy {
989
1121
  return;
990
1122
  const soundFont = this.soundFonts[soundFontIndex];
991
1123
  const isSF3 = soundFont.parsed.info.version.major === 3;
992
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
993
- if (!instrumentKey)
1124
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1125
+ if (!voice)
994
1126
  return;
995
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1127
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
996
1128
  note.volumeNode.connect(channel.gainL);
997
1129
  note.volumeNode.connect(channel.gainR);
998
- if (channel.sostenutoPedal) {
1130
+ if (channel.state.sostenutoPedal) {
999
1131
  channel.sostenutoNotes.set(noteNumber, note);
1000
1132
  }
1133
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1134
+ if (exclusiveClass !== 0) {
1135
+ if (this.exclusiveClassMap.has(exclusiveClass)) {
1136
+ const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1137
+ const [prevNote, prevChannelNumber] = prevEntry;
1138
+ if (!prevNote.ending) {
1139
+ this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1140
+ startTime, undefined, // portamentoNoteNumber
1141
+ true);
1142
+ }
1143
+ }
1144
+ this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
1145
+ }
1001
1146
  const scheduledNotes = channel.scheduledNotes;
1002
1147
  if (scheduledNotes.has(noteNumber)) {
1003
1148
  scheduledNotes.get(noteNumber).push(note);
@@ -1010,15 +1155,15 @@ class Midy {
1010
1155
  const now = this.audioContext.currentTime;
1011
1156
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
1012
1157
  }
1013
- stopNote(stopTime, endTime, scheduledNotes, index) {
1158
+ stopNote(endTime, stopTime, scheduledNotes, index) {
1014
1159
  const note = scheduledNotes[index];
1015
1160
  note.volumeNode.gain
1016
- .cancelScheduledValues(stopTime)
1017
- .linearRampToValueAtTime(0, endTime);
1161
+ .cancelScheduledValues(endTime)
1162
+ .linearRampToValueAtTime(0, stopTime);
1018
1163
  note.ending = true;
1019
1164
  this.scheduleTask(() => {
1020
1165
  note.bufferSource.loop = false;
1021
- }, endTime);
1166
+ }, stopTime);
1022
1167
  return new Promise((resolve) => {
1023
1168
  note.bufferSource.onended = () => {
1024
1169
  scheduledNotes[index] = null;
@@ -1034,15 +1179,22 @@ class Midy {
1034
1179
  note.vibratoDepth.disconnect();
1035
1180
  note.vibratoLFO.stop();
1036
1181
  }
1182
+ if (note.reverbEffectsSend) {
1183
+ note.reverbEffectsSend.disconnect();
1184
+ }
1185
+ if (note.chorusEffectsSend) {
1186
+ note.chorusEffectsSend.disconnect();
1187
+ }
1037
1188
  resolve();
1038
1189
  };
1039
- note.bufferSource.stop(endTime);
1190
+ note.bufferSource.stop(stopTime);
1040
1191
  });
1041
1192
  }
1042
- scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
1193
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1043
1194
  const channel = this.channels[channelNumber];
1195
+ const state = channel.state;
1044
1196
  if (!force) {
1045
- if (channel.sustainPedal)
1197
+ if (0.5 < state.sustainPedal)
1046
1198
  return;
1047
1199
  if (channel.sostenutoNotes.has(noteNumber))
1048
1200
  return;
@@ -1057,22 +1209,23 @@ class Midy {
1057
1209
  if (note.ending)
1058
1210
  continue;
1059
1211
  if (portamentoNoteNumber === undefined) {
1060
- const volEndTime = stopTime +
1061
- note.instrumentKey.volRelease * channel.releaseTime;
1062
- const modRelease = stopTime + note.instrumentKey.modRelease;
1212
+ const volRelease = endTime +
1213
+ note.voiceParams.volRelease * state.releaseTime * 2;
1214
+ const modRelease = endTime + note.voiceParams.modRelease;
1063
1215
  note.filterNode.frequency
1064
- .cancelScheduledValues(stopTime)
1216
+ .cancelScheduledValues(endTime)
1065
1217
  .linearRampToValueAtTime(0, modRelease);
1066
- return this.stopNote(stopTime, volEndTime, scheduledNotes, i);
1218
+ const stopTime = Math.min(volRelease, modRelease);
1219
+ return this.stopNote(endTime, stopTime, scheduledNotes, i);
1067
1220
  }
1068
1221
  else {
1069
- const portamentoTime = stopTime + channel.portamentoTime;
1222
+ const portamentoTime = endTime + state.portamentoTime;
1070
1223
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1071
1224
  const detune = note.bufferSource.detune.value + detuneChange;
1072
1225
  note.bufferSource.detune
1073
- .cancelScheduledValues(stopTime)
1226
+ .cancelScheduledValues(endTime)
1074
1227
  .linearRampToValueAtTime(detune, portamentoTime);
1075
- return this.stopNote(stopTime, portamentoTime, scheduledNotes, i);
1228
+ return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1076
1229
  }
1077
1230
  }
1078
1231
  }
@@ -1084,7 +1237,7 @@ class Midy {
1084
1237
  const velocity = halfVelocity * 2;
1085
1238
  const channel = this.channels[channelNumber];
1086
1239
  const promises = [];
1087
- channel.sustainPedal = false;
1240
+ channel.state.sustainPedal = halfVelocity;
1088
1241
  channel.scheduledNotes.forEach((noteList) => {
1089
1242
  for (let i = 0; i < noteList.length; i++) {
1090
1243
  const note = noteList[i];
@@ -1101,7 +1254,7 @@ class Midy {
1101
1254
  const velocity = halfVelocity * 2;
1102
1255
  const channel = this.channels[channelNumber];
1103
1256
  const promises = [];
1104
- channel.sostenutoPedal = false;
1257
+ channel.state.sostenutoPedal = 0;
1105
1258
  channel.sostenutoNotes.forEach((activeNote) => {
1106
1259
  const { noteNumber } = activeNote;
1107
1260
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1119,7 +1272,7 @@ class Midy {
1119
1272
  case 0x90:
1120
1273
  return this.noteOn(channelNumber, data1, data2);
1121
1274
  case 0xA0:
1122
- return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
1275
+ return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
1123
1276
  case 0xB0:
1124
1277
  return this.handleControlChange(channelNumber, data1, data2);
1125
1278
  case 0xC0:
@@ -1146,6 +1299,7 @@ class Midy {
1146
1299
  .setValueAtTime(gain * pressure, now);
1147
1300
  }
1148
1301
  }
1302
+ // this.applyVoiceParams(channel, 10);
1149
1303
  }
1150
1304
  handleProgramChange(channelNumber, program) {
1151
1305
  const channel = this.channels[channelNumber];
@@ -1166,18 +1320,232 @@ class Midy {
1166
1320
  .setValueAtTime(gain * pressure, now);
1167
1321
  });
1168
1322
  }
1323
+ // this.applyVoiceParams(channel, 13);
1169
1324
  }
1170
1325
  handlePitchBendMessage(channelNumber, lsb, msb) {
1171
- const pitchBend = msb * 128 + lsb - 8192;
1326
+ const pitchBend = msb * 128 + lsb;
1172
1327
  this.setPitchBend(channelNumber, pitchBend);
1173
1328
  }
1174
- setPitchBend(channelNumber, pitchBend) {
1329
+ setPitchBend(channelNumber, value) {
1175
1330
  const channel = this.channels[channelNumber];
1176
- const prevPitchBend = channel.pitchBend;
1177
- channel.pitchBend = pitchBend / 8192;
1178
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1179
- channel.pitchBendRange * 100;
1331
+ const state = channel.state;
1332
+ state.pitchWheel = value / 16383;
1333
+ const pitchWheel = (value - 8192) / 8192;
1334
+ const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1180
1335
  this.updateDetune(channel, detuneChange);
1336
+ this.applyVoiceParams(channel, 14);
1337
+ }
1338
+ setModLfoToPitch(channel, note) {
1339
+ const now = this.audioContext.currentTime;
1340
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1341
+ const modulationDepth = Math.abs(modLfoToPitch) +
1342
+ channel.state.modulationDepth;
1343
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1344
+ note.modulationDepth.gain
1345
+ .cancelScheduledValues(now)
1346
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1347
+ }
1348
+ setModLfoToVolume(note) {
1349
+ const now = this.audioContext.currentTime;
1350
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1351
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1352
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1353
+ note.volumeDepth.gain
1354
+ .cancelScheduledValues(now)
1355
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1356
+ }
1357
+ setChorusEffectsSend(note, prevValue) {
1358
+ if (0 < prevValue) {
1359
+ if (0 < note.voiceParams.chorusEffectsSend) {
1360
+ const now = this.audioContext.currentTime;
1361
+ const value = note.voiceParams.chorusEffectsSend;
1362
+ note.chorusEffectsSend.gain
1363
+ .cancelScheduledValues(now)
1364
+ .setValueAtTime(value, now);
1365
+ }
1366
+ else {
1367
+ note.chorusEffectsSend.disconnect();
1368
+ }
1369
+ }
1370
+ else {
1371
+ if (0 < note.voiceParams.chorusEffectsSend) {
1372
+ if (!note.chorusEffectsSend) {
1373
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1374
+ gain: note.voiceParams.chorusEffectsSend,
1375
+ });
1376
+ note.volumeNode.connect(note.chorusEffectsSend);
1377
+ }
1378
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1379
+ }
1380
+ }
1381
+ }
1382
+ setReverbEffectsSend(note, prevValue) {
1383
+ if (0 < prevValue) {
1384
+ if (0 < note.voiceParams.reverbEffectsSend) {
1385
+ const now = this.audioContext.currentTime;
1386
+ const value = note.voiceParams.reverbEffectsSend;
1387
+ note.reverbEffectsSend.gain
1388
+ .cancelScheduledValues(now)
1389
+ .setValueAtTime(value, now);
1390
+ }
1391
+ else {
1392
+ note.reverbEffectsSend.disconnect();
1393
+ }
1394
+ }
1395
+ else {
1396
+ if (0 < note.voiceParams.reverbEffectsSend) {
1397
+ if (!note.reverbEffectsSend) {
1398
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1399
+ gain: note.voiceParams.reverbEffectsSend,
1400
+ });
1401
+ note.volumeNode.connect(note.reverbEffectsSend);
1402
+ }
1403
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1404
+ }
1405
+ }
1406
+ }
1407
+ setVibLfoToPitch(channel, note) {
1408
+ const now = this.audioContext.currentTime;
1409
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1410
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1411
+ 2;
1412
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1413
+ note.vibratoDepth.gain
1414
+ .cancelScheduledValues(now)
1415
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1416
+ }
1417
+ setModLfoToFilterFc(note) {
1418
+ const now = this.audioContext.currentTime;
1419
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1420
+ note.filterDepth.gain
1421
+ .cancelScheduledValues(now)
1422
+ .setValueAtTime(modLfoToFilterFc, now);
1423
+ }
1424
+ setDelayModLFO(note) {
1425
+ const now = this.audioContext.currentTime;
1426
+ const startTime = note.startTime;
1427
+ if (startTime < now)
1428
+ return;
1429
+ note.modulationLFO.stop(now);
1430
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1431
+ note.modulationLFO.connect(note.filterDepth);
1432
+ }
1433
+ setFreqModLFO(note) {
1434
+ const now = this.audioContext.currentTime;
1435
+ const freqModLFO = note.voiceParams.freqModLFO;
1436
+ note.modulationLFO.frequency
1437
+ .cancelScheduledValues(now)
1438
+ .setValueAtTime(freqModLFO, now);
1439
+ }
1440
+ createVoiceParamsHandlers() {
1441
+ return {
1442
+ modLfoToPitch: (channel, note, _prevValue) => {
1443
+ if (0 < channel.state.modulationDepth) {
1444
+ this.setModLfoToPitch(channel, note);
1445
+ }
1446
+ },
1447
+ vibLfoToPitch: (channel, note, _prevValue) => {
1448
+ if (0 < channel.state.vibratoDepth) {
1449
+ this.setVibLfoToPitch(channel, note);
1450
+ }
1451
+ },
1452
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1453
+ if (0 < channel.state.modulationDepth)
1454
+ this.setModLfoToFilterFc(note);
1455
+ },
1456
+ modLfoToVolume: (channel, note) => {
1457
+ if (0 < channel.state.modulationDepth)
1458
+ this.setModLfoToVolume(note);
1459
+ },
1460
+ chorusEffectsSend: (_channel, note, prevValue) => {
1461
+ this.setChorusEffectsSend(note, prevValue);
1462
+ },
1463
+ reverbEffectsSend: (_channel, note, prevValue) => {
1464
+ this.setReverbEffectsSend(note, prevValue);
1465
+ },
1466
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1467
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1468
+ delayVibLFO: (channel, note, prevValue) => {
1469
+ if (0 < channel.state.vibratoDepth) {
1470
+ const now = this.audioContext.currentTime;
1471
+ const prevStartTime = note.startTime +
1472
+ prevValue * channel.state.vibratoDelay * 2;
1473
+ if (now < prevStartTime)
1474
+ return;
1475
+ const startTime = note.startTime +
1476
+ value * channel.state.vibratoDelay * 2;
1477
+ note.vibratoLFO.stop(now);
1478
+ note.vibratoLFO.start(startTime);
1479
+ }
1480
+ },
1481
+ freqVibLFO: (channel, note, _prevValue) => {
1482
+ if (0 < channel.state.vibratoDepth) {
1483
+ const now = this.audioContext.currentTime;
1484
+ note.vibratoLFO.frequency
1485
+ .cancelScheduledValues(now)
1486
+ .setValueAtTime(value * sate.vibratoRate, now);
1487
+ }
1488
+ },
1489
+ };
1490
+ }
1491
+ getControllerState(channel, noteNumber, velocity) {
1492
+ const state = new Float32Array(channel.state.array.length);
1493
+ state.set(channel.state.array);
1494
+ state[2] = velocity / 127;
1495
+ state[3] = noteNumber / 127;
1496
+ return state;
1497
+ }
1498
+ applyVoiceParams(channel, controllerType) {
1499
+ channel.scheduledNotes.forEach((noteList) => {
1500
+ for (let i = 0; i < noteList.length; i++) {
1501
+ const note = noteList[i];
1502
+ if (!note)
1503
+ continue;
1504
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1505
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1506
+ let appliedFilterEnvelope = false;
1507
+ let appliedVolumeEnvelope = false;
1508
+ for (const [key, value] of Object.entries(voiceParams)) {
1509
+ const prevValue = note.voiceParams[key];
1510
+ if (value === prevValue)
1511
+ continue;
1512
+ note.voiceParams[key] = value;
1513
+ if (key in this.voiceParamsHandlers) {
1514
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1515
+ }
1516
+ else if (filterEnvelopeKeySet.has(key)) {
1517
+ if (appliedFilterEnvelope)
1518
+ continue;
1519
+ appliedFilterEnvelope = true;
1520
+ const noteVoiceParams = note.voiceParams;
1521
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1522
+ const key = filterEnvelopeKeys[i];
1523
+ if (key in voiceParams)
1524
+ noteVoiceParams[key] = voiceParams[key];
1525
+ }
1526
+ if (note.portamento) {
1527
+ this.setPortamentoStartFilterEnvelope(channel, note);
1528
+ }
1529
+ else {
1530
+ this.setFilterEnvelope(channel, note);
1531
+ }
1532
+ this.setPitch(channel, note);
1533
+ }
1534
+ else if (volumeEnvelopeKeySet.has(key)) {
1535
+ if (appliedVolumeEnvelope)
1536
+ continue;
1537
+ appliedVolumeEnvelope = true;
1538
+ const noteVoiceParams = note.voiceParams;
1539
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1540
+ const key = volumeEnvelopeKeys[i];
1541
+ if (key in voiceParams)
1542
+ noteVoiceParams[key] = voiceParams[key];
1543
+ }
1544
+ this.setVolumeEnvelope(channel, note);
1545
+ }
1546
+ }
1547
+ }
1548
+ });
1181
1549
  }
1182
1550
  createControlChangeHandlers() {
1183
1551
  return {
@@ -1217,13 +1585,16 @@ class Midy {
1217
1585
  127: this.polyOn,
1218
1586
  };
1219
1587
  }
1220
- handleControlChange(channelNumber, controller, value) {
1221
- const handler = this.controlChangeHandlers[controller];
1588
+ handleControlChange(channelNumber, controllerType, value) {
1589
+ const handler = this.controlChangeHandlers[controllerType];
1222
1590
  if (handler) {
1223
1591
  handler.call(this, channelNumber, value);
1592
+ const channel = this.channels[channelNumber];
1593
+ const controller = 128 + controllerType;
1594
+ this.applyVoiceParams(channel, controller);
1224
1595
  }
1225
1596
  else {
1226
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1597
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1227
1598
  }
1228
1599
  }
1229
1600
  setBankMSB(channelNumber, msb) {
@@ -1237,11 +1608,10 @@ class Midy {
1237
1608
  if (!note)
1238
1609
  continue;
1239
1610
  if (note.modulationDepth) {
1240
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1611
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1241
1612
  }
1242
1613
  else {
1243
- const semitoneOffset = this.calcSemitoneOffset(channel);
1244
- this.setPitch(note, semitoneOffset);
1614
+ this.setPitch(channel, note);
1245
1615
  this.startModulation(channel, note, now);
1246
1616
  }
1247
1617
  }
@@ -1249,21 +1619,22 @@ class Midy {
1249
1619
  }
1250
1620
  setModulationDepth(channelNumber, modulation) {
1251
1621
  const channel = this.channels[channelNumber];
1252
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1622
+ channel.state.modulationDepth = (modulation / 127) *
1623
+ channel.modulationDepthRange;
1253
1624
  this.updateModulation(channel);
1254
1625
  }
1255
1626
  setPortamentoTime(channelNumber, portamentoTime) {
1256
1627
  const channel = this.channels[channelNumber];
1257
1628
  const factor = 5 * Math.log(10) / 127;
1258
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1629
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1259
1630
  }
1260
1631
  setVolume(channelNumber, volume) {
1261
1632
  const channel = this.channels[channelNumber];
1262
- channel.volume = volume / 127;
1633
+ channel.state.volume = volume / 127;
1263
1634
  this.updateChannelVolume(channel);
1264
1635
  }
1265
1636
  panToGain(pan) {
1266
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1637
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1267
1638
  return {
1268
1639
  gainLeft: Math.cos(theta),
1269
1640
  gainRight: Math.sin(theta),
@@ -1271,12 +1642,12 @@ class Midy {
1271
1642
  }
1272
1643
  setPan(channelNumber, pan) {
1273
1644
  const channel = this.channels[channelNumber];
1274
- channel.pan = pan;
1645
+ channel.state.pan = pan / 127;
1275
1646
  this.updateChannelVolume(channel);
1276
1647
  }
1277
1648
  setExpression(channelNumber, expression) {
1278
1649
  const channel = this.channels[channelNumber];
1279
- channel.expression = expression / 127;
1650
+ channel.state.expression = expression / 127;
1280
1651
  this.updateChannelVolume(channel);
1281
1652
  }
1282
1653
  setBankLSB(channelNumber, lsb) {
@@ -1288,8 +1659,9 @@ class Midy {
1288
1659
  }
1289
1660
  updateChannelVolume(channel) {
1290
1661
  const now = this.audioContext.currentTime;
1291
- const volume = channel.volume * channel.expression;
1292
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1662
+ const state = channel.state;
1663
+ const volume = state.volume * state.expression;
1664
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1293
1665
  channel.gainL.gain
1294
1666
  .cancelScheduledValues(now)
1295
1667
  .setValueAtTime(volume * gainLeft, now);
@@ -1298,68 +1670,100 @@ class Midy {
1298
1670
  .setValueAtTime(volume * gainRight, now);
1299
1671
  }
1300
1672
  setSustainPedal(channelNumber, value) {
1301
- const isOn = value >= 64;
1302
- this.channels[channelNumber].sustainPedal = isOn;
1303
- if (!isOn) {
1673
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1674
+ if (value < 64) {
1304
1675
  this.releaseSustainPedal(channelNumber, value);
1305
1676
  }
1306
1677
  }
1307
1678
  setPortamento(channelNumber, value) {
1308
- this.channels[channelNumber].portamento = value >= 64;
1679
+ this.channels[channelNumber].state.portamento = value / 127;
1309
1680
  }
1310
1681
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1311
1682
  const channel = this.channels[channelNumber];
1683
+ const state = channel.state;
1312
1684
  const reverbEffect = this.reverbEffect;
1313
- if (0 < channel.reverbSendLevel) {
1685
+ if (0 < state.reverbSendLevel) {
1314
1686
  if (0 < reverbSendLevel) {
1315
1687
  const now = this.audioContext.currentTime;
1316
- channel.reverbSendLevel = reverbSendLevel / 127;
1317
- reverbEffect.output.gain.cancelScheduledValues(now);
1318
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1688
+ state.reverbSendLevel = reverbSendLevel / 127;
1689
+ reverbEffect.input.gain.cancelScheduledValues(now);
1690
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1319
1691
  }
1320
1692
  else {
1321
- channel.merger.disconnect(reverbEffect.input);
1693
+ channel.scheduledNotes.forEach((noteList) => {
1694
+ for (let i = 0; i < noteList.length; i++) {
1695
+ const note = noteList[i];
1696
+ if (!note)
1697
+ continue;
1698
+ if (note.voiceParams.reverbEffectsSend <= 0)
1699
+ continue;
1700
+ note.reverbEffectsSend.disconnect();
1701
+ }
1702
+ });
1322
1703
  }
1323
1704
  }
1324
1705
  else {
1325
1706
  if (0 < reverbSendLevel) {
1326
- channel.merger.connect(reverbEffect.input);
1327
1707
  const now = this.audioContext.currentTime;
1328
- channel.reverbSendLevel = reverbSendLevel / 127;
1329
- reverbEffect.output.gain.cancelScheduledValues(now);
1330
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1708
+ channel.scheduledNotes.forEach((noteList) => {
1709
+ for (let i = 0; i < noteList.length; i++) {
1710
+ const note = noteList[i];
1711
+ if (!note)
1712
+ continue;
1713
+ this.setReverbEffectsSend(note, 0);
1714
+ }
1715
+ });
1716
+ state.reverbSendLevel = reverbSendLevel / 127;
1717
+ reverbEffect.input.gain.cancelScheduledValues(now);
1718
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1331
1719
  }
1332
1720
  }
1333
1721
  }
1334
1722
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1335
1723
  const channel = this.channels[channelNumber];
1724
+ const state = channel.state;
1336
1725
  const chorusEffect = this.chorusEffect;
1337
- if (0 < channel.chorusSendLevel) {
1726
+ if (0 < state.chorusSendLevel) {
1338
1727
  if (0 < chorusSendLevel) {
1339
1728
  const now = this.audioContext.currentTime;
1340
- channel.chorusSendLevel = chorusSendLevel / 127;
1341
- chorusEffect.output.gain.cancelScheduledValues(now);
1342
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1729
+ state.chorusSendLevel = chorusSendLevel / 127;
1730
+ chorusEffect.input.gain.cancelScheduledValues(now);
1731
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1343
1732
  }
1344
1733
  else {
1345
- channel.merger.disconnect(chorusEffect.input);
1734
+ channel.scheduledNotes.forEach((noteList) => {
1735
+ for (let i = 0; i < noteList.length; i++) {
1736
+ const note = noteList[i];
1737
+ if (!note)
1738
+ continue;
1739
+ if (note.voiceParams.chorusEffectsSend <= 0)
1740
+ continue;
1741
+ note.chorusEffectsSend.disconnect();
1742
+ }
1743
+ });
1346
1744
  }
1347
1745
  }
1348
1746
  else {
1349
1747
  if (0 < chorusSendLevel) {
1350
- channel.merger.connect(chorusEffect.input);
1351
1748
  const now = this.audioContext.currentTime;
1352
- channel.chorusSendLevel = chorusSendLevel / 127;
1353
- chorusEffect.output.gain.cancelScheduledValues(now);
1354
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1749
+ channel.scheduledNotes.forEach((noteList) => {
1750
+ for (let i = 0; i < noteList.length; i++) {
1751
+ const note = noteList[i];
1752
+ if (!note)
1753
+ continue;
1754
+ this.setChorusEffectsSend(note, 0);
1755
+ }
1756
+ });
1757
+ state.chorusSendLevel = chorusSendLevel / 127;
1758
+ chorusEffect.input.gain.cancelScheduledValues(now);
1759
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1355
1760
  }
1356
1761
  }
1357
1762
  }
1358
1763
  setSostenutoPedal(channelNumber, value) {
1359
- const isOn = value >= 64;
1360
1764
  const channel = this.channels[channelNumber];
1361
- channel.sostenutoPedal = isOn;
1362
- if (isOn) {
1765
+ channel.state.sostenutoPedal = value / 127;
1766
+ if (64 <= value) {
1363
1767
  const now = this.audioContext.currentTime;
1364
1768
  const activeNotes = this.getActiveNotes(channel, now);
1365
1769
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1370,31 +1774,31 @@ class Midy {
1370
1774
  }
1371
1775
  setSoftPedal(channelNumber, softPedal) {
1372
1776
  const channel = this.channels[channelNumber];
1373
- channel.softPedal = softPedal / 127;
1777
+ channel.state.softPedal = softPedal / 127;
1374
1778
  }
1375
1779
  setFilterResonance(channelNumber, filterResonance) {
1376
1780
  const now = this.audioContext.currentTime;
1377
1781
  const channel = this.channels[channelNumber];
1378
- channel.filterResonance = filterResonance / 64;
1782
+ const state = channel.state;
1783
+ state.filterResonance = filterResonance / 64;
1379
1784
  channel.scheduledNotes.forEach((noteList) => {
1380
1785
  for (let i = 0; i < noteList.length; i++) {
1381
1786
  const note = noteList[i];
1382
1787
  if (!note)
1383
1788
  continue;
1384
- const Q = note.instrumentKey.initialFilterQ / 10 *
1385
- channel.filterResonance;
1789
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1386
1790
  note.filterNode.Q.setValueAtTime(Q, now);
1387
1791
  }
1388
1792
  });
1389
1793
  }
1390
1794
  setReleaseTime(channelNumber, releaseTime) {
1391
1795
  const channel = this.channels[channelNumber];
1392
- channel.releaseTime = releaseTime / 64;
1796
+ channel.state.releaseTime = releaseTime / 64;
1393
1797
  }
1394
1798
  setAttackTime(channelNumber, attackTime) {
1395
1799
  const now = this.audioContext.currentTime;
1396
1800
  const channel = this.channels[channelNumber];
1397
- channel.attackTime = attackTime / 64;
1801
+ channel.state.attackTime = attackTime / 64;
1398
1802
  channel.scheduledNotes.forEach((noteList) => {
1399
1803
  for (let i = 0; i < noteList.length; i++) {
1400
1804
  const note = noteList[i];
@@ -1408,7 +1812,7 @@ class Midy {
1408
1812
  }
1409
1813
  setBrightness(channelNumber, brightness) {
1410
1814
  const channel = this.channels[channelNumber];
1411
- channel.brightness = brightness / 64;
1815
+ channel.state.brightness = brightness / 64;
1412
1816
  channel.scheduledNotes.forEach((noteList) => {
1413
1817
  for (let i = 0; i < noteList.length; i++) {
1414
1818
  const note = noteList[i];
@@ -1420,7 +1824,7 @@ class Midy {
1420
1824
  }
1421
1825
  setDecayTime(channelNumber, dacayTime) {
1422
1826
  const channel = this.channels[channelNumber];
1423
- channel.decayTime = dacayTime / 64;
1827
+ channel.state.decayTime = dacayTime / 64;
1424
1828
  channel.scheduledNotes.forEach((noteList) => {
1425
1829
  for (let i = 0; i < noteList.length; i++) {
1426
1830
  const note = noteList[i];
@@ -1432,7 +1836,7 @@ class Midy {
1432
1836
  }
1433
1837
  setVibratoRate(channelNumber, vibratoRate) {
1434
1838
  const channel = this.channels[channelNumber];
1435
- channel.vibratoRate = vibratoRate / 64;
1839
+ channel.state.vibratoRate = vibratoRate / 64;
1436
1840
  if (channel.vibratoDepth <= 0)
1437
1841
  return;
1438
1842
  const now = this.audioContext.currentTime;
@@ -1440,16 +1844,16 @@ class Midy {
1440
1844
  activeNotes.forEach((activeNote) => {
1441
1845
  activeNote.vibratoLFO.frequency
1442
1846
  .cancelScheduledValues(now)
1443
- .setValueAtTime(channel.vibratoRate, now);
1847
+ .setValueAtTime(channel.state.vibratoRate, now);
1444
1848
  });
1445
1849
  }
1446
1850
  setVibratoDepth(channelNumber, vibratoDepth) {
1447
1851
  const channel = this.channels[channelNumber];
1448
- channel.vibratoDepth = vibratoDepth / 64;
1852
+ channel.state.vibratoDepth = vibratoDepth / 64;
1449
1853
  }
1450
1854
  setVibratoDelay(channelNumber, vibratoDelay) {
1451
1855
  const channel = this.channels[channelNumber];
1452
- channel.vibratoDelay = vibratoDelay / 64;
1856
+ channel.state.vibratoDelay = vibratoDelay / 64;
1453
1857
  }
1454
1858
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1455
1859
  if (maxLSB < channel.dataLSB) {
@@ -1519,7 +1923,7 @@ class Midy {
1519
1923
  this.channels[channelNumber].dataMSB = value;
1520
1924
  this.handleRPN(channelNumber, 0);
1521
1925
  }
1522
- updateDetune(channel, detuneChange) {
1926
+ updateDetune(channel, detune) {
1523
1927
  const now = this.audioContext.currentTime;
1524
1928
  channel.scheduledNotes.forEach((noteList) => {
1525
1929
  for (let i = 0; i < noteList.length; i++) {
@@ -1527,7 +1931,6 @@ class Midy {
1527
1931
  if (!note)
1528
1932
  continue;
1529
1933
  const { bufferSource } = note;
1530
- const detune = bufferSource.detune.value + detuneChange;
1531
1934
  bufferSource.detune
1532
1935
  .cancelScheduledValues(now)
1533
1936
  .setValueAtTime(detune, now);
@@ -1540,13 +1943,13 @@ class Midy {
1540
1943
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1541
1944
  this.setPitchBendRange(channelNumber, pitchBendRange);
1542
1945
  }
1543
- setPitchBendRange(channelNumber, pitchBendRange) {
1946
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1544
1947
  const channel = this.channels[channelNumber];
1545
- const prevPitchBendRange = channel.pitchBendRange;
1546
- channel.pitchBendRange = pitchBendRange;
1547
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1548
- channel.pitchBend * 100;
1549
- this.updateDetune(channel, detuneChange);
1948
+ const state = channel.state;
1949
+ state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1950
+ const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1951
+ this.updateDetune(channel, detune);
1952
+ this.applyVoiceParams(channel, 16);
1550
1953
  }
1551
1954
  handleFineTuningRPN(channelNumber) {
1552
1955
  const channel = this.channels[channelNumber];
@@ -1590,7 +1993,30 @@ class Midy {
1590
1993
  return this.stopChannelNotes(channelNumber, 0, true);
1591
1994
  }
1592
1995
  resetAllControllers(channelNumber) {
1593
- Object.assign(this.channels[channelNumber], this.effectSettings);
1996
+ const stateTypes = [
1997
+ "expression",
1998
+ "modulationDepth",
1999
+ "sustainPedal",
2000
+ "portamento",
2001
+ "sostenutoPedal",
2002
+ "softPedal",
2003
+ "channelPressure",
2004
+ "pitchWheelSensitivity",
2005
+ ];
2006
+ const channel = this.channels[channelNumber];
2007
+ const state = channel.state;
2008
+ for (let i = 0; i < stateTypes.length; i++) {
2009
+ const type = stateTypes[i];
2010
+ state[type] = defaultControllerState[type];
2011
+ }
2012
+ const settingTypes = [
2013
+ "rpnMSB",
2014
+ "rpnLSB",
2015
+ ];
2016
+ for (let i = 0; i < settingTypes.length; i++) {
2017
+ const type = settingTypes[i];
2018
+ channel[type] = this.constructor.channelSettings[type];
2019
+ }
1594
2020
  }
1595
2021
  allNotesOff(channelNumber) {
1596
2022
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1784,10 +2210,8 @@ class Midy {
1784
2210
  }
1785
2211
  setReverbTime(value) {
1786
2212
  this.reverb.time = this.getReverbTime(value);
1787
- const { audioContext, channels, options } = this;
1788
- for (let i = 0; i < channels.length; i++) {
1789
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1790
- }
2213
+ const { audioContext, options } = this;
2214
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1791
2215
  }
1792
2216
  getReverbTime(value) {
1793
2217
  return Math.pow(Math.E, (value - 40) * 0.025);
@@ -1864,10 +2288,7 @@ class Midy {
1864
2288
  const now = this.audioContext.currentTime;
1865
2289
  const modRate = this.getChorusModRate(value);
1866
2290
  this.chorus.modRate = modRate;
1867
- for (let i = 0; i < this.channels.length; i++) {
1868
- const lfo = this.channels[i].chorusEffect.lfo;
1869
- lfo.frequency.setValueAtTime(modRate, now);
1870
- }
2291
+ this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
1871
2292
  }
1872
2293
  getChorusModRate(value) {
1873
2294
  return value * 0.122; // Hz
@@ -1876,12 +2297,9 @@ class Midy {
1876
2297
  const now = this.audioContext.currentTime;
1877
2298
  const modDepth = this.getChorusModDepth(value);
1878
2299
  this.chorus.modDepth = modDepth;
1879
- for (let i = 0; i < this.channels.length; i++) {
1880
- const chorusEffect = this.channels[i].chorusEffect;
1881
- chorusEffect.lfoGain.gain
1882
- .cancelScheduledValues(now)
1883
- .setValueAtTime(modDepth / 2, now);
1884
- }
2300
+ this.chorusEffect.lfoGain.gain
2301
+ .cancelScheduledValues(now)
2302
+ .setValueAtTime(modDepth / 2, now);
1885
2303
  }
1886
2304
  getChorusModDepth(value) {
1887
2305
  return (value + 1) / 3200; // second
@@ -1890,14 +2308,11 @@ class Midy {
1890
2308
  const now = this.audioContext.currentTime;
1891
2309
  const feedback = this.getChorusFeedback(value);
1892
2310
  this.chorus.feedback = feedback;
1893
- for (let i = 0; i < this.channels.length; i++) {
1894
- const chorusEffect = this.channels[i].chorusEffect;
1895
- for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1896
- const feedbackGain = chorusEffect.feedbackGains[j];
1897
- feedbackGain.gain
1898
- .cancelScheduledValues(now)
1899
- .setValueAtTime(feedback, now);
1900
- }
2311
+ const chorusEffect = this.chorusEffect;
2312
+ for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
2313
+ chorusEffect.feedbackGains[i].gain
2314
+ .cancelScheduledValues(now)
2315
+ .setValueAtTime(feedback, now);
1901
2316
  }
1902
2317
  }
1903
2318
  getChorusFeedback(value) {
@@ -1905,15 +2320,28 @@ class Midy {
1905
2320
  }
1906
2321
  setChorusSendToReverb(value) {
1907
2322
  const sendToReverb = this.getChorusSendToReverb(value);
1908
- if (0 < sendToReverb) {
1909
- const now = this.audioContext.currentTime;
2323
+ const sendGain = this.chorusEffect.sendGain;
2324
+ if (0 < this.chorus.sendToReverb) {
1910
2325
  this.chorus.sendToReverb = sendToReverb;
1911
- this.chorusEffect.sendGain.gain
1912
- .cancelScheduledValues(now)
1913
- .setValueAtTime(sendToReverb, now);
2326
+ if (0 < sendToReverb) {
2327
+ const now = this.audioContext.currentTime;
2328
+ sendGain.gain
2329
+ .cancelScheduledValues(now)
2330
+ .setValueAtTime(sendToReverb, now);
2331
+ }
2332
+ else {
2333
+ sendGain.disconnect();
2334
+ }
1914
2335
  }
1915
- else if (this.chorus.sendToReverb !== 0) {
1916
- this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
2336
+ else {
2337
+ this.chorus.sendToReverb = sendToReverb;
2338
+ if (0 < sendToReverb) {
2339
+ const now = this.audioContext.currentTime;
2340
+ sendGain.connect(this.reverbEffect.input);
2341
+ sendGain.gain
2342
+ .cancelScheduledValues(now)
2343
+ .setValueAtTime(sendToReverb, now);
2344
+ }
1917
2345
  }
1918
2346
  }
1919
2347
  getChorusSendToReverb(value) {
@@ -1951,48 +2379,19 @@ Object.defineProperty(Midy, "channelSettings", {
1951
2379
  writable: true,
1952
2380
  value: {
1953
2381
  currentBufferSource: null,
1954
- volume: 100 / 127,
1955
- pan: 64,
1956
- portamentoTime: 1, // sec
1957
- filterResonance: 1,
1958
- releaseTime: 1,
1959
- attackTime: 1,
1960
- brightness: 1,
1961
- decayTime: 1,
1962
- reverbSendLevel: 0,
1963
- chorusSendLevel: 0,
1964
- vibratoRate: 1,
1965
- vibratoDepth: 1,
1966
- vibratoDelay: 1,
2382
+ program: 0,
1967
2383
  bank: 121 * 128,
1968
2384
  bankMSB: 121,
1969
2385
  bankLSB: 0,
1970
2386
  dataMSB: 0,
1971
2387
  dataLSB: 0,
1972
- program: 0,
1973
- pitchBend: 0,
2388
+ rpnMSB: 127,
2389
+ rpnLSB: 127,
1974
2390
  fineTuning: 0, // cb
1975
2391
  coarseTuning: 0, // cb
1976
2392
  modulationDepthRange: 50, // cent
1977
2393
  }
1978
2394
  });
1979
- Object.defineProperty(Midy, "effectSettings", {
1980
- enumerable: true,
1981
- configurable: true,
1982
- writable: true,
1983
- value: {
1984
- expression: 1,
1985
- modulationDepth: 0,
1986
- sustainPedal: false,
1987
- portamento: false,
1988
- sostenutoPedal: false,
1989
- softPedal: 0,
1990
- rpnMSB: 127,
1991
- rpnLSB: 127,
1992
- channelPressure: 0,
1993
- pitchBendRange: 2,
1994
- }
1995
- });
1996
2395
  Object.defineProperty(Midy, "controllerDestinationSettings", {
1997
2396
  enumerable: true,
1998
2397
  configurable: true,