spessasynth_core 4.0.23 → 4.0.25

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/dist/index.js CHANGED
@@ -2999,6 +2999,41 @@ var BasicMIDI2 = class _BasicMIDI {
2999
2999
  }
3000
3000
  return totalSeconds;
3001
3001
  }
3002
+ /**
3003
+ * Converts seconds to time in MIDI ticks.
3004
+ * @param seconds The time in seconds.
3005
+ * @returns The time in MIDI ticks.
3006
+ */
3007
+ secondsToMIDITicks(seconds) {
3008
+ seconds = Math.max(seconds, 0);
3009
+ if (seconds === 0) return 0;
3010
+ if (this.tempoChanges.length < 1) {
3011
+ throw new Error(
3012
+ "There are no tempo changes in the sequence. At least one is needed."
3013
+ );
3014
+ }
3015
+ if (this.tempoChanges[this.tempoChanges.length - 1].ticks !== 0) {
3016
+ throw new Error(
3017
+ `The last tempo change is not at 0 ticks. Got ${this.tempoChanges[this.tempoChanges.length - 1].ticks} ticks.`
3018
+ );
3019
+ }
3020
+ let remainingSeconds = seconds;
3021
+ let totalTicks = 0;
3022
+ for (let i = this.tempoChanges.length - 1; i >= 0; i--) {
3023
+ const currentTempo = this.tempoChanges[i];
3024
+ const next = this.tempoChanges[i - 1];
3025
+ const ticksToNextTempo = next ? next.ticks - currentTempo.ticks : Infinity;
3026
+ const oneTickToSeconds = 60 / (currentTempo.tempo * this.timeDivision);
3027
+ const secondsToNextTempo = ticksToNextTempo * oneTickToSeconds;
3028
+ if (remainingSeconds <= secondsToNextTempo) {
3029
+ totalTicks += Math.round(remainingSeconds / oneTickToSeconds);
3030
+ return totalTicks;
3031
+ }
3032
+ totalTicks += ticksToNextTempo;
3033
+ remainingSeconds -= secondsToNextTempo;
3034
+ }
3035
+ return totalTicks;
3036
+ }
3002
3037
  /**
3003
3038
  * Gets the used programs and keys for this MIDI file with a given sound bank.
3004
3039
  * @param soundbank the sound bank.
@@ -3007,6 +3042,32 @@ var BasicMIDI2 = class _BasicMIDI {
3007
3042
  getUsedProgramsAndKeys(soundbank) {
3008
3043
  return getUsedProgramsAndKeys(this, soundbank);
3009
3044
  }
3045
+ /**
3046
+ * Preloads all voices for this sequence in a given synth.
3047
+ * This caches all the needed voices for playing back this sequencer, resulting in a smooth playback.
3048
+ * The sequencer calls this function by default when loading the songs.
3049
+ * @param synth
3050
+ */
3051
+ preloadSynth(synth) {
3052
+ SpessaSynthGroupCollapsed(
3053
+ `%cPreloading samples...`,
3054
+ consoleColors.info
3055
+ );
3056
+ const used = this.getUsedProgramsAndKeys(synth.soundBankManager);
3057
+ used.forEach((combos, preset) => {
3058
+ SpessaSynthInfo(
3059
+ `%cPreloading used samples on %c${preset.name}%c...`,
3060
+ consoleColors.info,
3061
+ consoleColors.recognized,
3062
+ consoleColors.info
3063
+ );
3064
+ for (const combo of combos) {
3065
+ const [midiNote, velocity] = combo.split("-").map(Number);
3066
+ synth.getVoicesForPreset(preset, midiNote, velocity, midiNote);
3067
+ }
3068
+ });
3069
+ SpessaSynthGroupEnd();
3070
+ }
3010
3071
  /**
3011
3072
  * Updates all internal values of the MIDI.
3012
3073
  * @param sortEvents if the events should be sorted by ticks. Recommended to be true.
@@ -3438,6 +3499,10 @@ var BasicMIDI2 = class _BasicMIDI {
3438
3499
  loopEnd = this.lastVoiceEventTick;
3439
3500
  }
3440
3501
  this.loop = { start: loopStart, end: loopEnd, type: loopType };
3502
+ this.lastVoiceEventTick = Math.max(
3503
+ this.lastVoiceEventTick,
3504
+ this.loop.end
3505
+ );
3441
3506
  SpessaSynthInfo(
3442
3507
  `%cLoop points: start: %c${this.loop.start}%c end: %c${this.loop.end}`,
3443
3508
  consoleColors.info,
@@ -3958,33 +4023,9 @@ function loadNewSequenceInternal(parsedMidi) {
3958
4023
  this._midiData.embeddedSoundBank,
3959
4024
  this._midiData.bankOffset
3960
4025
  );
3961
- }
3962
- if (this.preload) {
3963
- SpessaSynthGroupCollapsed(
3964
- "%cPreloading samples...",
3965
- consoleColors.info
3966
- );
3967
- const used = this._midiData.getUsedProgramsAndKeys(
3968
- this.synth.soundBankManager
3969
- );
3970
- used.forEach((combos, preset) => {
3971
- SpessaSynthInfo(
3972
- `%cPreloading used samples on %c${preset.name}%c...`,
3973
- consoleColors.info,
3974
- consoleColors.recognized,
3975
- consoleColors.info
3976
- );
3977
- for (const combo of combos) {
3978
- const [midiNote, velocity] = combo.split("-").map(Number);
3979
- this.synth.getVoicesForPreset(
3980
- preset,
3981
- midiNote,
3982
- velocity,
3983
- midiNote
3984
- );
3985
- }
3986
- });
3987
- SpessaSynthGroupEnd();
4026
+ if (this.preload) {
4027
+ this._midiData.preloadSynth(this.synth);
4028
+ }
3988
4029
  }
3989
4030
  this.currentMIDIPorts = this._midiData.tracks.map((t) => t.port);
3990
4031
  this.midiPortChannelOffset = 0;
@@ -4582,8 +4623,6 @@ setResetValue(midiControllers.registeredParameterLSB, 127);
4582
4623
  setResetValue(midiControllers.registeredParameterMSB, 127);
4583
4624
  setResetValue(midiControllers.nonRegisteredParameterLSB, 127);
4584
4625
  setResetValue(midiControllers.nonRegisteredParameterMSB, 127);
4585
- var PORTAMENTO_CONTROL_UNSET = 1;
4586
- defaultMIDIControllerValues[midiControllers.portamentoControl] = PORTAMENTO_CONTROL_UNSET;
4587
4626
  setResetValue(
4588
4627
  NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel,
4589
4628
  64
@@ -4597,12 +4636,9 @@ var customResetArray = new Float32Array(CUSTOM_CONTROLLER_TABLE_SIZE);
4597
4636
  customResetArray[customControllers.modulationMultiplier] = 1;
4598
4637
 
4599
4638
  // src/synthesizer/audio_engine/engine_methods/controller_control/reset_controllers.ts
4600
- function resetAllControllersInternal(log = true) {
4601
- if (log) {
4602
- SpessaSynthInfo("%cResetting all controllers!", consoleColors.info);
4603
- }
4639
+ function resetAllControllersInternal(system = DEFAULT_SYNTH_MODE) {
4604
4640
  this.privateProps.callEvent("allControllerReset", void 0);
4605
- this.setMasterParameter("midiSystem", DEFAULT_SYNTH_MODE);
4641
+ this.setMasterParameter("midiSystem", system);
4606
4642
  this.privateProps.tunings.length = 0;
4607
4643
  for (let i = 0; i < 128; i++) {
4608
4644
  this.privateProps.tunings.push([]);
@@ -4642,27 +4678,34 @@ function resetAllControllersInternal(log = true) {
4642
4678
  }
4643
4679
  }
4644
4680
  }
4681
+ function resetPortamento(sendCC) {
4682
+ if (this.lockedControllers[midiControllers.portamentoControl]) return;
4683
+ if (this.channelSystem === "xg") {
4684
+ this.controllerChange(midiControllers.portamentoControl, 60, sendCC);
4685
+ } else {
4686
+ this.controllerChange(midiControllers.portamentoControl, 0, sendCC);
4687
+ }
4688
+ }
4645
4689
  function resetControllers(sendCCEvents = true) {
4646
4690
  this.channelOctaveTuning.fill(0);
4647
- for (let i = 0; i < defaultMIDIControllerValues.length; i++) {
4648
- if (this.lockedControllers[i]) {
4691
+ for (let cc = 0; cc < defaultMIDIControllerValues.length; cc++) {
4692
+ if (this.lockedControllers[cc]) {
4649
4693
  continue;
4650
4694
  }
4651
- const resetValue = defaultMIDIControllerValues[i];
4652
- if (this.midiControllers[i] !== resetValue && i < 127) {
4653
- if (i === midiControllers.portamentoControl) {
4654
- this.midiControllers[i] = PORTAMENTO_CONTROL_UNSET;
4655
- } else if (i !== midiControllers.portamentoControl && i !== midiControllers.dataEntryMSB && i !== midiControllers.registeredParameterMSB && i !== midiControllers.registeredParameterLSB && i !== midiControllers.nonRegisteredParameterMSB && i !== midiControllers.nonRegisteredParameterLSB) {
4695
+ const resetValue = defaultMIDIControllerValues[cc];
4696
+ if (this.midiControllers[cc] !== resetValue && cc < 127) {
4697
+ if (cc !== midiControllers.portamentoControl && cc !== midiControllers.dataEntryMSB && cc !== midiControllers.registeredParameterMSB && cc !== midiControllers.registeredParameterLSB && cc !== midiControllers.nonRegisteredParameterMSB && cc !== midiControllers.nonRegisteredParameterLSB) {
4656
4698
  this.controllerChange(
4657
- i,
4699
+ cc,
4658
4700
  resetValue >> 7,
4659
4701
  sendCCEvents
4660
4702
  );
4661
4703
  }
4662
4704
  } else {
4663
- this.midiControllers[i] = resetValue;
4705
+ this.midiControllers[cc] = resetValue;
4664
4706
  }
4665
4707
  }
4708
+ resetPortamento.call(this, sendCCEvents);
4666
4709
  this.channelVibrato = { rate: 0, depth: 0, delay: 0 };
4667
4710
  this.randomPan = false;
4668
4711
  this.sysExModulators.resetModulators();
@@ -4708,13 +4751,12 @@ function resetControllersRP15Compliant() {
4708
4751
  for (let i = 0; i < 128; i++) {
4709
4752
  const resetValue = defaultMIDIControllerValues[i];
4710
4753
  if (!nonResettableCCs.has(i) && resetValue !== this.midiControllers[i]) {
4711
- if (i === midiControllers.portamentoControl) {
4712
- this.midiControllers[i] = PORTAMENTO_CONTROL_UNSET;
4713
- } else {
4754
+ if (i !== midiControllers.portamentoControl) {
4714
4755
  this.controllerChange(i, resetValue >> 7);
4715
4756
  }
4716
4757
  }
4717
4758
  }
4759
+ resetPortamento.call(this, true);
4718
4760
  this.resetGeneratorOverrides();
4719
4761
  this.resetGeneratorOffsets();
4720
4762
  }
@@ -5166,6 +5208,15 @@ var SpessaSynthSequencer = class {
5166
5208
  this._songIndex = 0;
5167
5209
  this.shuffleSongIndexes();
5168
5210
  this.callEvent("songListChange", { newSongList: [...this.songs] });
5211
+ if (this.preload) {
5212
+ SpessaSynthGroup("%cPreloading all songs...", consoleColors.info);
5213
+ this.songs.forEach((song) => {
5214
+ if (song.embeddedSoundBank === void 0) {
5215
+ song.preloadSynth(this.synth);
5216
+ }
5217
+ });
5218
+ SpessaSynthGroupEnd();
5219
+ }
5169
5220
  this.loadCurrentSong();
5170
5221
  }
5171
5222
  callEvent(type, data) {
@@ -6902,20 +6953,6 @@ var defaultSoundFont2Modulators = [
6902
6953
  ];
6903
6954
  var defaultSpessaSynthModulators = [
6904
6955
  // Custom modulators heck yeah
6905
- // Poly pressure to vibrato
6906
- new DecodedModulator(
6907
- getModSourceEnum(
6908
- modulatorCurveTypes.linear,
6909
- false,
6910
- false,
6911
- false,
6912
- modulatorSources.polyPressure
6913
- ),
6914
- 0,
6915
- generatorTypes.vibLfoToPitch,
6916
- 50,
6917
- 0
6918
- ),
6919
6956
  // Cc 92 (tremolo) to modLFO volume
6920
6957
  new DecodedModulator(
6921
6958
  getModSourceEnum(
@@ -6963,6 +7000,22 @@ var defaultSpessaSynthModulators = [
6963
7000
  3600,
6964
7001
  0
6965
7002
  ),
7003
+ // Cc 75 (decay time) to vol env decay
7004
+ new DecodedModulator(
7005
+ getModSourceEnum(
7006
+ modulatorCurveTypes.linear,
7007
+ true,
7008
+ false,
7009
+ true,
7010
+ midiControllers.decayTime
7011
+ ),
7012
+ // Linear forward bipolar cc 75
7013
+ 0,
7014
+ // No controller
7015
+ generatorTypes.decayVolEnv,
7016
+ 3600,
7017
+ 0
7018
+ ),
6966
7019
  // Cc 74 (brightness) to filterFc
6967
7020
  new DecodedModulator(
6968
7021
  getModSourceEnum(
@@ -6976,7 +7029,7 @@ var defaultSpessaSynthModulators = [
6976
7029
  0,
6977
7030
  // No controller
6978
7031
  generatorTypes.initialFilterFc,
6979
- 6e3,
7032
+ 9600,
6980
7033
  0
6981
7034
  ),
6982
7035
  // Cc 71 (filter Q) to filter Q (default resonant modulator)
@@ -6985,7 +7038,55 @@ var defaultSpessaSynthModulators = [
6985
7038
  0,
6986
7039
  // No controller
6987
7040
  generatorTypes.initialFilterQ,
6988
- 250,
7041
+ 200,
7042
+ 0
7043
+ ),
7044
+ // Cc 67 (soft pedal) to attenuation
7045
+ new DecodedModulator(
7046
+ getModSourceEnum(
7047
+ modulatorCurveTypes.switch,
7048
+ false,
7049
+ false,
7050
+ true,
7051
+ midiControllers.softPedal
7052
+ ),
7053
+ // Switch unipolar positive 67
7054
+ 0,
7055
+ // No controller
7056
+ generatorTypes.initialAttenuation,
7057
+ 50,
7058
+ 0
7059
+ ),
7060
+ // Cc 67 (soft pedal) to filter fc
7061
+ new DecodedModulator(
7062
+ getModSourceEnum(
7063
+ modulatorCurveTypes.switch,
7064
+ false,
7065
+ false,
7066
+ true,
7067
+ midiControllers.softPedal
7068
+ ),
7069
+ // Switch unipolar positive 67
7070
+ 0,
7071
+ // No controller
7072
+ generatorTypes.initialFilterFc,
7073
+ -2400,
7074
+ 0
7075
+ ),
7076
+ // Cc 8 (balance) to pan
7077
+ new DecodedModulator(
7078
+ getModSourceEnum(
7079
+ modulatorCurveTypes.linear,
7080
+ true,
7081
+ false,
7082
+ true,
7083
+ midiControllers.balance
7084
+ ),
7085
+ // Linear bipolar positive 8
7086
+ 0,
7087
+ // No controller
7088
+ generatorTypes.pan,
7089
+ 500,
6989
7090
  0
6990
7091
  )
6991
7092
  ];
@@ -8274,7 +8375,7 @@ function panAndMixVoice(voice, inputBuffer, outputLeft, outputRight, reverbLeft,
8274
8375
  }
8275
8376
 
8276
8377
  // src/synthesizer/audio_engine/engine_components/dsp_chain/lowpass_filter.ts
8277
- var FILTER_SMOOTHING_FACTOR = 0.1;
8378
+ var FILTER_SMOOTHING_FACTOR = 0.03;
8278
8379
  var LowpassFilter = class _LowpassFilter {
8279
8380
  /**
8280
8381
  * Cached coefficient calculations.
@@ -8955,7 +9056,29 @@ function getVoicesInternal(channel, midiNote, velocity, realKey) {
8955
9056
  return this.getVoicesForPreset(preset, midiNote, velocity, realKey);
8956
9057
  }
8957
9058
 
8958
- // src/synthesizer/audio_engine/engine_methods/system_exclusive.ts
9059
+ // src/synthesizer/audio_engine/engine_methods/system_exclusive/helpers.ts
9060
+ function sysExLogging(syx, channel, value, what, units) {
9061
+ SpessaSynthInfo(
9062
+ `%cChannel %c${channel}%c ${what}. %c${value} ${units}%c, with %c${arrayToHexString(syx)}`,
9063
+ consoleColors.info,
9064
+ consoleColors.recognized,
9065
+ consoleColors.info,
9066
+ consoleColors.value,
9067
+ consoleColors.info,
9068
+ consoleColors.value
9069
+ );
9070
+ }
9071
+ function sysExNotRecognized(syx, what) {
9072
+ SpessaSynthInfo(
9073
+ `%cUnrecognized %c${what} %cSysEx: %c${arrayToHexString(syx)}`,
9074
+ consoleColors.warn,
9075
+ consoleColors.recognized,
9076
+ consoleColors.warn,
9077
+ consoleColors.unrecognized
9078
+ );
9079
+ }
9080
+
9081
+ // src/synthesizer/audio_engine/engine_methods/system_exclusive/handle_gm.ts
8959
9082
  function getTuning(byte1, byte2, byte3) {
8960
9083
  const midiNote = byte1;
8961
9084
  const fraction = byte2 << 7 | byte3;
@@ -8964,900 +9087,971 @@ function getTuning(byte1, byte2, byte3) {
8964
9087
  }
8965
9088
  return { midiNote, centTuning: fraction * 61e-4 };
8966
9089
  }
8967
- function systemExclusiveInternal(syx, channelOffset = 0) {
8968
- const manufacturer = syx[0];
8969
- if (this.privateProps.masterParameters.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && syx[1] !== 127) {
8970
- if (this.privateProps.masterParameters.deviceID !== syx[1]) {
8971
- return;
9090
+ function handleGM(syx, channelOffset = 0) {
9091
+ switch (syx[2]) {
9092
+ case 4: {
9093
+ let cents;
9094
+ switch (syx[3]) {
9095
+ case 1: {
9096
+ const vol = syx[5] << 7 | syx[4];
9097
+ this.setMIDIVolume(vol / 16384);
9098
+ SpessaSynthInfo(
9099
+ `%cMaster Volume. Volume: %c${vol}`,
9100
+ consoleColors.info,
9101
+ consoleColors.value
9102
+ );
9103
+ break;
9104
+ }
9105
+ case 2: {
9106
+ const balance = syx[5] << 7 | syx[4];
9107
+ const pan = (balance - 8192) / 8192;
9108
+ this.setMasterParameter("masterPan", pan);
9109
+ SpessaSynthInfo(
9110
+ `%cMaster Pan. Pan: %c${pan}`,
9111
+ consoleColors.info,
9112
+ consoleColors.value
9113
+ );
9114
+ break;
9115
+ }
9116
+ case 3: {
9117
+ const tuningValue = (syx[5] << 7 | syx[6]) - 8192;
9118
+ cents = Math.floor(tuningValue / 81.92);
9119
+ this.setMasterTuning(cents);
9120
+ SpessaSynthInfo(
9121
+ `%cMaster Fine Tuning. Cents: %c${cents}`,
9122
+ consoleColors.info,
9123
+ consoleColors.value
9124
+ );
9125
+ break;
9126
+ }
9127
+ case 4: {
9128
+ const semitones = syx[5] - 64;
9129
+ cents = semitones * 100;
9130
+ this.setMasterTuning(cents);
9131
+ SpessaSynthInfo(
9132
+ `%cMaster Coarse Tuning. Cents: %c${cents}`,
9133
+ consoleColors.info,
9134
+ consoleColors.value
9135
+ );
9136
+ break;
9137
+ }
9138
+ default:
9139
+ SpessaSynthInfo(
9140
+ `%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(syx)}`,
9141
+ consoleColors.warn,
9142
+ consoleColors.unrecognized
9143
+ );
9144
+ }
9145
+ break;
8972
9146
  }
8973
- }
8974
- function niceLogging(channel, value, what, units) {
8975
- SpessaSynthInfo(
8976
- `%cChannel %c${channel}%c ${what}. %c${value} ${units}%c, with %c${arrayToHexString(syx)}`,
8977
- consoleColors.info,
8978
- consoleColors.recognized,
8979
- consoleColors.info,
8980
- consoleColors.value,
8981
- consoleColors.info,
8982
- consoleColors.value
8983
- );
8984
- }
8985
- switch (manufacturer) {
8986
- default:
8987
- SpessaSynthInfo(
8988
- `%cUnrecognized SysEx: %c${arrayToHexString(syx)}`,
8989
- consoleColors.warn,
8990
- consoleColors.unrecognized
8991
- );
9147
+ case 9:
9148
+ if (syx[3] === 1) {
9149
+ SpessaSynthInfo("%cGM1 system on", consoleColors.info);
9150
+ this.resetAllControllers("gm");
9151
+ } else if (syx[3] === 3) {
9152
+ SpessaSynthInfo("%cGM2 system on", consoleColors.info);
9153
+ this.resetAllControllers("gm2");
9154
+ } else {
9155
+ SpessaSynthInfo(
9156
+ "%cGM system off, defaulting to GS",
9157
+ consoleColors.info
9158
+ );
9159
+ this.setMasterParameter("midiSystem", "gs");
9160
+ }
8992
9161
  break;
8993
- // Non realtime GM
8994
- case 126:
8995
- // Realtime GM
8996
- case 127:
8997
- switch (syx[2]) {
8998
- case 4: {
8999
- let cents;
9000
- switch (syx[3]) {
9001
- case 1: {
9002
- const vol = syx[5] << 7 | syx[4];
9003
- this.setMIDIVolume(vol / 16384);
9004
- SpessaSynthInfo(
9005
- `%cMaster Volume. Volume: %c${vol}`,
9006
- consoleColors.info,
9007
- consoleColors.value
9008
- );
9009
- break;
9010
- }
9011
- case 2: {
9012
- const balance = syx[5] << 7 | syx[4];
9013
- const pan = (balance - 8192) / 8192;
9014
- this.setMasterParameter("masterPan", pan);
9015
- SpessaSynthInfo(
9016
- `%cMaster Pan. Pan: %c${pan}`,
9017
- consoleColors.info,
9018
- consoleColors.value
9019
- );
9020
- break;
9021
- }
9022
- case 3: {
9023
- const tuningValue = (syx[5] << 7 | syx[6]) - 8192;
9024
- cents = Math.floor(tuningValue / 81.92);
9025
- this.setMasterTuning(cents);
9026
- SpessaSynthInfo(
9027
- `%cMaster Fine Tuning. Cents: %c${cents}`,
9028
- consoleColors.info,
9029
- consoleColors.value
9030
- );
9031
- break;
9032
- }
9033
- case 4: {
9034
- const semitones = syx[5] - 64;
9035
- cents = semitones * 100;
9036
- this.setMasterTuning(cents);
9037
- SpessaSynthInfo(
9038
- `%cMaster Coarse Tuning. Cents: %c${cents}`,
9039
- consoleColors.info,
9040
- consoleColors.value
9041
- );
9042
- break;
9043
- }
9044
- default:
9045
- SpessaSynthInfo(
9046
- `%cUnrecognized MIDI Device Control Real-time message: %c${arrayToHexString(syx)}`,
9047
- consoleColors.warn,
9048
- consoleColors.unrecognized
9049
- );
9162
+ // MIDI Tuning standard
9163
+ // https://midi.org/midi-tuning-updated-specification
9164
+ case 8: {
9165
+ let currentMessageIndex = 4;
9166
+ switch (syx[3]) {
9167
+ // Bulk tuning dump: all 128 notes
9168
+ case 1: {
9169
+ const program = syx[currentMessageIndex++];
9170
+ const tuningName = readBinaryString(
9171
+ syx,
9172
+ 16,
9173
+ currentMessageIndex
9174
+ );
9175
+ currentMessageIndex += 16;
9176
+ if (syx.length < 384) {
9177
+ SpessaSynthWarn(
9178
+ `The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 384 are expected)`
9179
+ );
9180
+ return;
9050
9181
  }
9182
+ for (let i = 0; i < 128; i++) {
9183
+ this.privateProps.tunings[program][i] = getTuning(
9184
+ syx[currentMessageIndex++],
9185
+ syx[currentMessageIndex++],
9186
+ syx[currentMessageIndex++]
9187
+ );
9188
+ }
9189
+ SpessaSynthInfo(
9190
+ `%cBulk Tuning Dump %c${tuningName}%c Program: %c${program}`,
9191
+ consoleColors.info,
9192
+ consoleColors.value,
9193
+ consoleColors.info,
9194
+ consoleColors.recognized
9195
+ );
9051
9196
  break;
9052
9197
  }
9198
+ // Single note change
9199
+ // Single note change bank
9200
+ case 2:
9201
+ case 7: {
9202
+ if (syx[3] === 7) {
9203
+ currentMessageIndex++;
9204
+ }
9205
+ const tuningProgram = syx[currentMessageIndex++];
9206
+ const numberOfChanges = syx[currentMessageIndex++];
9207
+ for (let i = 0; i < numberOfChanges; i++) {
9208
+ this.privateProps.tunings[tuningProgram][syx[currentMessageIndex++]] = getTuning(
9209
+ syx[currentMessageIndex++],
9210
+ syx[currentMessageIndex++],
9211
+ syx[currentMessageIndex++]
9212
+ );
9213
+ }
9214
+ SpessaSynthInfo(
9215
+ `%cSingle Note Tuning. Program: %c${tuningProgram}%c Keys affected: %c${numberOfChanges}`,
9216
+ consoleColors.info,
9217
+ consoleColors.recognized,
9218
+ consoleColors.info,
9219
+ consoleColors.recognized
9220
+ );
9221
+ break;
9222
+ }
9223
+ // Octave tuning (1 byte)
9224
+ // And octave tuning (2 bytes)
9053
9225
  case 9:
9054
- if (syx[3] === 1) {
9055
- SpessaSynthInfo("%cGM1 system on", consoleColors.info);
9056
- this.setMasterParameter("midiSystem", "gm");
9057
- } else if (syx[3] === 3) {
9058
- SpessaSynthInfo("%cGM2 system on", consoleColors.info);
9059
- this.setMasterParameter("midiSystem", "gm2");
9226
+ case 8: {
9227
+ const newOctaveTuning = new Int8Array(12);
9228
+ if (syx[3] === 8) {
9229
+ for (let i = 0; i < 12; i++) {
9230
+ newOctaveTuning[i] = syx[7 + i] - 64;
9231
+ }
9060
9232
  } else {
9061
- SpessaSynthInfo(
9062
- "%cGM system off, defaulting to GS",
9063
- consoleColors.info
9233
+ for (let i = 0; i < 24; i += 2) {
9234
+ const tuning = (syx[7 + i] << 7 | syx[8 + i]) - 8192;
9235
+ newOctaveTuning[i / 2] = Math.floor(tuning / 81.92);
9236
+ }
9237
+ }
9238
+ if ((syx[4] & 1) === 1) {
9239
+ this.midiChannels[14 + channelOffset].setOctaveTuning(
9240
+ newOctaveTuning
9064
9241
  );
9065
- this.setMasterParameter("midiSystem", "gs");
9066
9242
  }
9243
+ if ((syx[4] >> 1 & 1) === 1) {
9244
+ this.midiChannels[15 + channelOffset].setOctaveTuning(
9245
+ newOctaveTuning
9246
+ );
9247
+ }
9248
+ for (let i = 0; i < 7; i++) {
9249
+ const bit = syx[5] >> i & 1;
9250
+ if (bit === 1) {
9251
+ this.midiChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
9252
+ }
9253
+ }
9254
+ for (let i = 0; i < 7; i++) {
9255
+ const bit = syx[6] >> i & 1;
9256
+ if (bit === 1) {
9257
+ this.midiChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
9258
+ }
9259
+ }
9260
+ SpessaSynthInfo(
9261
+ `%cMIDI Octave Scale ${syx[3] === 8 ? "(1 byte)" : "(2 bytes)"} tuning via Tuning: %c${newOctaveTuning.join(" ")}`,
9262
+ consoleColors.info,
9263
+ consoleColors.value
9264
+ );
9067
9265
  break;
9068
- // MIDI Tuning standard
9069
- // https://midi.org/midi-tuning-updated-specification
9070
- case 8: {
9071
- let currentMessageIndex = 4;
9072
- switch (syx[3]) {
9073
- // Bulk tuning dump: all 128 notes
9074
- case 1: {
9075
- const program = syx[currentMessageIndex++];
9076
- const tuningName = readBinaryString(
9077
- syx,
9078
- 16,
9079
- currentMessageIndex
9080
- );
9081
- currentMessageIndex += 16;
9082
- if (syx.length < 384) {
9083
- SpessaSynthWarn(
9084
- `The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 384 are expected)`
9266
+ }
9267
+ default:
9268
+ sysExNotRecognized(syx, "MIDI Tuning Standard");
9269
+ break;
9270
+ }
9271
+ break;
9272
+ }
9273
+ default:
9274
+ sysExNotRecognized(syx, "General MIDI");
9275
+ }
9276
+ }
9277
+
9278
+ // src/synthesizer/audio_engine/engine_methods/system_exclusive/handle_gs.ts
9279
+ function handleGS(syx, channelOffset = 0) {
9280
+ if (syx[3] === 18) {
9281
+ switch (syx[2]) {
9282
+ case 66: {
9283
+ const messageValue = syx[7];
9284
+ if (syx[4] === 64 || syx[4] === 0 && syx[6] === 127) {
9285
+ if ((syx[5] & 16) > 0) {
9286
+ const channel = [
9287
+ 9,
9288
+ 0,
9289
+ 1,
9290
+ 2,
9291
+ 3,
9292
+ 4,
9293
+ 5,
9294
+ 6,
9295
+ 7,
9296
+ 8,
9297
+ 10,
9298
+ 11,
9299
+ 12,
9300
+ 13,
9301
+ 14,
9302
+ 15
9303
+ ][syx[5] & 15] + channelOffset;
9304
+ const channelObject = this.midiChannels[channel];
9305
+ switch (syx[6]) {
9306
+ default:
9307
+ sysExNotRecognized(syx, "Roland GS");
9308
+ break;
9309
+ case 21: {
9310
+ const isDrums = messageValue > 0 && syx[5] >> 4 > 0;
9311
+ channelObject.setGSDrums(isDrums);
9312
+ SpessaSynthInfo(
9313
+ `%cChannel %c${channel}%c ${isDrums ? "is now a drum channel" : "now isn't a drum channel"}%c via: %c${arrayToHexString(syx)}`,
9314
+ consoleColors.info,
9315
+ consoleColors.value,
9316
+ consoleColors.recognized,
9317
+ consoleColors.info,
9318
+ consoleColors.value
9085
9319
  );
9086
9320
  return;
9087
9321
  }
9088
- for (let i = 0; i < 128; i++) {
9089
- this.privateProps.tunings[program][i] = getTuning(
9090
- syx[currentMessageIndex++],
9091
- syx[currentMessageIndex++],
9092
- syx[currentMessageIndex++]
9322
+ case 22: {
9323
+ const keyShift = messageValue - 64;
9324
+ channelObject.setCustomController(
9325
+ customControllers.channelKeyShift,
9326
+ keyShift
9093
9327
  );
9094
- }
9095
- SpessaSynthInfo(
9096
- `%cBulk Tuning Dump %c${tuningName}%c Program: %c${program}`,
9097
- consoleColors.info,
9098
- consoleColors.value,
9099
- consoleColors.info,
9100
- consoleColors.recognized
9101
- );
9102
- break;
9103
- }
9104
- // Single note change
9105
- // Single note change bank
9106
- case 2:
9107
- case 7: {
9108
- if (syx[3] === 7) {
9109
- currentMessageIndex++;
9110
- }
9111
- const tuningProgram = syx[currentMessageIndex++];
9112
- const numberOfChanges = syx[currentMessageIndex++];
9113
- for (let i = 0; i < numberOfChanges; i++) {
9114
- this.privateProps.tunings[tuningProgram][syx[currentMessageIndex++]] = getTuning(
9115
- syx[currentMessageIndex++],
9116
- syx[currentMessageIndex++],
9117
- syx[currentMessageIndex++]
9328
+ sysExLogging(
9329
+ syx,
9330
+ channel,
9331
+ keyShift,
9332
+ "key shift",
9333
+ "keys"
9118
9334
  );
9335
+ return;
9119
9336
  }
9120
- SpessaSynthInfo(
9121
- `%cSingle Note Tuning. Program: %c${tuningProgram}%c Keys affected: %c${numberOfChanges}`,
9122
- consoleColors.info,
9123
- consoleColors.recognized,
9124
- consoleColors.info,
9125
- consoleColors.recognized
9126
- );
9127
- break;
9128
- }
9129
- // Octave tuning (1 byte)
9130
- // And octave tuning (2 bytes)
9131
- case 9:
9132
- case 8: {
9133
- const newOctaveTuning = new Int8Array(12);
9134
- if (syx[3] === 8) {
9135
- for (let i = 0; i < 12; i++) {
9136
- newOctaveTuning[i] = syx[7 + i] - 64;
9137
- }
9138
- } else {
9139
- for (let i = 0; i < 24; i += 2) {
9140
- const tuning = (syx[7 + i] << 7 | syx[8 + i]) - 8192;
9141
- newOctaveTuning[i / 2] = Math.floor(
9142
- tuning / 81.92
9337
+ // Pan position
9338
+ case 28: {
9339
+ const panPosition = messageValue;
9340
+ if (panPosition === 0) {
9341
+ channelObject.randomPan = true;
9342
+ SpessaSynthInfo(
9343
+ `%cRandom pan is set to %cON%c for %c${channel}`,
9344
+ consoleColors.info,
9345
+ consoleColors.recognized,
9346
+ consoleColors.info,
9347
+ consoleColors.value
9348
+ );
9349
+ } else {
9350
+ channelObject.randomPan = false;
9351
+ channelObject.controllerChange(
9352
+ midiControllers.pan,
9353
+ panPosition
9143
9354
  );
9144
9355
  }
9356
+ break;
9145
9357
  }
9146
- if ((syx[4] & 1) === 1) {
9147
- this.midiChannels[14 + channelOffset].setOctaveTuning(newOctaveTuning);
9148
- }
9149
- if ((syx[4] >> 1 & 1) === 1) {
9150
- this.midiChannels[15 + channelOffset].setOctaveTuning(newOctaveTuning);
9151
- }
9152
- for (let i = 0; i < 7; i++) {
9153
- const bit = syx[5] >> i & 1;
9154
- if (bit === 1) {
9155
- this.midiChannels[7 + i + channelOffset].setOctaveTuning(newOctaveTuning);
9156
- }
9157
- }
9158
- for (let i = 0; i < 7; i++) {
9159
- const bit = syx[6] >> i & 1;
9160
- if (bit === 1) {
9161
- this.midiChannels[i + channelOffset].setOctaveTuning(newOctaveTuning);
9358
+ // Chorus send
9359
+ case 33:
9360
+ channelObject.controllerChange(
9361
+ midiControllers.chorusDepth,
9362
+ messageValue
9363
+ );
9364
+ break;
9365
+ // Reverb send
9366
+ case 34:
9367
+ channelObject.controllerChange(
9368
+ midiControllers.reverbDepth,
9369
+ messageValue
9370
+ );
9371
+ break;
9372
+ case 64:
9373
+ case 65:
9374
+ case 66:
9375
+ case 67:
9376
+ case 68:
9377
+ case 69:
9378
+ case 70:
9379
+ case 71:
9380
+ case 72:
9381
+ case 73:
9382
+ case 74:
9383
+ case 75: {
9384
+ const tuningBytes = syx.length - 9;
9385
+ const newTuning = new Int8Array(12);
9386
+ for (let i = 0; i < tuningBytes; i++) {
9387
+ newTuning[i] = syx[i + 7] - 64;
9162
9388
  }
9389
+ channelObject.setOctaveTuning(newTuning);
9390
+ const cents = messageValue - 64;
9391
+ sysExLogging(
9392
+ syx,
9393
+ channel,
9394
+ newTuning.join(" "),
9395
+ "octave scale tuning",
9396
+ "cents"
9397
+ );
9398
+ channelObject.setTuning(cents);
9399
+ break;
9163
9400
  }
9164
- SpessaSynthInfo(
9165
- `%cMIDI Octave Scale ${syx[3] === 8 ? "(1 byte)" : "(2 bytes)"} tuning via Tuning: %c${newOctaveTuning.join(" ")}`,
9166
- consoleColors.info,
9167
- consoleColors.value
9168
- );
9169
- break;
9170
9401
  }
9171
- default:
9172
- SpessaSynthInfo(
9173
- `%cUnrecognized MIDI Tuning standard message: %c${arrayToHexString(syx)}`,
9174
- consoleColors.warn,
9175
- consoleColors.unrecognized
9176
- );
9177
- break;
9178
- }
9179
- break;
9180
- }
9181
- default:
9182
- SpessaSynthInfo(
9183
- `%cUnrecognized MIDI Realtime/non realtime message: %c${arrayToHexString(syx)}`,
9184
- consoleColors.warn,
9185
- consoleColors.unrecognized
9186
- );
9187
- }
9188
- break;
9189
- // This is a roland sysex
9190
- // http://www.bandtrax.com.au/sysex.htm
9191
- // https://cdn.roland.com/assets/media/pdf/AT-20R_30R_MI.pdf
9192
- case 65:
9193
- if (syx[3] === 18) {
9194
- let notRecognized2 = function() {
9195
- SpessaSynthInfo(
9196
- `%cUnrecognized Roland %cGS %cSysEx: %c${arrayToHexString(syx)}`,
9197
- consoleColors.warn,
9198
- consoleColors.recognized,
9199
- consoleColors.warn,
9200
- consoleColors.unrecognized
9201
- );
9202
- };
9203
- var notRecognized = notRecognized2;
9204
- switch (syx[2]) {
9205
- case 66: {
9206
- const messageValue = syx[7];
9207
- if (syx[4] === 64 || syx[4] === 0 && syx[6] === 127) {
9208
- if ((syx[5] & 16) > 0) {
9209
- const channel = [
9210
- 9,
9211
- 0,
9212
- 1,
9213
- 2,
9214
- 3,
9215
- 4,
9216
- 5,
9217
- 6,
9218
- 7,
9219
- 8,
9220
- 10,
9221
- 11,
9222
- 12,
9223
- 13,
9224
- 14,
9225
- 15
9226
- ][syx[5] & 15] + channelOffset;
9227
- const channelObject = this.midiChannels[channel];
9228
- switch (syx[6]) {
9229
- default:
9230
- notRecognized2();
9231
- break;
9232
- case 21: {
9233
- const isDrums = messageValue > 0 && syx[5] >> 4 > 0;
9234
- channelObject.setGSDrums(isDrums);
9235
- SpessaSynthInfo(
9236
- `%cChannel %c${channel}%c ${isDrums ? "is now a drum channel" : "now isn't a drum channel"}%c via: %c${arrayToHexString(syx)}`,
9237
- consoleColors.info,
9238
- consoleColors.value,
9239
- consoleColors.recognized,
9240
- consoleColors.info,
9241
- consoleColors.value
9242
- );
9243
- return;
9244
- }
9245
- case 22: {
9246
- const keyShift = messageValue - 64;
9247
- channelObject.setCustomController(
9248
- customControllers.channelKeyShift,
9249
- keyShift
9250
- );
9251
- niceLogging(
9252
- channel,
9253
- keyShift,
9254
- "key shift",
9255
- "keys"
9256
- );
9257
- return;
9258
- }
9259
- // Pan position
9260
- case 28: {
9261
- const panPosition = messageValue;
9262
- if (panPosition === 0) {
9263
- channelObject.randomPan = true;
9264
- SpessaSynthInfo(
9265
- `%cRandom pan is set to %cON%c for %c${channel}`,
9266
- consoleColors.info,
9267
- consoleColors.recognized,
9268
- consoleColors.info,
9269
- consoleColors.value
9270
- );
9271
- } else {
9272
- channelObject.randomPan = false;
9273
- channelObject.controllerChange(
9274
- midiControllers.pan,
9275
- panPosition
9276
- );
9277
- }
9278
- break;
9279
- }
9280
- // Chorus send
9281
- case 33:
9402
+ return;
9403
+ } else if ((syx[5] & 32) > 0) {
9404
+ const channel = [
9405
+ 9,
9406
+ 0,
9407
+ 1,
9408
+ 2,
9409
+ 3,
9410
+ 4,
9411
+ 5,
9412
+ 6,
9413
+ 7,
9414
+ 8,
9415
+ 10,
9416
+ 11,
9417
+ 12,
9418
+ 13,
9419
+ 14,
9420
+ 15
9421
+ ][syx[5] & 15] + channelOffset;
9422
+ const channelObject = this.midiChannels[channel];
9423
+ const centeredValue = messageValue - 64;
9424
+ const normalizedValue = centeredValue / 64;
9425
+ const normalizedNotCentered = messageValue / 128;
9426
+ const setupReceivers = (source, sourceName, bipolar = false) => {
9427
+ switch (syx[6] & 15) {
9428
+ case 0:
9429
+ if (source === NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel) {
9282
9430
  channelObject.controllerChange(
9283
- midiControllers.chorusDepth,
9284
- messageValue
9431
+ midiControllers.registeredParameterMSB,
9432
+ 0
9285
9433
  );
9286
- break;
9287
- // Reverb send
9288
- case 34:
9289
9434
  channelObject.controllerChange(
9290
- midiControllers.reverbDepth,
9291
- messageValue
9292
- );
9293
- break;
9294
- case 64:
9295
- case 65:
9296
- case 66:
9297
- case 67:
9298
- case 68:
9299
- case 69:
9300
- case 70:
9301
- case 71:
9302
- case 72:
9303
- case 73:
9304
- case 74:
9305
- case 75: {
9306
- const tuningBytes = syx.length - 9;
9307
- const newTuning = new Int8Array(12);
9308
- for (let i = 0; i < tuningBytes; i++) {
9309
- newTuning[i] = syx[i + 7] - 64;
9310
- }
9311
- channelObject.setOctaveTuning(
9312
- newTuning
9313
- );
9314
- const cents = messageValue - 64;
9315
- niceLogging(
9316
- channel,
9317
- newTuning.join(" "),
9318
- "octave scale tuning",
9319
- "cents"
9320
- );
9321
- channelObject.setTuning(cents);
9322
- break;
9323
- }
9324
- }
9325
- return;
9326
- } else if ((syx[5] & 32) > 0) {
9327
- const channel = [
9328
- 9,
9329
- 0,
9330
- 1,
9331
- 2,
9332
- 3,
9333
- 4,
9334
- 5,
9335
- 6,
9336
- 7,
9337
- 8,
9338
- 10,
9339
- 11,
9340
- 12,
9341
- 13,
9342
- 14,
9343
- 15
9344
- ][syx[5] & 15] + channelOffset;
9345
- const channelObject = this.midiChannels[channel];
9346
- const centeredValue = messageValue - 64;
9347
- const normalizedValue = centeredValue / 64;
9348
- const normalizedNotCentered = messageValue / 128;
9349
- const setupReceivers = (source, sourceName, bipolar = false) => {
9350
- switch (syx[6] & 15) {
9351
- case 0:
9352
- if (source === NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel) {
9353
- channelObject.controllerChange(
9354
- midiControllers.registeredParameterMSB,
9355
- 0
9356
- );
9357
- channelObject.controllerChange(
9358
- midiControllers.registeredParameterLSB,
9359
- 0
9360
- );
9361
- channelObject.controllerChange(
9362
- midiControllers.dataEntryMSB,
9363
- Math.floor(centeredValue)
9364
- );
9365
- } else {
9366
- channelObject.sysExModulators.setModulator(
9367
- source,
9368
- generatorTypes.fineTune,
9369
- centeredValue * 100,
9370
- bipolar
9371
- );
9372
- niceLogging(
9373
- channel,
9374
- centeredValue,
9375
- `${sourceName} pitch control`,
9376
- "semitones"
9377
- );
9378
- }
9379
- break;
9380
- case 1:
9381
- channelObject.sysExModulators.setModulator(
9382
- source,
9383
- generatorTypes.initialFilterFc,
9384
- normalizedValue * 9600,
9385
- bipolar
9386
- );
9387
- niceLogging(
9388
- channel,
9389
- normalizedValue * 9600,
9390
- `${sourceName} pitch control`,
9391
- "cents"
9392
- );
9393
- break;
9394
- case 2:
9395
- channelObject.sysExModulators.setModulator(
9396
- source,
9397
- generatorTypes.initialAttenuation,
9398
- normalizedValue * 960,
9399
- // Spec says "100%" so 960cB in sf2
9400
- bipolar
9401
- );
9402
- niceLogging(
9403
- channel,
9404
- normalizedValue * 960,
9405
- `${sourceName} amplitude`,
9406
- "cB"
9407
- );
9408
- break;
9409
- // Rate control is ignored as it is in hertz
9410
- case 4:
9411
- channelObject.sysExModulators.setModulator(
9412
- source,
9413
- generatorTypes.vibLfoToPitch,
9414
- normalizedNotCentered * 600,
9415
- bipolar
9416
- );
9417
- niceLogging(
9418
- channel,
9419
- normalizedNotCentered * 600,
9420
- `${sourceName} LFO1 pitch depth`,
9421
- "cents"
9422
- );
9423
- break;
9424
- case 5:
9425
- channelObject.sysExModulators.setModulator(
9426
- source,
9427
- generatorTypes.vibLfoToFilterFc,
9428
- normalizedNotCentered * 2400,
9429
- bipolar
9430
- );
9431
- niceLogging(
9432
- channel,
9433
- normalizedNotCentered * 2400,
9434
- `${sourceName} LFO1 filter depth`,
9435
- "cents"
9436
- );
9437
- break;
9438
- case 6:
9439
- channelObject.sysExModulators.setModulator(
9440
- source,
9441
- generatorTypes.vibLfoToVolume,
9442
- normalizedValue * 960,
9443
- bipolar
9444
- );
9445
- niceLogging(
9446
- channel,
9447
- normalizedValue * 960,
9448
- `${sourceName} LFO1 amplitude depth`,
9449
- "cB"
9450
- );
9451
- break;
9452
- // Rate control is ignored as it is in hertz
9453
- case 8:
9454
- channelObject.sysExModulators.setModulator(
9455
- source,
9456
- generatorTypes.modLfoToPitch,
9457
- normalizedNotCentered * 600,
9458
- bipolar
9459
- );
9460
- niceLogging(
9461
- channel,
9462
- normalizedNotCentered * 600,
9463
- `${sourceName} LFO2 pitch depth`,
9464
- "cents"
9465
- );
9466
- break;
9467
- case 9:
9468
- channelObject.sysExModulators.setModulator(
9469
- source,
9470
- generatorTypes.modLfoToFilterFc,
9471
- normalizedNotCentered * 2400,
9472
- bipolar
9473
- );
9474
- niceLogging(
9475
- channel,
9476
- normalizedNotCentered * 2400,
9477
- `${sourceName} LFO2 filter depth`,
9478
- "cents"
9479
- );
9480
- break;
9481
- case 10:
9482
- channelObject.sysExModulators.setModulator(
9483
- source,
9484
- generatorTypes.modLfoToVolume,
9485
- normalizedValue * 960,
9486
- bipolar
9487
- );
9488
- niceLogging(
9489
- channel,
9490
- normalizedValue * 960,
9491
- `${sourceName} LFO2 amplitude depth`,
9492
- "cB"
9493
- );
9494
- break;
9495
- }
9496
- };
9497
- switch (syx[6] & 240) {
9498
- default:
9499
- notRecognized2();
9500
- break;
9501
- case 0:
9502
- setupReceivers(
9503
- midiControllers.modulationWheel,
9504
- "mod wheel"
9505
- );
9506
- break;
9507
- case 16:
9508
- setupReceivers(
9509
- NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel,
9510
- "pitch wheel",
9511
- true
9435
+ midiControllers.registeredParameterLSB,
9436
+ 0
9512
9437
  );
9513
- break;
9514
- case 32:
9515
- setupReceivers(
9516
- NON_CC_INDEX_OFFSET + modulatorSources.channelPressure,
9517
- "channel pressure"
9518
- );
9519
- break;
9520
- case 48:
9521
- setupReceivers(
9522
- NON_CC_INDEX_OFFSET + modulatorSources.polyPressure,
9523
- "poly pressure"
9524
- );
9525
- break;
9526
- }
9527
- return;
9528
- } else if (syx[5] === 0) {
9529
- switch (syx[6]) {
9530
- default:
9531
- notRecognized2();
9532
- break;
9533
- case 127:
9534
- if (messageValue === 0) {
9535
- SpessaSynthInfo(
9536
- "%cGS Reset received!",
9537
- consoleColors.info
9538
- );
9539
- this.resetAllControllers(false);
9540
- this.setMasterParameter(
9541
- "midiSystem",
9542
- "gs"
9543
- );
9544
- } else if (messageValue === 127) {
9545
- SpessaSynthInfo(
9546
- "%cGS system off, switching to GM",
9547
- consoleColors.info
9548
- );
9549
- this.resetAllControllers(false);
9550
- this.setMasterParameter(
9551
- "midiSystem",
9552
- "gm"
9553
- );
9554
- }
9555
- break;
9556
- case 6:
9557
- SpessaSynthInfo(
9558
- `%cRoland GS Master Pan set to: %c${messageValue}%c with: %c${arrayToHexString(
9559
- syx
9560
- )}`,
9561
- consoleColors.info,
9562
- consoleColors.value,
9563
- consoleColors.info,
9564
- consoleColors.value
9565
- );
9566
- this.setMasterParameter(
9567
- "masterPan",
9568
- (messageValue - 64) / 64
9569
- );
9570
- break;
9571
- case 4:
9572
- SpessaSynthInfo(
9573
- `%cRoland GS Master Volume set to: %c${messageValue}%c with: %c${arrayToHexString(
9574
- syx
9575
- )}`,
9576
- consoleColors.info,
9577
- consoleColors.value,
9578
- consoleColors.info,
9579
- consoleColors.value
9438
+ channelObject.controllerChange(
9439
+ midiControllers.dataEntryMSB,
9440
+ Math.floor(centeredValue)
9580
9441
  );
9581
- this.setMIDIVolume(messageValue / 127);
9582
- break;
9583
- case 5: {
9584
- const transpose = messageValue - 64;
9585
- SpessaSynthInfo(
9586
- `%cRoland GS Master Key-Shift set to: %c${transpose}%c with: %c${arrayToHexString(
9587
- syx
9588
- )}`,
9589
- consoleColors.info,
9590
- consoleColors.value,
9591
- consoleColors.info,
9592
- consoleColors.value
9442
+ } else {
9443
+ channelObject.sysExModulators.setModulator(
9444
+ source,
9445
+ generatorTypes.fineTune,
9446
+ centeredValue * 100,
9447
+ bipolar
9593
9448
  );
9594
- this.setMasterTuning(transpose * 100);
9595
- break;
9596
- }
9597
- }
9598
- return;
9599
- } else if (syx[5] === 1) {
9600
- switch (syx[6]) {
9601
- default:
9602
- notRecognized2();
9603
- break;
9604
- case 0: {
9605
- const patchName = readBinaryString(
9449
+ sysExLogging(
9606
9450
  syx,
9607
- 16,
9608
- 7
9609
- );
9610
- SpessaSynthInfo(
9611
- `%cGS Patch name: %c${patchName}`,
9612
- consoleColors.info,
9613
- consoleColors.value
9451
+ channel,
9452
+ centeredValue,
9453
+ `${sourceName} pitch control`,
9454
+ "semitones"
9614
9455
  );
9615
- break;
9616
9456
  }
9617
- case 51:
9618
- SpessaSynthInfo(
9619
- `%cGS Reverb level: %c${messageValue}`,
9620
- consoleColors.info,
9621
- consoleColors.value
9622
- );
9623
- this.privateProps.reverbSend = messageValue / 64;
9624
- break;
9625
- // Unsupported reverb params
9626
- case 48:
9627
- case 49:
9628
- case 50:
9629
- case 52:
9630
- case 53:
9631
- case 55:
9632
- SpessaSynthInfo(
9633
- `%cUnsupported GS Reverb Parameter: %c${syx[6].toString(16)}`,
9634
- consoleColors.warn,
9635
- consoleColors.unrecognized
9636
- );
9637
- break;
9638
- case 58:
9639
- SpessaSynthInfo(
9640
- `%cGS Chorus level: %c${messageValue}`,
9641
- consoleColors.info,
9642
- consoleColors.value
9643
- );
9644
- this.privateProps.chorusSend = messageValue / 64;
9645
- break;
9646
- // Unsupported chorus params
9647
- case 56:
9648
- case 57:
9649
- case 59:
9650
- case 60:
9651
- case 61:
9652
- case 62:
9653
- case 63:
9654
- case 64:
9655
- SpessaSynthInfo(
9656
- `%cUnsupported GS Chorus Parameter: %c${syx[6].toString(16)}`,
9657
- consoleColors.warn,
9658
- consoleColors.unrecognized
9659
- );
9660
- break;
9661
- }
9457
+ break;
9458
+ case 1:
9459
+ channelObject.sysExModulators.setModulator(
9460
+ source,
9461
+ generatorTypes.initialFilterFc,
9462
+ normalizedValue * 9600,
9463
+ bipolar
9464
+ );
9465
+ sysExLogging(
9466
+ syx,
9467
+ channel,
9468
+ normalizedValue * 9600,
9469
+ `${sourceName} pitch control`,
9470
+ "cents"
9471
+ );
9472
+ break;
9473
+ case 2:
9474
+ channelObject.sysExModulators.setModulator(
9475
+ source,
9476
+ generatorTypes.initialAttenuation,
9477
+ normalizedValue * 960,
9478
+ // Spec says "100%" so 960cB in sf2
9479
+ bipolar
9480
+ );
9481
+ sysExLogging(
9482
+ syx,
9483
+ channel,
9484
+ normalizedValue * 960,
9485
+ `${sourceName} amplitude`,
9486
+ "cB"
9487
+ );
9488
+ break;
9489
+ // Rate control is ignored as it is in hertz
9490
+ case 4:
9491
+ channelObject.sysExModulators.setModulator(
9492
+ source,
9493
+ generatorTypes.vibLfoToPitch,
9494
+ normalizedNotCentered * 600,
9495
+ bipolar
9496
+ );
9497
+ sysExLogging(
9498
+ syx,
9499
+ channel,
9500
+ normalizedNotCentered * 600,
9501
+ `${sourceName} LFO1 pitch depth`,
9502
+ "cents"
9503
+ );
9504
+ break;
9505
+ case 5:
9506
+ channelObject.sysExModulators.setModulator(
9507
+ source,
9508
+ generatorTypes.vibLfoToFilterFc,
9509
+ normalizedNotCentered * 2400,
9510
+ bipolar
9511
+ );
9512
+ sysExLogging(
9513
+ syx,
9514
+ channel,
9515
+ normalizedNotCentered * 2400,
9516
+ `${sourceName} LFO1 filter depth`,
9517
+ "cents"
9518
+ );
9519
+ break;
9520
+ case 6:
9521
+ channelObject.sysExModulators.setModulator(
9522
+ source,
9523
+ generatorTypes.vibLfoToVolume,
9524
+ normalizedValue * 960,
9525
+ bipolar
9526
+ );
9527
+ sysExLogging(
9528
+ syx,
9529
+ channel,
9530
+ normalizedValue * 960,
9531
+ `${sourceName} LFO1 amplitude depth`,
9532
+ "cB"
9533
+ );
9534
+ break;
9535
+ // Rate control is ignored as it is in hertz
9536
+ case 8:
9537
+ channelObject.sysExModulators.setModulator(
9538
+ source,
9539
+ generatorTypes.modLfoToPitch,
9540
+ normalizedNotCentered * 600,
9541
+ bipolar
9542
+ );
9543
+ sysExLogging(
9544
+ syx,
9545
+ channel,
9546
+ normalizedNotCentered * 600,
9547
+ `${sourceName} LFO2 pitch depth`,
9548
+ "cents"
9549
+ );
9550
+ break;
9551
+ case 9:
9552
+ channelObject.sysExModulators.setModulator(
9553
+ source,
9554
+ generatorTypes.modLfoToFilterFc,
9555
+ normalizedNotCentered * 2400,
9556
+ bipolar
9557
+ );
9558
+ sysExLogging(
9559
+ syx,
9560
+ channel,
9561
+ normalizedNotCentered * 2400,
9562
+ `${sourceName} LFO2 filter depth`,
9563
+ "cents"
9564
+ );
9565
+ break;
9566
+ case 10:
9567
+ channelObject.sysExModulators.setModulator(
9568
+ source,
9569
+ generatorTypes.modLfoToVolume,
9570
+ normalizedValue * 960,
9571
+ bipolar
9572
+ );
9573
+ sysExLogging(
9574
+ syx,
9575
+ channel,
9576
+ normalizedValue * 960,
9577
+ `${sourceName} LFO2 amplitude depth`,
9578
+ "cB"
9579
+ );
9580
+ break;
9662
9581
  }
9663
- } else {
9664
- notRecognized2();
9665
- }
9666
- return;
9667
- }
9668
- case 69: {
9669
- if (syx[4] === 16) {
9670
- if (syx[5] === 0) {
9671
- this.privateProps.callEvent(
9672
- "synthDisplay",
9673
- Array.from(syx)
9582
+ };
9583
+ switch (syx[6] & 240) {
9584
+ default:
9585
+ sysExNotRecognized(syx, "Roland GS");
9586
+ break;
9587
+ case 0:
9588
+ setupReceivers(
9589
+ midiControllers.modulationWheel,
9590
+ "mod wheel"
9674
9591
  );
9675
- } else if (syx[5] === 1) {
9676
- this.privateProps.callEvent(
9677
- "synthDisplay",
9678
- Array.from(syx)
9592
+ break;
9593
+ case 16:
9594
+ setupReceivers(
9595
+ NON_CC_INDEX_OFFSET + modulatorSources.pitchWheel,
9596
+ "pitch wheel",
9597
+ true
9679
9598
  );
9680
- } else {
9681
- notRecognized2();
9682
- }
9683
- }
9684
- return;
9685
- }
9686
- case 22:
9687
- if (syx[4] === 16) {
9688
- this.setMIDIVolume(syx[7] / 100);
9689
- SpessaSynthInfo(
9690
- `%cRoland Master Volume control set to: %c${syx[7]}%c via: %c${arrayToHexString(
9691
- syx
9692
- )}`,
9693
- consoleColors.info,
9694
- consoleColors.value,
9695
- consoleColors.info,
9696
- consoleColors.value
9697
- );
9698
- return;
9699
- }
9700
- }
9701
- } else {
9702
- SpessaSynthInfo(
9703
- `%cUnrecognized Roland SysEx: %c${arrayToHexString(syx)}`,
9704
- consoleColors.warn,
9705
- consoleColors.unrecognized
9706
- );
9707
- return;
9708
- }
9709
- break;
9710
- // Yamaha
9711
- // http://www.studio4all.de/htmle/main91.html
9712
- case 67:
9713
- if (syx[2] === 76) {
9714
- if (syx[3] === 0 && syx[4] === 0) {
9715
- switch (syx[5]) {
9716
- // Master volume
9717
- case 4: {
9718
- const vol = syx[6];
9719
- this.setMIDIVolume(vol / 127);
9720
- SpessaSynthInfo(
9721
- `%cXG master volume. Volume: %c${vol}`,
9722
- consoleColors.info,
9723
- consoleColors.recognized
9724
- );
9725
- break;
9726
- }
9727
- // Master transpose
9728
- case 6: {
9729
- const transpose = syx[6] - 64;
9730
- this.setMasterParameter("transposition", transpose);
9731
- SpessaSynthInfo(
9732
- `%cXG master transpose. Volume: %c${transpose}`,
9733
- consoleColors.info,
9734
- consoleColors.recognized
9735
- );
9736
- break;
9737
- }
9738
- // XG on
9739
- case 126:
9740
- SpessaSynthInfo(
9741
- "%cXG system on",
9742
- consoleColors.info
9743
- );
9744
- this.resetAllControllers(false);
9745
- this.setMasterParameter("midiSystem", "xg");
9746
- break;
9747
- }
9748
- } else if (syx[3] === 8) {
9749
- if (!BankSelectHacks.isSystemXG(
9750
- this.privateProps.masterParameters.midiSystem
9751
- )) {
9752
- return;
9753
- }
9754
- const channel = syx[4] + channelOffset;
9755
- if (channel >= this.midiChannels.length) {
9599
+ break;
9600
+ case 32:
9601
+ setupReceivers(
9602
+ NON_CC_INDEX_OFFSET + modulatorSources.channelPressure,
9603
+ "channel pressure"
9604
+ );
9605
+ break;
9606
+ case 48:
9607
+ setupReceivers(
9608
+ NON_CC_INDEX_OFFSET + modulatorSources.polyPressure,
9609
+ "poly pressure"
9610
+ );
9611
+ break;
9612
+ }
9756
9613
  return;
9757
- }
9758
- const channelObject = this.midiChannels[channel];
9759
- const value = syx[6];
9760
- switch (syx[5]) {
9761
- // Bank-select MSB
9762
- case 1:
9763
- channelObject.controllerChange(
9764
- midiControllers.bankSelect,
9765
- value
9766
- );
9767
- break;
9768
- // Bank-select LSB
9769
- case 2:
9770
- channelObject.controllerChange(
9771
- midiControllers.bankSelectLSB,
9772
- value
9773
- );
9774
- break;
9775
- // Program change
9776
- case 3:
9777
- channelObject.programChange(value);
9778
- break;
9779
- // Note shift
9780
- case 8: {
9781
- if (channelObject.drumChannel) {
9782
- return;
9614
+ } else if (syx[5] === 0) {
9615
+ switch (syx[6]) {
9616
+ default:
9617
+ sysExNotRecognized(syx, "Roland GS");
9618
+ break;
9619
+ case 127:
9620
+ if (messageValue === 0) {
9621
+ SpessaSynthInfo(
9622
+ "%cGS Reset received!",
9623
+ consoleColors.info
9624
+ );
9625
+ this.resetAllControllers("gs");
9626
+ } else if (messageValue === 127) {
9627
+ SpessaSynthInfo(
9628
+ "%cGS system off, switching to GM",
9629
+ consoleColors.info
9630
+ );
9631
+ this.resetAllControllers("gm");
9632
+ }
9633
+ break;
9634
+ case 6:
9635
+ SpessaSynthInfo(
9636
+ `%cRoland GS Master Pan set to: %c${messageValue}%c with: %c${arrayToHexString(
9637
+ syx
9638
+ )}`,
9639
+ consoleColors.info,
9640
+ consoleColors.value,
9641
+ consoleColors.info,
9642
+ consoleColors.value
9643
+ );
9644
+ this.setMasterParameter(
9645
+ "masterPan",
9646
+ (messageValue - 64) / 64
9647
+ );
9648
+ break;
9649
+ case 4:
9650
+ SpessaSynthInfo(
9651
+ `%cRoland GS Master Volume set to: %c${messageValue}%c with: %c${arrayToHexString(
9652
+ syx
9653
+ )}`,
9654
+ consoleColors.info,
9655
+ consoleColors.value,
9656
+ consoleColors.info,
9657
+ consoleColors.value
9658
+ );
9659
+ this.setMIDIVolume(messageValue / 127);
9660
+ break;
9661
+ case 5: {
9662
+ const transpose = messageValue - 64;
9663
+ SpessaSynthInfo(
9664
+ `%cRoland GS Master Key-Shift set to: %c${transpose}%c with: %c${arrayToHexString(
9665
+ syx
9666
+ )}`,
9667
+ consoleColors.info,
9668
+ consoleColors.value,
9669
+ consoleColors.info,
9670
+ consoleColors.value
9671
+ );
9672
+ this.setMasterTuning(transpose * 100);
9673
+ break;
9783
9674
  }
9784
- channelObject.channelTransposeKeyShift = value - 64;
9785
- break;
9786
9675
  }
9787
- // Volume
9788
- case 11:
9789
- channelObject.controllerChange(
9790
- midiControllers.mainVolume,
9791
- value
9792
- );
9793
- break;
9794
- // Pan position
9795
- case 14: {
9796
- const pan = value;
9797
- if (pan === 0) {
9798
- channelObject.randomPan = true;
9676
+ return;
9677
+ } else if (syx[5] === 1) {
9678
+ switch (syx[6]) {
9679
+ default:
9680
+ sysExNotRecognized(syx, "Roland GS");
9681
+ break;
9682
+ case 0: {
9683
+ const patchName = readBinaryString(syx, 16, 7);
9799
9684
  SpessaSynthInfo(
9800
- `%cRandom pan is set to %cON%c for %c${channel}`,
9685
+ `%cGS Patch name: %c${patchName}`,
9801
9686
  consoleColors.info,
9802
- consoleColors.recognized,
9687
+ consoleColors.value
9688
+ );
9689
+ break;
9690
+ }
9691
+ case 51:
9692
+ SpessaSynthInfo(
9693
+ `%cGS Reverb level: %c${messageValue}`,
9803
9694
  consoleColors.info,
9804
9695
  consoleColors.value
9805
9696
  );
9806
- } else {
9807
- channelObject.controllerChange(
9808
- midiControllers.pan,
9809
- pan
9697
+ this.privateProps.reverbSend = messageValue / 64;
9698
+ break;
9699
+ // Unsupported reverb params
9700
+ case 48:
9701
+ case 49:
9702
+ case 50:
9703
+ case 52:
9704
+ case 53:
9705
+ case 55:
9706
+ SpessaSynthInfo(
9707
+ `%cUnsupported GS Reverb Parameter: %c${syx[6].toString(16)}`,
9708
+ consoleColors.warn,
9709
+ consoleColors.unrecognized
9810
9710
  );
9811
- }
9812
- break;
9711
+ break;
9712
+ case 58:
9713
+ SpessaSynthInfo(
9714
+ `%cGS Chorus level: %c${messageValue}`,
9715
+ consoleColors.info,
9716
+ consoleColors.value
9717
+ );
9718
+ this.privateProps.chorusSend = messageValue / 64;
9719
+ break;
9720
+ // Unsupported chorus params
9721
+ case 56:
9722
+ case 57:
9723
+ case 59:
9724
+ case 60:
9725
+ case 61:
9726
+ case 62:
9727
+ case 63:
9728
+ case 64:
9729
+ SpessaSynthInfo(
9730
+ `%cUnsupported GS Chorus Parameter: %c${syx[6].toString(16)}`,
9731
+ consoleColors.warn,
9732
+ consoleColors.unrecognized
9733
+ );
9734
+ break;
9813
9735
  }
9814
- // Reverb
9815
- case 19:
9816
- channelObject.controllerChange(
9817
- midiControllers.reverbDepth,
9818
- value
9819
- );
9820
- break;
9821
- // Chorus
9822
- case 18:
9823
- channelObject.controllerChange(
9824
- midiControllers.chorusDepth,
9825
- value
9826
- );
9827
- break;
9828
- default:
9829
- SpessaSynthInfo(
9830
- `%cUnrecognized Yamaha XG Part Setup: %c${syx[5].toString(16).toUpperCase()}`,
9831
- consoleColors.warn,
9832
- consoleColors.unrecognized
9833
- );
9834
9736
  }
9835
- } else if (syx[3] === 6 && // XG System parameter
9836
- syx[4] === 0) {
9837
- this.privateProps.callEvent(
9838
- "synthDisplay",
9839
- Array.from(syx)
9737
+ } else {
9738
+ sysExNotRecognized(syx, "Roland GS");
9739
+ }
9740
+ return;
9741
+ }
9742
+ case 69: {
9743
+ if (syx[4] === 16) {
9744
+ if (syx[5] === 0) {
9745
+ this.privateProps.callEvent(
9746
+ "synthDisplay",
9747
+ Array.from(syx)
9748
+ );
9749
+ } else if (syx[5] === 1) {
9750
+ this.privateProps.callEvent(
9751
+ "synthDisplay",
9752
+ Array.from(syx)
9753
+ );
9754
+ } else {
9755
+ sysExNotRecognized(syx, "Roland GS");
9756
+ }
9757
+ }
9758
+ return;
9759
+ }
9760
+ case 22:
9761
+ if (syx[4] === 16) {
9762
+ this.setMIDIVolume(syx[7] / 100);
9763
+ SpessaSynthInfo(
9764
+ `%cRoland Master Volume control set to: %c${syx[7]}%c via: %c${arrayToHexString(
9765
+ syx
9766
+ )}`,
9767
+ consoleColors.info,
9768
+ consoleColors.value,
9769
+ consoleColors.info,
9770
+ consoleColors.value
9771
+ );
9772
+ return;
9773
+ }
9774
+ }
9775
+ } else {
9776
+ sysExNotRecognized(syx, "Roland GS");
9777
+ return;
9778
+ }
9779
+ }
9780
+
9781
+ // src/synthesizer/audio_engine/engine_methods/system_exclusive/handle_xg.ts
9782
+ function handleXG(syx, channelOffset = 0) {
9783
+ if (syx[2] === 76) {
9784
+ const a1 = syx[3];
9785
+ const a2 = syx[4];
9786
+ if (a1 === 0 && a2 === 0) {
9787
+ switch (syx[5]) {
9788
+ // Master tune
9789
+ case 0:
9790
+ {
9791
+ const tune = (syx[6] & 15) << 12 | (syx[7] & 15) << 8 | (syx[8] & 15) << 4 | syx[9] & 15;
9792
+ const cents = (tune - 1024) / 10;
9793
+ this.setMasterTuning(cents);
9794
+ SpessaSynthInfo(
9795
+ `%cXG master tune. Cents: %c${cents}`,
9796
+ consoleColors.info,
9797
+ consoleColors.recognized
9798
+ );
9799
+ }
9800
+ break;
9801
+ // Master volume
9802
+ case 4: {
9803
+ const vol = syx[6];
9804
+ this.setMIDIVolume(vol / 127);
9805
+ SpessaSynthInfo(
9806
+ `%cXG master volume. Volume: %c${vol}`,
9807
+ consoleColors.info,
9808
+ consoleColors.recognized
9840
9809
  );
9841
- } else if (BankSelectHacks.isSystemXG(
9842
- this.privateProps.masterParameters.midiSystem
9843
- )) {
9810
+ break;
9811
+ }
9812
+ // Master attenuation
9813
+ case 5: {
9814
+ const vol = 127 - syx[6];
9815
+ this.setMIDIVolume(vol / 127);
9844
9816
  SpessaSynthInfo(
9845
- `%cUnrecognized Yamaha XG SysEx: %c${arrayToHexString(syx)}`,
9846
- consoleColors.warn,
9847
- consoleColors.unrecognized
9817
+ `%cXG master attenuation. Volume: %c${vol}`,
9818
+ consoleColors.info,
9819
+ consoleColors.recognized
9848
9820
  );
9821
+ break;
9849
9822
  }
9850
- } else {
9851
- if (BankSelectHacks.isSystemXG(
9852
- this.privateProps.masterParameters.midiSystem
9853
- )) {
9823
+ // Master transpose
9824
+ case 6: {
9825
+ const transpose = syx[6] - 64;
9826
+ this.setMasterParameter("transposition", transpose);
9854
9827
  SpessaSynthInfo(
9855
- `%cUnrecognized Yamaha SysEx: %c${arrayToHexString(syx)}`,
9856
- consoleColors.warn,
9857
- consoleColors.unrecognized
9828
+ `%cXG master transpose. Volume: %c${transpose}`,
9829
+ consoleColors.info,
9830
+ consoleColors.recognized
9831
+ );
9832
+ break;
9833
+ }
9834
+ //
9835
+ // XG on
9836
+ case 126:
9837
+ SpessaSynthInfo("%cXG system on", consoleColors.info);
9838
+ this.resetAllControllers("xg");
9839
+ break;
9840
+ }
9841
+ } else if (a1 === 2 && a2 === 1) {
9842
+ let effectType;
9843
+ const effect = syx[5];
9844
+ if (effect <= 21) effectType = "Reverb";
9845
+ else if (effect <= 35) effectType = "Chorus";
9846
+ else effectType = "Variation";
9847
+ SpessaSynthInfo(
9848
+ `%cUnsupported XG ${effectType} Parameter: %c${effect.toString(16)}`,
9849
+ consoleColors.warn,
9850
+ consoleColors.unrecognized
9851
+ );
9852
+ } else if (a1 === 8) {
9853
+ if (!BankSelectHacks.isSystemXG(
9854
+ this.privateProps.masterParameters.midiSystem
9855
+ )) {
9856
+ return;
9857
+ }
9858
+ const channel = a2 + channelOffset;
9859
+ if (channel >= this.midiChannels.length) {
9860
+ return;
9861
+ }
9862
+ const channelObject = this.midiChannels[channel];
9863
+ const value = syx[6];
9864
+ switch (syx[5]) {
9865
+ // Bank-select MSB
9866
+ case 1:
9867
+ channelObject.controllerChange(
9868
+ midiControllers.bankSelect,
9869
+ value
9870
+ );
9871
+ break;
9872
+ // Bank-select LSB
9873
+ case 2:
9874
+ channelObject.controllerChange(
9875
+ midiControllers.bankSelectLSB,
9876
+ value
9877
+ );
9878
+ break;
9879
+ // Program change
9880
+ case 3:
9881
+ channelObject.programChange(value);
9882
+ break;
9883
+ // Part mode
9884
+ case 7:
9885
+ channelObject.setDrums(value != 0);
9886
+ break;
9887
+ // Note shift
9888
+ case 8: {
9889
+ if (channelObject.drumChannel) {
9890
+ break;
9891
+ }
9892
+ channelObject.setCustomController(
9893
+ customControllers.channelKeyShift,
9894
+ value - 64
9858
9895
  );
9896
+ break;
9897
+ }
9898
+ // Volume
9899
+ case 11:
9900
+ channelObject.controllerChange(
9901
+ midiControllers.mainVolume,
9902
+ value
9903
+ );
9904
+ break;
9905
+ // Pan position
9906
+ case 14: {
9907
+ const pan = value;
9908
+ if (pan === 0) {
9909
+ channelObject.randomPan = true;
9910
+ SpessaSynthInfo(
9911
+ `%cRandom pan is set to %cON%c for %c${channel}`,
9912
+ consoleColors.info,
9913
+ consoleColors.recognized,
9914
+ consoleColors.info,
9915
+ consoleColors.value
9916
+ );
9917
+ } else {
9918
+ channelObject.controllerChange(
9919
+ midiControllers.pan,
9920
+ pan
9921
+ );
9922
+ }
9923
+ break;
9859
9924
  }
9925
+ // Dry
9926
+ case 17:
9927
+ channelObject.controllerChange(
9928
+ midiControllers.mainVolume,
9929
+ value
9930
+ );
9931
+ break;
9932
+ // Chorus
9933
+ case 18:
9934
+ channelObject.controllerChange(
9935
+ midiControllers.chorusDepth,
9936
+ value
9937
+ );
9938
+ break;
9939
+ // Reverb
9940
+ case 19:
9941
+ channelObject.controllerChange(
9942
+ midiControllers.reverbDepth,
9943
+ value
9944
+ );
9945
+ break;
9946
+ // Vibrato rate
9947
+ case 21:
9948
+ channelObject.controllerChange(
9949
+ midiControllers.vibratoRate,
9950
+ value
9951
+ );
9952
+ break;
9953
+ // Vibrato depth
9954
+ case 22:
9955
+ channelObject.controllerChange(
9956
+ midiControllers.vibratoDepth,
9957
+ value
9958
+ );
9959
+ break;
9960
+ // Vibrato delay
9961
+ case 23:
9962
+ channelObject.controllerChange(
9963
+ midiControllers.vibratoDelay,
9964
+ value
9965
+ );
9966
+ break;
9967
+ // Filter cutoff
9968
+ case 24:
9969
+ channelObject.controllerChange(
9970
+ midiControllers.brightness,
9971
+ value
9972
+ );
9973
+ break;
9974
+ // Filter resonance
9975
+ case 25:
9976
+ channelObject.controllerChange(
9977
+ midiControllers.filterResonance,
9978
+ value
9979
+ );
9980
+ break;
9981
+ // Attack time
9982
+ case 26:
9983
+ channelObject.controllerChange(
9984
+ midiControllers.attackTime,
9985
+ value
9986
+ );
9987
+ break;
9988
+ // Decay time
9989
+ case 27:
9990
+ channelObject.controllerChange(
9991
+ midiControllers.decayTime,
9992
+ value
9993
+ );
9994
+ break;
9995
+ // Release time
9996
+ case 28:
9997
+ channelObject.controllerChange(
9998
+ midiControllers.releaseTime,
9999
+ value
10000
+ );
10001
+ break;
10002
+ default:
10003
+ SpessaSynthInfo(
10004
+ `%cUnsupported Yamaha XG Part Setup: %c${syx[5].toString(16).toUpperCase()}%c for channel ${channel}`,
10005
+ consoleColors.warn,
10006
+ consoleColors.unrecognized,
10007
+ consoleColors.warn
10008
+ );
9860
10009
  }
10010
+ } else if (a1 === 6 && // XG System parameter
10011
+ a2 === 0) {
10012
+ this.privateProps.callEvent("synthDisplay", Array.from(syx));
10013
+ } else if (BankSelectHacks.isSystemXG(
10014
+ this.privateProps.masterParameters.midiSystem
10015
+ )) {
10016
+ sysExNotRecognized(syx, "Yamaha XG");
10017
+ }
10018
+ } else {
10019
+ sysExNotRecognized(syx, "Yamaha");
10020
+ }
10021
+ }
10022
+
10023
+ // src/synthesizer/audio_engine/engine_methods/system_exclusive.ts
10024
+ function systemExclusiveInternal(syx, channelOffset = 0) {
10025
+ const manufacturer = syx[0];
10026
+ if (
10027
+ // The device ID can be set to "all" which it is by default
10028
+ this.privateProps.masterParameters.deviceID !== ALL_CHANNELS_OR_DIFFERENT_ACTION && syx[1] !== 127
10029
+ ) {
10030
+ if (this.privateProps.masterParameters.deviceID !== syx[1]) {
10031
+ return;
10032
+ }
10033
+ }
10034
+ switch (manufacturer) {
10035
+ default:
10036
+ SpessaSynthInfo(
10037
+ `%cUnrecognized SysEx: %c${arrayToHexString(syx)} (unknown manufacturer)`,
10038
+ consoleColors.warn,
10039
+ consoleColors.unrecognized
10040
+ );
10041
+ break;
10042
+ // Non realtime GM
10043
+ case 126:
10044
+ // Realtime GM
10045
+ case 127:
10046
+ handleGM.call(this, syx, channelOffset);
10047
+ break;
10048
+ // Roland
10049
+ case 65:
10050
+ handleGS.call(this, syx, channelOffset);
10051
+ break;
10052
+ // Yamaha
10053
+ case 67:
10054
+ handleXG.call(this, syx, channelOffset);
9861
10055
  break;
9862
10056
  }
9863
10057
  }
@@ -10251,8 +10445,7 @@ var ProtectedSynthValues = class {
10251
10445
  voiceKilling;
10252
10446
  /**
10253
10447
  * Cached voices for all presets for this synthesizer.
10254
- * Nesting goes like this:
10255
- * this.cachedVoices[bankMSB][bankLSB][programNumber][midiNote][velocity] = a list of voices for that.
10448
+ * Nesting is calculated in getCachedVoiceIndex, returns a list of voices for this note.
10256
10449
  */
