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