@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.
@@ -4,7 +4,7 @@ exports.MidyGM2 = 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;
79
+ }
80
+ }
81
+ // normalized to 0-1 for use with the SF2 modulator model
82
+ const defaultControllerState = {
83
+ noteOnVelocity: { type: 2, defaultValue: 0 },
84
+ noteOnKeyNumber: { type: 3, defaultValue: 0 },
85
+ polyPressure: { type: 10, defaultValue: 0 },
86
+ channelPressure: { type: 13, defaultValue: 0 },
87
+ pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
88
+ pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
89
+ link: { type: 127, defaultValue: 0 },
90
+ // bankMSB: { type: 128 + 0, defaultValue: 121, },
91
+ modulationDepth: { type: 128 + 1, defaultValue: 0 },
92
+ portamentoTime: { type: 128 + 5, defaultValue: 0 },
93
+ // dataMSB: { type: 128 + 6, defaultValue: 0, },
94
+ volume: { type: 128 + 7, defaultValue: 100 / 127 },
95
+ pan: { type: 128 + 10, defaultValue: 0.5 },
96
+ expression: { type: 128 + 11, defaultValue: 1 },
97
+ // bankLSB: { type: 128 + 32, defaultValue: 0, },
98
+ // dataLSB: { type: 128 + 38, defaultValue: 0, },
99
+ sustainPedal: { type: 128 + 64, defaultValue: 0 },
100
+ portamento: { type: 128 + 65, defaultValue: 0 },
101
+ sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
102
+ softPedal: { type: 128 + 67, defaultValue: 0 },
103
+ filterResonance: { type: 128 + 71, defaultValue: 0.5 },
104
+ releaseTime: { type: 128 + 72, defaultValue: 0.5 },
105
+ attackTime: { type: 128 + 73, defaultValue: 0.5 },
106
+ brightness: { type: 128 + 74, defaultValue: 0.5 },
107
+ decayTime: { type: 128 + 75, defaultValue: 0.5 },
108
+ vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
109
+ vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
110
+ vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
111
+ reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
112
+ chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
113
+ // dataIncrement: { type: 128 + 96, defaultValue: 0 },
114
+ // dataDecrement: { type: 128 + 97, defaultValue: 0 },
115
+ // rpnLSB: { type: 128 + 100, defaultValue: 127 },
116
+ // rpnMSB: { type: 128 + 101, defaultValue: 127 },
117
+ // allSoundOff: { type: 128 + 120, defaultValue: 0 },
118
+ // resetAllControllers: { type: 128 + 121, defaultValue: 0 },
119
+ // allNotesOff: { type: 128 + 123, defaultValue: 0 },
120
+ // omniOff: { type: 128 + 124, defaultValue: 0 },
121
+ // omniOn: { type: 128 + 125, defaultValue: 0 },
122
+ // monoOn: { type: 128 + 126, defaultValue: 0 },
123
+ // polyOn: { type: 128 + 127, defaultValue: 0 },
124
+ };
125
+ class ControllerState {
126
+ constructor() {
127
+ Object.defineProperty(this, "array", {
128
+ enumerable: true,
129
+ configurable: true,
130
+ writable: true,
131
+ value: new Float32Array(256)
132
+ });
133
+ const entries = Object.entries(defaultControllerState);
134
+ for (const [name, { type, defaultValue }] of entries) {
135
+ this.array[type] = defaultValue;
136
+ Object.defineProperty(this, name, {
137
+ get: () => this.array[type],
138
+ set: (value) => this.array[type] = value,
139
+ enumerable: true,
140
+ configurable: true,
141
+ });
142
+ }
72
143
  }
73
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 MidyGM2 {
75
169
  constructor(audioContext, options = this.defaultOptions) {
76
170
  Object.defineProperty(this, "ticksPerBeat", {
@@ -251,6 +345,7 @@ class MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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(),
@@ -325,11 +420,11 @@ class MidyGM2 {
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 @@ class MidyGM2 {
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 @@ class MidyGM2 {
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
  }
@@ -416,7 +510,7 @@ class MidyGM2 {
416
510
  this.handleChannelPressure(event.channel, event.amount);
417
511
  break;
418
512
  case "pitchBend":
419
- this.setPitchBend(event.channel, event.value);
513
+ this.setPitchBend(event.channel, event.value + 8192);
420
514
  break;
421
515
  case "sysEx":
422
516
  this.handleSysEx(event.data);
@@ -807,63 +901,94 @@ class MidyGM2 {
807
901
  cbToRatio(cb) {
808
902
  return Math.pow(10, cb / 200);
809
903
  }
904
+ rateToCent(rate) {
905
+ return 1200 * Math.log2(rate);
906
+ }
907
+ centToRate(cent) {
908
+ return Math.pow(2, cent / 1200);
909
+ }
810
910
  centToHz(cent) {
811
- return 8.176 * Math.pow(2, cent / 1200);
911
+ return 8.176 * this.centToRate(cent);
812
912
  }
813
- calcSemitoneOffset(channel) {
913
+ calcDetune(channel, note) {
814
914
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
815
915
  const channelTuning = channel.coarseTuning + channel.fineTuning;
816
- const tuning = masterTuning + channelTuning;
817
- return channel.pitchBend * channel.pitchBendRange + tuning;
916
+ const scaleOctaveTuning = channel.scaleOctaveTuningTable[note.noteNumber % 12];
917
+ const tuning = masterTuning + channelTuning + scaleOctaveTuning;
918
+ const pitchWheel = channel.state.pitchWheel * 2 - 1;
919
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
920
+ const pitch = pitchWheel * pitchWheelSensitivity;
921
+ return tuning + pitch;
922
+ }
923
+ calcNoteDetune(channel, note) {
924
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
818
925
  }
819
- calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
820
- return instrumentKey.playbackRate(noteNumber) *
821
- Math.pow(2, semitoneOffset / 12);
926
+ updateDetune(channel) {
927
+ const now = this.audioContext.currentTime;
928
+ channel.scheduledNotes.forEach((noteList) => {
929
+ for (let i = 0; i < noteList.length; i++) {
930
+ const note = noteList[i];
931
+ if (!note)
932
+ continue;
933
+ const noteDetune = this.calcNoteDetune(channel, note);
934
+ const detune = channel.detune + noteDetune;
935
+ note.bufferSource.detune
936
+ .cancelScheduledValues(now)
937
+ .setValueAtTime(detune, now);
938
+ }
939
+ });
822
940
  }
823
941
  setPortamentoStartVolumeEnvelope(channel, note) {
824
- const { instrumentKey, startTime } = note;
825
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
826
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
827
- const volDelay = startTime + instrumentKey.volDelay;
828
- const portamentoTime = volDelay + channel.portamentoTime;
942
+ const now = this.audioContext.currentTime;
943
+ const { voiceParams, startTime } = note;
944
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
945
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
946
+ const volDelay = startTime + voiceParams.volDelay;
947
+ const portamentoTime = volDelay + channel.state.portamentoTime;
829
948
  note.volumeNode.gain
830
- .cancelScheduledValues(startTime)
949
+ .cancelScheduledValues(now)
831
950
  .setValueAtTime(0, volDelay)
832
951
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
833
952
  }
834
953
  setVolumeEnvelope(note) {
835
- const { instrumentKey, startTime } = note;
836
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
837
- const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
838
- const volDelay = startTime + instrumentKey.volDelay;
839
- const volAttack = volDelay + instrumentKey.volAttack;
840
- const volHold = volAttack + instrumentKey.volHold;
841
- const volDecay = volHold + instrumentKey.volDecay;
954
+ const now = this.audioContext.currentTime;
955
+ const { voiceParams, startTime } = note;
956
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
957
+ const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
958
+ const volDelay = startTime + voiceParams.volDelay;
959
+ const volAttack = volDelay + voiceParams.volAttack;
960
+ const volHold = volAttack + voiceParams.volHold;
961
+ const volDecay = volHold + voiceParams.volDecay;
842
962
  note.volumeNode.gain
843
- .cancelScheduledValues(startTime)
963
+ .cancelScheduledValues(now)
844
964
  .setValueAtTime(0, startTime)
845
965
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
846
966
  .exponentialRampToValueAtTime(attackVolume, volAttack)
847
967
  .setValueAtTime(attackVolume, volHold)
848
968
  .linearRampToValueAtTime(sustainVolume, volDecay);
849
969
  }
850
- setPitch(note, semitoneOffset) {
851
- const { instrumentKey, noteNumber, startTime } = note;
852
- const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
853
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
970
+ setPitchEnvelope(note) {
971
+ const now = this.audioContext.currentTime;
972
+ const { voiceParams } = note;
973
+ const baseRate = voiceParams.playbackRate;
974
+ note.bufferSource.playbackRate
975
+ .cancelScheduledValues(now)
976
+ .setValueAtTime(baseRate, now);
977
+ const modEnvToPitch = voiceParams.modEnvToPitch;
854
978
  if (modEnvToPitch === 0)
855
979
  return;
856
- const basePitch = note.bufferSource.playbackRate.value;
857
- const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
858
- const modDelay = startTime + instrumentKey.modDelay;
859
- const modAttack = modDelay + instrumentKey.modAttack;
860
- const modHold = modAttack + instrumentKey.modHold;
861
- const modDecay = modHold + instrumentKey.modDecay;
862
- note.bufferSource.playbackRate.value
863
- .setValueAtTime(basePitch, modDelay)
864
- .exponentialRampToValueAtTime(peekPitch, modAttack)
865
- .setValueAtTime(peekPitch, modHold)
866
- .linearRampToValueAtTime(basePitch, modDecay);
980
+ const basePitch = this.rateToCent(baseRate);
981
+ const peekPitch = basePitch + modEnvToPitch;
982
+ const peekRate = this.centToRate(peekPitch);
983
+ const modDelay = startTime + voiceParams.modDelay;
984
+ const modAttack = modDelay + voiceParams.modAttack;
985
+ const modHold = modAttack + voiceParams.modHold;
986
+ const modDecay = modHold + voiceParams.modDecay;
987
+ note.bufferSource.playbackRate
988
+ .setValueAtTime(baseRate, modDelay)
989
+ .exponentialRampToValueAtTime(peekRate, modAttack)
990
+ .setValueAtTime(peekRate, modHold)
991
+ .linearRampToValueAtTime(baseRate, modDecay);
867
992
  }
868
993
  clampCutoffFrequency(frequency) {
869
994
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -871,42 +996,46 @@ class MidyGM2 {
871
996
  return Math.max(minFrequency, Math.min(frequency, maxFrequency));
872
997
  }
873
998
  setPortamentoStartFilterEnvelope(channel, note) {
874
- const { instrumentKey, noteNumber, startTime } = note;
999
+ const now = this.audioContext.currentTime;
1000
+ const state = channel.state;
1001
+ const { voiceParams, noteNumber, startTime } = note;
875
1002
  const softPedalFactor = 1 -
876
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
877
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
1003
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1004
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
878
1005
  softPedalFactor;
879
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
1006
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
880
1007
  const sustainFreq = baseFreq +
881
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1008
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
882
1009
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
883
1010
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
884
- const portamentoTime = startTime + channel.portamentoTime;
885
- const modDelay = startTime + instrumentKey.modDelay;
1011
+ const portamentoTime = startTime + channel.state.portamentoTime;
1012
+ const modDelay = startTime + voiceParams.modDelay;
886
1013
  note.filterNode.frequency
887
- .cancelScheduledValues(startTime)
1014
+ .cancelScheduledValues(now)
888
1015
  .setValueAtTime(adjustedBaseFreq, startTime)
889
1016
  .setValueAtTime(adjustedBaseFreq, modDelay)
890
1017
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
891
1018
  }
892
1019
  setFilterEnvelope(channel, note) {
893
- const { instrumentKey, noteNumber, startTime } = note;
1020
+ const now = this.audioContext.currentTime;
1021
+ const state = channel.state;
1022
+ const { voiceParams, noteNumber, startTime } = note;
894
1023
  const softPedalFactor = 1 -
895
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
896
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
1024
+ (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1025
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
897
1026
  softPedalFactor;
898
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
1027
+ const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
899
1028
  const sustainFreq = baseFreq +
900
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
1029
+ (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
901
1030
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
902
1031
  const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
903
1032
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
904
- const modDelay = startTime + instrumentKey.modDelay;
905
- const modAttack = modDelay + instrumentKey.modAttack;
906
- const modHold = modAttack + instrumentKey.modHold;
907
- const modDecay = modHold + instrumentKey.modDecay;
1033
+ const modDelay = startTime + voiceParams.modDelay;
1034
+ const modAttack = modDelay + voiceParams.modAttack;
1035
+ const modHold = modAttack + voiceParams.modHold;
1036
+ const modDecay = modHold + voiceParams.modDecay;
908
1037
  note.filterNode.frequency
909
- .cancelScheduledValues(startTime)
1038
+ .cancelScheduledValues(now)
910
1039
  .setValueAtTime(adjustedBaseFreq, startTime)
911
1040
  .setValueAtTime(adjustedBaseFreq, modDelay)
912
1041
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
@@ -914,25 +1043,18 @@ class MidyGM2 {
914
1043
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
915
1044
  }
916
1045
  startModulation(channel, note, startTime) {
917
- const { instrumentKey } = note;
918
- const { modLfoToPitch, modLfoToVolume } = instrumentKey;
1046
+ const { voiceParams } = note;
919
1047
  note.modulationLFO = new OscillatorNode(this.audioContext, {
920
- frequency: this.centToHz(instrumentKey.freqModLFO),
1048
+ frequency: this.centToHz(voiceParams.freqModLFO),
921
1049
  });
922
1050
  note.filterDepth = new GainNode(this.audioContext, {
923
- gain: instrumentKey.modLfoToFilterFc,
924
- });
925
- const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
926
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
927
- note.modulationDepth = new GainNode(this.audioContext, {
928
- gain: modulationDepth * modulationDepthSign,
1051
+ gain: voiceParams.modLfoToFilterFc,
929
1052
  });
930
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
931
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
932
- note.volumeDepth = new GainNode(this.audioContext, {
933
- gain: volumeDepth * volumeDepthSign,
934
- });
935
- note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
1053
+ note.modulationDepth = new GainNode(this.audioContext);
1054
+ this.setModLfoToPitch(channel, note);
1055
+ note.volumeDepth = new GainNode(this.audioContext);
1056
+ this.setModLfoToVolume(note);
1057
+ note.modulationLFO.start(startTime + voiceParams.delayModLFO);
936
1058
  note.modulationLFO.connect(note.filterDepth);
937
1059
  note.filterDepth.connect(note.filterNode.frequency);
938
1060
  note.modulationLFO.connect(note.modulationDepth);
@@ -941,67 +1063,57 @@ class MidyGM2 {
941
1063
  note.volumeDepth.connect(note.volumeNode.gain);
942
1064
  }
943
1065
  startVibrato(channel, note, startTime) {
944
- const { instrumentKey } = note;
945
- const { vibLfoToPitch } = instrumentKey;
1066
+ const { voiceParams } = note;
1067
+ const state = channel.state;
946
1068
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
947
- frequency: this.centToHz(instrumentKey.freqVibLFO) *
948
- channel.vibratoRate,
949
- });
950
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
951
- const vibratoDepthSign = 0 < vibLfoToPitch;
952
- note.vibratoDepth = new GainNode(this.audioContext, {
953
- gain: vibratoDepth * vibratoDepthSign,
1069
+ frequency: this.centToHz(voiceParams.freqVibLFO) *
1070
+ state.vibratoRate,
954
1071
  });
955
- note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
1072
+ note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1073
+ note.vibratoDepth = new GainNode(this.audioContext);
1074
+ this.setVibLfoToPitch(channel, note);
956
1075
  note.vibratoLFO.connect(note.vibratoDepth);
957
1076
  note.vibratoDepth.connect(note.bufferSource.detune);
958
1077
  }
959
- async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
960
- const semitoneOffset = this.calcSemitoneOffset(channel);
961
- const note = new Note(noteNumber, velocity, startTime, instrumentKey);
962
- note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
1078
+ async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1079
+ const state = channel.state;
1080
+ const controllerState = this.getControllerState(channel, noteNumber, velocity);
1081
+ const voiceParams = voice.getAllParams(controllerState);
1082
+ const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1083
+ note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
963
1084
  note.volumeNode = new GainNode(this.audioContext);
964
1085
  note.filterNode = new BiquadFilterNode(this.audioContext, {
965
1086
  type: "lowpass",
966
- Q: instrumentKey.initialFilterQ / 10, // dB
1087
+ Q: voiceParams.initialFilterQ / 10, // dB
967
1088
  });
968
1089
  if (portamento) {
1090
+ note.portamento = true;
969
1091
  this.setPortamentoStartVolumeEnvelope(channel, note);
970
1092
  this.setPortamentoStartFilterEnvelope(channel, note);
971
1093
  }
972
1094
  else {
973
- this.setVolumeEnvelope(note);
1095
+ note.portamento = false;
1096
+ this.setVolumeEnvelope(channel, note);
974
1097
  this.setFilterEnvelope(channel, note);
975
1098
  }
976
- if (0 < channel.vibratoDepth) {
1099
+ if (0 < state.vibratoDepth) {
977
1100
  this.startVibrato(channel, note, startTime);
978
1101
  }
979
- if (0 < channel.modulationDepth) {
980
- this.setPitch(note, semitoneOffset);
1102
+ this.setPitchEnvelope(note);
1103
+ if (0 < state.modulationDepth) {
981
1104
  this.startModulation(channel, note, startTime);
982
1105
  }
983
- else {
984
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
985
- }
986
1106
  if (this.mono && channel.currentBufferSource) {
987
1107
  channel.currentBufferSource.stop(startTime);
988
1108
  channel.currentBufferSource = note.bufferSource;
989
1109
  }
990
1110
  note.bufferSource.connect(note.filterNode);
991
1111
  note.filterNode.connect(note.volumeNode);
992
- if (0 < channel.reverbSendLevel && 0 < instrumentKey.reverbEffectsSend) {
993
- note.reverbEffectsSend = new GainNode(this.audioContext, {
994
- gain: instrumentKey.reverbEffectsSend,
995
- });
996
- note.volumeNode.connect(note.reverbEffectsSend);
997
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1112
+ if (0 < channel.chorusSendLevel) {
1113
+ this.setChorusEffectsSend(channel, note, 0);
998
1114
  }
999
- if (0 < channel.chorusSendLevel && 0 < instrumentKey.chorusEffectsSend) {
1000
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1001
- gain: instrumentKey.chorusEffectsSend,
1002
- });
1003
- note.volumeNode.connect(note.chorusEffectsSend);
1004
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1115
+ if (0 < channel.reverbSendLevel) {
1116
+ this.setReverbEffectsSend(channel, note, 0);
1005
1117
  }
1006
1118
  note.bufferSource.start(startTime);
1007
1119
  return note;
@@ -1023,16 +1135,16 @@ class MidyGM2 {
1023
1135
  return;
1024
1136
  const soundFont = this.soundFonts[soundFontIndex];
1025
1137
  const isSF3 = soundFont.parsed.info.version.major === 3;
1026
- const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
1027
- if (!instrumentKey)
1138
+ const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1139
+ if (!voice)
1028
1140
  return;
1029
- const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
1141
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1030
1142
  note.volumeNode.connect(channel.gainL);
1031
1143
  note.volumeNode.connect(channel.gainR);
1032
- if (channel.sostenutoPedal) {
1144
+ if (channel.state.sostenutoPedal) {
1033
1145
  channel.sostenutoNotes.set(noteNumber, note);
1034
1146
  }
1035
- const exclusiveClass = instrumentKey.exclusiveClass;
1147
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1036
1148
  if (exclusiveClass !== 0) {
1037
1149
  if (this.exclusiveClassMap.has(exclusiveClass)) {
1038
1150
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
@@ -1094,8 +1206,9 @@ class MidyGM2 {
1094
1206
  }
1095
1207
  scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
1096
1208
  const channel = this.channels[channelNumber];
1209
+ const state = channel.state;
1097
1210
  if (!force) {
1098
- if (channel.sustainPedal)
1211
+ if (0.5 < state.sustainPedal)
1099
1212
  return;
1100
1213
  if (channel.sostenutoNotes.has(noteNumber))
1101
1214
  return;
@@ -1110,8 +1223,8 @@ class MidyGM2 {
1110
1223
  if (note.ending)
1111
1224
  continue;
1112
1225
  if (portamentoNoteNumber === undefined) {
1113
- const volRelease = endTime + note.instrumentKey.volRelease;
1114
- const modRelease = endTime + note.instrumentKey.modRelease;
1226
+ const volRelease = endTime + note.voiceParams.volRelease;
1227
+ const modRelease = endTime + note.voiceParams.modRelease;
1115
1228
  note.filterNode.frequency
1116
1229
  .cancelScheduledValues(endTime)
1117
1230
  .linearRampToValueAtTime(0, modRelease);
@@ -1119,12 +1232,13 @@ class MidyGM2 {
1119
1232
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1120
1233
  }
1121
1234
  else {
1122
- const portamentoTime = endTime + channel.portamentoTime;
1123
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1124
- const detune = note.bufferSource.detune.value + detuneChange;
1125
- note.bufferSource.detune
1235
+ const portamentoTime = endTime + state.portamentoTime;
1236
+ const deltaNote = portamentoNoteNumber - noteNumber;
1237
+ const baseRate = note.voiceParams.playbackRate;
1238
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1239
+ note.bufferSource.playbackRate
1126
1240
  .cancelScheduledValues(endTime)
1127
- .linearRampToValueAtTime(detune, portamentoTime);
1241
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1128
1242
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1129
1243
  }
1130
1244
  }
@@ -1137,7 +1251,7 @@ class MidyGM2 {
1137
1251
  const velocity = halfVelocity * 2;
1138
1252
  const channel = this.channels[channelNumber];
1139
1253
  const promises = [];
1140
- channel.sustainPedal = false;
1254
+ channel.state.sustainPedal = halfVelocity;
1141
1255
  channel.scheduledNotes.forEach((noteList) => {
1142
1256
  for (let i = 0; i < noteList.length; i++) {
1143
1257
  const note = noteList[i];
@@ -1154,7 +1268,7 @@ class MidyGM2 {
1154
1268
  const velocity = halfVelocity * 2;
1155
1269
  const channel = this.channels[channelNumber];
1156
1270
  const promises = [];
1157
- channel.sostenutoPedal = false;
1271
+ channel.state.sostenutoPedal = 0;
1158
1272
  channel.sostenutoNotes.forEach((activeNote) => {
1159
1273
  const { noteNumber } = activeNote;
1160
1274
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
@@ -1202,18 +1316,233 @@ class MidyGM2 {
1202
1316
  .setValueAtTime(gain * pressure, now);
1203
1317
  });
1204
1318
  }
1319
+ // this.applyVoiceParams(channel, 13);
1205
1320
  }
1206
1321
  handlePitchBendMessage(channelNumber, lsb, msb) {
1207
- const pitchBend = msb * 128 + lsb - 8192;
1322
+ const pitchBend = msb * 128 + lsb;
1208
1323
  this.setPitchBend(channelNumber, pitchBend);
1209
1324
  }
1210
- setPitchBend(channelNumber, pitchBend) {
1325
+ setPitchBend(channelNumber, value) {
1211
1326
  const channel = this.channels[channelNumber];
1212
- const prevPitchBend = channel.pitchBend;
1213
- channel.pitchBend = pitchBend / 8192;
1214
- const detuneChange = (channel.pitchBend - prevPitchBend) *
1215
- channel.pitchBendRange * 100;
1216
- this.updateDetune(channel, detuneChange);
1327
+ const state = channel.state;
1328
+ const prev = state.pitchWheel * 2 - 1;
1329
+ const next = (value - 8192) / 8192;
1330
+ state.pitchWheel = value / 16383;
1331
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1332
+ this.updateDetune(channel);
1333
+ this.applyVoiceParams(channel, 14);
1334
+ }
1335
+ setModLfoToPitch(channel, note) {
1336
+ const now = this.audioContext.currentTime;
1337
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1338
+ const modulationDepth = Math.abs(modLfoToPitch) +
1339
+ channel.state.modulationDepth;
1340
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1341
+ note.modulationDepth.gain
1342
+ .cancelScheduledValues(now)
1343
+ .setValueAtTime(modulationDepth * modulationDepthSign, now);
1344
+ }
1345
+ setVibLfoToPitch(channel, note) {
1346
+ const now = this.audioContext.currentTime;
1347
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1348
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1349
+ 2;
1350
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1351
+ note.vibratoDepth.gain
1352
+ .cancelScheduledValues(now)
1353
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1354
+ }
1355
+ setModLfoToFilterFc(note) {
1356
+ const now = this.audioContext.currentTime;
1357
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1358
+ note.filterDepth.gain
1359
+ .cancelScheduledValues(now)
1360
+ .setValueAtTime(modLfoToFilterFc, now);
1361
+ }
1362
+ setModLfoToVolume(note) {
1363
+ const now = this.audioContext.currentTime;
1364
+ const modLfoToVolume = note.voiceParams.modLfoToVolume;
1365
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1366
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1367
+ note.volumeDepth.gain
1368
+ .cancelScheduledValues(now)
1369
+ .setValueAtTime(volumeDepth * volumeDepthSign, now);
1370
+ }
1371
+ setChorusEffectsSend(note, prevValue) {
1372
+ if (0 < prevValue) {
1373
+ if (0 < note.voiceParams.chorusEffectsSend) {
1374
+ const now = this.audioContext.currentTime;
1375
+ const value = note.voiceParams.chorusEffectsSend;
1376
+ note.chorusEffectsSend.gain
1377
+ .cancelScheduledValues(now)
1378
+ .setValueAtTime(value, now);
1379
+ }
1380
+ else {
1381
+ note.chorusEffectsSend.disconnect();
1382
+ }
1383
+ }
1384
+ else {
1385
+ if (0 < note.voiceParams.chorusEffectsSend) {
1386
+ if (!note.chorusEffectsSend) {
1387
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1388
+ gain: note.voiceParams.chorusEffectsSend,
1389
+ });
1390
+ note.volumeNode.connect(note.chorusEffectsSend);
1391
+ }
1392
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1393
+ }
1394
+ }
1395
+ }
1396
+ setReverbEffectsSend(note, prevValue) {
1397
+ if (0 < prevValue) {
1398
+ if (0 < note.voiceParams.reverbEffectsSend) {
1399
+ const now = this.audioContext.currentTime;
1400
+ const value = note.voiceParams.reverbEffectsSend;
1401
+ note.reverbEffectsSend.gain
1402
+ .cancelScheduledValues(now)
1403
+ .setValueAtTime(value, now);
1404
+ }
1405
+ else {
1406
+ note.reverbEffectsSend.disconnect();
1407
+ }
1408
+ }
1409
+ else {
1410
+ if (0 < note.voiceParams.reverbEffectsSend) {
1411
+ if (!note.reverbEffectsSend) {
1412
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1413
+ gain: note.voiceParams.reverbEffectsSend,
1414
+ });
1415
+ note.volumeNode.connect(note.reverbEffectsSend);
1416
+ }
1417
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1418
+ }
1419
+ }
1420
+ }
1421
+ setDelayModLFO(note) {
1422
+ const now = this.audioContext.currentTime;
1423
+ const startTime = note.startTime;
1424
+ if (startTime < now)
1425
+ return;
1426
+ note.modulationLFO.stop(now);
1427
+ note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1428
+ note.modulationLFO.connect(note.filterDepth);
1429
+ }
1430
+ setFreqModLFO(note) {
1431
+ const now = this.audioContext.currentTime;
1432
+ const freqModLFO = note.voiceParams.freqModLFO;
1433
+ note.modulationLFO.frequency
1434
+ .cancelScheduledValues(now)
1435
+ .setValueAtTime(freqModLFO, now);
1436
+ }
1437
+ createVoiceParamsHandlers() {
1438
+ return {
1439
+ modLfoToPitch: (channel, note, _prevValue) => {
1440
+ if (0 < channel.state.modulationDepth) {
1441
+ this.setModLfoToPitch(channel, note);
1442
+ }
1443
+ },
1444
+ vibLfoToPitch: (channel, note, _prevValue) => {
1445
+ if (0 < channel.state.vibratoDepth) {
1446
+ this.setVibLfoToPitch(channel, note);
1447
+ }
1448
+ },
1449
+ modLfoToFilterFc: (channel, note, _prevValue) => {
1450
+ if (0 < channel.state.modulationDepth)
1451
+ this.setModLfoToFilterFc(note);
1452
+ },
1453
+ modLfoToVolume: (channel, note) => {
1454
+ if (0 < channel.state.modulationDepth)
1455
+ this.setModLfoToVolume(note);
1456
+ },
1457
+ chorusEffectsSend: (_channel, note, prevValue) => {
1458
+ this.setChorusEffectsSend(note, prevValue);
1459
+ },
1460
+ reverbEffectsSend: (_channel, note, prevValue) => {
1461
+ this.setReverbEffectsSend(note, prevValue);
1462
+ },
1463
+ delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1464
+ freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
1465
+ delayVibLFO: (channel, note, prevValue) => {
1466
+ if (0 < channel.state.vibratoDepth) {
1467
+ const now = this.audioContext.currentTime;
1468
+ const prevStartTime = note.startTime +
1469
+ prevValue * channel.state.vibratoDelay * 2;
1470
+ if (now < prevStartTime)
1471
+ return;
1472
+ const startTime = note.startTime +
1473
+ value * channel.state.vibratoDelay * 2;
1474
+ note.vibratoLFO.stop(now);
1475
+ note.vibratoLFO.start(startTime);
1476
+ }
1477
+ },
1478
+ freqVibLFO: (channel, note, _prevValue) => {
1479
+ if (0 < channel.state.vibratoDepth) {
1480
+ const now = this.audioContext.currentTime;
1481
+ note.vibratoLFO.frequency
1482
+ .cancelScheduledValues(now)
1483
+ .setValueAtTime(value * sate.vibratoRate, now);
1484
+ }
1485
+ },
1486
+ };
1487
+ }
1488
+ getControllerState(channel, noteNumber, velocity) {
1489
+ const state = new Float32Array(channel.state.array.length);
1490
+ state.set(channel.state.array);
1491
+ state[2] = velocity / 127;
1492
+ state[3] = noteNumber / 127;
1493
+ return state;
1494
+ }
1495
+ applyVoiceParams(channel, controllerType) {
1496
+ channel.scheduledNotes.forEach((noteList) => {
1497
+ for (let i = 0; i < noteList.length; i++) {
1498
+ const note = noteList[i];
1499
+ if (!note)
1500
+ continue;
1501
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1502
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1503
+ let appliedFilterEnvelope = false;
1504
+ let appliedVolumeEnvelope = false;
1505
+ for (const [key, value] of Object.entries(voiceParams)) {
1506
+ const prevValue = note.voiceParams[key];
1507
+ if (value === prevValue)
1508
+ continue;
1509
+ note.voiceParams[key] = value;
1510
+ if (key in this.voiceParamsHandlers) {
1511
+ this.voiceParamsHandlers[key](channel, note, prevValue);
1512
+ }
1513
+ else if (filterEnvelopeKeySet.has(key)) {
1514
+ if (appliedFilterEnvelope)
1515
+ continue;
1516
+ appliedFilterEnvelope = true;
1517
+ const noteVoiceParams = note.voiceParams;
1518
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1519
+ const key = filterEnvelopeKeys[i];
1520
+ if (key in voiceParams)
1521
+ noteVoiceParams[key] = voiceParams[key];
1522
+ }
1523
+ if (note.portamento) {
1524
+ this.setPortamentoStartFilterEnvelope(channel, note);
1525
+ }
1526
+ else {
1527
+ this.setFilterEnvelope(channel, note);
1528
+ }
1529
+ this.setPitchEnvelope(note);
1530
+ }
1531
+ else if (volumeEnvelopeKeySet.has(key)) {
1532
+ if (appliedVolumeEnvelope)
1533
+ continue;
1534
+ appliedVolumeEnvelope = true;
1535
+ const noteVoiceParams = note.voiceParams;
1536
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1537
+ const key = volumeEnvelopeKeys[i];
1538
+ if (key in voiceParams)
1539
+ noteVoiceParams[key] = voiceParams[key];
1540
+ }
1541
+ this.setVolumeEnvelope(channel, note);
1542
+ }
1543
+ }
1544
+ }
1545
+ });
1217
1546
  }
1218
1547
  createControlChangeHandlers() {
1219
1548
  return {
@@ -1243,13 +1572,16 @@ class MidyGM2 {
1243
1572
  127: this.polyOn,
1244
1573
  };
1245
1574
  }
1246
- handleControlChange(channelNumber, controller, value) {
1247
- const handler = this.controlChangeHandlers[controller];
1575
+ handleControlChange(channelNumber, controllerType, value) {
1576
+ const handler = this.controlChangeHandlers[controllerType];
1248
1577
  if (handler) {
1249
1578
  handler.call(this, channelNumber, value);
1579
+ const channel = this.channels[channelNumber];
1580
+ const controller = 128 + controllerType;
1581
+ this.applyVoiceParams(channel, controller);
1250
1582
  }
1251
1583
  else {
1252
- console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
1584
+ console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
1253
1585
  }
1254
1586
  }
1255
1587
  setBankMSB(channelNumber, msb) {
@@ -1263,11 +1595,10 @@ class MidyGM2 {
1263
1595
  if (!note)
1264
1596
  continue;
1265
1597
  if (note.modulationDepth) {
1266
- note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1598
+ note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1267
1599
  }
1268
1600
  else {
1269
- const semitoneOffset = this.calcSemitoneOffset(channel);
1270
- this.setPitch(note, semitoneOffset);
1601
+ this.setPitchEnvelope(note);
1271
1602
  this.startModulation(channel, note, now);
1272
1603
  }
1273
1604
  }
@@ -1275,21 +1606,22 @@ class MidyGM2 {
1275
1606
  }
1276
1607
  setModulationDepth(channelNumber, modulation) {
1277
1608
  const channel = this.channels[channelNumber];
1278
- channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1609
+ channel.state.modulationDepth = (modulation / 127) *
1610
+ channel.modulationDepthRange;
1279
1611
  this.updateModulation(channel);
1280
1612
  }
1281
1613
  setPortamentoTime(channelNumber, portamentoTime) {
1282
1614
  const channel = this.channels[channelNumber];
1283
1615
  const factor = 5 * Math.log(10) / 127;
1284
- channel.portamentoTime = Math.exp(factor * portamentoTime);
1616
+ channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1285
1617
  }
1286
1618
  setVolume(channelNumber, volume) {
1287
1619
  const channel = this.channels[channelNumber];
1288
- channel.volume = volume / 127;
1620
+ channel.state.volume = volume / 127;
1289
1621
  this.updateChannelVolume(channel);
1290
1622
  }
1291
1623
  panToGain(pan) {
1292
- const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1624
+ const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
1293
1625
  return {
1294
1626
  gainLeft: Math.cos(theta),
1295
1627
  gainRight: Math.sin(theta),
@@ -1297,12 +1629,12 @@ class MidyGM2 {
1297
1629
  }
1298
1630
  setPan(channelNumber, pan) {
1299
1631
  const channel = this.channels[channelNumber];
1300
- channel.pan = pan;
1632
+ channel.state.pan = pan / 127;
1301
1633
  this.updateChannelVolume(channel);
1302
1634
  }
1303
1635
  setExpression(channelNumber, expression) {
1304
1636
  const channel = this.channels[channelNumber];
1305
- channel.expression = expression / 127;
1637
+ channel.state.expression = expression / 127;
1306
1638
  this.updateChannelVolume(channel);
1307
1639
  }
1308
1640
  setBankLSB(channelNumber, lsb) {
@@ -1314,8 +1646,9 @@ class MidyGM2 {
1314
1646
  }
1315
1647
  updateChannelVolume(channel) {
1316
1648
  const now = this.audioContext.currentTime;
1317
- const volume = channel.volume * channel.expression;
1318
- const { gainLeft, gainRight } = this.panToGain(channel.pan);
1649
+ const state = channel.state;
1650
+ const volume = state.volume * state.expression;
1651
+ const { gainLeft, gainRight } = this.panToGain(state.pan);
1319
1652
  channel.gainL.gain
1320
1653
  .cancelScheduledValues(now)
1321
1654
  .setValueAtTime(volume * gainLeft, now);
@@ -1324,24 +1657,24 @@ class MidyGM2 {
1324
1657
  .setValueAtTime(volume * gainRight, now);
1325
1658
  }
1326
1659
  setSustainPedal(channelNumber, value) {
1327
- const isOn = value >= 64;
1328
- this.channels[channelNumber].sustainPedal = isOn;
1329
- if (!isOn) {
1660
+ this.channels[channelNumber].state.sustainPedal = value / 127;
1661
+ if (value < 64) {
1330
1662
  this.releaseSustainPedal(channelNumber, value);
1331
1663
  }
1332
1664
  }
1333
1665
  setPortamento(channelNumber, value) {
1334
- this.channels[channelNumber].portamento = value >= 64;
1666
+ this.channels[channelNumber].state.portamento = value / 127;
1335
1667
  }
1336
1668
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1337
1669
  const channel = this.channels[channelNumber];
1670
+ const state = channel.state;
1338
1671
  const reverbEffect = this.reverbEffect;
1339
- if (0 < channel.reverbSendLevel) {
1672
+ if (0 < state.reverbSendLevel) {
1340
1673
  if (0 < reverbSendLevel) {
1341
1674
  const now = this.audioContext.currentTime;
1342
- channel.reverbSendLevel = reverbSendLevel / 127;
1675
+ state.reverbSendLevel = reverbSendLevel / 127;
1343
1676
  reverbEffect.input.gain.cancelScheduledValues(now);
1344
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1677
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1345
1678
  }
1346
1679
  else {
1347
1680
  channel.scheduledNotes.forEach((noteList) => {
@@ -1349,7 +1682,7 @@ class MidyGM2 {
1349
1682
  const note = noteList[i];
1350
1683
  if (!note)
1351
1684
  continue;
1352
- if (note.instrumentKey.reverbEffectsSend <= 0)
1685
+ if (note.voiceParams.reverbEffectsSend <= 0)
1353
1686
  continue;
1354
1687
  note.reverbEffectsSend.disconnect();
1355
1688
  }
@@ -1364,32 +1697,25 @@ class MidyGM2 {
1364
1697
  const note = noteList[i];
1365
1698
  if (!note)
1366
1699
  continue;
1367
- if (note.instrumentKey.reverbEffectsSend <= 0)
1368
- continue;
1369
- if (!note.reverbEffectsSend) {
1370
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1371
- gain: note.instrumentKey.reverbEffectsSend,
1372
- });
1373
- note.volumeNode.connect(note.reverbEffectsSend);
1374
- }
1375
- note.reverbEffectsSend.connect(reverbEffect.input);
1700
+ this.setReverbEffectsSend(note, 0);
1376
1701
  }
1377
1702
  });
1378
- channel.reverbSendLevel = reverbSendLevel / 127;
1703
+ state.reverbSendLevel = reverbSendLevel / 127;
1379
1704
  reverbEffect.input.gain.cancelScheduledValues(now);
1380
- reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
1705
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1381
1706
  }
1382
1707
  }
1383
1708
  }
1384
1709
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1385
1710
  const channel = this.channels[channelNumber];
1711
+ const state = channel.state;
1386
1712
  const chorusEffect = this.chorusEffect;
1387
- if (0 < channel.chorusSendLevel) {
1713
+ if (0 < state.chorusSendLevel) {
1388
1714
  if (0 < chorusSendLevel) {
1389
1715
  const now = this.audioContext.currentTime;
1390
- channel.chorusSendLevel = chorusSendLevel / 127;
1716
+ state.chorusSendLevel = chorusSendLevel / 127;
1391
1717
  chorusEffect.input.gain.cancelScheduledValues(now);
1392
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1718
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1393
1719
  }
1394
1720
  else {
1395
1721
  channel.scheduledNotes.forEach((noteList) => {
@@ -1397,7 +1723,7 @@ class MidyGM2 {
1397
1723
  const note = noteList[i];
1398
1724
  if (!note)
1399
1725
  continue;
1400
- if (note.instrumentKey.chorusEffectsSend <= 0)
1726
+ if (note.voiceParams.chorusEffectsSend <= 0)
1401
1727
  continue;
1402
1728
  note.chorusEffectsSend.disconnect();
1403
1729
  }
@@ -1412,28 +1738,19 @@ class MidyGM2 {
1412
1738
  const note = noteList[i];
1413
1739
  if (!note)
1414
1740
  continue;
1415
- if (note.instrumentKey.chorusEffectsSend <= 0)
1416
- continue;
1417
- if (!note.chorusEffectsSend) {
1418
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1419
- gain: note.instrumentKey.chorusEffectsSend,
1420
- });
1421
- note.volumeNode.connect(note.chorusEffectsSend);
1422
- }
1423
- note.chorusEffectsSend.connect(chorusEffect.input);
1741
+ this.setChorusEffectsSend(note, 0);
1424
1742
  }
1425
1743
  });
1426
- channel.chorusSendLevel = chorusSendLevel / 127;
1744
+ state.chorusSendLevel = chorusSendLevel / 127;
1427
1745
  chorusEffect.input.gain.cancelScheduledValues(now);
1428
- chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
1746
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1429
1747
  }
1430
1748
  }
1431
1749
  }
1432
1750
  setSostenutoPedal(channelNumber, value) {
1433
- const isOn = value >= 64;
1434
1751
  const channel = this.channels[channelNumber];
1435
- channel.sostenutoPedal = isOn;
1436
- if (isOn) {
1752
+ channel.state.sostenutoPedal = value / 127;
1753
+ if (64 <= value) {
1437
1754
  const now = this.audioContext.currentTime;
1438
1755
  const activeNotes = this.getActiveNotes(channel, now);
1439
1756
  channel.sostenutoNotes = new Map(activeNotes);
@@ -1444,7 +1761,7 @@ class MidyGM2 {
1444
1761
  }
1445
1762
  setSoftPedal(channelNumber, softPedal) {
1446
1763
  const channel = this.channels[channelNumber];
1447
- channel.softPedal = softPedal / 127;
1764
+ channel.state.softPedal = softPedal / 127;
1448
1765
  }
1449
1766
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1450
1767
  if (maxLSB < channel.dataLSB) {
@@ -1502,60 +1819,49 @@ class MidyGM2 {
1502
1819
  this.channels[channelNumber].dataMSB = value;
1503
1820
  this.handleRPN(channelNumber);
1504
1821
  }
1505
- updateDetune(channel, detuneChange) {
1506
- const now = this.audioContext.currentTime;
1507
- channel.scheduledNotes.forEach((noteList) => {
1508
- for (let i = 0; i < noteList.length; i++) {
1509
- const note = noteList[i];
1510
- if (!note)
1511
- continue;
1512
- const { bufferSource } = note;
1513
- const detune = bufferSource.detune.value + detuneChange;
1514
- bufferSource.detune
1515
- .cancelScheduledValues(now)
1516
- .setValueAtTime(detune, now);
1517
- }
1518
- });
1519
- }
1520
1822
  handlePitchBendRangeRPN(channelNumber) {
1521
1823
  const channel = this.channels[channelNumber];
1522
1824
  this.limitData(channel, 0, 127, 0, 99);
1523
1825
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1524
1826
  this.setPitchBendRange(channelNumber, pitchBendRange);
1525
1827
  }
1526
- setPitchBendRange(channelNumber, pitchBendRange) {
1828
+ setPitchBendRange(channelNumber, value) {
1527
1829
  const channel = this.channels[channelNumber];
1528
- const prevPitchBendRange = channel.pitchBendRange;
1529
- channel.pitchBendRange = pitchBendRange;
1530
- const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1531
- channel.pitchBend * 100;
1532
- this.updateDetune(channel, detuneChange);
1830
+ const state = channel.state;
1831
+ const prev = state.pitchWheelSensitivity;
1832
+ const next = value / 128;
1833
+ state.pitchWheelSensitivity = next;
1834
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1835
+ this.updateDetune(channel);
1836
+ this.applyVoiceParams(channel, 16);
1533
1837
  }
1534
1838
  handleFineTuningRPN(channelNumber) {
1535
1839
  const channel = this.channels[channelNumber];
1536
1840
  this.limitData(channel, 0, 127, 0, 127);
1537
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
1841
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1538
1842
  this.setFineTuning(channelNumber, fineTuning);
1539
1843
  }
1540
- setFineTuning(channelNumber, fineTuning) {
1844
+ setFineTuning(channelNumber, value) {
1541
1845
  const channel = this.channels[channelNumber];
1542
- const prevFineTuning = channel.fineTuning;
1543
- channel.fineTuning = fineTuning;
1544
- const detuneChange = channel.fineTuning - prevFineTuning;
1545
- this.updateDetune(channel, detuneChange);
1846
+ const prev = channel.fineTuning;
1847
+ const next = (value - 8192) / 8.192; // cent
1848
+ channel.fineTuning = next;
1849
+ channel.detune += next - prev;
1850
+ this.updateDetune(channel);
1546
1851
  }
1547
1852
  handleCoarseTuningRPN(channelNumber) {
1548
1853
  const channel = this.channels[channelNumber];
1549
1854
  this.limitDataMSB(channel, 0, 127);
1550
- const coarseTuning = channel.dataMSB - 64;
1551
- this.setFineTuning(channelNumber, coarseTuning);
1855
+ const coarseTuning = channel.dataMSB;
1856
+ this.setCoarseTuning(channelNumber, coarseTuning);
1552
1857
  }
1553
- setCoarseTuning(channelNumber, coarseTuning) {
1858
+ setCoarseTuning(channelNumber, value) {
1554
1859
  const channel = this.channels[channelNumber];
1555
- const prevCoarseTuning = channel.coarseTuning;
1556
- channel.coarseTuning = coarseTuning;
1557
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1558
- this.updateDetune(channel, detuneChange);
1860
+ const prev = channel.coarseTuning;
1861
+ const next = (value - 64) * 100; // cent
1862
+ channel.coarseTuning = next;
1863
+ channel.detune += next - prev;
1864
+ this.updateDetune(channel);
1559
1865
  }
1560
1866
  handleModulationDepthRangeRPN(channelNumber) {
1561
1867
  const channel = this.channels[channelNumber];
@@ -1573,7 +1879,30 @@ class MidyGM2 {
1573
1879
  return this.stopChannelNotes(channelNumber, 0, true);
1574
1880
  }
1575
1881
  resetAllControllers(channelNumber) {
1576
- Object.assign(this.channels[channelNumber], this.effectSettings);
1882
+ const stateTypes = [
1883
+ "expression",
1884
+ "modulationDepth",
1885
+ "sustainPedal",
1886
+ "portamento",
1887
+ "sostenutoPedal",
1888
+ "softPedal",
1889
+ "channelPressure",
1890
+ "pitchWheelSensitivity",
1891
+ ];
1892
+ const channel = this.channels[channelNumber];
1893
+ const state = channel.state;
1894
+ for (let i = 0; i < stateTypes.length; i++) {
1895
+ const type = stateTypes[i];
1896
+ state[type] = defaultControllerState[type];
1897
+ }
1898
+ const settingTypes = [
1899
+ "rpnMSB",
1900
+ "rpnLSB",
1901
+ ];
1902
+ for (let i = 0; i < settingTypes.length; i++) {
1903
+ const type = settingTypes[i];
1904
+ channel[type] = this.constructor.channelSettings[type];
1905
+ }
1577
1906
  }
1578
1907
  allNotesOff(channelNumber) {
1579
1908
  return this.stopChannelNotes(channelNumber, 0, false);
@@ -1592,6 +1921,15 @@ class MidyGM2 {
1592
1921
  }
1593
1922
  handleUniversalNonRealTimeExclusiveMessage(data) {
1594
1923
  switch (data[2]) {
1924
+ case 8:
1925
+ switch (data[3]) {
1926
+ case 8:
1927
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
1928
+ return this.handleScaleOctaveTuning1ByteFormat(data);
1929
+ default:
1930
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1931
+ }
1932
+ break;
1595
1933
  case 9:
1596
1934
  switch (data[3]) {
1597
1935
  case 1:
@@ -1646,15 +1984,6 @@ class MidyGM2 {
1646
1984
  console.warn(`Unsupported Exclusive Message: ${data}`);
1647
1985
  }
1648
1986
  break;
1649
- case 8:
1650
- switch (data[3]) {
1651
- // case 8:
1652
- // // TODO
1653
- // return this.handleScaleOctaveTuning1ByteFormat();
1654
- default:
1655
- console.warn(`Unsupported Exclusive Message: ${data}`);
1656
- }
1657
- break;
1658
1987
  case 9:
1659
1988
  switch (data[3]) {
1660
1989
  // case 1:
@@ -1695,27 +2024,59 @@ class MidyGM2 {
1695
2024
  }
1696
2025
  }
1697
2026
  handleMasterFineTuningSysEx(data) {
1698
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2027
+ const fineTuning = data[5] * 128 + data[4];
1699
2028
  this.setMasterFineTuning(fineTuning);
1700
2029
  }
1701
- setMasterFineTuning(fineTuning) {
1702
- if (fineTuning < -1 && 1 < fineTuning) {
1703
- console.error("Master Fine Tuning value is out of range");
1704
- }
1705
- else {
1706
- this.masterFineTuning = fineTuning;
1707
- }
2030
+ setMasterFineTuning(value) {
2031
+ const prev = this.masterFineTuning;
2032
+ const next = (value - 8192) / 8.192; // cent
2033
+ this.masterFineTuning = next;
2034
+ channel.detune += next - prev;
2035
+ this.updateDetune(channel);
1708
2036
  }
1709
2037
  handleMasterCoarseTuningSysEx(data) {
1710
2038
  const coarseTuning = data[4];
1711
2039
  this.setMasterCoarseTuning(coarseTuning);
1712
2040
  }
1713
- setMasterCoarseTuning(coarseTuning) {
1714
- if (coarseTuning < 0 && 127 < coarseTuning) {
1715
- console.error("Master Coarse Tuning value is out of range");
2041
+ setMasterCoarseTuning(value) {
2042
+ const prev = this.masterCoarseTuning;
2043
+ const next = (value - 64) * 100; // cent
2044
+ this.masterCoarseTuning = next;
2045
+ channel.detune += next - prev;
2046
+ this.updateDetune(channel);
2047
+ }
2048
+ getChannelBitmap(data) {
2049
+ const bitmap = new Array(16).fill(false);
2050
+ const ff = data[4] & 0b11;
2051
+ const gg = data[5] & 0x7F;
2052
+ const hh = data[6] & 0x7F;
2053
+ for (let bit = 0; bit < 7; bit++) {
2054
+ if (hh & (1 << bit))
2055
+ bitmap[bit] = true;
2056
+ }
2057
+ for (let bit = 0; bit < 7; bit++) {
2058
+ if (gg & (1 << bit))
2059
+ bitmap[bit + 7] = true;
2060
+ }
2061
+ for (let bit = 0; bit < 2; bit++) {
2062
+ if (ff & (1 << bit))
2063
+ bitmap[bit + 14] = true;
2064
+ }
2065
+ return bitmap;
2066
+ }
2067
+ handleScaleOctaveTuning1ByteFormat(data) {
2068
+ if (data.length < 18) {
2069
+ console.error("Data length is too short");
2070
+ return;
1716
2071
  }
1717
- else {
1718
- this.masterCoarseTuning = coarseTuning - 64;
2072
+ const channelBitmap = this.getChannelBitmap(data);
2073
+ for (let i = 0; i < channelBitmap.length; i++) {
2074
+ if (!channelBitmap[i])
2075
+ continue;
2076
+ for (let j = 0; j < 12; j++) {
2077
+ const value = data[j + 7] - 64; // cent
2078
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2079
+ }
1719
2080
  }
1720
2081
  }
1721
2082
  handleGlobalParameterControlSysEx(data) {
@@ -1936,43 +2297,21 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1936
2297
  writable: true,
1937
2298
  value: {
1938
2299
  currentBufferSource: null,
1939
- volume: 100 / 127,
1940
- pan: 64,
1941
- portamentoTime: 1, // sec
1942
- reverbSendLevel: 0,
1943
- chorusSendLevel: 0,
1944
- vibratoRate: 1,
1945
- vibratoDepth: 1,
1946
- vibratoDelay: 1,
2300
+ detune: 0,
2301
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2302
+ program: 0,
1947
2303
  bank: 121 * 128,
1948
2304
  bankMSB: 121,
1949
2305
  bankLSB: 0,
1950
2306
  dataMSB: 0,
1951
2307
  dataLSB: 0,
1952
- program: 0,
1953
- pitchBend: 0,
2308
+ rpnMSB: 127,
2309
+ rpnLSB: 127,
1954
2310
  fineTuning: 0, // cb
1955
2311
  coarseTuning: 0, // cb
1956
2312
  modulationDepthRange: 50, // cent
1957
2313
  }
1958
2314
  });
1959
- Object.defineProperty(MidyGM2, "effectSettings", {
1960
- enumerable: true,
1961
- configurable: true,
1962
- writable: true,
1963
- value: {
1964
- expression: 1,
1965
- modulationDepth: 0,
1966
- sustainPedal: false,
1967
- portamento: false,
1968
- sostenutoPedal: false,
1969
- softPedal: 0,
1970
- rpnMSB: 127,
1971
- rpnLSB: 127,
1972
- channelPressure: 0,
1973
- pitchBendRange: 2,
1974
- }
1975
- });
1976
2315
  Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
1977
2316
  enumerable: true,
1978
2317
  configurable: true,