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