@marmooo/midy 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/script/midy.js CHANGED
@@ -4,7 +4,7 @@ exports.Midy = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
6
  class Note {
7
- constructor(noteNumber, velocity, startTime, instrumentKey) {
7
+ constructor(noteNumber, velocity, startTime, voice, voiceParams) {
8
8
  Object.defineProperty(this, "bufferSource", {
9
9
  enumerable: true,
10
10
  configurable: true,
@@ -65,12 +65,106 @@ class Note {
65
65
  writable: true,
66
66
  value: void 0
67
67
  });
68
+ Object.defineProperty(this, "portamento", {
69
+ enumerable: true,
70
+ configurable: true,
71
+ writable: true,
72
+ value: void 0
73
+ });
68
74
  this.noteNumber = noteNumber;
69
75
  this.velocity = velocity;
70
76
  this.startTime = startTime;
71
- this.instrumentKey = instrumentKey;
77
+ this.voice = voice;
78
+ this.voiceParams = voiceParams;
72
79
  }
73
80
  }
81
+ // normalized to 0-1 for use with the SF2 modulator model
82
+ const defaultControllerState = {
83
+ noteOnVelocity: { type: 2, defaultValue: 0 },
84
+ noteOnKeyNumber: { type: 3, defaultValue: 0 },
85
+ polyPressure: { type: 10, defaultValue: 0 },
86
+ channelPressure: { type: 13, defaultValue: 0 },
87
+ pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
88
+ pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
89
+ link: { type: 127, defaultValue: 0 },
90
+ // bankMSB: { type: 128 + 0, defaultValue: 121, },
91
+ modulationDepth: { type: 128 + 1, defaultValue: 0 },
92
+ portamentoTime: { type: 128 + 5, defaultValue: 0 },
93
+ // dataMSB: { type: 128 + 6, defaultValue: 0, },
94
+ volume: { type: 128 + 7, defaultValue: 100 / 127 },
95
+ pan: { type: 128 + 10, defaultValue: 0.5 },
96
+ expression: { type: 128 + 11, defaultValue: 1 },
97
+ // bankLSB: { type: 128 + 32, defaultValue: 0, },
98
+ // dataLSB: { type: 128 + 38, defaultValue: 0, },
99
+ sustainPedal: { type: 128 + 64, defaultValue: 0 },
100
+ portamento: { type: 128 + 65, defaultValue: 0 },
101
+ sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
102
+ softPedal: { type: 128 + 67, defaultValue: 0 },
103
+ filterResonance: { type: 128 + 71, defaultValue: 0.5 },
104
+ releaseTime: { type: 128 + 72, defaultValue: 0.5 },
105
+ attackTime: { type: 128 + 73, defaultValue: 0.5 },
106
+ brightness: { type: 128 + 74, defaultValue: 0.5 },
107
+ decayTime: { type: 128 + 75, defaultValue: 0.5 },
108
+ vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
109
+ vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
110
+ vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
111
+ reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
112
+ chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
113
+ // dataIncrement: { type: 128 + 96, defaultValue: 0 },
114
+ // dataDecrement: { type: 128 + 97, defaultValue: 0 },
115
+ // rpnLSB: { type: 128 + 100, defaultValue: 127 },
116
+ // rpnMSB: { type: 128 + 101, defaultValue: 127 },
117
+ // allSoundOff: { type: 128 + 120, defaultValue: 0 },
118
+ // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
119
+ // allNotesOff: { type: 128 + 123, defaultValue: 0 },
120
+ // omniOff: { type: 128 + 124, defaultValue: 0 },
121
+ // omniOn: { type: 128 + 125, defaultValue: 0 },
122
+ // monoOn: { type: 128 + 126, defaultValue: 0 },
123
+ // polyOn: { type: 128 + 127, defaultValue: 0 },
124
+ };
125
+ class ControllerState {
126
+ constructor() {
127
+ Object.defineProperty(this, "array", {
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true,
131
+ value: new Float32Array(256)
132
+ });
133
+ const entries = Object.entries(defaultControllerState);
134
+ for (const [name, { type, defaultValue }] of entries) {
135
+ this.array[type] = defaultValue;
136
+ Object.defineProperty(this, name, {
137
+ get: () => this.array[type],
138
+ set: (value) => this.array[type] = value,
139
+ enumerable: true,
140
+ configurable: true,
141
+ });
142
+ }
143
+ }
144
+ }
145
+ const filterEnvelopeKeys = [
146
+ "modEnvToPitch",
147
+ "initialFilterFc",
148
+ "modEnvToFilterFc",
149
+ "modDelay",
150
+ "modAttack",
151
+ "modHold",
152
+ "modDecay",
153
+ "modSustain",
154
+ "modRelease",
155
+ "playbackRate",
156
+ ];
157
+ const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
158
+ const volumeEnvelopeKeys = [
159
+ "volDelay",
160
+ "volAttack",
161
+ "volHold",
162
+ "volDecay",
163
+ "volSustain",
164
+ "volRelease",
165
+ "initialAttenuation",
166
+ ];
167
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
74
168
  class Midy {
75
169
  constructor(audioContext, options = this.defaultOptions) {
76
170
  Object.defineProperty(this, "ticksPerBeat", {
@@ -251,6 +345,7 @@ class Midy {
251
345
  this.audioContext = audioContext;
252
346
  this.options = { ...this.defaultOptions, ...options };
253
347
  this.masterGain = new GainNode(audioContext);
348
+ this.voiceParamsHandlers = this.createVoiceParamsHandlers();
254
349
  this.controlChangeHandlers = this.createControlChangeHandlers();
255
350
  this.channels = this.createChannels(audioContext);
256
351
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
@@ -297,7 +392,7 @@ class Midy {
297
392
  this.totalTime = this.calcTotalTime();
298
393
  }
299
394
  setChannelAudioNodes(audioContext) {
300
- const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
395
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
301
396
  const gainL = new GainNode(audioContext, { gain: gainLeft });
302
397
  const gainR = new GainNode(audioContext, { gain: gainRight });
303
398
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -314,7 +409,7 @@ class Midy {
314
409
  const channels = Array.from({ length: 16 }, () => {
315
410
  return {
316
411
  ...this.constructor.channelSettings,
317
- ...this.constructor.effectSettings,
412
+ state: new ControllerState(),
318
413
  ...this.setChannelAudioNodes(audioContext),
319
414
  scheduledNotes: new Map(),
320
415
  sostenutoNotes: new Map(),
@@ -328,11 +423,11 @@ class Midy {
328
423
  });
329
424
  return channels;
330
425
  }
331
- async createNoteBuffer(instrumentKey, isSF3) {
332
- const sampleStart = instrumentKey.start;
333
- const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
426
+ async createNoteBuffer(voiceParams, isSF3) {
427
+ const sampleStart = voiceParams.start;
428
+ const sampleEnd = voiceParams.sample.length + voiceParams.end;
334
429
  if (isSF3) {
335
- const sample = instrumentKey.sample;
430
+ const sample = voiceParams.sample;
336
431
  const start = sample.byteOffset + sampleStart;
337
432
  const end = sample.byteOffset + sampleEnd;
338
433
  const buffer = sample.buffer.slice(start, end);
@@ -340,14 +435,14 @@ class Midy {
340
435
  return audioBuffer;
341
436
  }
342
437
  else {
343
- const sample = instrumentKey.sample;
438
+ const sample = voiceParams.sample;
344
439
  const start = sample.byteOffset + sampleStart;
345
440
  const end = sample.byteOffset + sampleEnd;
346
441
  const buffer = sample.buffer.slice(start, end);
347
442
  const audioBuffer = new AudioBuffer({
348
443
  numberOfChannels: 1,
349
444
  length: sample.length,
350
- sampleRate: instrumentKey.sampleRate,
445
+ sampleRate: voiceParams.sampleRate,
351
446
  });
352
447
  const channelData = audioBuffer.getChannelData(0);
353
448
  const int16Array = new Int16Array(buffer);
@@ -357,15 +452,14 @@ class Midy {
357
452
  return audioBuffer;
358
453
  }
359
454
  }
360
- async createNoteBufferNode(instrumentKey, isSF3) {
455
+ async createNoteBufferNode(voiceParams, isSF3) {
361
456
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
362
- const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
457
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
363
458
  bufferSource.buffer = audioBuffer;
364
- bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
459
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
365
460
  if (bufferSource.loop) {
366
- bufferSource.loopStart = instrumentKey.loopStart /
367
- instrumentKey.sampleRate;
368
- bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
461
+ bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
462
+ bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
369
463
  }
370
464
  return bufferSource;
371
465
  }
@@ -422,7 +516,7 @@ class Midy {
422
516
  this.handleChannelPressure(event.channel, event.amount);
423
517
  break;
424
518
  case "pitchBend":
425
- this.setPitchBend(event.channel, event.value);
519
+ this.setPitchBend(event.channel, event.value + 8192);
426
520
  break;
427
521
  case "sysEx":
428
522
  this.handleSysEx(event.data);
@@ -813,63 +907,94 @@ class Midy {
813
907
  cbToRatio(cb) {
814
908
  return Math.pow(10, cb / 200);
815
909
  }
910
+ rateToCent(rate) {
911
+ return 1200 * Math.log2(rate);
912
+ }
913
+ centToRate(cent) {
914
+ return Math.pow(2, cent / 1200);
915
+ }
816
916
  centToHz(cent) {
817
- return 8.176 * Math.pow(2, cent / 1200);
917
+ return 8.176 * this.centToRate(cent);
818
918
  }
819
- calcSemitoneOffset(channel) {
919
+ calcChannelDetune(channel) {
820
920
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
821
921
  const channelTuning = channel.coarseTuning + channel.fineTuning;
822
922
  const tuning = masterTuning + channelTuning;
823
- return channel.pitchBend * channel.pitchBendRange + tuning;
923
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
924
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
925
+ const pitch = pitchWheel * pitchWheelSensitivity;
926
+ return tuning + pitch;
824
927
  }
825
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
826
- return instrumentKey.playbackRate(noteNumber) *
827
- Math.pow(2, semitoneOffset / 12);
928
+ calcNoteDetune(channel, note) {
929
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
930
+ }
931
+ updateDetune(channel) {
932
+ const now = this.audioContext.currentTime;
933
+ channel.scheduledNotes.forEach((noteList) => {
934
+ for (let i = 0; i < noteList.length; i++) {
935
+ const note = noteList[i];
936
+ if (!note)
937
+ continue;
938
+ const noteDetune = this.calcNoteDetune(channel, note);
939
+ const detune = channel.detune + noteDetune;
940
+ note.bufferSource.detune
941
+ .cancelScheduledValues(now)
942
+ .setValueAtTime(detune, now);
943
+ }
944
+ });
828
945
  }
829
946
  setPortamentoStartVolumeEnvelope(channel, note) {
830
- const { instrumentKey, startTime } = note;
831
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
832
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
833
- const volDelay = startTime + instrumentKey.volDelay;
834
- const portamentoTime = volDelay + channel.portamentoTime;
947
+ const now = this.audioContext.currentTime;
948
+ const { voiceParams, startTime } = note;
949
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
950
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
951
+ const volDelay = startTime + voiceParams.volDelay;
952
+ const portamentoTime = volDelay + channel.state.portamentoTime;
835
953
  note.volumeNode.gain
836
- .cancelScheduledValues(startTime)
954
+ .cancelScheduledValues(now)
837
955
  .setValueAtTime(0, volDelay)
838
956
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
839
957
  }
840
958
  setVolumeEnvelope(channel, note) {
841
- const { instrumentKey, startTime } = note;
842
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
843
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
844
- const volDelay = startTime + instrumentKey.volDelay;
845
- const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
846
- const volHold = volAttack + instrumentKey.volHold;
847
- const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
959
+ const now = this.audioContext.currentTime;
960
+ const state = channel.state;
961
+ const { voiceParams, startTime } = note;
962
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
963
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
964
+ const volDelay = startTime + voiceParams.volDelay;
965
+ const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
966
+ const volHold = volAttack + voiceParams.volHold;
967
+ const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
848
968
  note.volumeNode.gain
849
- .cancelScheduledValues(startTime)
969
+ .cancelScheduledValues(now)
850
970
  .setValueAtTime(0, startTime)
851
971
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
852
972
  .exponentialRampToValueAtTime(attackVolume, volAttack)
853
973
  .setValueAtTime(attackVolume, volHold)
854
974
  .linearRampToValueAtTime(sustainVolume, volDecay);
855
975
  }
856
- setPitch(note, semitoneOffset) {
857
- const { instrumentKey, noteNumber, startTime } = note;
858
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
859
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
976
+ setPitchEnvelope(note) {
977
+ const now = this.audioContext.currentTime;
978
+ const { voiceParams } = note;
979
+ const baseRate = voiceParams.playbackRate;
980
+ note.bufferSource.playbackRate
981
+ .cancelScheduledValues(now)
982
+ .setValueAtTime(baseRate, now);
983
+ const modEnvToPitch = voiceParams.modEnvToPitch;
860
984
  if (modEnvToPitch === 0)
861
985
  return;
862
- const basePitch = note.bufferSource.playbackRate.value;
863
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
864
- const modDelay = startTime + instrumentKey.modDelay;
865
- const modAttack = modDelay + instrumentKey.modAttack;
866
- const modHold = modAttack + instrumentKey.modHold;
867
- const modDecay = modHold + instrumentKey.modDecay;
868
- note.bufferSource.playbackRate.value
869
- .setValueAtTime(basePitch, modDelay)
870
- .exponentialRampToValueAtTime(peekPitch, modAttack)
871
- .setValueAtTime(peekPitch, modHold)
872
- .linearRampToValueAtTime(basePitch, modDecay);
986
+ const basePitch = this.rateToCent(baseRate);
987
+ const peekPitch = basePitch + modEnvToPitch;
988
+ const peekRate = this.centToRate(peekPitch);
989
+ const modDelay = startTime + voiceParams.modDelay;
990
+ const modAttack = modDelay + voiceParams.modAttack;
991
+ const modHold = modAttack + voiceParams.modHold;
992
+ const modDecay = modHold + voiceParams.modDecay;
993
+ note.bufferSource.playbackRate
994
+ .setValueAtTime(baseRate, modDelay)
995
+ .exponentialRampToValueAtTime(peekRate, modAttack)
996
+ .setValueAtTime(peekRate, modHold)
997
+ .linearRampToValueAtTime(baseRate, modDecay);
873
998
  }
874
999
  clampCutoffFrequency(frequency) {
875
1000
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -877,42 +1002,46 @@ class Midy {
877
1002
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
878
1003
  }
879
1004
  setPortamentoStartFilterEnvelope(channel, note) {
880
- const { instrumentKey, noteNumber, startTime } = note;
1005
+ const now = this.audioContext.currentTime;
1006
+ const state = channel.state;
1007
+ const { voiceParams, noteNumber, startTime } = note;
881
1008
  const softPedalFactor = 1 -
882
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
883
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
884
- softPedalFactor * channel.brightness;
885
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
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;
886
1013
  const sustainFreq = baseFreq +
887
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1014
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
888
1015
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
889
1016
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
890
- const portamentoTime = startTime + channel.portamentoTime;
891
- const modDelay = startTime + instrumentKey.modDelay;
1017
+ const portamentoTime = startTime + channel.state.portamentoTime;
1018
+ const modDelay = startTime + voiceParams.modDelay;
892
1019
  note.filterNode.frequency
893
- .cancelScheduledValues(startTime)
1020
+ .cancelScheduledValues(now)
894
1021
  .setValueAtTime(adjustedBaseFreq, startTime)
895
1022
  .setValueAtTime(adjustedBaseFreq, modDelay)
896
1023
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
897
1024
  }
898
1025
  setFilterEnvelope(channel, note) {
899
- const { instrumentKey, noteNumber, startTime } = note;
1026
+ const now = this.audioContext.currentTime;
1027
+ const state = channel.state;
1028
+ const { voiceParams, noteNumber, startTime } = note;
900
1029
  const softPedalFactor = 1 -
901
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
902
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
903
- softPedalFactor * channel.brightness;
904
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
1030
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1031
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1032
+ softPedalFactor * state.brightness * 2;
1033
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
905
1034
  const sustainFreq = baseFreq +
906
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1035
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
907
1036
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
908
1037
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
909
1038
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
910
- const modDelay = startTime + instrumentKey.modDelay;
911
- const modAttack = modDelay + instrumentKey.modAttack;
912
- const modHold = modAttack + instrumentKey.modHold;
913
- const modDecay = modHold + instrumentKey.modDecay;
1039
+ const modDelay = startTime + voiceParams.modDelay;
1040
+ const modAttack = modDelay + voiceParams.modAttack;
1041
+ const modHold = modAttack + voiceParams.modHold;
1042
+ const modDecay = modHold + voiceParams.modDecay;
914
1043
  note.filterNode.frequency
915
- .cancelScheduledValues(startTime)
1044
+ .cancelScheduledValues(now)
916
1045
  .setValueAtTime(adjustedBaseFreq, startTime)
917
1046
  .setValueAtTime(adjustedBaseFreq, modDelay)
918
1047
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -920,25 +1049,18 @@ class Midy {
920
1049
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
921
1050
  }
922
1051
  startModulation(channel, note, startTime) {
923
- const { instrumentKey } = note;
924
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1052
+ const { voiceParams } = note;
925
1053
  note.modulationLFO = new OscillatorNode(this.audioContext, {
926
- frequency: this.centToHz(instrumentKey.freqModLFO),
1054
+ frequency: this.centToHz(voiceParams.freqModLFO),
927
1055
  });
928
1056
  note.filterDepth = new GainNode(this.audioContext, {
929
- gain: instrumentKey.modLfoToFilterFc,
930
- });
931
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
932
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
933
- note.modulationDepth = new GainNode(this.audioContext, {
934
- gain: modulationDepth * modulationDepthSign,
1057
+ gain: voiceParams.modLfoToFilterFc,
935
1058
  });
936
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
937
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
938
- note.volumeDepth = new GainNode(this.audioContext, {
939
- gain: volumeDepth * volumeDepthSign,
940
- });
941
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1059
+ note.modulationDepth = new GainNode(this.audioContext);
1060
+ this.setModLfoToPitch(channel, note);
1061
+ note.volumeDepth = new GainNode(this.audioContext);
1062
+ this.setModLfoToVolume(note);
1063
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
942
1064
  note.modulationLFO.connect(note.filterDepth);
943
1065
  note.filterDepth.connect(note.filterNode.frequency);
944
1066
  note.modulationLFO.connect(note.modulationDepth);
@@ -947,67 +1069,57 @@ class Midy {
947
1069
  note.volumeDepth.connect(note.volumeNode.gain);
948
1070
  }
949
1071
  startVibrato(channel, note, startTime) {
950
- const { instrumentKey } = note;
951
- const { vibLfoToPitch } = instrumentKey;
1072
+ const { voiceParams } = note;
1073
+ const state = channel.state;
952
1074
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
953
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
954
- channel.vibratoRate,
955
- });
956
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
957
- const vibratoDepthSign = 0 < vibLfoToPitch;
958
- note.vibratoDepth = new GainNode(this.audioContext, {
959
- gain: vibratoDepth * vibratoDepthSign,
1075
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1076
+ state.vibratoRate,
960
1077
  });
961
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1078
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1079
+ note.vibratoDepth = new GainNode(this.audioContext);
1080
+ this.setVibLfoToPitch(channel, note);
962
1081
  note.vibratoLFO.connect(note.vibratoDepth);
963
1082
  note.vibratoDepth.connect(note.bufferSource.detune);
964
1083
  }
965
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
966
- const semitoneOffset = this.calcSemitoneOffset(channel);
967
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
968
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1084
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1085
+ const state = channel.state;
1086
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1087
+ const voiceParams = voice.getAllParams(controllerState);
1088
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1089
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
969
1090
  note.volumeNode = new GainNode(this.audioContext);
970
1091
  note.filterNode = new BiquadFilterNode(this.audioContext, {
971
1092
  type: "lowpass",
972
- Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
1093
+ Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
973
1094
  });
974
1095
  if (portamento) {
1096
+ note.portamento = true;
975
1097
  this.setPortamentoStartVolumeEnvelope(channel, note);
976
1098
  this.setPortamentoStartFilterEnvelope(channel, note);
977
1099
  }
978
1100
  else {
1101
+ note.portamento = false;
979
1102
  this.setVolumeEnvelope(channel, note);
980
1103
  this.setFilterEnvelope(channel, note);
981
1104
  }
982
- if (0 < channel.vibratoDepth) {
1105
+ if (0 < state.vibratoDepth) {
983
1106
  this.startVibrato(channel, note, startTime);
984
1107
  }
985
- if (0 < channel.modulationDepth) {
986
- this.setPitch(note, semitoneOffset);
1108
+ this.setPitchEnvelope(note);
1109
+ if (0 < state.modulationDepth) {
987
1110
  this.startModulation(channel, note, startTime);
988
1111
  }
989
- else {
990
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
991
- }
992
1112
  if (this.mono && channel.currentBufferSource) {
993
1113
  channel.currentBufferSource.stop(startTime);
994
1114
  channel.currentBufferSource = note.bufferSource;
995
1115
  }
996
1116
  note.bufferSource.connect(note.filterNode);
997
1117
  note.filterNode.connect(note.volumeNode);
998
- if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
999
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1000
- gain: instrumentKey.reverbEffectsSend,
1001
- });
1002
- note.volumeNode.connect(note.reverbEffectsSend);
1003
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1118
+ if (0 < channel.chorusSendLevel) {
1119
+ this.setChorusEffectsSend(channel, note, 0);
1004
1120
  }
1005
- if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
1006
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1007
- gain: instrumentKey.chorusEffectsSend,
1008
- });
1009
- note.volumeNode.connect(note.chorusEffectsSend);
1010
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1121
+ if (0 < channel.reverbSendLevel) {
1122
+ this.setReverbEffectsSend(channel, note, 0);
1011
1123
  }
1012
1124
  note.bufferSource.start(startTime);
1013
1125
  return note;
@@ -1029,16 +1141,16 @@ class Midy {
1029
1141
  return;
1030
1142
  const soundFont = this.soundFonts[soundFontIndex];
1031
1143
  const isSF3 = soundFont.parsed.info.version.major === 3;
1032
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
1033
- if (!instrumentKey)
1144
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1145
+ if (!voice)
1034
1146
  return;
1035
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1147
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1036
1148
  note.volumeNode.connect(channel.gainL);
1037
1149
  note.volumeNode.connect(channel.gainR);
1038
- if (channel.sostenutoPedal) {
1150
+ if (channel.state.sostenutoPedal) {
1039
1151
  channel.sostenutoNotes.set(noteNumber, note);
1040
1152
  }
1041
- const exclusiveClass = instrumentKey.exclusiveClass;
1153
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1042
1154
  if (exclusiveClass !== 0) {
1043
1155
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1044
1156
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
@@ -1100,8 +1212,9 @@ class Midy {
1100
1212
  }
1101
1213
  scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1102
1214
  const channel = this.channels[channelNumber];
1215
+ const state = channel.state;
1103
1216
  if (!force) {
1104
- if (channel.sustainPedal)
1217
+ if (0.5 < state.sustainPedal)
1105
1218
  return;
1106
1219
  if (channel.sostenutoNotes.has(noteNumber))
1107
1220
  return;
@@ -1117,8 +1230,8 @@ class Midy {
1117
1230
  continue;
1118
1231
  if (portamentoNoteNumber === undefined) {
1119
1232
  const volRelease = endTime +
1120
- note.instrumentKey.volRelease * channel.releaseTime;
1121
- const modRelease = endTime + note.instrumentKey.modRelease;
1233
+ note.voiceParams.volRelease * state.releaseTime * 2;
1234
+ const modRelease = endTime + note.voiceParams.modRelease;
1122
1235
  note.filterNode.frequency
1123
1236
  .cancelScheduledValues(endTime)
1124
1237
  .linearRampToValueAtTime(0, modRelease);
@@ -1126,12 +1239,13 @@ class Midy {
1126
1239
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1127
1240
  }
1128
1241
  else {
1129
- const portamentoTime = endTime + channel.portamentoTime;
1130
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1131
- const detune = note.bufferSource.detune.value + detuneChange;
1132
- note.bufferSource.detune
1242
+ const portamentoTime = endTime + state.portamentoTime;
1243
+ const deltaNote = portamentoNoteNumber - noteNumber;
1244
+ const baseRate = note.voiceParams.playbackRate;
1245
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1246
+ note.bufferSource.playbackRate
1133
1247
  .cancelScheduledValues(endTime)
1134
- .linearRampToValueAtTime(detune, portamentoTime);
1248
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1135
1249
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1136
1250
  }
1137
1251
  }
@@ -1144,7 +1258,7 @@ class Midy {
1144
1258
  const velocity = halfVelocity * 2;
1145
1259
  const channel = this.channels[channelNumber];
1146
1260
  const promises = [];
1147
- channel.sustainPedal = false;
1261
+ channel.state.sustainPedal = halfVelocity;
1148
1262
  channel.scheduledNotes.forEach((noteList) => {
1149
1263
  for (let i = 0; i < noteList.length; i++) {
1150
1264
  const note = noteList[i];
@@ -1161,7 +1275,7 @@ class Midy {
1161
1275
  const velocity = halfVelocity * 2;
1162
1276
  const channel = this.channels[channelNumber];
1163
1277
  const promises = [];
1164
- channel.sostenutoPedal = false;
1278
+ channel.state.sostenutoPedal = 0;
1165
1279
  channel.sostenutoNotes.forEach((activeNote) => {
1166
1280
  const { noteNumber } = activeNote;
1167
1281
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1206,6 +1320,7 @@ class Midy {
1206
1320
  .setValueAtTime(gain * pressure, now);
1207
1321
  }
1208
1322
  }
1323
+ // this.applyVoiceParams(channel, 10);
1209
1324
  }
1210
1325
  handleProgramChange(channelNumber, program) {
1211
1326
  const channel = this.channels[channelNumber];
@@ -1226,18 +1341,233 @@ class Midy {
1226
1341
  .setValueAtTime(gain * pressure, now);
1227
1342
  });
1228
1343
  }
1344
+ // this.applyVoiceParams(channel, 13);
1229
1345
  }
1230
1346
  handlePitchBendMessage(channelNumber, lsb, msb) {
1231
- const pitchBend = msb * 128 + lsb - 8192;
1347
+ const pitchBend = msb * 128 + lsb;
1232
1348
  this.setPitchBend(channelNumber, pitchBend);
1233
1349
  }
1234
- setPitchBend(channelNumber, pitchBend) {
1350
+ setPitchBend(channelNumber, value) {
1235
1351
  const channel = this.channels[channelNumber];
1236
- const prevPitchBend = channel.pitchBend;
1237
- channel.pitchBend = pitchBend / 8192;
1238
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1239
- channel.pitchBendRange * 100;
1240
- this.updateDetune(channel, detuneChange);
1352
+ const state = channel.state;
1353
+ const prev = state.pitchWheel * 2 - 1;
1354
+ const next = (value - 8192) / 8192;
1355
+ state.pitchWheel = value / 16383;
1356
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1357
+ this.updateDetune(channel);
1358
+ this.applyVoiceParams(channel, 14);
1359
+ }
1360
+ setModLfoToPitch(channel, note) {
1361
+ const now = this.audioContext.currentTime;
1362
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1363
+ const modulationDepth = Math.abs(modLfoToPitch) +
1364
+ channel.state.modulationDepth;
1365
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1366
+ note.modulationDepth.gain
1367
+ .cancelScheduledValues(now)
1368
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1369
+ }
1370
+ setVibLfoToPitch(channel, note) {
1371
+ const now = this.audioContext.currentTime;
1372
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1373
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1374
+ 2;
1375
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1376
+ note.vibratoDepth.gain
1377
+ .cancelScheduledValues(now)
1378
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1379
+ }
1380
+ setModLfoToFilterFc(note) {
1381
+ const now = this.audioContext.currentTime;
1382
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1383
+ note.filterDepth.gain
1384
+ .cancelScheduledValues(now)
1385
+ .setValueAtTime(modLfoToFilterFc, now);
1386
+ }
1387
+ setModLfoToVolume(note) {
1388
+ const now = this.audioContext.currentTime;
1389
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1390
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1391
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1392
+ note.volumeDepth.gain
1393
+ .cancelScheduledValues(now)
1394
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1395
+ }
1396
+ setChorusEffectsSend(note, prevValue) {
1397
+ if (0 < prevValue) {
1398
+ if (0 < note.voiceParams.chorusEffectsSend) {
1399
+ const now = this.audioContext.currentTime;
1400
+ const value = note.voiceParams.chorusEffectsSend;
1401
+ note.chorusEffectsSend.gain
1402
+ .cancelScheduledValues(now)
1403
+ .setValueAtTime(value, now);
1404
+ }
1405
+ else {
1406
+ note.chorusEffectsSend.disconnect();
1407
+ }
1408
+ }
1409
+ else {
1410
+ if (0 < note.voiceParams.chorusEffectsSend) {
1411
+ if (!note.chorusEffectsSend) {
1412
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1413
+ gain: note.voiceParams.chorusEffectsSend,
1414
+ });
1415
+ note.volumeNode.connect(note.chorusEffectsSend);
1416
+ }
1417
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1418
+ }
1419
+ }
1420
+ }
1421
+ setReverbEffectsSend(note, prevValue) {
1422
+ if (0 < prevValue) {
1423
+ if (0 < note.voiceParams.reverbEffectsSend) {
1424
+ const now = this.audioContext.currentTime;
1425
+ const value = note.voiceParams.reverbEffectsSend;
1426
+ note.reverbEffectsSend.gain
1427
+ .cancelScheduledValues(now)
1428
+ .setValueAtTime(value, now);
1429
+ }
1430
+ else {
1431
+ note.reverbEffectsSend.disconnect();
1432
+ }
1433
+ }
1434
+ else {
1435
+ if (0 < note.voiceParams.reverbEffectsSend) {
1436
+ if (!note.reverbEffectsSend) {
1437
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1438
+ gain: note.voiceParams.reverbEffectsSend,
1439
+ });
1440
+ note.volumeNode.connect(note.reverbEffectsSend);
1441
+ }
1442
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1443
+ }
1444
+ }
1445
+ }
1446
+ setDelayModLFO(note) {
1447
+ const now = this.audioContext.currentTime;
1448
+ const startTime = note.startTime;
1449
+ if (startTime < now)
1450
+ return;
1451
+ note.modulationLFO.stop(now);
1452
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1453
+ note.modulationLFO.connect(note.filterDepth);
1454
+ }
1455
+ setFreqModLFO(note) {
1456
+ const now = this.audioContext.currentTime;
1457
+ const freqModLFO = note.voiceParams.freqModLFO;
1458
+ note.modulationLFO.frequency
1459
+ .cancelScheduledValues(now)
1460
+ .setValueAtTime(freqModLFO, now);
1461
+ }
1462
+ createVoiceParamsHandlers() {
1463
+ return {
1464
+ modLfoToPitch: (channel, note, _prevValue) => {
1465
+ if (0 < channel.state.modulationDepth) {
1466
+ this.setModLfoToPitch(channel, note);
1467
+ }
1468
+ },
1469
+ vibLfoToPitch: (channel, note, _prevValue) => {
1470
+ if (0 < channel.state.vibratoDepth) {
1471
+ this.setVibLfoToPitch(channel, note);
1472
+ }
1473
+ },
1474
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1475
+ if (0 < channel.state.modulationDepth)
1476
+ this.setModLfoToFilterFc(note);
1477
+ },
1478
+ modLfoToVolume: (channel, note) => {
1479
+ if (0 < channel.state.modulationDepth)
1480
+ this.setModLfoToVolume(note);
1481
+ },
1482
+ chorusEffectsSend: (_channel, note, prevValue) => {
1483
+ this.setChorusEffectsSend(note, prevValue);
1484
+ },
1485
+ reverbEffectsSend: (_channel, note, prevValue) => {
1486
+ this.setReverbEffectsSend(note, prevValue);
1487
+ },
1488
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1489
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1490
+ delayVibLFO: (channel, note, prevValue) => {
1491
+ if (0 < channel.state.vibratoDepth) {
1492
+ const now = this.audioContext.currentTime;
1493
+ const prevStartTime = note.startTime +
1494
+ prevValue * channel.state.vibratoDelay * 2;
1495
+ if (now < prevStartTime)
1496
+ return;
1497
+ const startTime = note.startTime +
1498
+ value * channel.state.vibratoDelay * 2;
1499
+ note.vibratoLFO.stop(now);
1500
+ note.vibratoLFO.start(startTime);
1501
+ }
1502
+ },
1503
+ freqVibLFO: (channel, note, _prevValue) => {
1504
+ if (0 < channel.state.vibratoDepth) {
1505
+ const now = this.audioContext.currentTime;
1506
+ note.vibratoLFO.frequency
1507
+ .cancelScheduledValues(now)
1508
+ .setValueAtTime(value * sate.vibratoRate, now);
1509
+ }
1510
+ },
1511
+ };
1512
+ }
1513
+ getControllerState(channel, noteNumber, velocity) {
1514
+ const state = new Float32Array(channel.state.array.length);
1515
+ state.set(channel.state.array);
1516
+ state[2] = velocity / 127;
1517
+ state[3] = noteNumber / 127;
1518
+ return state;
1519
+ }
1520
+ applyVoiceParams(channel, controllerType) {
1521
+ channel.scheduledNotes.forEach((noteList) => {
1522
+ for (let i = 0; i < noteList.length; i++) {
1523
+ const note = noteList[i];
1524
+ if (!note)
1525
+ continue;
1526
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1527
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1528
+ let appliedFilterEnvelope = false;
1529
+ let appliedVolumeEnvelope = false;
1530
+ for (const [key, value] of Object.entries(voiceParams)) {
1531
+ const prevValue = note.voiceParams[key];
1532
+ if (value === prevValue)
1533
+ continue;
1534
+ note.voiceParams[key] = value;
1535
+ if (key in this.voiceParamsHandlers) {
1536
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1537
+ }
1538
+ else if (filterEnvelopeKeySet.has(key)) {
1539
+ if (appliedFilterEnvelope)
1540
+ continue;
1541
+ appliedFilterEnvelope = true;
1542
+ const noteVoiceParams = note.voiceParams;
1543
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1544
+ const key = filterEnvelopeKeys[i];
1545
+ if (key in voiceParams)
1546
+ noteVoiceParams[key] = voiceParams[key];
1547
+ }
1548
+ if (note.portamento) {
1549
+ this.setPortamentoStartFilterEnvelope(channel, note);
1550
+ }
1551
+ else {
1552
+ this.setFilterEnvelope(channel, note);
1553
+ }
1554
+ this.setPitchEnvelope(note);
1555
+ }
1556
+ else if (volumeEnvelopeKeySet.has(key)) {
1557
+ if (appliedVolumeEnvelope)
1558
+ continue;
1559
+ appliedVolumeEnvelope = true;
1560
+ const noteVoiceParams = note.voiceParams;
1561
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1562
+ const key = volumeEnvelopeKeys[i];
1563
+ if (key in voiceParams)
1564
+ noteVoiceParams[key] = voiceParams[key];
1565
+ }
1566
+ this.setVolumeEnvelope(channel, note);
1567
+ }
1568
+ }
1569
+ }
1570
+ });
1241
1571
  }
1242
1572
  createControlChangeHandlers() {
1243
1573
  return {
@@ -1277,13 +1607,16 @@ class Midy {
1277
1607
  127: this.polyOn,
1278
1608
  };
1279
1609
  }
1280
- handleControlChange(channelNumber, controller, value) {
1281
- const handler = this.controlChangeHandlers[controller];
1610
+ handleControlChange(channelNumber, controllerType, value) {
1611
+ const handler = this.controlChangeHandlers[controllerType];
1282
1612
  if (handler) {
1283
1613
  handler.call(this, channelNumber, value);
1614
+ const channel = this.channels[channelNumber];
1615
+ const controller = 128 + controllerType;
1616
+ this.applyVoiceParams(channel, controller);
1284
1617
  }
1285
1618
  else {
1286
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1619
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1287
1620
  }
1288
1621
  }
1289
1622
  setBankMSB(channelNumber, msb) {
@@ -1297,11 +1630,10 @@ class Midy {
1297
1630
  if (!note)
1298
1631
  continue;
1299
1632
  if (note.modulationDepth) {
1300
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1633
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1301
1634
  }
1302
1635
  else {
1303
- const semitoneOffset = this.calcSemitoneOffset(channel);
1304
- this.setPitch(note, semitoneOffset);
1636
+ this.setPitchEnvelope(note);
1305
1637
  this.startModulation(channel, note, now);
1306
1638
  }
1307
1639
  }
@@ -1309,21 +1641,22 @@ class Midy {
1309
1641
  }
1310
1642
  setModulationDepth(channelNumber, modulation) {
1311
1643
  const channel = this.channels[channelNumber];
1312
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1644
+ channel.state.modulationDepth = (modulation / 127) *
1645
+ channel.modulationDepthRange;
1313
1646
  this.updateModulation(channel);
1314
1647
  }
1315
1648
  setPortamentoTime(channelNumber, portamentoTime) {
1316
1649
  const channel = this.channels[channelNumber];
1317
1650
  const factor = 5 * Math.log(10) / 127;
1318
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1651
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1319
1652
  }
1320
1653
  setVolume(channelNumber, volume) {
1321
1654
  const channel = this.channels[channelNumber];
1322
- channel.volume = volume / 127;
1655
+ channel.state.volume = volume / 127;
1323
1656
  this.updateChannelVolume(channel);
1324
1657
  }
1325
1658
  panToGain(pan) {
1326
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1659
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1327
1660
  return {
1328
1661
  gainLeft: Math.cos(theta),
1329
1662
  gainRight: Math.sin(theta),
@@ -1331,12 +1664,12 @@ class Midy {
1331
1664
  }
1332
1665
  setPan(channelNumber, pan) {
1333
1666
  const channel = this.channels[channelNumber];
1334
- channel.pan = pan;
1667
+ channel.state.pan = pan / 127;
1335
1668
  this.updateChannelVolume(channel);
1336
1669
  }
1337
1670
  setExpression(channelNumber, expression) {
1338
1671
  const channel = this.channels[channelNumber];
1339
- channel.expression = expression / 127;
1672
+ channel.state.expression = expression / 127;
1340
1673
  this.updateChannelVolume(channel);
1341
1674
  }
1342
1675
  setBankLSB(channelNumber, lsb) {
@@ -1348,8 +1681,9 @@ class Midy {
1348
1681
  }
1349
1682
  updateChannelVolume(channel) {
1350
1683
  const now = this.audioContext.currentTime;
1351
- const volume = channel.volume * channel.expression;
1352
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1684
+ const state = channel.state;
1685
+ const volume = state.volume * state.expression;
1686
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1353
1687
  channel.gainL.gain
1354
1688
  .cancelScheduledValues(now)
1355
1689
  .setValueAtTime(volume * gainLeft, now);
@@ -1358,116 +1692,18 @@ class Midy {
1358
1692
  .setValueAtTime(volume * gainRight, now);
1359
1693
  }
1360
1694
  setSustainPedal(channelNumber, value) {
1361
- const isOn = value >= 64;
1362
- this.channels[channelNumber].sustainPedal = isOn;
1363
- if (!isOn) {
1695
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1696
+ if (value < 64) {
1364
1697
  this.releaseSustainPedal(channelNumber, value);
1365
1698
  }
1366
1699
  }
1367
1700
  setPortamento(channelNumber, value) {
1368
- this.channels[channelNumber].portamento = value >= 64;
1369
- }
1370
- setReverbSendLevel(channelNumber, reverbSendLevel) {
1371
- const channel = this.channels[channelNumber];
1372
- const reverbEffect = this.reverbEffect;
1373
- if (0 < channel.reverbSendLevel) {
1374
- if (0 < reverbSendLevel) {
1375
- const now = this.audioContext.currentTime;
1376
- channel.reverbSendLevel = reverbSendLevel / 127;
1377
- reverbEffect.input.gain.cancelScheduledValues(now);
1378
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1379
- }
1380
- else {
1381
- channel.scheduledNotes.forEach((noteList) => {
1382
- for (let i = 0; i < noteList.length; i++) {
1383
- const note = noteList[i];
1384
- if (!note)
1385
- continue;
1386
- if (note.instrumentKey.reverbEffectsSend <= 0)
1387
- continue;
1388
- note.reverbEffectsSend.disconnect();
1389
- }
1390
- });
1391
- }
1392
- }
1393
- else {
1394
- if (0 < reverbSendLevel) {
1395
- const now = this.audioContext.currentTime;
1396
- channel.scheduledNotes.forEach((noteList) => {
1397
- for (let i = 0; i < noteList.length; i++) {
1398
- const note = noteList[i];
1399
- if (!note)
1400
- continue;
1401
- if (note.instrumentKey.reverbEffectsSend <= 0)
1402
- continue;
1403
- if (!note.reverbEffectsSend) {
1404
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1405
- gain: note.instrumentKey.reverbEffectsSend,
1406
- });
1407
- note.volumeNode.connect(note.reverbEffectsSend);
1408
- }
1409
- note.reverbEffectsSend.connect(reverbEffect.input);
1410
- }
1411
- });
1412
- channel.reverbSendLevel = reverbSendLevel / 127;
1413
- reverbEffect.input.gain.cancelScheduledValues(now);
1414
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1415
- }
1416
- }
1417
- }
1418
- setChorusSendLevel(channelNumber, chorusSendLevel) {
1419
- const channel = this.channels[channelNumber];
1420
- const chorusEffect = this.chorusEffect;
1421
- if (0 < channel.chorusSendLevel) {
1422
- if (0 < chorusSendLevel) {
1423
- const now = this.audioContext.currentTime;
1424
- channel.chorusSendLevel = chorusSendLevel / 127;
1425
- chorusEffect.input.gain.cancelScheduledValues(now);
1426
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1427
- }
1428
- else {
1429
- channel.scheduledNotes.forEach((noteList) => {
1430
- for (let i = 0; i < noteList.length; i++) {
1431
- const note = noteList[i];
1432
- if (!note)
1433
- continue;
1434
- if (note.instrumentKey.chorusEffectsSend <= 0)
1435
- continue;
1436
- note.chorusEffectsSend.disconnect();
1437
- }
1438
- });
1439
- }
1440
- }
1441
- else {
1442
- if (0 < chorusSendLevel) {
1443
- const now = this.audioContext.currentTime;
1444
- channel.scheduledNotes.forEach((noteList) => {
1445
- for (let i = 0; i < noteList.length; i++) {
1446
- const note = noteList[i];
1447
- if (!note)
1448
- continue;
1449
- if (note.instrumentKey.chorusEffectsSend <= 0)
1450
- continue;
1451
- if (!note.chorusEffectsSend) {
1452
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1453
- gain: note.instrumentKey.chorusEffectsSend,
1454
- });
1455
- note.volumeNode.connect(note.chorusEffectsSend);
1456
- }
1457
- note.chorusEffectsSend.connect(chorusEffect.input);
1458
- }
1459
- });
1460
- channel.chorusSendLevel = chorusSendLevel / 127;
1461
- chorusEffect.input.gain.cancelScheduledValues(now);
1462
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1463
- }
1464
- }
1701
+ this.channels[channelNumber].state.portamento = value / 127;
1465
1702
  }
1466
1703
  setSostenutoPedal(channelNumber, value) {
1467
- const isOn = value >= 64;
1468
1704
  const channel = this.channels[channelNumber];
1469
- channel.sostenutoPedal = isOn;
1470
- if (isOn) {
1705
+ channel.state.sostenutoPedal = value / 127;
1706
+ if (64 <= value) {
1471
1707
  const now = this.audioContext.currentTime;
1472
1708
  const activeNotes = this.getActiveNotes(channel, now);
1473
1709
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1478,31 +1714,31 @@ class Midy {
1478
1714
  }
1479
1715
  setSoftPedal(channelNumber, softPedal) {
1480
1716
  const channel = this.channels[channelNumber];
1481
- channel.softPedal = softPedal / 127;
1717
+ channel.state.softPedal = softPedal / 127;
1482
1718
  }
1483
1719
  setFilterResonance(channelNumber, filterResonance) {
1484
1720
  const now = this.audioContext.currentTime;
1485
1721
  const channel = this.channels[channelNumber];
1486
- channel.filterResonance = filterResonance / 64;
1722
+ const state = channel.state;
1723
+ state.filterResonance = filterResonance / 64;
1487
1724
  channel.scheduledNotes.forEach((noteList) => {
1488
1725
  for (let i = 0; i < noteList.length; i++) {
1489
1726
  const note = noteList[i];
1490
1727
  if (!note)
1491
1728
  continue;
1492
- const Q = note.instrumentKey.initialFilterQ / 10 *
1493
- channel.filterResonance;
1729
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1494
1730
  note.filterNode.Q.setValueAtTime(Q, now);
1495
1731
  }
1496
1732
  });
1497
1733
  }
1498
1734
  setReleaseTime(channelNumber, releaseTime) {
1499
1735
  const channel = this.channels[channelNumber];
1500
- channel.releaseTime = releaseTime / 64;
1736
+ channel.state.releaseTime = releaseTime / 64;
1501
1737
  }
1502
1738
  setAttackTime(channelNumber, attackTime) {
1503
1739
  const now = this.audioContext.currentTime;
1504
1740
  const channel = this.channels[channelNumber];
1505
- channel.attackTime = attackTime / 64;
1741
+ channel.state.attackTime = attackTime / 64;
1506
1742
  channel.scheduledNotes.forEach((noteList) => {
1507
1743
  for (let i = 0; i < noteList.length; i++) {
1508
1744
  const note = noteList[i];
@@ -1516,7 +1752,7 @@ class Midy {
1516
1752
  }
1517
1753
  setBrightness(channelNumber, brightness) {
1518
1754
  const channel = this.channels[channelNumber];
1519
- channel.brightness = brightness / 64;
1755
+ channel.state.brightness = brightness / 64;
1520
1756
  channel.scheduledNotes.forEach((noteList) => {
1521
1757
  for (let i = 0; i < noteList.length; i++) {
1522
1758
  const note = noteList[i];
@@ -1528,7 +1764,7 @@ class Midy {
1528
1764
  }
1529
1765
  setDecayTime(channelNumber, dacayTime) {
1530
1766
  const channel = this.channels[channelNumber];
1531
- channel.decayTime = dacayTime / 64;
1767
+ channel.state.decayTime = dacayTime / 64;
1532
1768
  channel.scheduledNotes.forEach((noteList) => {
1533
1769
  for (let i = 0; i < noteList.length; i++) {
1534
1770
  const note = noteList[i];
@@ -1540,7 +1776,7 @@ class Midy {
1540
1776
  }
1541
1777
  setVibratoRate(channelNumber, vibratoRate) {
1542
1778
  const channel = this.channels[channelNumber];
1543
- channel.vibratoRate = vibratoRate / 64;
1779
+ channel.state.vibratoRate = vibratoRate / 64;
1544
1780
  if (channel.vibratoDepth <= 0)
1545
1781
  return;
1546
1782
  const now = this.audioContext.currentTime;
@@ -1548,16 +1784,98 @@ class Midy {
1548
1784
  activeNotes.forEach((activeNote) => {
1549
1785
  activeNote.vibratoLFO.frequency
1550
1786
  .cancelScheduledValues(now)
1551
- .setValueAtTime(channel.vibratoRate, now);
1787
+ .setValueAtTime(channel.state.vibratoRate, now);
1552
1788
  });
1553
1789
  }
1554
1790
  setVibratoDepth(channelNumber, vibratoDepth) {
1555
1791
  const channel = this.channels[channelNumber];
1556
- channel.vibratoDepth = vibratoDepth / 64;
1792
+ channel.state.vibratoDepth = vibratoDepth / 64;
1557
1793
  }
1558
1794
  setVibratoDelay(channelNumber, vibratoDelay) {
1559
1795
  const channel = this.channels[channelNumber];
1560
- channel.vibratoDelay = vibratoDelay / 64;
1796
+ channel.state.vibratoDelay = vibratoDelay / 64;
1797
+ }
1798
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1799
+ const channel = this.channels[channelNumber];
1800
+ const state = channel.state;
1801
+ const reverbEffect = this.reverbEffect;
1802
+ if (0 < state.reverbSendLevel) {
1803
+ if (0 < reverbSendLevel) {
1804
+ const now = this.audioContext.currentTime;
1805
+ state.reverbSendLevel = reverbSendLevel / 127;
1806
+ reverbEffect.input.gain.cancelScheduledValues(now);
1807
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1808
+ }
1809
+ else {
1810
+ channel.scheduledNotes.forEach((noteList) => {
1811
+ for (let i = 0; i < noteList.length; i++) {
1812
+ const note = noteList[i];
1813
+ if (!note)
1814
+ continue;
1815
+ if (note.voiceParams.reverbEffectsSend <= 0)
1816
+ continue;
1817
+ note.reverbEffectsSend.disconnect();
1818
+ }
1819
+ });
1820
+ }
1821
+ }
1822
+ else {
1823
+ if (0 < reverbSendLevel) {
1824
+ const now = this.audioContext.currentTime;
1825
+ channel.scheduledNotes.forEach((noteList) => {
1826
+ for (let i = 0; i < noteList.length; i++) {
1827
+ const note = noteList[i];
1828
+ if (!note)
1829
+ continue;
1830
+ this.setReverbEffectsSend(note, 0);
1831
+ }
1832
+ });
1833
+ state.reverbSendLevel = reverbSendLevel / 127;
1834
+ reverbEffect.input.gain.cancelScheduledValues(now);
1835
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1836
+ }
1837
+ }
1838
+ }
1839
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1840
+ const channel = this.channels[channelNumber];
1841
+ const state = channel.state;
1842
+ const chorusEffect = this.chorusEffect;
1843
+ if (0 < state.chorusSendLevel) {
1844
+ if (0 < chorusSendLevel) {
1845
+ const now = this.audioContext.currentTime;
1846
+ state.chorusSendLevel = chorusSendLevel / 127;
1847
+ chorusEffect.input.gain.cancelScheduledValues(now);
1848
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1849
+ }
1850
+ else {
1851
+ channel.scheduledNotes.forEach((noteList) => {
1852
+ for (let i = 0; i < noteList.length; i++) {
1853
+ const note = noteList[i];
1854
+ if (!note)
1855
+ continue;
1856
+ if (note.voiceParams.chorusEffectsSend <= 0)
1857
+ continue;
1858
+ note.chorusEffectsSend.disconnect();
1859
+ }
1860
+ });
1861
+ }
1862
+ }
1863
+ else {
1864
+ if (0 < chorusSendLevel) {
1865
+ const now = this.audioContext.currentTime;
1866
+ channel.scheduledNotes.forEach((noteList) => {
1867
+ for (let i = 0; i < noteList.length; i++) {
1868
+ const note = noteList[i];
1869
+ if (!note)
1870
+ continue;
1871
+ this.setChorusEffectsSend(note, 0);
1872
+ }
1873
+ });
1874
+ state.chorusSendLevel = chorusSendLevel / 127;
1875
+ chorusEffect.input.gain.cancelScheduledValues(now);
1876
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1877
+ }
1878
+ }
1561
1879
  }
1562
1880
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1563
1881
  if (maxLSB < channel.dataLSB) {
@@ -1627,60 +1945,49 @@ class Midy {
1627
1945
  this.channels[channelNumber].dataMSB = value;
1628
1946
  this.handleRPN(channelNumber, 0);
1629
1947
  }
1630
- updateDetune(channel, detuneChange) {
1631
- const now = this.audioContext.currentTime;
1632
- channel.scheduledNotes.forEach((noteList) => {
1633
- for (let i = 0; i < noteList.length; i++) {
1634
- const note = noteList[i];
1635
- if (!note)
1636
- continue;
1637
- const { bufferSource } = note;
1638
- const detune = bufferSource.detune.value + detuneChange;
1639
- bufferSource.detune
1640
- .cancelScheduledValues(now)
1641
- .setValueAtTime(detune, now);
1642
- }
1643
- });
1644
- }
1645
1948
  handlePitchBendRangeRPN(channelNumber) {
1646
1949
  const channel = this.channels[channelNumber];
1647
1950
  this.limitData(channel, 0, 127, 0, 99);
1648
1951
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1649
1952
  this.setPitchBendRange(channelNumber, pitchBendRange);
1650
1953
  }
1651
- setPitchBendRange(channelNumber, pitchBendRange) {
1954
+ setPitchBendRange(channelNumber, value) {
1652
1955
  const channel = this.channels[channelNumber];
1653
- const prevPitchBendRange = channel.pitchBendRange;
1654
- channel.pitchBendRange = pitchBendRange;
1655
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1656
- channel.pitchBend * 100;
1657
- this.updateDetune(channel, detuneChange);
1956
+ const state = channel.state;
1957
+ const prev = state.pitchWheelSensitivity;
1958
+ const next = value / 128;
1959
+ state.pitchWheelSensitivity = next;
1960
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1961
+ this.updateDetune(channel);
1962
+ this.applyVoiceParams(channel, 16);
1658
1963
  }
1659
1964
  handleFineTuningRPN(channelNumber) {
1660
1965
  const channel = this.channels[channelNumber];
1661
1966
  this.limitData(channel, 0, 127, 0, 127);
1662
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
1967
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1663
1968
  this.setFineTuning(channelNumber, fineTuning);
1664
1969
  }
1665
- setFineTuning(channelNumber, fineTuning) {
1970
+ setFineTuning(channelNumber, value) {
1666
1971
  const channel = this.channels[channelNumber];
1667
- const prevFineTuning = channel.fineTuning;
1668
- channel.fineTuning = fineTuning;
1669
- const detuneChange = channel.fineTuning - prevFineTuning;
1670
- this.updateDetune(channel, detuneChange);
1972
+ const prev = channel.fineTuning;
1973
+ const next = (value - 8192) / 8.192; // cent
1974
+ channel.fineTuning = next;
1975
+ channel.detune += next - prev;
1976
+ this.updateDetune(channel);
1671
1977
  }
1672
1978
  handleCoarseTuningRPN(channelNumber) {
1673
1979
  const channel = this.channels[channelNumber];
1674
1980
  this.limitDataMSB(channel, 0, 127);
1675
- const coarseTuning = channel.dataMSB - 64;
1676
- this.setFineTuning(channelNumber, coarseTuning);
1981
+ const coarseTuning = channel.dataMSB;
1982
+ this.setCoarseTuning(channelNumber, coarseTuning);
1677
1983
  }
1678
- setCoarseTuning(channelNumber, coarseTuning) {
1984
+ setCoarseTuning(channelNumber, value) {
1679
1985
  const channel = this.channels[channelNumber];
1680
- const prevCoarseTuning = channel.coarseTuning;
1681
- channel.coarseTuning = coarseTuning;
1682
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1683
- this.updateDetune(channel, detuneChange);
1986
+ const prev = channel.coarseTuning;
1987
+ const next = (value - 64) * 100; // cent
1988
+ channel.coarseTuning = next;
1989
+ channel.detune += next - prev;
1990
+ this.updateDetune(channel);
1684
1991
  }
1685
1992
  handleModulationDepthRangeRPN(channelNumber) {
1686
1993
  const channel = this.channels[channelNumber];
@@ -1698,7 +2005,30 @@ class Midy {
1698
2005
  return this.stopChannelNotes(channelNumber, 0, true);
1699
2006
  }
1700
2007
  resetAllControllers(channelNumber) {
1701
- Object.assign(this.channels[channelNumber], this.effectSettings);
2008
+ const stateTypes = [
2009
+ "expression",
2010
+ "modulationDepth",
2011
+ "sustainPedal",
2012
+ "portamento",
2013
+ "sostenutoPedal",
2014
+ "softPedal",
2015
+ "channelPressure",
2016
+ "pitchWheelSensitivity",
2017
+ ];
2018
+ const channel = this.channels[channelNumber];
2019
+ const state = channel.state;
2020
+ for (let i = 0; i < stateTypes.length; i++) {
2021
+ const type = stateTypes[i];
2022
+ state[type] = defaultControllerState[type];
2023
+ }
2024
+ const settingTypes = [
2025
+ "rpnMSB",
2026
+ "rpnLSB",
2027
+ ];
2028
+ for (let i = 0; i < settingTypes.length; i++) {
2029
+ const type = settingTypes[i];
2030
+ channel[type] = this.constructor.channelSettings[type];
2031
+ }
1702
2032
  }
1703
2033
  allNotesOff(channelNumber) {
1704
2034
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1717,6 +2047,15 @@ class Midy {
1717
2047
  }
1718
2048
  handleUniversalNonRealTimeExclusiveMessage(data) {
1719
2049
  switch (data[2]) {
2050
+ case 8:
2051
+ switch (data[3]) {
2052
+ case 8:
2053
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2054
+ return this.handleScaleOctaveTuning1ByteFormat(data);
2055
+ default:
2056
+ console.warn(`Unsupported Exclusive Message: ${data}`);
2057
+ }
2058
+ break;
1720
2059
  case 9:
1721
2060
  switch (data[3]) {
1722
2061
  case 1:
@@ -1773,9 +2112,10 @@ class Midy {
1773
2112
  break;
1774
2113
  case 8:
1775
2114
  switch (data[3]) {
1776
- // case 8:
1777
- // // TODO
1778
- // return this.handleScaleOctaveTuning1ByteFormat();
2115
+ case 8:
2116
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2117
+ // TODO: realtime
2118
+ return this.handleScaleOctaveTuning1ByteFormat(data);
1779
2119
  default:
1780
2120
  console.warn(`Unsupported Exclusive Message: ${data}`);
1781
2121
  }
@@ -1820,27 +2160,59 @@ class Midy {
1820
2160
  }
1821
2161
  }
1822
2162
  handleMasterFineTuningSysEx(data) {
1823
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2163
+ const fineTuning = data[5] * 128 + data[4];
1824
2164
  this.setMasterFineTuning(fineTuning);
1825
2165
  }
1826
- setMasterFineTuning(fineTuning) {
1827
- if (fineTuning < -1 && 1 < fineTuning) {
1828
- console.error("Master Fine Tuning value is out of range");
1829
- }
1830
- else {
1831
- this.masterFineTuning = fineTuning;
1832
- }
2166
+ setMasterFineTuning(value) {
2167
+ const prev = this.masterFineTuning;
2168
+ const next = (value - 8192) / 8.192; // cent
2169
+ this.masterFineTuning = next;
2170
+ channel.detune += next - prev;
2171
+ this.updateDetune(channel);
1833
2172
  }
1834
2173
  handleMasterCoarseTuningSysEx(data) {
1835
2174
  const coarseTuning = data[4];
1836
2175
  this.setMasterCoarseTuning(coarseTuning);
1837
2176
  }
1838
- setMasterCoarseTuning(coarseTuning) {
1839
- if (coarseTuning < 0 && 127 < coarseTuning) {
1840
- console.error("Master Coarse Tuning value is out of range");
2177
+ setMasterCoarseTuning(value) {
2178
+ const prev = this.masterCoarseTuning;
2179
+ const next = (value - 64) * 100; // cent
2180
+ this.masterCoarseTuning = next;
2181
+ channel.detune += next - prev;
2182
+ this.updateDetune(channel);
2183
+ }
2184
+ getChannelBitmap(data) {
2185
+ const bitmap = new Array(16).fill(false);
2186
+ const ff = data[4] & 0b11;
2187
+ const gg = data[5] & 0x7F;
2188
+ const hh = data[6] & 0x7F;
2189
+ for (let bit = 0; bit < 7; bit++) {
2190
+ if (hh & (1 << bit))
2191
+ bitmap[bit] = true;
2192
+ }
2193
+ for (let bit = 0; bit < 7; bit++) {
2194
+ if (gg & (1 << bit))
2195
+ bitmap[bit + 7] = true;
2196
+ }
2197
+ for (let bit = 0; bit < 2; bit++) {
2198
+ if (ff & (1 << bit))
2199
+ bitmap[bit + 14] = true;
2200
+ }
2201
+ return bitmap;
2202
+ }
2203
+ handleScaleOctaveTuning1ByteFormat(data) {
2204
+ if (data.length < 18) {
2205
+ console.error("Data length is too short");
2206
+ return;
1841
2207
  }
1842
- else {
1843
- this.masterCoarseTuning = coarseTuning - 64;
2208
+ const channelBitmap = this.getChannelBitmap(data);
2209
+ for (let i = 0; i < channelBitmap.length; i++) {
2210
+ if (!channelBitmap[i])
2211
+ continue;
2212
+ for (let j = 0; j < 12; j++) {
2213
+ const value = data[j + 7] - 64; // cent
2214
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2215
+ }
1844
2216
  }
1845
2217
  }
1846
2218
  handleGlobalParameterControlSysEx(data) {
@@ -2061,48 +2433,21 @@ Object.defineProperty(Midy, "channelSettings", {
2061
2433
  writable: true,
2062
2434
  value: {
2063
2435
  currentBufferSource: null,
2064
- volume: 100 / 127,
2065
- pan: 64,
2066
- portamentoTime: 1, // sec
2067
- filterResonance: 1,
2068
- releaseTime: 1,
2069
- attackTime: 1,
2070
- brightness: 1,
2071
- decayTime: 1,
2072
- reverbSendLevel: 0,
2073
- chorusSendLevel: 0,
2074
- vibratoRate: 1,
2075
- vibratoDepth: 1,
2076
- vibratoDelay: 1,
2436
+ detune: 0,
2437
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2438
+ program: 0,
2077
2439
  bank: 121 * 128,
2078
2440
  bankMSB: 121,
2079
2441
  bankLSB: 0,
2080
2442
  dataMSB: 0,
2081
2443
  dataLSB: 0,
2082
- program: 0,
2083
- pitchBend: 0,
2444
+ rpnMSB: 127,
2445
+ rpnLSB: 127,
2084
2446
  fineTuning: 0, // cb
2085
2447
  coarseTuning: 0, // cb
2086
2448
  modulationDepthRange: 50, // cent
2087
2449
  }
2088
2450
  });
2089
- Object.defineProperty(Midy, "effectSettings", {
2090
- enumerable: true,
2091
- configurable: true,
2092
- writable: true,
2093
- value: {
2094
- expression: 1,
2095
- modulationDepth: 0,
2096
- sustainPedal: false,
2097
- portamento: false,
2098
- sostenutoPedal: false,
2099
- softPedal: 0,
2100
- rpnMSB: 127,
2101
- rpnLSB: 127,
2102
- channelPressure: 0,
2103
- pitchBendRange: 2,
2104
- }
2105
- });
2106
2451
  Object.defineProperty(Midy, "controllerDestinationSettings", {
2107
2452
  enumerable: true,
2108
2453
  configurable: true,