10257
10450
  cachedVoices = [];
10258
10451
  constructor(eventCallbackHandler, getVoices, voiceKillingFunction, volumeEnvelopeSmoothingFactor, panSmoothingFactor, filterSmoothingFactor) {
@@ -10606,13 +10799,14 @@ var nonRegisteredMSB = {
10606
10799
  awe32: 127,
10607
10800
  SF2: 120
10608
10801
  };
10609
- var nonRegisteredGSLSB = {
10802
+ var nonRegisteredLSB = {
10610
10803
  vibratoRate: 8,
10611
10804
  vibratoDepth: 9,
10612
10805
  vibratoDelay: 10,
10613
10806
  TVFFilterCutoff: 32,
10614
10807
  TVFFilterResonance: 33,
10615
10808
  EGAttackTime: 99,
10809
+ EGDecayTime: 100,
10616
10810
  EGReleaseTime: 102
10617
10811
  };
10618
10812
  function dataEntryCoarse(dataValue) {
@@ -10641,7 +10835,7 @@ function dataEntryCoarse(dataValue) {
10641
10835
  default:
10642
10836
  case dataEntryStates.Idle:
10643
10837
  break;
10644
- // Process GS NRPNs
10838
+ // Process NRPNs
10645
10839
  case dataEntryStates.NRPFine: {
10646
10840
  if (this.lockGSNRPNParams) {
10647
10841
  return;
@@ -10668,7 +10862,7 @@ function dataEntryCoarse(dataValue) {
10668
10862
  consoleColors.value
10669
10863
  );
10670
10864
  break;
10671
- // Part parameters: vibrato, cutoff
10865
+ // Part parameters
10672
10866
  case nonRegisteredMSB.partParameter:
10673
10867
  switch (NRPNFine) {
10674
10868
  default:
@@ -10687,8 +10881,8 @@ function dataEntryCoarse(dataValue) {
10687
10881
  consoleColors.value
10688
10882
  );
10689
10883
  break;
10690
- // Vibrato rate
10691
- case nonRegisteredGSLSB.vibratoRate:
10884
+ // Vibrato rate (custom vibrato)
10885
+ case nonRegisteredLSB.vibratoRate:
10692
10886
  if (dataValue === 64) {
10693
10887
  return;
10694
10888
  }
@@ -10700,8 +10894,8 @@ function dataEntryCoarse(dataValue) {
10700
10894
  "Hz"
10701
10895
  );
10702
10896
  break;
10703
- // Vibrato depth
10704
- case nonRegisteredGSLSB.vibratoDepth:
10897
+ // Vibrato depth (custom vibrato)
10898
+ case nonRegisteredLSB.vibratoDepth:
10705
10899
  if (dataValue === 64) {
10706
10900
  return;
10707
10901
  }
@@ -10713,8 +10907,8 @@ function dataEntryCoarse(dataValue) {
10713
10907
  "cents of detune"
10714
10908
  );
10715
10909
  break;
10716
- // Vibrato delay
10717
- case nonRegisteredGSLSB.vibratoDelay:
10910
+ // Vibrato delay (custom vibrato)
10911
+ case nonRegisteredLSB.vibratoDelay:
10718
10912
  if (dataValue === 64) {
10719
10913
  return;
10720
10914
  }
@@ -10727,15 +10921,26 @@ function dataEntryCoarse(dataValue) {
10727
10921
  );
10728
10922
  break;
10729
10923
  // Filter cutoff
10730
- case nonRegisteredGSLSB.TVFFilterCutoff:
10924
+ case nonRegisteredLSB.TVFFilterCutoff:
10731
10925
  this.controllerChange(
10732
10926
  midiControllers.brightness,
10733
10927
  dataValue
10734
10928
  );
10735
10929
  coolInfo("Filter cutoff", dataValue.toString(), "");
10736
10930
  break;
10931
+ case nonRegisteredLSB.TVFFilterResonance:
10932
+ this.controllerChange(
10933
+ midiControllers.filterResonance,
10934
+ dataValue
10935
+ );
10936
+ coolInfo(
10937
+ "Filter resonance",
10938
+ dataValue.toString(),
10939
+ ""
10940
+ );
10941
+ break;
10737
10942
  // Attack time
10738
- case nonRegisteredGSLSB.EGAttackTime:
10943
+ case nonRegisteredLSB.EGAttackTime:
10739
10944
  this.controllerChange(
10740
10945
  midiControllers.attackTime,
10741
10946
  dataValue
@@ -10746,8 +10951,15 @@ function dataEntryCoarse(dataValue) {
10746
10951
  ""
10747
10952
  );
10748
10953
  break;
10954
+ case nonRegisteredLSB.EGDecayTime:
10955
+ this.controllerChange(
10956
+ midiControllers.decayTime,
10957
+ dataValue
10958
+ );
10959
+ coolInfo("EG decay time", dataValue.toString(), "");
10960
+ break;
10749
10961
  // Release time
10750
- case nonRegisteredGSLSB.EGReleaseTime:
10962
+ case nonRegisteredLSB.EGReleaseTime:
10751
10963
  this.controllerChange(
10752
10964
  midiControllers.releaseTime,
10753
10965
  dataValue
@@ -11147,47 +11359,92 @@ function controllerChange(controllerNumber, controllerValue, sendEvent = true) {
11147
11359
  }
11148
11360
 
11149
11361
  // src/synthesizer/audio_engine/engine_methods/portamento_time.ts
11150
- var portamentoLookup = {
11151
- 0: 0,
11152
- 1: 6e-3,
11153
- 2: 0.023,
11154
- 4: 0.05,
11155
- 8: 0.11,
11156
- 16: 0.25,
11157
- 32: 0.5,
11158
- 64: 2.06,
11159
- 80: 4.2,
11160
- 96: 8.4,
11161
- 112: 19.5,
11162
- 116: 26.7,
11163
- 120: 40,
11164
- 124: 80,
11165
- 127: 480
11166
- };
11167
- function getLookup(value) {
11168
- if (portamentoLookup[value] !== void 0) {
11169
- return portamentoLookup[value];
11170
- }
11171
- let lower = null;
11172
- let upper = null;
11173
- for (const k of Object.keys(portamentoLookup)) {
11174
- const key = parseInt(k);
11175
- if (key < value && (lower === null || key > lower)) {
11176
- lower = key;
11177
- }
11178
- if (key > value && (upper === null || key < upper)) {
11179
- upper = key;
11180
- }
11181
- }
11182
- if (lower !== null && upper !== null) {
11183
- const lowerTime = portamentoLookup[lower];
11184
- const upperTime = portamentoLookup[upper];
11185
- return lowerTime + (value - lower) * (upperTime - lowerTime) / (upper - lower);
11362
+ var PORTA_DIVISION_CONSTANT = 40;
11363
+ function portaTimeToRate(cc) {
11364
+ if (cc < 1) {
11365
+ return 0;
11366
+ } else {
11367
+ const x0 = [1, 2, 4, 8, 16, 32, 64, 80, 96, 112, 120, 124];
11368
+ const ih = [
11369
+ 1,
11370
+ 0.5,
11371
+ 0.25,
11372
+ 0.125,
11373
+ 0.0625,
11374
+ 0.03125,
11375
+ 0.0625,
11376
+ 0.0625,
11377
+ 0.0625,
11378
+ 0.125,
11379
+ 0.25,
11380
+ 1 / 3
11381
+ ];
11382
+ const a = [
11383
+ -0.16653127382501215,
11384
+ 0.11863875218299408,
11385
+ 0.029479047361245264,
11386
+ -0.005442312089231738,
11387
+ 0.1451520875973037,
11388
+ -0.005056281449558275,
11389
+ -0.005095486882876532,
11390
+ 0.03334009551111544,
11391
+ -0.09361368678020432,
11392
+ 0.14132569702451822,
11393
+ -0.15805565301011382,
11394
+ -0.09918856955881927
11395
+ ];
11396
+ const b = [
11397
+ 0.028212773333433472,
11398
+ -0.3388502064992847,
11399
+ -0.15839529890929713,
11400
+ -0.12398131766775483,
11401
+ -0.2874848552685111,
11402
+ 0.012254866302537692,
11403
+ 0.005957797193345771,
11404
+ -0.03745899330347374,
11405
+ 0.12911781869810196,
11406
+ -0.15867193224162568,
11407
+ 0.504406322732748,
11408
+ 0.3786845131875458
11409
+ ];
11410
+ const c = [
11411
+ 0.7218950861255283,
11412
+ 0.5574536226347168,
11413
+ 0.47133893237025826,
11414
+ 0.48597095327079914,
11415
+ 0.44336276333518854,
11416
+ 0.6076986311801551,
11417
+ 0.30851975971827794,
11418
+ 0.30514889345633955,
11419
+ 0.3302511933827384,
11420
+ 0.153822885219165,
11421
+ 0.1302280559047337,
11422
+ 0.49865530675491687
11423
+ ];
11424
+ const d = [
11425
+ -2.2218487496163566,
11426
+ -1.6382721639824072,
11427
+ -1.3010299956639813,
11428
+ -0.958607314841775,
11429
+ -0.6020599913279624,
11430
+ -0.3010299956639812,
11431
+ 0.31386722036915343,
11432
+ 0.6232492903979004,
11433
+ 0.9242792860618817,
11434
+ 1.290034611362518,
11435
+ 1.4265112613645752,
11436
+ 1.9030899869919435
11437
+ ];
11438
+ const thresholds = [2, 4, 8, 16, 32, 64, 80, 96, 112, 120, 124];
11439
+ const s = thresholds.findLastIndex((t2) => t2 < cc) + 1;
11440
+ const t = (cc - x0[s]) * ih[s];
11441
+ return Math.exp(
11442
+ 2.302585092994046 * (((a[s] * t + b[s]) * t + c[s]) * t + d[s])
11443
+ ) / PORTA_DIVISION_CONSTANT;
11186
11444
  }
11187
- return 0;
11188
11445
  }
11189
11446
  function portamentoTimeToSeconds(time, distance) {
11190
- return getLookup(time) * (distance / 36);
11447
+ return portaTimeToRate(time) * distance;
11191
11448
  }
11192
11449
 
11193
11450
  // src/synthesizer/audio_engine/engine_methods/note_on.ts
@@ -11231,16 +11488,15 @@ function noteOn(midiNote, velocity) {
11231
11488
  let portamentoFromKey = -1;
11232
11489
  let portamentoDuration = 0;
11233
11490
  const portamentoTime = this.midiControllers[midiControllers.portamentoTime] >> 7;
11234
- const control = this.midiControllers[midiControllers.portamentoControl];
11235
- const currentFromKey = control >> 7;
11491
+ const portaControl = this.midiControllers[midiControllers.portamentoControl] >> 7;
11236
11492
  if (!this.drumChannel && // No portamento on drum channel
11237
- currentFromKey !== internalMidiNote && // If the same note, there's no portamento
11493
+ portaControl !== internalMidiNote && // If the same note, there's no portamento
11238
11494
  this.midiControllers[midiControllers.portamentoOnOff] >= 8192 && // (64 << 7)
11239
11495
  portamentoTime > 0) {
11240
- if (control !== 1) {
11241
- const diff = Math.abs(internalMidiNote - currentFromKey);
11496
+ if (portaControl > 0) {
11497
+ const diff = Math.abs(internalMidiNote - portaControl);
11242
11498
  portamentoDuration = portamentoTimeToSeconds(portamentoTime, diff);
11243
- portamentoFromKey = currentFromKey;
11499
+ portamentoFromKey = portaControl;
11244
11500
  }
11245
11501
  this.controllerChange(
11246
11502
  midiControllers.portamentoControl,
@@ -17058,16 +17314,12 @@ var SpessaSynthProcessor3 = class {
17058
17314
  });
17059
17315
  }
17060
17316
  getCachedVoice(patch, midiNote, velocity) {
17061
- let bankMSB = patch.bankMSB;
17062
- let bankLSB = patch.bankLSB;
17063
- const { isGMGSDrum, program } = patch;
17064
- if (isGMGSDrum) {
17065
- bankMSB = 128;
17066
- bankLSB = 0;
17067
- }
17068
- return this.privateProps.cachedVoices?.[bankMSB]?.[bankLSB]?.[program]?.[midiNote]?.[velocity];
17317
+ return this.privateProps.cachedVoices?.[this.getCachedVoiceIndex(patch, midiNote, velocity)];
17069
17318
  }
17070
17319
  setCachedVoice(patch, midiNote, velocity, voices) {
17320
+ this.privateProps.cachedVoices[this.getCachedVoiceIndex(patch, midiNote, velocity)] = voices;
17321
+ }
17322
+ getCachedVoiceIndex(patch, midiNote, velocity) {
17071
17323
  let bankMSB = patch.bankMSB;
17072
17324
  let bankLSB = patch.bankLSB;
17073
17325
  const { isGMGSDrum, program } = patch;
@@ -17075,19 +17327,11 @@ var SpessaSynthProcessor3 = class {
17075
17327
  bankMSB = 128;
17076
17328
  bankLSB = 0;
17077
17329
  }
17078
- if (!this.privateProps.cachedVoices[bankMSB]) {
17079
- this.privateProps.cachedVoices[bankMSB] = [];
17080
- }
17081
- if (!this.privateProps.cachedVoices[bankMSB][bankLSB]) {
17082
- this.privateProps.cachedVoices[bankMSB][bankLSB] = [];
17083
- }
17084
- if (!this.privateProps.cachedVoices[bankMSB][bankLSB][program]) {
17085
- this.privateProps.cachedVoices[bankMSB][bankLSB][program] = [];
17086
- }
17087
- if (!this.privateProps.cachedVoices[bankMSB][bankLSB][program][midiNote]) {
17088
- this.privateProps.cachedVoices[bankMSB][bankLSB][program][midiNote] = [];
17089
- }
17090
- this.privateProps.cachedVoices[bankMSB][bankLSB][program][midiNote][velocity] = voices;
17330
+ return bankMSB + // 128 ^ 0
17331
+ bankLSB * 128 + // 128 ^ 1
17332
+ program * 16384 + // 128 ^ 2
17333
+ 2097152 * midiNote + // 128 ^ 3
17334
+ 268435456 * velocity;
17091
17335
  }
17092
17336
  createMIDIChannelInternal(sendEvent) {
17093
17337
  const channel = new MIDIChannel(
@@ -17111,7 +17355,7 @@ var SpessaSynthProcessor3 = class {
17111
17355
  this.midiChannels.forEach((c) => {
17112
17356
  c.setPresetLock(false);
17113
17357
  });
17114
- this.resetAllControllers(false);
17358
+ this.resetAllControllers();
17115
17359
  }
17116
17360
  getDefaultPresets() {
17117
17361
  this.privateProps.defaultPreset = this.soundBankManager.getPreset(
@@ -17165,7 +17409,6 @@ export {
17165
17409
  Modulator,
17166
17410
  ModulatorSource,
17167
17411
  NON_CC_INDEX_OFFSET,
17168
- PORTAMENTO_CONTROL_UNSET,
17169
17412
  SoundBankLoader,
17170
17413
  SpessaSynthCoreUtils,
17171
17414
  SpessaSynthLogging,