@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-GM2.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 MidyGM2 {
60
166
  constructor(audioContext, options = this.defaultOptions) {
61
167
  Object.defineProperty(this, "ticksPerBeat", {
@@ -205,6 +311,12 @@ export class MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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(),
@@ -304,38 +417,43 @@ export class MidyGM2 {
304
417
  });
305
418
  return channels;
306
419
  }
307
- async createNoteBuffer(instrumentKey, isSF3) {
308
- const sampleStart = instrumentKey.start;
309
- const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
420
+ async createNoteBuffer(voiceParams, isSF3) {
421
+ const sampleStart = voiceParams.start;
422
+ const sampleEnd = voiceParams.sample.length + voiceParams.end;
310
423
  if (isSF3) {
311
- const sample = instrumentKey.sample.slice(sampleStart, sampleEnd);
312
- const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
424
+ const sample = voiceParams.sample;
425
+ const start = sample.byteOffset + sampleStart;
426
+ const end = sample.byteOffset + sampleEnd;
427
+ const buffer = sample.buffer.slice(start, end);
428
+ const audioBuffer = await this.audioContext.decodeAudioData(buffer);
313
429
  return audioBuffer;
314
430
  }
315
431
  else {
316
- const sample = instrumentKey.sample.subarray(sampleStart, sampleEnd);
432
+ const sample = voiceParams.sample;
433
+ const start = sample.byteOffset + sampleStart;
434
+ const end = sample.byteOffset + sampleEnd;
435
+ const buffer = sample.buffer.slice(start, end);
317
436
  const audioBuffer = new AudioBuffer({
318
437
  numberOfChannels: 1,
319
438
  length: sample.length,
320
- sampleRate: instrumentKey.sampleRate,
439
+ sampleRate: voiceParams.sampleRate,
321
440
  });
322
441
  const channelData = audioBuffer.getChannelData(0);
323
- const int16Array = new Int16Array(sample.buffer);
442
+ const int16Array = new Int16Array(buffer);
324
443
  for (let i = 0; i < int16Array.length; i++) {
325
444
  channelData[i] = int16Array[i] / 32768;
326
445
  }
327
446
  return audioBuffer;
328
447
  }
329
448
  }
330
- async createNoteBufferNode(instrumentKey, isSF3) {
449
+ async createNoteBufferNode(voiceParams, isSF3) {
331
450
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
332
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
451
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
333
452
  bufferSource.buffer = audioBuffer;
334
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
453
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
335
454
  if (bufferSource.loop) {
336
- bufferSource.loopStart = instrumentKey.loopStart /
337
- instrumentKey.sampleRate;
338
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
455
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
456
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
339
457
  }
340
458
  return bufferSource;
341
459
  }
@@ -389,7 +507,7 @@ export class MidyGM2 {
389
507
  this.handleChannelPressure(event.channel, event.amount);
390
508
  break;
391
509
  case "pitchBend":
392
- this.setPitchBend(event.channel, event.value);
510
+ this.setPitchBend(event.channel, event.value + 8192);
393
511
  break;
394
512
  case "sysEx":
395
513
  this.handleSysEx(event.data);
@@ -418,6 +536,7 @@ export class MidyGM2 {
418
536
  if (queueIndex >= this.timeline.length) {
419
537
  await Promise.all(this.notePromises);
420
538
  this.notePromises = [];
539
+ this.exclusiveClassMap.clear();
421
540
  resolve();
422
541
  return;
423
542
  }
@@ -433,6 +552,7 @@ export class MidyGM2 {
433
552
  }
434
553
  else if (this.isStopping) {
435
554
  await this.stopNotes(0, true);
555
+ this.exclusiveClassMap.clear();
436
556
  this.notePromises = [];
437
557
  resolve();
438
558
  this.isStopping = false;
@@ -441,6 +561,7 @@ export class MidyGM2 {
441
561
  }
442
562
  else if (this.isSeeking) {
443
563
  this.stopNotes(0, true);
564
+ this.exclusiveClassMap.clear();
444
565
  this.startTime = this.audioContext.currentTime;
445
566
  queueIndex = this.getQueueIndex(this.resumeTime);
446
567
  offset = this.resumeTime - this.startTime;
@@ -669,14 +790,14 @@ export class MidyGM2 {
669
790
  return impulse;
670
791
  }
671
792
  createConvolutionReverb(audioContext, impulse) {
672
- const output = new GainNode(audioContext);
793
+ const input = new GainNode(audioContext);
673
794
  const convolverNode = new ConvolverNode(audioContext, {
674
795
  buffer: impulse,
675
796
  });
676
- convolverNode.connect(output);
797
+ input.connect(convolverNode);
677
798
  return {
678
- input: convolverNode,
679
- output,
799
+ input,
800
+ output: convolverNode,
680
801
  convolverNode,
681
802
  };
682
803
  }
@@ -718,7 +839,6 @@ export class MidyGM2 {
718
839
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
719
840
  createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
720
841
  const input = new GainNode(audioContext);
721
- const output = new GainNode(audioContext);
722
842
  const mergerGain = new GainNode(audioContext);
723
843
  for (let i = 0; i < combDelays.length; i++) {
724
844
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
@@ -729,7 +849,7 @@ export class MidyGM2 {
729
849
  const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
730
850
  allpasses.push(allpass);
731
851
  }
732
- allpasses.at(-1).connect(output);
852
+ const output = allpasses.at(-1);
733
853
  return { input, output };
734
854
  }
735
855
  createChorusEffect(audioContext) {
@@ -784,53 +904,62 @@ export class MidyGM2 {
784
904
  calcSemitoneOffset(channel) {
785
905
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
786
906
  const channelTuning = channel.coarseTuning + channel.fineTuning;
787
- const tuning = masterTuning + channelTuning;
788
- return channel.pitchBend * channel.pitchBendRange + tuning;
789
- }
790
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
791
- return instrumentKey.playbackRate(noteNumber) *
792
- Math.pow(2, semitoneOffset / 12);
907
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
908
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
909
+ const pitch = pitchWheel * pitchWheelSensitivity;
910
+ return masterTuning + channelTuning + pitch;
793
911
  }
794
912
  setPortamentoStartVolumeEnvelope(channel, note) {
795
- const { instrumentKey, startTime } = note;
796
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
797
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
798
- const volDelay = startTime + instrumentKey.volDelay;
799
- const portamentoTime = volDelay + channel.portamentoTime;
913
+ const now = this.audioContext.currentTime;
914
+ const { voiceParams, startTime } = note;
915
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
916
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
917
+ const volDelay = startTime + voiceParams.volDelay;
918
+ const portamentoTime = volDelay + channel.state.portamentoTime;
800
919
  note.volumeNode.gain
801
- .cancelScheduledValues(startTime)
920
+ .cancelScheduledValues(now)
802
921
  .setValueAtTime(0, volDelay)
803
922
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
804
923
  }
805
924
  setVolumeEnvelope(note) {
806
- const { instrumentKey, startTime } = note;
807
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
808
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
809
- const volDelay = startTime + instrumentKey.volDelay;
810
- const volAttack = volDelay + instrumentKey.volAttack;
811
- const volHold = volAttack + instrumentKey.volHold;
812
- const volDecay = volHold + instrumentKey.volDecay;
925
+ const now = this.audioContext.currentTime;
926
+ const { voiceParams, startTime } = note;
927
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
928
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
929
+ const volDelay = startTime + voiceParams.volDelay;
930
+ const volAttack = volDelay + voiceParams.volAttack;
931
+ const volHold = volAttack + voiceParams.volHold;
932
+ const volDecay = volHold + voiceParams.volDecay;
813
933
  note.volumeNode.gain
814
- .cancelScheduledValues(startTime)
934
+ .cancelScheduledValues(now)
815
935
  .setValueAtTime(0, startTime)
816
936
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
817
937
  .exponentialRampToValueAtTime(attackVolume, volAttack)
818
938
  .setValueAtTime(attackVolume, volHold)
819
939
  .linearRampToValueAtTime(sustainVolume, volDecay);
820
940
  }
821
- setPitch(note, semitoneOffset) {
822
- const { instrumentKey, noteNumber, startTime } = note;
823
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
824
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
941
+ setPlaybackRate(note) {
942
+ const now = this.audioContext.currentTime;
943
+ note.bufferSource.playbackRate
944
+ .cancelScheduledValues(now)
945
+ .setValueAtTime(note.voiceParams.playbackRate, now);
946
+ }
947
+ setPitch(channel, note) {
948
+ const now = this.audioContext.currentTime;
949
+ const { startTime } = note;
950
+ const basePitch = this.calcSemitoneOffset(channel) * 100;
951
+ note.bufferSource.detune
952
+ .cancelScheduledValues(now)
953
+ .setValueAtTime(basePitch, startTime);
954
+ const modEnvToPitch = note.voiceParams.modEnvToPitch;
825
955
  if (modEnvToPitch === 0)
826
956
  return;
827
- const basePitch = note.bufferSource.playbackRate.value;
828
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
829
- const modDelay = startTime + instrumentKey.modDelay;
830
- const modAttack = modDelay + instrumentKey.modAttack;
831
- const modHold = modAttack + instrumentKey.modHold;
832
- const modDecay = modHold + instrumentKey.modDecay;
833
- note.bufferSource.playbackRate.value
957
+ const peekPitch = basePitch + modEnvToPitch;
958
+ const modDelay = startTime + voiceParams.modDelay;
959
+ const modAttack = modDelay + voiceParams.modAttack;
960
+ const modHold = modAttack + voiceParams.modHold;
961
+ const modDecay = modHold + voiceParams.modDecay;
962
+ note.bufferSource.detune
834
963
  .setValueAtTime(basePitch, modDelay)
835
964
  .exponentialRampToValueAtTime(peekPitch, modAttack)
836
965
  .setValueAtTime(peekPitch, modHold)
@@ -842,42 +971,46 @@ export class MidyGM2 {
842
971
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
843
972
  }
844
973
  setPortamentoStartFilterEnvelope(channel, note) {
845
- const { instrumentKey, noteNumber, startTime } = note;
974
+ const now = this.audioContext.currentTime;
975
+ const state = channel.state;
976
+ const { voiceParams, noteNumber, startTime } = note;
846
977
  const softPedalFactor = 1 -
847
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
848
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
978
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
979
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
849
980
  softPedalFactor;
850
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
981
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
851
982
  const sustainFreq = baseFreq +
852
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
983
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
853
984
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
854
985
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
855
- const portamentoTime = startTime + channel.portamentoTime;
856
- const modDelay = startTime + instrumentKey.modDelay;
986
+ const portamentoTime = startTime + channel.state.portamentoTime;
987
+ const modDelay = startTime + voiceParams.modDelay;
857
988
  note.filterNode.frequency
858
- .cancelScheduledValues(startTime)
989
+ .cancelScheduledValues(now)
859
990
  .setValueAtTime(adjustedBaseFreq, startTime)
860
991
  .setValueAtTime(adjustedBaseFreq, modDelay)
861
992
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
862
993
  }
863
994
  setFilterEnvelope(channel, note) {
864
- const { instrumentKey, noteNumber, startTime } = note;
995
+ const now = this.audioContext.currentTime;
996
+ const state = channel.state;
997
+ const { voiceParams, noteNumber, startTime } = note;
865
998
  const softPedalFactor = 1 -
866
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
867
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
999
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1000
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
868
1001
  softPedalFactor;
869
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
1002
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
870
1003
  const sustainFreq = baseFreq +
871
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1004
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
872
1005
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
873
1006
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
874
1007
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
875
- const modDelay = startTime + instrumentKey.modDelay;
876
- const modAttack = modDelay + instrumentKey.modAttack;
877
- const modHold = modAttack + instrumentKey.modHold;
878
- const modDecay = modHold + instrumentKey.modDecay;
1008
+ const modDelay = startTime + voiceParams.modDelay;
1009
+ const modAttack = modDelay + voiceParams.modAttack;
1010
+ const modHold = modAttack + voiceParams.modHold;
1011
+ const modDecay = modHold + voiceParams.modDecay;
879
1012
  note.filterNode.frequency
880
- .cancelScheduledValues(startTime)
1013
+ .cancelScheduledValues(now)
881
1014
  .setValueAtTime(adjustedBaseFreq, startTime)
882
1015
  .setValueAtTime(adjustedBaseFreq, modDelay)
883
1016
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -885,25 +1018,18 @@ export class MidyGM2 {
885
1018
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
886
1019
  }
887
1020
  startModulation(channel, note, startTime) {
888
- const { instrumentKey } = note;
889
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1021
+ const { voiceParams } = note;
890
1022
  note.modulationLFO = new OscillatorNode(this.audioContext, {
891
- frequency: this.centToHz(instrumentKey.freqModLFO),
1023
+ frequency: this.centToHz(voiceParams.freqModLFO),
892
1024
  });
893
1025
  note.filterDepth = new GainNode(this.audioContext, {
894
- gain: instrumentKey.modLfoToFilterFc,
1026
+ gain: voiceParams.modLfoToFilterFc,
895
1027
  });
896
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
897
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
898
- note.modulationDepth = new GainNode(this.audioContext, {
899
- gain: modulationDepth * modulationDepthSign,
900
- });
901
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
902
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
903
- note.volumeDepth = new GainNode(this.audioContext, {
904
- gain: volumeDepth * volumeDepthSign,
905
- });
906
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1028
+ note.modulationDepth = new GainNode(this.audioContext);
1029
+ this.setModLfoToPitch(channel, note);
1030
+ note.volumeDepth = new GainNode(this.audioContext);
1031
+ this.setModLfoToVolume(note);
1032
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
907
1033
  note.modulationLFO.connect(note.filterDepth);
908
1034
  note.filterDepth.connect(note.filterNode.frequency);
909
1035
  note.modulationLFO.connect(note.modulationDepth);
@@ -912,54 +1038,59 @@ export class MidyGM2 {
912
1038
  note.volumeDepth.connect(note.volumeNode.gain);
913
1039
  }
914
1040
  startVibrato(channel, note, startTime) {
915
- const { instrumentKey } = note;
916
- const { vibLfoToPitch } = instrumentKey;
1041
+ const { voiceParams } = note;
1042
+ const state = channel.state;
917
1043
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
918
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
919
- channel.vibratoRate,
920
- });
921
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
922
- const vibratoDepthSign = 0 < vibLfoToPitch;
923
- note.vibratoDepth = new GainNode(this.audioContext, {
924
- gain: vibratoDepth * vibratoDepthSign,
1044
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1045
+ state.vibratoRate,
925
1046
  });
926
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1047
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1048
+ note.vibratoDepth = new GainNode(this.audioContext);
1049
+ this.setVibLfoToPitch(channel, note);
927
1050
  note.vibratoLFO.connect(note.vibratoDepth);
928
1051
  note.vibratoDepth.connect(note.bufferSource.detune);
929
1052
  }
930
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
931
- const semitoneOffset = this.calcSemitoneOffset(channel);
932
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
933
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1053
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1054
+ const state = channel.state;
1055
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1056
+ const voiceParams = voice.getAllParams(controllerState);
1057
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1058
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
934
1059
  note.volumeNode = new GainNode(this.audioContext);
935
1060
  note.filterNode = new BiquadFilterNode(this.audioContext, {
936
1061
  type: "lowpass",
937
- Q: instrumentKey.initialFilterQ / 10, // dB
1062
+ Q: voiceParams.initialFilterQ / 10, // dB
938
1063
  });
939
1064
  if (portamento) {
1065
+ note.portamento = true;
940
1066
  this.setPortamentoStartVolumeEnvelope(channel, note);
941
1067
  this.setPortamentoStartFilterEnvelope(channel, note);
942
1068
  }
943
1069
  else {
944
- this.setVolumeEnvelope(note);
1070
+ note.portamento = false;
1071
+ this.setVolumeEnvelope(channel, note);
945
1072
  this.setFilterEnvelope(channel, note);
946
1073
  }
947
- if (0 < channel.vibratoDepth) {
1074
+ if (0 < state.vibratoDepth) {
948
1075
  this.startVibrato(channel, note, startTime);
949
1076
  }
950
- if (0 < channel.modulationDepth) {
951
- this.setPitch(note, semitoneOffset);
1077
+ this.setPlaybackRate(note);
1078
+ if (0 < state.modulationDepth) {
1079
+ this.setPitch(channel, note);
952
1080
  this.startModulation(channel, note, startTime);
953
1081
  }
954
- else {
955
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
956
- }
957
1082
  if (this.mono && channel.currentBufferSource) {
958
1083
  channel.currentBufferSource.stop(startTime);
959
1084
  channel.currentBufferSource = note.bufferSource;
960
1085
  }
961
1086
  note.bufferSource.connect(note.filterNode);
962
1087
  note.filterNode.connect(note.volumeNode);
1088
+ if (0 < channel.chorusSendLevel) {
1089
+ this.setChorusEffectsSend(channel, note, 0);
1090
+ }
1091
+ if (0 < channel.reverbSendLevel) {
1092
+ this.setReverbEffectsSend(channel, note, 0);
1093
+ }
963
1094
  note.bufferSource.start(startTime);
964
1095
  return note;
965
1096
  }
@@ -980,15 +1111,28 @@ export class MidyGM2 {
980
1111
  return;
981
1112
  const soundFont = this.soundFonts[soundFontIndex];
982
1113
  const isSF3 = soundFont.parsed.info.version.major === 3;
983
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
984
- if (!instrumentKey)
1114
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1115
+ if (!voice)
985
1116
  return;
986
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1117
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
987
1118
  note.volumeNode.connect(channel.gainL);
988
1119
  note.volumeNode.connect(channel.gainR);
989
- if (channel.sostenutoPedal) {
1120
+ if (channel.state.sostenutoPedal) {
990
1121
  channel.sostenutoNotes.set(noteNumber, note);
991
1122
  }
1123
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1124
+ if (exclusiveClass !== 0) {
1125
+ if (this.exclusiveClassMap.has(exclusiveClass)) {
1126
+ const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1127
+ const [prevNote, prevChannelNumber] = prevEntry;
1128
+ if (!prevNote.ending) {
1129
+ this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1130
+ startTime, undefined, // portamentoNoteNumber
1131
+ true);
1132
+ }
1133
+ }
1134
+ this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
1135
+ }
992
1136
  const scheduledNotes = channel.scheduledNotes;
993
1137
  if (scheduledNotes.has(noteNumber)) {
994
1138
  scheduledNotes.get(noteNumber).push(note);
@@ -1001,15 +1145,15 @@ export class MidyGM2 {
1001
1145
  const now = this.audioContext.currentTime;
1002
1146
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
1003
1147
  }
1004
- stopNote(stopTime, endTime, scheduledNotes, index) {
1148
+ stopNote(endTime, stopTime, scheduledNotes, index) {
1005
1149
  const note = scheduledNotes[index];
1006
1150
  note.volumeNode.gain
1007
- .cancelScheduledValues(stopTime)
1008
- .linearRampToValueAtTime(0, endTime);
1151
+ .cancelScheduledValues(endTime)
1152
+ .linearRampToValueAtTime(0, stopTime);
1009
1153
  note.ending = true;
1010
1154
  this.scheduleTask(() => {
1011
1155
  note.bufferSource.loop = false;
1012
- }, endTime);
1156
+ }, stopTime);
1013
1157
  return new Promise((resolve) => {
1014
1158
  note.bufferSource.onended = () => {
1015
1159
  scheduledNotes[index] = null;
@@ -1025,15 +1169,22 @@ export class MidyGM2 {
1025
1169
  note.vibratoDepth.disconnect();
1026
1170
  note.vibratoLFO.stop();
1027
1171
  }
1172
+ if (note.reverbEffectsSend) {
1173
+ note.reverbEffectsSend.disconnect();
1174
+ }
1175
+ if (note.chorusEffectsSend) {
1176
+ note.chorusEffectsSend.disconnect();
1177
+ }
1028
1178
  resolve();
1029
1179
  };
1030
- note.bufferSource.stop(endTime);
1180
+ note.bufferSource.stop(stopTime);
1031
1181
  });
1032
1182
  }
1033
- scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
1183
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1034
1184
  const channel = this.channels[channelNumber];
1185
+ const state = channel.state;
1035
1186
  if (!force) {
1036
- if (channel.sustainPedal)
1187
+ if (0.5 < state.sustainPedal)
1037
1188
  return;
1038
1189
  if (channel.sostenutoNotes.has(noteNumber))
1039
1190
  return;
@@ -1048,21 +1199,22 @@ export class MidyGM2 {
1048
1199
  if (note.ending)
1049
1200
  continue;
1050
1201
  if (portamentoNoteNumber === undefined) {
1051
- const volEndTime = stopTime + note.instrumentKey.volRelease;
1052
- const modRelease = stopTime + note.instrumentKey.modRelease;
1202
+ const volRelease = endTime + note.voiceParams.volRelease;
1203
+ const modRelease = endTime + note.voiceParams.modRelease;
1053
1204
  note.filterNode.frequency
1054
- .cancelScheduledValues(stopTime)
1205
+ .cancelScheduledValues(endTime)
1055
1206
  .linearRampToValueAtTime(0, modRelease);
1056
- return this.stopNote(stopTime, volEndTime, scheduledNotes, i);
1207
+ const stopTime = Math.min(volRelease, modRelease);
1208
+ return this.stopNote(endTime, stopTime, scheduledNotes, i);
1057
1209
  }
1058
1210
  else {
1059
- const portamentoTime = stopTime + channel.portamentoTime;
1211
+ const portamentoTime = endTime + state.portamentoTime;
1060
1212
  const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1061
1213
  const detune = note.bufferSource.detune.value + detuneChange;
1062
1214
  note.bufferSource.detune
1063
- .cancelScheduledValues(stopTime)
1215
+ .cancelScheduledValues(endTime)
1064
1216
  .linearRampToValueAtTime(detune, portamentoTime);
1065
- return this.stopNote(stopTime, portamentoTime, scheduledNotes, i);
1217
+ return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1066
1218
  }
1067
1219
  }
1068
1220
  }
@@ -1074,7 +1226,7 @@ export class MidyGM2 {
1074
1226
  const velocity = halfVelocity * 2;
1075
1227
  const channel = this.channels[channelNumber];
1076
1228
  const promises = [];
1077
- channel.sustainPedal = false;
1229
+ channel.state.sustainPedal = halfVelocity;
1078
1230
  channel.scheduledNotes.forEach((noteList) => {
1079
1231
  for (let i = 0; i < noteList.length; i++) {
1080
1232
  const note = noteList[i];
@@ -1091,7 +1243,7 @@ export class MidyGM2 {
1091
1243
  const velocity = halfVelocity * 2;
1092
1244
  const channel = this.channels[channelNumber];
1093
1245
  const promises = [];
1094
- channel.sostenutoPedal = false;
1246
+ channel.state.sostenutoPedal = 0;
1095
1247
  channel.sostenutoNotes.forEach((activeNote) => {
1096
1248
  const { noteNumber } = activeNote;
1097
1249
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1139,18 +1291,232 @@ export class MidyGM2 {
1139
1291
  .setValueAtTime(gain * pressure, now);
1140
1292
  });
1141
1293
  }
1294
+ // this.applyVoiceParams(channel, 13);
1142
1295
  }
1143
1296
  handlePitchBendMessage(channelNumber, lsb, msb) {
1144
- const pitchBend = msb * 128 + lsb - 8192;
1297
+ const pitchBend = msb * 128 + lsb;
1145
1298
  this.setPitchBend(channelNumber, pitchBend);
1146
1299
  }
1147
- setPitchBend(channelNumber, pitchBend) {
1300
+ setPitchBend(channelNumber, value) {
1148
1301
  const channel = this.channels[channelNumber];
1149
- const prevPitchBend = channel.pitchBend;
1150
- channel.pitchBend = pitchBend / 8192;
1151
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1152
- channel.pitchBendRange * 100;
1302
+ const state = channel.state;
1303
+ state.pitchWheel = value / 16383;
1304
+ const pitchWheel = (value - 8192) / 8192;
1305
+ const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1153
1306
  this.updateDetune(channel, detuneChange);
1307
+ this.applyVoiceParams(channel, 14);
1308
+ }
1309
+ setModLfoToPitch(channel, note) {
1310
+ const now = this.audioContext.currentTime;
1311
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1312
+ const modulationDepth = Math.abs(modLfoToPitch) +
1313
+ channel.state.modulationDepth;
1314
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1315
+ note.modulationDepth.gain
1316
+ .cancelScheduledValues(now)
1317
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1318
+ }
1319
+ setModLfoToVolume(note) {
1320
+ const now = this.audioContext.currentTime;
1321
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1322
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1323
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1324
+ note.volumeDepth.gain
1325
+ .cancelScheduledValues(now)
1326
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1327
+ }
1328
+ setChorusEffectsSend(note, prevValue) {
1329
+ if (0 < prevValue) {
1330
+ if (0 < note.voiceParams.chorusEffectsSend) {
1331
+ const now = this.audioContext.currentTime;
1332
+ const value = note.voiceParams.chorusEffectsSend;
1333
+ note.chorusEffectsSend.gain
1334
+ .cancelScheduledValues(now)
1335
+ .setValueAtTime(value, now);
1336
+ }
1337
+ else {
1338
+ note.chorusEffectsSend.disconnect();
1339
+ }
1340
+ }
1341
+ else {
1342
+ if (0 < note.voiceParams.chorusEffectsSend) {
1343
+ if (!note.chorusEffectsSend) {
1344
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1345
+ gain: note.voiceParams.chorusEffectsSend,
1346
+ });
1347
+ note.volumeNode.connect(note.chorusEffectsSend);
1348
+ }
1349
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1350
+ }
1351
+ }
1352
+ }
1353
+ setReverbEffectsSend(note, prevValue) {
1354
+ if (0 < prevValue) {
1355
+ if (0 < note.voiceParams.reverbEffectsSend) {
1356
+ const now = this.audioContext.currentTime;
1357
+ const value = note.voiceParams.reverbEffectsSend;
1358
+ note.reverbEffectsSend.gain
1359
+ .cancelScheduledValues(now)
1360
+ .setValueAtTime(value, now);
1361
+ }
1362
+ else {
1363
+ note.reverbEffectsSend.disconnect();
1364
+ }
1365
+ }
1366
+ else {
1367
+ if (0 < note.voiceParams.reverbEffectsSend) {
1368
+ if (!note.reverbEffectsSend) {
1369
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1370
+ gain: note.voiceParams.reverbEffectsSend,
1371
+ });
1372
+ note.volumeNode.connect(note.reverbEffectsSend);
1373
+ }
1374
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1375
+ }
1376
+ }
1377
+ }
1378
+ setVibLfoToPitch(channel, note) {
1379
+ const now = this.audioContext.currentTime;
1380
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1381
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1382
+ 2;
1383
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1384
+ note.vibratoDepth.gain
1385
+ .cancelScheduledValues(now)
1386
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1387
+ }
1388
+ setModLfoToFilterFc(note) {
1389
+ const now = this.audioContext.currentTime;
1390
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1391
+ note.filterDepth.gain
1392
+ .cancelScheduledValues(now)
1393
+ .setValueAtTime(modLfoToFilterFc, now);
1394
+ }
1395
+ setDelayModLFO(note) {
1396
+ const now = this.audioContext.currentTime;
1397
+ const startTime = note.startTime;
1398
+ if (startTime < now)
1399
+ return;
1400
+ note.modulationLFO.stop(now);
1401
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1402
+ note.modulationLFO.connect(note.filterDepth);
1403
+ }
1404
+ setFreqModLFO(note) {
1405
+ const now = this.audioContext.currentTime;
1406
+ const freqModLFO = note.voiceParams.freqModLFO;
1407
+ note.modulationLFO.frequency
1408
+ .cancelScheduledValues(now)
1409
+ .setValueAtTime(freqModLFO, now);
1410
+ }
1411
+ createVoiceParamsHandlers() {
1412
+ return {
1413
+ modLfoToPitch: (channel, note, _prevValue) => {
1414
+ if (0 < channel.state.modulationDepth) {
1415
+ this.setModLfoToPitch(channel, note);
1416
+ }
1417
+ },
1418
+ vibLfoToPitch: (channel, note, _prevValue) => {
1419
+ if (0 < channel.state.vibratoDepth) {
1420
+ this.setVibLfoToPitch(channel, note);
1421
+ }
1422
+ },
1423
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1424
+ if (0 < channel.state.modulationDepth)
1425
+ this.setModLfoToFilterFc(note);
1426
+ },
1427
+ modLfoToVolume: (channel, note) => {
1428
+ if (0 < channel.state.modulationDepth)
1429
+ this.setModLfoToVolume(note);
1430
+ },
1431
+ chorusEffectsSend: (_channel, note, prevValue) => {
1432
+ this.setChorusEffectsSend(note, prevValue);
1433
+ },
1434
+ reverbEffectsSend: (_channel, note, prevValue) => {
1435
+ this.setReverbEffectsSend(note, prevValue);
1436
+ },
1437
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1438
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1439
+ delayVibLFO: (channel, note, prevValue) => {
1440
+ if (0 < channel.state.vibratoDepth) {
1441
+ const now = this.audioContext.currentTime;
1442
+ const prevStartTime = note.startTime +
1443
+ prevValue * channel.state.vibratoDelay * 2;
1444
+ if (now < prevStartTime)
1445
+ return;
1446
+ const startTime = note.startTime +
1447
+ value * channel.state.vibratoDelay * 2;
1448
+ note.vibratoLFO.stop(now);
1449
+ note.vibratoLFO.start(startTime);
1450
+ }
1451
+ },
1452
+ freqVibLFO: (channel, note, _prevValue) => {
1453
+ if (0 < channel.state.vibratoDepth) {
1454
+ const now = this.audioContext.currentTime;
1455
+ note.vibratoLFO.frequency
1456
+ .cancelScheduledValues(now)
1457
+ .setValueAtTime(value * sate.vibratoRate, now);
1458
+ }
1459
+ },
1460
+ };
1461
+ }
1462
+ getControllerState(channel, noteNumber, velocity) {
1463
+ const state = new Float32Array(channel.state.array.length);
1464
+ state.set(channel.state.array);
1465
+ state[2] = velocity / 127;
1466
+ state[3] = noteNumber / 127;
1467
+ return state;
1468
+ }
1469
+ applyVoiceParams(channel, controllerType) {
1470
+ channel.scheduledNotes.forEach((noteList) => {
1471
+ for (let i = 0; i < noteList.length; i++) {
1472
+ const note = noteList[i];
1473
+ if (!note)
1474
+ continue;
1475
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1476
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1477
+ let appliedFilterEnvelope = false;
1478
+ let appliedVolumeEnvelope = false;
1479
+ for (const [key, value] of Object.entries(voiceParams)) {
1480
+ const prevValue = note.voiceParams[key];
1481
+ if (value === prevValue)
1482
+ continue;
1483
+ note.voiceParams[key] = value;
1484
+ if (key in this.voiceParamsHandlers) {
1485
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1486
+ }
1487
+ else if (filterEnvelopeKeySet.has(key)) {
1488
+ if (appliedFilterEnvelope)
1489
+ continue;
1490
+ appliedFilterEnvelope = true;
1491
+ const noteVoiceParams = note.voiceParams;
1492
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1493
+ const key = filterEnvelopeKeys[i];
1494
+ if (key in voiceParams)
1495
+ noteVoiceParams[key] = voiceParams[key];
1496
+ }
1497
+ if (note.portamento) {
1498
+ this.setPortamentoStartFilterEnvelope(channel, note);
1499
+ }
1500
+ else {
1501
+ this.setFilterEnvelope(channel, note);
1502
+ }
1503
+ this.setPitch(channel, note);
1504
+ }
1505
+ else if (volumeEnvelopeKeySet.has(key)) {
1506
+ if (appliedVolumeEnvelope)
1507
+ continue;
1508
+ appliedVolumeEnvelope = true;
1509
+ const noteVoiceParams = note.voiceParams;
1510
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1511
+ const key = volumeEnvelopeKeys[i];
1512
+ if (key in voiceParams)
1513
+ noteVoiceParams[key] = voiceParams[key];
1514
+ }
1515
+ this.setVolumeEnvelope(channel, note);
1516
+ }
1517
+ }
1518
+ }
1519
+ });
1154
1520
  }
1155
1521
  createControlChangeHandlers() {
1156
1522
  return {
@@ -1180,13 +1546,16 @@ export class MidyGM2 {
1180
1546
  127: this.polyOn,
1181
1547
  };
1182
1548
  }
1183
- handleControlChange(channelNumber, controller, value) {
1184
- const handler = this.controlChangeHandlers[controller];
1549
+ handleControlChange(channelNumber, controllerType, value) {
1550
+ const handler = this.controlChangeHandlers[controllerType];
1185
1551
  if (handler) {
1186
1552
  handler.call(this, channelNumber, value);
1553
+ const channel = this.channels[channelNumber];
1554
+ const controller = 128 + controllerType;
1555
+ this.applyVoiceParams(channel, controller);
1187
1556
  }
1188
1557
  else {
1189
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1558
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1190
1559
  }
1191
1560
  }
1192
1561
  setBankMSB(channelNumber, msb) {
@@ -1200,11 +1569,10 @@ export class MidyGM2 {
1200
1569
  if (!note)
1201
1570
  continue;
1202
1571
  if (note.modulationDepth) {
1203
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1572
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1204
1573
  }
1205
1574
  else {
1206
- const semitoneOffset = this.calcSemitoneOffset(channel);
1207
- this.setPitch(note, semitoneOffset);
1575
+ this.setPitch(channel, note);
1208
1576
  this.startModulation(channel, note, now);
1209
1577
  }
1210
1578
  }
@@ -1212,21 +1580,22 @@ export class MidyGM2 {
1212
1580
  }
1213
1581
  setModulationDepth(channelNumber, modulation) {
1214
1582
  const channel = this.channels[channelNumber];
1215
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1583
+ channel.state.modulationDepth = (modulation / 127) *
1584
+ channel.modulationDepthRange;
1216
1585
  this.updateModulation(channel);
1217
1586
  }
1218
1587
  setPortamentoTime(channelNumber, portamentoTime) {
1219
1588
  const channel = this.channels[channelNumber];
1220
1589
  const factor = 5 * Math.log(10) / 127;
1221
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1590
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1222
1591
  }
1223
1592
  setVolume(channelNumber, volume) {
1224
1593
  const channel = this.channels[channelNumber];
1225
- channel.volume = volume / 127;
1594
+ channel.state.volume = volume / 127;
1226
1595
  this.updateChannelVolume(channel);
1227
1596
  }
1228
1597
  panToGain(pan) {
1229
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1598
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1230
1599
  return {
1231
1600
  gainLeft: Math.cos(theta),
1232
1601
  gainRight: Math.sin(theta),
@@ -1234,12 +1603,12 @@ export class MidyGM2 {
1234
1603
  }
1235
1604
  setPan(channelNumber, pan) {
1236
1605
  const channel = this.channels[channelNumber];
1237
- channel.pan = pan;
1606
+ channel.state.pan = pan / 127;
1238
1607
  this.updateChannelVolume(channel);
1239
1608
  }
1240
1609
  setExpression(channelNumber, expression) {
1241
1610
  const channel = this.channels[channelNumber];
1242
- channel.expression = expression / 127;
1611
+ channel.state.expression = expression / 127;
1243
1612
  this.updateChannelVolume(channel);
1244
1613
  }
1245
1614
  setBankLSB(channelNumber, lsb) {
@@ -1251,8 +1620,9 @@ export class MidyGM2 {
1251
1620
  }
1252
1621
  updateChannelVolume(channel) {
1253
1622
  const now = this.audioContext.currentTime;
1254
- const volume = channel.volume * channel.expression;
1255
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1623
+ const state = channel.state;
1624
+ const volume = state.volume * state.expression;
1625
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1256
1626
  channel.gainL.gain
1257
1627
  .cancelScheduledValues(now)
1258
1628
  .setValueAtTime(volume * gainLeft, now);
@@ -1261,68 +1631,100 @@ export class MidyGM2 {
1261
1631
  .setValueAtTime(volume * gainRight, now);
1262
1632
  }
1263
1633
  setSustainPedal(channelNumber, value) {
1264
- const isOn = value >= 64;
1265
- this.channels[channelNumber].sustainPedal = isOn;
1266
- if (!isOn) {
1634
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1635
+ if (value < 64) {
1267
1636
  this.releaseSustainPedal(channelNumber, value);
1268
1637
  }
1269
1638
  }
1270
1639
  setPortamento(channelNumber, value) {
1271
- this.channels[channelNumber].portamento = value >= 64;
1640
+ this.channels[channelNumber].state.portamento = value / 127;
1272
1641
  }
1273
1642
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1274
1643
  const channel = this.channels[channelNumber];
1644
+ const state = channel.state;
1275
1645
  const reverbEffect = this.reverbEffect;
1276
- if (0 < channel.reverbSendLevel) {
1646
+ if (0 < state.reverbSendLevel) {
1277
1647
  if (0 < reverbSendLevel) {
1278
1648
  const now = this.audioContext.currentTime;
1279
- channel.reverbSendLevel = reverbSendLevel / 127;
1280
- reverbEffect.output.gain.cancelScheduledValues(now);
1281
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1649
+ state.reverbSendLevel = reverbSendLevel / 127;
1650
+ reverbEffect.input.gain.cancelScheduledValues(now);
1651
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1282
1652
  }
1283
1653
  else {
1284
- channel.merger.disconnect(reverbEffect.input);
1654
+ channel.scheduledNotes.forEach((noteList) => {
1655
+ for (let i = 0; i < noteList.length; i++) {
1656
+ const note = noteList[i];
1657
+ if (!note)
1658
+ continue;
1659
+ if (note.voiceParams.reverbEffectsSend <= 0)
1660
+ continue;
1661
+ note.reverbEffectsSend.disconnect();
1662
+ }
1663
+ });
1285
1664
  }
1286
1665
  }
1287
1666
  else {
1288
1667
  if (0 < reverbSendLevel) {
1289
- channel.merger.connect(reverbEffect.input);
1290
1668
  const now = this.audioContext.currentTime;
1291
- channel.reverbSendLevel = reverbSendLevel / 127;
1292
- reverbEffect.output.gain.cancelScheduledValues(now);
1293
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1669
+ channel.scheduledNotes.forEach((noteList) => {
1670
+ for (let i = 0; i < noteList.length; i++) {
1671
+ const note = noteList[i];
1672
+ if (!note)
1673
+ continue;
1674
+ this.setReverbEffectsSend(note, 0);
1675
+ }
1676
+ });
1677
+ state.reverbSendLevel = reverbSendLevel / 127;
1678
+ reverbEffect.input.gain.cancelScheduledValues(now);
1679
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1294
1680
  }
1295
1681
  }
1296
1682
  }
1297
1683
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1298
1684
  const channel = this.channels[channelNumber];
1685
+ const state = channel.state;
1299
1686
  const chorusEffect = this.chorusEffect;
1300
- if (0 < channel.chorusSendLevel) {
1687
+ if (0 < state.chorusSendLevel) {
1301
1688
  if (0 < chorusSendLevel) {
1302
1689
  const now = this.audioContext.currentTime;
1303
- channel.chorusSendLevel = chorusSendLevel / 127;
1304
- chorusEffect.output.gain.cancelScheduledValues(now);
1305
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1690
+ state.chorusSendLevel = chorusSendLevel / 127;
1691
+ chorusEffect.input.gain.cancelScheduledValues(now);
1692
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1306
1693
  }
1307
1694
  else {
1308
- channel.merger.disconnect(chorusEffect.input);
1695
+ channel.scheduledNotes.forEach((noteList) => {
1696
+ for (let i = 0; i < noteList.length; i++) {
1697
+ const note = noteList[i];
1698
+ if (!note)
1699
+ continue;
1700
+ if (note.voiceParams.chorusEffectsSend <= 0)
1701
+ continue;
1702
+ note.chorusEffectsSend.disconnect();
1703
+ }
1704
+ });
1309
1705
  }
1310
1706
  }
1311
1707
  else {
1312
1708
  if (0 < chorusSendLevel) {
1313
- channel.merger.connect(chorusEffect.input);
1314
1709
  const now = this.audioContext.currentTime;
1315
- channel.chorusSendLevel = chorusSendLevel / 127;
1316
- chorusEffect.output.gain.cancelScheduledValues(now);
1317
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1710
+ channel.scheduledNotes.forEach((noteList) => {
1711
+ for (let i = 0; i < noteList.length; i++) {
1712
+ const note = noteList[i];
1713
+ if (!note)
1714
+ continue;
1715
+ this.setChorusEffectsSend(note, 0);
1716
+ }
1717
+ });
1718
+ state.chorusSendLevel = chorusSendLevel / 127;
1719
+ chorusEffect.input.gain.cancelScheduledValues(now);
1720
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1318
1721
  }
1319
1722
  }
1320
1723
  }
1321
1724
  setSostenutoPedal(channelNumber, value) {
1322
- const isOn = value >= 64;
1323
1725
  const channel = this.channels[channelNumber];
1324
- channel.sostenutoPedal = isOn;
1325
- if (isOn) {
1726
+ channel.state.sostenutoPedal = value / 127;
1727
+ if (64 <= value) {
1326
1728
  const now = this.audioContext.currentTime;
1327
1729
  const activeNotes = this.getActiveNotes(channel, now);
1328
1730
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1333,7 +1735,7 @@ export class MidyGM2 {
1333
1735
  }
1334
1736
  setSoftPedal(channelNumber, softPedal) {
1335
1737
  const channel = this.channels[channelNumber];
1336
- channel.softPedal = softPedal / 127;
1738
+ channel.state.softPedal = softPedal / 127;
1337
1739
  }
1338
1740
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1339
1741
  if (maxLSB < channel.dataLSB) {
@@ -1391,7 +1793,7 @@ export class MidyGM2 {
1391
1793
  this.channels[channelNumber].dataMSB = value;
1392
1794
  this.handleRPN(channelNumber);
1393
1795
  }
1394
- updateDetune(channel, detuneChange) {
1796
+ updateDetune(channel, detune) {
1395
1797
  const now = this.audioContext.currentTime;
1396
1798
  channel.scheduledNotes.forEach((noteList) => {
1397
1799
  for (let i = 0; i < noteList.length; i++) {
@@ -1399,7 +1801,6 @@ export class MidyGM2 {
1399
1801
  if (!note)
1400
1802
  continue;
1401
1803
  const { bufferSource } = note;
1402
- const detune = bufferSource.detune.value + detuneChange;
1403
1804
  bufferSource.detune
1404
1805
  .cancelScheduledValues(now)
1405
1806
  .setValueAtTime(detune, now);
@@ -1412,13 +1813,13 @@ export class MidyGM2 {
1412
1813
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1413
1814
  this.setPitchBendRange(channelNumber, pitchBendRange);
1414
1815
  }
1415
- setPitchBendRange(channelNumber, pitchBendRange) {
1816
+ setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1416
1817
  const channel = this.channels[channelNumber];
1417
- const prevPitchBendRange = channel.pitchBendRange;
1418
- channel.pitchBendRange = pitchBendRange;
1419
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1420
- channel.pitchBend * 100;
1421
- this.updateDetune(channel, detuneChange);
1818
+ const state = channel.state;
1819
+ state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1820
+ const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1821
+ this.updateDetune(channel, detune);
1822
+ this.applyVoiceParams(channel, 16);
1422
1823
  }
1423
1824
  handleFineTuningRPN(channelNumber) {
1424
1825
  const channel = this.channels[channelNumber];
@@ -1462,7 +1863,30 @@ export class MidyGM2 {
1462
1863
  return this.stopChannelNotes(channelNumber, 0, true);
1463
1864
  }
1464
1865
  resetAllControllers(channelNumber) {
1465
- Object.assign(this.channels[channelNumber], this.effectSettings);
1866
+ const stateTypes = [
1867
+ "expression",
1868
+ "modulationDepth",
1869
+ "sustainPedal",
1870
+ "portamento",
1871
+ "sostenutoPedal",
1872
+ "softPedal",
1873
+ "channelPressure",
1874
+ "pitchWheelSensitivity",
1875
+ ];
1876
+ const channel = this.channels[channelNumber];
1877
+ const state = channel.state;
1878
+ for (let i = 0; i < stateTypes.length; i++) {
1879
+ const type = stateTypes[i];
1880
+ state[type] = defaultControllerState[type];
1881
+ }
1882
+ const settingTypes = [
1883
+ "rpnMSB",
1884
+ "rpnLSB",
1885
+ ];
1886
+ for (let i = 0; i < settingTypes.length; i++) {
1887
+ const type = settingTypes[i];
1888
+ channel[type] = this.constructor.channelSettings[type];
1889
+ }
1466
1890
  }
1467
1891
  allNotesOff(channelNumber) {
1468
1892
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1656,10 +2080,8 @@ export class MidyGM2 {
1656
2080
  }
1657
2081
  setReverbTime(value) {
1658
2082
  this.reverb.time = this.getReverbTime(value);
1659
- const { audioContext, channels, options } = this;
1660
- for (let i = 0; i < channels.length; i++) {
1661
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1662
- }
2083
+ const { audioContext, options } = this;
2084
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1663
2085
  }
1664
2086
  getReverbTime(value) {
1665
2087
  return Math.pow(Math.E, (value - 40) * 0.025);
@@ -1736,10 +2158,7 @@ export class MidyGM2 {
1736
2158
  const now = this.audioContext.currentTime;
1737
2159
  const modRate = this.getChorusModRate(value);
1738
2160
  this.chorus.modRate = modRate;
1739
- for (let i = 0; i < this.channels.length; i++) {
1740
- const lfo = this.channels[i].chorusEffect.lfo;
1741
- lfo.frequency.setValueAtTime(modRate, now);
1742
- }
2161
+ this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
1743
2162
  }
1744
2163
  getChorusModRate(value) {
1745
2164
  return value * 0.122; // Hz
@@ -1748,12 +2167,9 @@ export class MidyGM2 {
1748
2167
  const now = this.audioContext.currentTime;
1749
2168
  const modDepth = this.getChorusModDepth(value);
1750
2169
  this.chorus.modDepth = modDepth;
1751
- for (let i = 0; i < this.channels.length; i++) {
1752
- const chorusEffect = this.channels[i].chorusEffect;
1753
- chorusEffect.lfoGain.gain
1754
- .cancelScheduledValues(now)
1755
- .setValueAtTime(modDepth / 2, now);
1756
- }
2170
+ this.chorusEffect.lfoGain.gain
2171
+ .cancelScheduledValues(now)
2172
+ .setValueAtTime(modDepth / 2, now);
1757
2173
  }
1758
2174
  getChorusModDepth(value) {
1759
2175
  return (value + 1) / 3200; // second
@@ -1762,14 +2178,11 @@ export class MidyGM2 {
1762
2178
  const now = this.audioContext.currentTime;
1763
2179
  const feedback = this.getChorusFeedback(value);
1764
2180
  this.chorus.feedback = feedback;
1765
- for (let i = 0; i < this.channels.length; i++) {
1766
- const chorusEffect = this.channels[i].chorusEffect;
1767
- for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1768
- const feedbackGain = chorusEffect.feedbackGains[j];
1769
- feedbackGain.gain
1770
- .cancelScheduledValues(now)
1771
- .setValueAtTime(feedback, now);
1772
- }
2181
+ const chorusEffect = this.chorusEffect;
2182
+ for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
2183
+ chorusEffect.feedbackGains[i].gain
2184
+ .cancelScheduledValues(now)
2185
+ .setValueAtTime(feedback, now);
1773
2186
  }
1774
2187
  }
1775
2188
  getChorusFeedback(value) {
@@ -1777,15 +2190,28 @@ export class MidyGM2 {
1777
2190
  }
1778
2191
  setChorusSendToReverb(value) {
1779
2192
  const sendToReverb = this.getChorusSendToReverb(value);
1780
- if (0 < sendToReverb) {
1781
- const now = this.audioContext.currentTime;
2193
+ const sendGain = this.chorusEffect.sendGain;
2194
+ if (0 < this.chorus.sendToReverb) {
1782
2195
  this.chorus.sendToReverb = sendToReverb;
1783
- this.chorusEffect.sendGain.gain
1784
- .cancelScheduledValues(now)
1785
- .setValueAtTime(sendToReverb, now);
2196
+ if (0 < sendToReverb) {
2197
+ const now = this.audioContext.currentTime;
2198
+ sendGain.gain
2199
+ .cancelScheduledValues(now)
2200
+ .setValueAtTime(sendToReverb, now);
2201
+ }
2202
+ else {
2203
+ sendGain.disconnect();
2204
+ }
1786
2205
  }
1787
- else if (this.chorus.sendToReverb !== 0) {
1788
- this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
2206
+ else {
2207
+ this.chorus.sendToReverb = sendToReverb;
2208
+ if (0 < sendToReverb) {
2209
+ const now = this.audioContext.currentTime;
2210
+ sendGain.connect(this.reverbEffect.input);
2211
+ sendGain.gain
2212
+ .cancelScheduledValues(now)
2213
+ .setValueAtTime(sendToReverb, now);
2214
+ }
1789
2215
  }
1790
2216
  }
1791
2217
  getChorusSendToReverb(value) {
@@ -1822,43 +2248,19 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1822
2248
  writable: true,
1823
2249
  value: {
1824
2250
  currentBufferSource: null,
1825
- volume: 100 / 127,
1826
- pan: 64,
1827
- portamentoTime: 1, // sec
1828
- reverbSendLevel: 0,
1829
- chorusSendLevel: 0,
1830
- vibratoRate: 1,
1831
- vibratoDepth: 1,
1832
- vibratoDelay: 1,
2251
+ program: 0,
1833
2252
  bank: 121 * 128,
1834
2253
  bankMSB: 121,
1835
2254
  bankLSB: 0,
1836
2255
  dataMSB: 0,
1837
2256
  dataLSB: 0,
1838
- program: 0,
1839
- pitchBend: 0,
2257
+ rpnMSB: 127,
2258
+ rpnLSB: 127,
1840
2259
  fineTuning: 0, // cb
1841
2260
  coarseTuning: 0, // cb
1842
2261
  modulationDepthRange: 50, // cent
1843
2262
  }
1844
2263
  });
1845
- Object.defineProperty(MidyGM2, "effectSettings", {
1846
- enumerable: true,
1847
- configurable: true,
1848
- writable: true,
1849
- value: {
1850
- expression: 1,
1851
- modulationDepth: 0,
1852
- sustainPedal: false,
1853
- portamento: false,
1854
- sostenutoPedal: false,
1855
- softPedal: 0,
1856
- rpnMSB: 127,
1857
- rpnLSB: 127,
1858
- channelPressure: 0,
1859
- pitchBendRange: 2,
1860
- }
1861
- });
1862
2264
  Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
1863
2265
  enumerable: true,
1864
2266
  configurable: true,