Skip to content

Commit

Permalink
Rotary speaker port to sst effects (#7786)
Browse files Browse the repository at this point in the history
* backup commit

* fix constructor and add process_control_only to surge adapter

* update sst-effects

* fix processControlOnly names and other sst-effects things

* update sst-effects

* fix clang format
  • Loading branch information
luismrguimaraes authored Sep 17, 2024
1 parent 1d98626 commit be317f2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 341 deletions.
2 changes: 1 addition & 1 deletion libs/sst/sst-effects
Submodule sst-effects updated 35 files
+17 −0 .github/workflows/code-checks.yml
+2 −1 CMakeLists.txt
+8 −0 include/sst/effects/EffectCore.h
+3 −1 include/sst/effects/NimbusImpl.h
+421 −0 include/sst/effects/RotarySpeaker.h
+13 −7 include/sst/voice-effects/VoiceEffectCore.h
+128 −36 include/sst/voice-effects/delay/Chorus.h
+16 −1 include/sst/voice-effects/delay/DelaySupport.h
+61 −5 include/sst/voice-effects/delay/ShortDelay.h
+170 −130 include/sst/voice-effects/delay/StringResonator.h
+24 −4 include/sst/voice-effects/distortion/BitCrusher.h
+0 −2 include/sst/voice-effects/dynamics/AutoWah.h
+50 −70 include/sst/voice-effects/dynamics/Compressor.h
+137 −0 include/sst/voice-effects/eq/TiltEQ.h
+6 −10 include/sst/voice-effects/filter/CytomicSVF.h
+10 −3 include/sst/voice-effects/generator/GenCorrelatedNoise.h
+66 −20 include/sst/voice-effects/generator/TiltNoise.h
+121 −0 include/sst/voice-effects/lifted_bus_effects/FXConfigFromVFXConfig.h
+85 −0 include/sst/voice-effects/lifted_bus_effects/LiftedDelay.h
+91 −0 include/sst/voice-effects/lifted_bus_effects/LiftedFlanger.h
+91 −0 include/sst/voice-effects/lifted_bus_effects/LiftedReverb1.h
+78 −0 include/sst/voice-effects/lifted_bus_effects/LiftedReverb2.h
+39 −12 include/sst/voice-effects/modulation/FMFilter.h
+1 −1 include/sst/voice-effects/modulation/NoiseAM.h
+45 −20 include/sst/voice-effects/modulation/Phaser.h
+1 −7 include/sst/voice-effects/modulation/RingMod.h
+344 −0 include/sst/voice-effects/modulation/ShepardPhaser.h
+179 −112 include/sst/voice-effects/modulation/Tremolo.h
+113 −0 include/sst/voice-effects/utilities/GainMatrix.h
+157 −0 include/sst/voice-effects/utilities/StereoTool.h
+13 −14 include/sst/voice-effects/utilities/VolumeAndPan.h
+0 −0 libs/catch2/catch2.hpp
+5 −0 tests/concrete-runs.cpp
+5 −0 tests/create-effect.cpp
+44 −0 tests/create-voice-effect.cpp
295 changes: 1 addition & 294 deletions src/common/dsp/effects/RotarySpeakerEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,67 +23,6 @@

using namespace std;

RotarySpeakerEffect::RotarySpeakerEffect(SurgeStorage *storage, FxStorage *fxdata, pdata *pd)
: Effect(storage, fxdata, pd), xover(storage), lowbass(storage)
{
mix.set_blocksize(BLOCK_SIZE);
width.set_blocksize(BLOCK_SIZE);
}

RotarySpeakerEffect::~RotarySpeakerEffect() {}

void RotarySpeakerEffect::init()
{
memset(buffer, 0, max_delay_length * sizeof(float));

wpos = 0;

xover.suspend();
lowbass.suspend();

xover.coeff_LP2B(xover.calc_omega(0.862496f), 0.707); // 800 Hz
lowbass.coeff_LP2B(xover.calc_omega(-1.14f), 0.707); // 200 Hz

setvars(true);
}

void RotarySpeakerEffect::setvars(bool init)
{
drive.newValue(*pd_float[rot_drive]);
width.set_target_smoothed(storage->db_to_linear(*pd_float[rot_width]));
mix.set_target_smoothed(*pd_float[rot_mix]);

if (init)
{
drive.instantize();
width.instantize();
mix.instantize();

for (int i = 0; i < sst::waveshapers::n_waveshaper_registers; ++i)
wsState.R[i] = _mm_setzero_ps();
}
}

void RotarySpeakerEffect::suspend()
{
memset(buffer, 0, max_delay_length * sizeof(float));
xover.suspend();
lowbass.suspend();
wpos = 0;
}

void RotarySpeakerEffect::init_default_values()
{
fxdata->p[rot_horn_rate].val.f = 1.f;
fxdata->p[rot_rotor_rate].val.f = 0.7;
fxdata->p[rot_drive].val.f = 0.f;
fxdata->p[rot_waveshape].val.i = 0;
fxdata->p[rot_doppler].val.f = 0.25f;
fxdata->p[rot_tremolo].val.f = 0.5f;
fxdata->p[rot_width].val.f = 1.0f;
fxdata->p[rot_mix].val.f = 1.0f;
}

const char *RotarySpeakerEffect::group_label(int id)
{
switch (id)
Expand Down Expand Up @@ -149,240 +88,8 @@ void RotarySpeakerEffect::init_ctrltypes()

fxdata->p[rot_width].posy_offset = 7;
fxdata->p[rot_mix].posy_offset = 7;
}

void RotarySpeakerEffect::process_only_control()
{
float frate = *pd_float[rot_horn_rate] *
(fxdata->p[rot_horn_rate].temposync ? storage->temposyncratio : 1.f);

lfo.set_rate(2 * M_PI * powf(2, frate) * storage->dsamplerate_inv * BLOCK_SIZE);
lf_lfo.set_rate(*pd_float[rot_rotor_rate] * 2 * M_PI * powf(2, frate) *
storage->dsamplerate_inv * BLOCK_SIZE);

lfo.process();
lf_lfo.process();
}

void RotarySpeakerEffect::process(float *dataL, float *dataR)
{
setvars(false);

float frate = *pd_float[rot_horn_rate] *
(fxdata->p[rot_horn_rate].temposync ? storage->temposyncratio : 1.f);

/*
** lf_lfo.process drives the sub-frequency and processes inside the iteration over samples>
** lfo.process drives the speaker and processes outside the iteration
** therefore lf_lfo processes BLOCK_SIZE more times
** hence the lack of BLOCK_SIZE here
*/
lfo.set_rate(2 * M_PI * powf(2, frate) * storage->dsamplerate_inv * BLOCK_SIZE);
lf_lfo.set_rate(*pd_float[rot_rotor_rate] * 2 * M_PI * powf(2, frate) *
storage->dsamplerate_inv);

float precalc0 = (-2 - (float)lfo.i);
float precalc1 = (-1 - (float)lfo.r);
float precalc2 = (+1 - (float)lfo.r);
float lenL = sqrt(precalc0 * precalc0 + precalc1 * precalc1);
float lenR = sqrt(precalc0 * precalc0 + precalc2 * precalc2);

float delay = storage->samplerate * 0.0018f * *pd_float[rot_doppler];

dL.newValue(delay * lenL);
dR.newValue(delay * lenR);

float dotp_L = (precalc1 * (float)lfo.r + precalc0 * (float)lfo.i) / lenL;
float dotp_R = (precalc2 * (float)lfo.r + precalc0 * (float)lfo.i) / lenR;

float a = *pd_float[rot_tremolo] * 0.6f;

hornamp[0].newValue((1.f - a) + a * dotp_L);
hornamp[1].newValue((1.f - a) + a * dotp_R);

lfo.process();

float upper alignas(16)[BLOCK_SIZE];
float lower alignas(16)[BLOCK_SIZE];
float lower_sub alignas(16)[BLOCK_SIZE];
float tbufferL alignas(16)[BLOCK_SIZE];
float tbufferR alignas(16)[BLOCK_SIZE];
float wbL alignas(16)[BLOCK_SIZE];
float wbR alignas(16)[BLOCK_SIZE];

int k;

drive.newValue(*pd_float[rot_drive]);

int wsi = *pd_int[rot_waveshape];

if (wsi < 0 || wsi >= n_fxws)
{
wsi = 0;
}

auto ws = FXWaveShapers[wsi];

/*
** This is a set of completely empirical scaling settings to offset gain being too crazy
** in the drive cycle. There's no science really, just us playing with it and listening
*/
float gain_tweak{1.f}, compensate{1.f}, drive_factor{1.f}, gain_comp_factor{1.0};
float compensateStartsAt = 0.18;
bool square_drive_comp = false;

switch (ws)
{
case sst::waveshapers::WaveshaperType::wst_hard:
{
gain_tweak = 1.4;
compensate = 3.f;
}
case sst::waveshapers::WaveshaperType::wst_asym:
{
gain_tweak = 1.15;
compensate = 9.f;
compensateStartsAt = 0.05;
break;
}
case sst::waveshapers::WaveshaperType::wst_sine:
{
gain_tweak = 4.4;
compensate = 10.f;
compensateStartsAt = 0.f;
square_drive_comp = true;
break;
}
case sst::waveshapers::WaveshaperType::wst_digital:
{
gain_tweak = 1.f;
compensate = 4.f;
compensateStartsAt = 0.f;
break;
}
case sst::waveshapers::WaveshaperType::wst_fwrectify:
case sst::waveshapers::WaveshaperType::wst_fuzzsoft:
{
gain_tweak = 1.f;
compensate = 2.f;
compensateStartsAt = 0.f;
break;
}
default:
{
gain_tweak = 1.f;
compensate = 4.f;
break;
}
}

if (!fxdata->p[rot_drive].deactivated)
{
drive_factor = 1.f + (drive.v * drive.v * 15.f);

if (drive.v < compensateStartsAt)
gain_comp_factor = 1.0;
else if (square_drive_comp)
gain_comp_factor = 1.f + (((drive.v * drive.v) - compensateStartsAt) * compensate);
else
gain_comp_factor = 1.f + ((drive.v - compensateStartsAt) * compensate);
}

// FX waveshapers have value at wst_soft for 0; so don't add wst_soft here (like we did in 1.9)
bool useSSEShaper = (ws >= sst::waveshapers::WaveshaperType::wst_sine);
auto wsop = sst::waveshapers::GetQuadWaveshaper(ws);

for (k = 0; k < BLOCK_SIZE; k++)
{
float input;

if (!fxdata->p[rot_drive].deactivated)
{
drive_factor = 1.f + (drive.v * drive.v * 15.f);
if (useSSEShaper)
{
auto inp = _mm_set1_ps(0.5 * (dataL[k] + dataR[k]));
auto wsres = wsop(&wsState, inp, _mm_set1_ps(drive_factor));
float r[4];
_mm_store_ps(r, wsres);
input = r[0] * gain_tweak;
}
else
{
input = storage->lookup_waveshape(ws, 0.5f * (dataL[k] + dataR[k]) * drive_factor) *
gain_tweak;
}
input /= gain_comp_factor;

drive.process();
}
else
{
input = 0.5f * (dataL[k] + dataR[k]);
}

upper[k] = input;
lower[k] = input;
}

xover.process_block(lower);

for (k = 0; k < BLOCK_SIZE; k++)
{
// feed delay input
int wp = (wpos + k) & (max_delay_length - 1);
lower_sub[k] = lower[k];
upper[k] -= lower[k];
buffer[wp] = upper[k];

int i_dtimeL = max(BLOCK_SIZE, min((int)dL.v, max_delay_length - FIRipol_N - 1));
int i_dtimeR = max(BLOCK_SIZE, min((int)dR.v, max_delay_length - FIRipol_N - 1));

int rpL = (wpos - i_dtimeL + k);
int rpR = (wpos - i_dtimeR + k);

int sincL = FIRipol_N *
limit_range((int)(FIRipol_M * (float(i_dtimeL + 1) - dL.v)), 0, FIRipol_M - 1);
int sincR = FIRipol_N *
limit_range((int)(FIRipol_M * (float(i_dtimeR + 1) - dR.v)), 0, FIRipol_M - 1);

// get delay output
tbufferL[k] = 0;
tbufferR[k] = 0;
for (int i = 0; i < FIRipol_N; i++)
{
tbufferL[k] += buffer[(rpL - i) & (max_delay_length - 1)] *
storage->sinctable1X[sincL + FIRipol_N - i];
tbufferR[k] += buffer[(rpR - i) & (max_delay_length - 1)] *
storage->sinctable1X[sincR + FIRipol_N - i];
}
dL.process();
dR.process();
}

lowbass.process_block(lower_sub);

for (k = 0; k < BLOCK_SIZE; k++)
{
lower[k] -= lower_sub[k];

float bass = lower_sub[k] + lower[k] * (lf_lfo.r * 0.6f + 0.3f);

wbL[k] = hornamp[0].v * tbufferL[k] + bass;
wbR[k] = hornamp[1].v * tbufferR[k] + bass;

lf_lfo.process();
hornamp[0].process();
hornamp[1].process();
}

// scale width
applyWidth(wbL, wbR, width);

mix.fade_2_blocks_inplace(dataL, wbL, dataR, wbR, BLOCK_SIZE_QUAD);

wpos += BLOCK_SIZE;
wpos = wpos & (max_delay_length - 1);
configureControlsFromFXMetadata();
}

void RotarySpeakerEffect::handleStreamingMismatches(int streamingRevision,
Expand Down
60 changes: 15 additions & 45 deletions src/common/dsp/effects/RotarySpeakerEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,58 +31,28 @@
#include "sst/waveshapers.h"
#include "sst/basic-blocks/dsp/QuadratureOscillators.h"

class RotarySpeakerEffect : public Effect
#include "sst/effects/RotarySpeaker.h"
#include "SurgeSSTFXAdapter.h"

class RotarySpeakerEffect
: public surge::sstfx::SurgeSSTFXBase<
sst::effects::rotaryspeaker::RotarySpeaker<surge::sstfx::SurgeFXConfig>>
{
public:
lipol_ps_blocksz width alignas(16), mix alignas(16);
sst::waveshapers::QuadWaveshaperState wsState alignas(16);
RotarySpeakerEffect(SurgeStorage *storage, FxStorage *fxdata, pdata *pd)
: surge::sstfx::SurgeSSTFXBase<
sst::effects::rotaryspeaker::RotarySpeaker<surge::sstfx::SurgeFXConfig>>(storage,
fxdata, pd)
{
}
virtual ~RotarySpeakerEffect() = default;

RotarySpeakerEffect(SurgeStorage *storage, FxStorage *fxdata, pdata *pd);
virtual ~RotarySpeakerEffect();
virtual void process_only_control() override;
virtual const char *get_effectname() override { return "rotary"; }
virtual void process(float *dataL, float *dataR) override;
virtual int get_ringout_decay() override { return max_delay_length >> 5; }
void setvars(bool init);
virtual void suspend() override;
virtual void init() override;
virtual void init_ctrltypes() override;
virtual void init_default_values() override;
virtual const char *group_label(int id) override;
virtual int group_label_ypos(int id) override;

virtual void handleStreamingMismatches(int streamingRevision,
int currentSynthStreamingRevision) override;

enum rotary_params
{
rot_horn_rate = 0,
rot_doppler,
rot_tremolo,
rot_rotor_rate,
rot_drive,
rot_waveshape,
rot_width,
rot_mix,

rot_num_params,
};

protected:
float buffer[max_delay_length];
int wpos;
// filter *lp[2],*hp[2];
// biquadunit rotor_lpL,rotor_lpR;
BiquadFilter xover, lowbass;
// float
// f_rotor_lp[2][n_filter_parameters],f_xover[n_filter_parameters],f_lowbass[n_filter_parameters];

using quadr_osc = sst::basic_blocks::dsp::SurgeQuadrOsc<float>;
quadr_osc lfo;
quadr_osc lf_lfo;
lipol<float> dL, dR, hornamp[2];
lag<float, true> drive;
bool first_run;
void handleStreamingMismatches(int streamingRevision,
int currentSynthStreamingRevision) override;
};

#endif // SURGE_SRC_COMMON_DSP_EFFECTS_ROTARYSPEAKEREFFECT_H
Loading

0 comments on commit be317f2

Please sign in to comment.