diff --git a/CMakeLists.txt b/CMakeLists.txt
index 374991f199d..efe9b091ab8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -205,6 +205,8 @@ set(SURGE_SHARED_SOURCES
src/common/dsp/effect/VocoderEffect.cpp
src/common/dsp/effect/airwindows/AirWindowsEffect.cpp
src/common/dsp/effect/airwindows/AirWindowsEffect.h
+ src/common/dsp/effect/chowdsp/Neuron.cpp
+ src/common/dsp/effect/chowdsp/shared/DelayLine.cpp
src/common/dsp/filters/VintageLadders.cpp
src/common/dsp/filters/Obxd.cpp
src/common/dsp/filters/K35.cpp
diff --git a/doc/AddingAnFX.md b/doc/AddingAnFX.md
new file mode 100644
index 00000000000..26c2d10d634
--- /dev/null
+++ b/doc/AddingAnFX.md
@@ -0,0 +1,26 @@
+# How to add a effect
+
+This tutorial will demonstrate how to add an example effect called `MyEffect`.
+
+1. Create `src/common/dsp/effect/MyEffect.h` and `MyEffect.cpp`. Look at Flanger as an example.
+ - The parameters for your effect should be placed in an enum.
+ - Override `init_ctrltypes()` to initialise the parameter names and types, and `init_default_values()` to set the default parameter values.
+ - The guts of your signal processing should go in the `process()` function.
+
+2. In `Effect.cpp` add the newly created header file to the list of includes at the top. Then in `spawn_effect()` add a case to spawn your new effect:
+```cpp
+case fxt_myeffect:
+ return new MyEffect( storage, fxdata, pd );
+```
+
+Note that we have chosen the effect ID `fxt_myeffect` for our example effect.
+
+3. In SurgeStorage.h:
+* add an enum to `fx_type` with your effect ID.
+* add the name of your effect to `fx_type_names`.
+
+4. In `resources/data/configuration.xml` add your effect to the `fx` XML group. Later, you may also add "snapshots" for presets of your effect.
+
+Note that the configuration file DOES NOT automatically reload when you compile and run the plugin! Whenever you make changes to this file, you must copy it to the correct "installed" location where the plugin is expecting to find it. For Windows,this is `C:/ProgramData/Surge/configuration.xml`.
+
+Then you can finish coding up your effect!
\ No newline at end of file
diff --git a/resources/data/configuration.xml b/resources/data/configuration.xml
index 92944acddff..9b279ce4b6a 100644
--- a/resources/data/configuration.xml
+++ b/resources/data/configuration.xml
@@ -246,6 +246,10 @@
+
+
+
+
diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h
index 547f27e4683..4ee55a3c5d9 100644
--- a/src/common/SurgeStorage.h
+++ b/src/common/SurgeStorage.h
@@ -280,6 +280,7 @@ enum fx_type
fxt_flanger,
fxt_ringmod,
fxt_airwindows,
+ fxt_neuron,
n_fx_types,
};
@@ -300,6 +301,7 @@ const char fx_type_names[n_fx_types][16] =
"Flanger",
"Ring Mod",
"Airwindows",
+ "Neuron",
};
enum fx_bypass
diff --git a/src/common/dsp/effect/Effect.cpp b/src/common/dsp/effect/Effect.cpp
index 7cf4ec66b61..d332b1b2f54 100644
--- a/src/common/dsp/effect/Effect.cpp
+++ b/src/common/dsp/effect/Effect.cpp
@@ -12,6 +12,7 @@
#include "FlangerEffect.h"
#include "RingModulatorEffect.h"
#include "airwindows/AirWindowsEffect.h"
+#include "chowdsp/Neuron.h"
#include "DebugHelpers.h"
using namespace std;
@@ -50,6 +51,8 @@ Effect* spawn_effect(int id, SurgeStorage* storage, FxStorage* fxdata, pdata* pd
return new RingModulatorEffect(storage, fxdata, pd);
case fxt_airwindows:
return new AirWindowsEffect( storage, fxdata, pd );
+ case fxt_neuron:
+ return new chowdsp::Neuron(storage, fxdata, pd);
default:
return 0;
};
diff --git a/src/common/dsp/effect/chowdsp/Neuron.cpp b/src/common/dsp/effect/chowdsp/Neuron.cpp
new file mode 100644
index 00000000000..bcf27c7f905
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/Neuron.cpp
@@ -0,0 +1,235 @@
+/*
+** Surge Synthesizer is Free and Open Source Software
+**
+** Surge is made available under the Gnu General Public License, v3.0
+** https://www.gnu.org/licenses/gpl-3.0.en.html
+**
+** Copyright 2004-2020 by various individuals as described by the Git transaction log
+**
+** All source at: https://github.com/surge-synthesizer/surge.git
+**
+** Surge was a commercial product from 2004-2018, with Copyright and ownership
+** in that period held by Claes Johanson at Vember Audio. Claes made Surge
+** open source in September 2018.
+*/
+
+#include "Neuron.h"
+
+namespace chowdsp
+{
+
+Neuron::Neuron(SurgeStorage* storage, FxStorage* fxdata, pdata* pd)
+ : Effect(storage, fxdata, pd)
+{
+ dc_blocker.setBlockSize(BLOCK_SIZE);
+ makeup.set_blocksize(BLOCK_SIZE);
+ outgain.set_blocksize(BLOCK_SIZE);
+}
+
+Neuron::~Neuron()
+{}
+
+void Neuron::init()
+{
+ Wf.reset(numSteps);
+ Wh.reset(numSteps);
+ Uf.reset(numSteps);
+ Uh.reset(numSteps);
+ bf.reset(numSteps);
+
+ delay1Smooth.reset(numSteps);
+ delay2Smooth.reset(numSteps);
+
+ os.reset();
+
+ delay1.prepare(dsamplerate * os.getOSRatio(), BLOCK_SIZE, 2);
+ delay2.prepare(dsamplerate * os.getOSRatio(), BLOCK_SIZE, 2);
+ delay1.setDelay(0.0f);
+ delay2.setDelay(0.0f);
+
+ y1[0] = 0.0f;
+ y1[1] = 0.0f;
+
+ dc_blocker.suspend();
+ dc_blocker.coeff_HP(35.0f / samplerate, 0.707);
+ dc_blocker.coeff_instantize();
+
+ width.instantize();
+
+ makeup.set_target(1.0f);
+ outgain.set_target(0.0f);
+}
+
+void Neuron::process(float* dataL, float* dataR)
+{
+ set_params();
+
+ os.upsample(dataL, dataR);
+ process_internal(os.leftUp, os.rightUp, os.getUpBlockSize());
+ os.downsample(dataL, dataR);
+
+ dc_blocker.process_block(dataL, dataR);
+ makeup.multiply_2_blocks(dataL, dataR, BLOCK_SIZE_QUAD);
+
+ // scale width
+ float M alignas(16)[BLOCK_SIZE], S alignas(16)[BLOCK_SIZE];
+
+ encodeMS(dataL, dataR, M, S, BLOCK_SIZE_QUAD);
+ width.multiply_block(S, BLOCK_SIZE_QUAD);
+ decodeMS(M, S, dataL, dataR, BLOCK_SIZE_QUAD);
+
+ outgain.multiply_2_blocks(dataL, dataR, BLOCK_SIZE_QUAD);
+}
+
+void Neuron::process_internal(float* dataL, float* dataR, const int numSamples)
+{
+ for(int k = 0; k < numSamples; k++)
+ {
+ dataL[k] = processSample(dataL[k], y1[0]);
+ dataR[k] = processSample(dataR[k], y1[1]);
+
+ delay1.setDelay(delay1Smooth.getNextValue());
+ delay2.setDelay(delay2Smooth.getNextValue());
+ delay1.pushSample(0, dataL[k]);
+ delay2.pushSample(1, dataR[k]);
+
+ y1[0] = delay1.popSample(0);
+ y1[1] = delay2.popSample(1);
+ }
+}
+
+void Neuron::set_params()
+{
+ auto bf_clamped = limit_range(*f[neuron_bias_bf], 0.0f, 1.0f);
+
+ Wf.setTargetValue(*f[neuron_squash_wf] * 20.0f);
+ Wh.setTargetValue(db_to_linear(*f[neuron_drive_wh]));
+ Uf.setTargetValue(*f[neuron_stab_uf] * 5.0f);
+ Uh.setTargetValue(*f[neuron_asym_uh] * 0.9f);
+ bf.setTargetValue(bf_clamped * 6.0f - 1.0f);
+
+ // tune delay length
+ auto freqHz1 = (2 * 3.14159265358979323846) * 440 *
+ storage->note_to_pitch_ignoring_tuning(*f[neuron_comb_freq]);
+ auto freqHz2 =
+ (2 * 3.14159265358979323846) * 440 *
+ storage->note_to_pitch_ignoring_tuning(*f[neuron_comb_freq] + *f[neuron_comb_sep]);
+ auto delayTimeSec1 = 1.0f / (float) freqHz1;
+ auto delayTimeSec2 = 1.0f / (float) freqHz2;
+
+ delay1Smooth.setTargetValue(delayTimeSec1 * 0.5f * samplerate * os.getOSRatio());
+ delay2Smooth.setTargetValue(delayTimeSec2 * 0.5f * samplerate * os.getOSRatio());
+
+ // calc makeup gain
+ auto drive_makeup = [](float wh) -> float
+ {
+ return std::exp(-0.11898f * wh) + 1.0f;
+ };
+
+ auto bias_makeup = [](float bf) -> float
+ {
+ return 6.0f * std::pow(bf, 7.5f) + 0.9f;
+ };
+
+ const auto makeupGain = drive_makeup(*f[neuron_drive_wh]) * bias_makeup(bf_clamped);
+
+ makeup.set_target_smoothed(makeupGain);
+
+ width.set_target_smoothed(db_to_linear(*f[neuron_width]));
+ outgain.set_target_smoothed(db_to_linear(*f[neuron_gain]));
+}
+
+
+void Neuron::suspend()
+{
+ init();
+}
+
+const char* Neuron::group_label(int id)
+{
+ switch (id)
+ {
+ case 0:
+ return "Distortion";
+ case 1:
+ return "Comb";
+ case 2:
+ return "Output";
+ }
+
+ return 0;
+}
+
+int Neuron::group_label_ypos(int id)
+{
+ switch (id)
+ {
+ case 0:
+ return 1;
+ case 1:
+ return 13;
+ case 2:
+ return 19;
+ }
+ return 0;
+}
+
+void Neuron::init_ctrltypes()
+{
+ Effect::init_ctrltypes();
+
+ fxdata->p[neuron_drive_wh].set_name("Drive");
+ fxdata->p[neuron_drive_wh].set_type(ct_decibel_narrow);
+ fxdata->p[neuron_drive_wh].posy_offset = 1;
+
+ fxdata->p[neuron_squash_wf].set_name("Squash");
+ fxdata->p[neuron_squash_wf].set_type(ct_percent);
+ fxdata->p[neuron_squash_wf].posy_offset = 1;
+ fxdata->p[neuron_squash_wf].val_default.f = 0.5f;
+
+ fxdata->p[neuron_stab_uf].set_name("Stab");
+ fxdata->p[neuron_stab_uf].set_type(ct_percent);
+ fxdata->p[neuron_stab_uf].posy_offset = 1;
+ fxdata->p[neuron_stab_uf].val_default.f = 0.5f;
+
+ fxdata->p[neuron_asym_uh].set_name("Asymmetry");
+ fxdata->p[neuron_asym_uh].set_type(ct_percent);
+ fxdata->p[neuron_asym_uh].posy_offset = 1;
+ fxdata->p[neuron_asym_uh].val_default.f = 1.0f;
+
+ fxdata->p[neuron_bias_bf].set_name("Bias");
+ fxdata->p[neuron_bias_bf].set_type(ct_percent);
+ fxdata->p[neuron_bias_bf].posy_offset = 1;
+
+ fxdata->p[neuron_comb_freq].set_name("Frequency");
+ fxdata->p[neuron_comb_freq].set_type(ct_freq_audible);
+ fxdata->p[neuron_comb_freq].posy_offset = 3;
+ fxdata->p[neuron_comb_freq].val_default.f = 70.0f;
+
+ fxdata->p[neuron_comb_sep].set_name("Separation");
+ fxdata->p[neuron_comb_sep].set_type(ct_freq_mod);
+ fxdata->p[neuron_comb_sep].posy_offset = 3;
+
+ fxdata->p[neuron_width].set_name("Width");
+ fxdata->p[neuron_width].set_type(ct_decibel_narrow);
+ fxdata->p[neuron_width].posy_offset = 5;
+
+ fxdata->p[neuron_gain].set_name("Gain");
+ fxdata->p[neuron_gain].set_type(ct_decibel_narrow);
+ fxdata->p[neuron_gain].posy_offset = 5;
+}
+
+void Neuron::init_default_values()
+{
+ fxdata->p[neuron_drive_wh].val.f = 0.0f;
+ fxdata->p[neuron_squash_wf].val.f = 0.5f;
+ fxdata->p[neuron_stab_uf].val.f = 0.5f;
+ fxdata->p[neuron_asym_uh].val.f = 1.0f;
+ fxdata->p[neuron_bias_bf].val.f = 0.0f;
+ fxdata->p[neuron_comb_freq].val.f = 70.0f;
+ fxdata->p[neuron_comb_sep].val.f = 0.5f;
+ fxdata->p[neuron_width].val.f = 0.0f;
+ fxdata->p[neuron_gain].val.f = 0.0f;
+}
+
+} // namespace chowdsp
diff --git a/src/common/dsp/effect/chowdsp/Neuron.h b/src/common/dsp/effect/chowdsp/Neuron.h
new file mode 100644
index 00000000000..270ec2099c5
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/Neuron.h
@@ -0,0 +1,110 @@
+/*
+** Surge Synthesizer is Free and Open Source Software
+**
+** Surge is made available under the Gnu General Public License, v3.0
+** https://www.gnu.org/licenses/gpl-3.0.en.html
+**
+** Copyright 2004-2020 by various individuals as described by the Git transaction log
+**
+** All source at: https://github.com/surge-synthesizer/surge.git
+**
+** Surge was a commercial product from 2004-2018, with Copyright and ownership
+** in that period held by Claes Johanson at Vember Audio. Claes made Surge
+** open source in September 2018.
+*/
+
+#pragma once
+
+#include "../Effect.h"
+#include "BiquadFilter.h"
+#include "shared/DelayLine.h"
+#include "shared/Oversampling.h"
+#include "shared/SmoothedValue.h"
+
+#include
+
+namespace chowdsp
+{
+
+/*
+** CHOWDSP Neuron:
+** Neuron is a distortion effect based on the Gated Recurrent Unit,
+** a sort of "neuron" model commonly used in recurrent neural networks.
+**
+** For more details on the original idea and implementation of this effect,
+** please see this Medium article: https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-10-gated-recurrent-distortion-6d60948323cf
+*/
+class Neuron : public Effect
+{
+public:
+ enum neuron_params
+ {
+ neuron_drive_wh = 0,
+ neuron_squash_wf,
+ neuron_stab_uf,
+ neuron_asym_uh,
+ neuron_bias_bf,
+
+ neuron_comb_freq,
+ neuron_comb_sep,
+
+ neuron_width,
+ neuron_gain,
+
+ neuron_num_params,
+ };
+
+ Neuron(SurgeStorage* storage, FxStorage* fxdata, pdata* pd);
+ virtual ~Neuron();
+
+ virtual const char* get_effectname() override
+ {
+ return "Neuron";
+ }
+
+ virtual void init() override;
+ virtual void process(float* dataL, float* dataR) override;
+ virtual void suspend() 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;
+
+private:
+ void set_params();
+ void process_internal(float* dataL, float* dataR, const int numSamples);
+
+ inline float processSample (float x, float yPrev) noexcept
+ {
+ float f = sigmoid (Wf.getNextValue()*x + Uf.getNextValue()*yPrev + bf.getNextValue());
+ return f*yPrev + (1.0f-f) * std::tanh (Wh.getNextValue()*x + Uh.getNextValue()*f*yPrev);
+ }
+
+ inline float sigmoid (float x) const noexcept
+ {
+ return 1.0f / (1.0f + std::exp (-x));
+ }
+
+ enum {
+ numSteps = 200,
+ };
+
+ SmoothedValue Wf = 0.5f;
+ SmoothedValue Wh = 0.5f;
+ SmoothedValue Uf = 0.5f;
+ SmoothedValue Uh = 0.5f;
+ SmoothedValue bf = 0.0f;
+ SmoothedValue delay1Smooth = 0.0f;
+ SmoothedValue delay2Smooth = 0.0f;
+
+ float y1[2] = {0.0f, 0.0f};
+
+ BiquadFilter dc_blocker;
+ lipol_ps makeup alignas(16), width alignas(16), outgain alignas(16);
+ chowdsp::DelayLine delay1{1 << 18};
+ chowdsp::DelayLine delay2{1 << 18};
+ Oversampling<2, BLOCK_SIZE> os;
+};
+
+} // namespace chowdsp
diff --git a/src/common/dsp/effect/chowdsp/shared/DelayInterpolation.h b/src/common/dsp/effect/chowdsp/shared/DelayInterpolation.h
new file mode 100644
index 00000000000..a0067a19f67
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/shared/DelayInterpolation.h
@@ -0,0 +1,240 @@
+#pragma once
+
+#include
+
+namespace chowdsp
+{
+
+/**
+ A collection of structs to pass as the template argument when setting the
+ interpolation type for the DelayLine class.
+*/
+namespace DelayLineInterpolationTypes
+{
+ /**
+ No interpolation between successive samples in the delay line will be
+ performed. This is useful when the delay is a constant integer or to
+ create lo-fi audio effects.
+ */
+ struct None
+ {
+ void reset (int newTotalSize) { totalSize = newTotalSize; }
+
+ template
+ void updateInternalVariables (int& /*delayIntOffset*/, T& /*delayFrac*/) {}
+
+ template
+ inline T call (const T* buffer, int delayInt, T /*delayFrac*/, const T& /*state*/)
+ {
+ return buffer[delayInt % totalSize];
+ }
+
+ int totalSize;
+ };
+
+ /**
+ Successive samples in the delay line will be linearly interpolated. This
+ type of interpolation has a low compuational cost where the delay can be
+ modulated in real time, but it also introduces a low-pass filtering effect
+ into your audio signal.
+ */
+ struct Linear
+ {
+ void reset (int newTotalSize) { totalSize = newTotalSize; }
+
+ template
+ void updateInternalVariables (int& /*delayIntOffset*/, T& /*delayFrac*/) {}
+
+ template
+ inline T call (const T* buffer, int delayInt, T delayFrac, const T& /*state*/)
+ {
+ auto index1 = delayInt;
+ auto index2 = index1 + 1;
+
+ if (index2 >= totalSize)
+ {
+ index1 %= totalSize;
+ index2 %= totalSize;
+ }
+
+ auto value1 = buffer[index1];
+ auto value2 = buffer[index2];
+
+ return value1 + delayFrac * (value2 - value1);
+ }
+
+ int totalSize;
+ };
+
+ /**
+ Successive samples in the delay line will be interpolated using a 3rd order
+ Lagrange interpolator. This method incurs more computational overhead than
+ linear interpolation but reduces the low-pass filtering effect whilst
+ remaining amenable to real time delay modulation.
+ */
+ struct Lagrange3rd
+ {
+ void reset (int newTotalSize) { totalSize = newTotalSize; }
+
+ template
+ void updateInternalVariables (int& delayIntOffset, T& delayFrac)
+ {
+ if (delayIntOffset >= 1)
+ {
+ delayFrac++;
+ delayIntOffset--;
+ }
+ }
+
+ template
+ inline T call (const T* buffer, int delayInt, T delayFrac, const T& /*state*/)
+ {
+ auto index1 = delayInt;
+ auto index2 = index1 + 1;
+ auto index3 = index2 + 1;
+ auto index4 = index3 + 1;
+
+ if (index4 >= totalSize)
+ {
+ index1 %= totalSize;
+ index2 %= totalSize;
+ index3 %= totalSize;
+ index4 %= totalSize;
+ }
+
+ auto value1 = buffer[index1];
+ auto value2 = buffer[index2];
+ auto value3 = buffer[index3];
+ auto value4 = buffer[index4];
+
+ auto d1 = delayFrac - (T) 1.0;
+ auto d2 = delayFrac - (T) 2.0;
+ auto d3 = delayFrac - (T) 3.0;
+
+ auto c1 = -d1 * d2 * d3 / (T) 6.0;
+ auto c2 = d2 * d3 * (T) 0.5;
+ auto c3 = -d1 * d3 * (T) 0.5;
+ auto c4 = d1 * d2 / (T) 6.0;
+
+ return value1 * c1 + delayFrac * (value2 * c2 + value3 * c3 + value4 * c4);
+ }
+
+ int totalSize;
+ };
+
+ /**
+ Successive samples in the delay line will be interpolated using a 5th order
+ Lagrange interpolator. This method incurs more computational overhead than
+ linear interpolation.
+ */
+ struct Lagrange5th
+ {
+ void reset (int newTotalSize) { totalSize = newTotalSize; }
+
+ template
+ void updateInternalVariables (int& delayIntOffset, T& delayFrac)
+ {
+ if (delayIntOffset >= 2)
+ {
+ delayFrac += (T) 2;
+ delayIntOffset -= 2;
+ }
+ }
+
+ template
+ inline T call (const T* buffer, int delayInt, T delayFrac, const T& /*state*/)
+ {
+ auto index1 = delayInt;
+ auto index2 = index1 + 1;
+ auto index3 = index2 + 1;
+ auto index4 = index3 + 1;
+ auto index5 = index4 + 1;
+ auto index6 = index5 + 1;
+
+ if (index6 >= totalSize)
+ {
+ index1 %= totalSize;
+ index2 %= totalSize;
+ index3 %= totalSize;
+ index4 %= totalSize;
+ index5 %= totalSize;
+ index6 %= totalSize;
+ }
+
+ auto value1 = buffer[index1];
+ auto value2 = buffer[index2];
+ auto value3 = buffer[index3];
+ auto value4 = buffer[index4];
+ auto value5 = buffer[index5];
+ auto value6 = buffer[index6];
+
+ auto d1 = delayFrac - (T) 1.0;
+ auto d2 = delayFrac - (T) 2.0;
+ auto d3 = delayFrac - (T) 3.0;
+ auto d4 = delayFrac - (T) 4.0;
+ auto d5 = delayFrac - (T) 5.0;
+
+ auto c1 = -d1 * d2 * d3 * d4 * d5 / (T) 120.0;
+ auto c2 = d2 * d3 * d4 * d5 / (T) 24.0;
+ auto c3 = -d1 * d3 * d4 * d5 / (T) 12.0;
+ auto c4 = d1 * d2 * d4 * d5 / (T) 12.0;
+ auto c5 = -d1 * d2 * d3 * d5 / (T) 24.0;
+ auto c6 = d1 * d2 * d3 * d4 / (T) 120.0;
+
+ return value1 * c1 + delayFrac * (value2 * c2
+ + value3 * c3 + value4 * c4 + value5 * c5 + value6 * c6);
+ }
+
+ int totalSize;
+ };
+
+ /**
+ Successive samples in the delay line will be interpolated using 1st order
+ Thiran interpolation. This method is very efficient, and features a flat
+ amplitude frequency response in exchange for less accuracy in the phase
+ response. This interpolation method is stateful so is unsuitable for
+ applications requiring fast delay modulation.
+ */
+ struct Thiran
+ {
+ void reset (int newTotalSize) { totalSize = newTotalSize; }
+
+ template
+ void updateInternalVariables (int& delayIntOffset, T& delayFrac)
+ {
+ if (delayFrac < (T) 0.618 && delayIntOffset >= 1)
+ {
+ delayFrac++;
+ delayIntOffset--;
+ }
+
+ alpha = double ((1 - delayFrac) / (1 + delayFrac));
+ }
+
+ template
+ inline T call (const T* buffer, int delayInt, T delayFrac, T& state)
+ {
+ auto index1 = delayInt;
+ auto index2 = index1 + 1;
+
+ if (index2 >= totalSize)
+ {
+ index1 %= totalSize;
+ index2 %= totalSize;
+ }
+
+ auto value1 = buffer[index1];
+ auto value2 = buffer[index2];
+
+ auto output = delayFrac == 0 ? value1 : value2 + (T) alpha * (value1 - state);
+ state = output;
+
+ return output;
+ }
+
+ int totalSize;
+ double alpha = 0.0;
+ };
+}
+
+} // chowdsp
diff --git a/src/common/dsp/effect/chowdsp/shared/DelayLine.cpp b/src/common/dsp/effect/chowdsp/shared/DelayLine.cpp
new file mode 100644
index 00000000000..9f5bccf9437
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/shared/DelayLine.cpp
@@ -0,0 +1,129 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2020 - Raw Material Software Limited
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ By using JUCE, you agree to the terms of both the JUCE 6 End-User License
+ Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
+
+ End User License Agreement: www.juce.com/juce-6-licence
+ Privacy Policy: www.juce.com/juce-privacy-policy
+
+ Or: You may also use this code under the terms of the GPL v3 (see
+ www.gnu.org/licenses).
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+#include
+#include
+#include
+#include "DelayLine.h"
+
+namespace chowdsp
+{
+
+//==============================================================================
+template
+DelayLine::DelayLine()
+ : DelayLine (0)
+{
+}
+
+template
+DelayLine::DelayLine (size_t maximumDelayInSamples)
+{
+ totalSize = std::max ((size_t) 4, maximumDelayInSamples + 1);
+}
+
+//==============================================================================
+template
+void DelayLine::setDelay (SampleType newDelayInSamples)
+{
+ auto upperLimit = (SampleType) (totalSize - 1);
+
+ delay = limit_range (newDelayInSamples, (SampleType) 0, upperLimit);
+ delayInt = static_cast (std::floor (delay));
+ delayFrac = delay - (SampleType) delayInt;
+
+ interpolator.updateInternalVariables (delayInt, delayFrac);
+}
+
+template
+SampleType DelayLine::getDelay() const
+{
+ return delay;
+}
+
+//==============================================================================
+template
+void DelayLine::prepare (double sampleRate, size_t blockSize, size_t numChannels)
+{
+ this->bufferData.clear();
+ for (size_t ch = 0; ch < numChannels; ++ch)
+ this->bufferData.push_back (std::vector (totalSize, (SampleType) 0));
+
+ this->writePos.resize (numChannels);
+ this->readPos.resize (numChannels);
+
+ this->v.resize (numChannels);
+ interpolator.reset (totalSize);
+
+ reset();
+}
+
+template
+void DelayLine::reset()
+{
+ for (auto vec : { &this->writePos, &this->readPos })
+ std::fill (vec->begin(), vec->end(), 0);
+
+ std::fill (this->v.begin(), this->v.end(), static_cast (0));
+
+ for (size_t ch = 0; ch < this->bufferData.size(); ++ch)
+ std::fill (this->bufferData[ch].begin(), this->bufferData[ch].end(), static_cast (0));
+}
+
+//==============================================================================
+template
+void DelayLine::pushSample (size_t channel, SampleType sample)
+{
+ this->bufferData[channel][this->writePos[channel]] = sample;
+ this->writePos[channel] = (this->writePos[channel] + totalSize - 1) % totalSize;
+}
+
+template
+SampleType DelayLine::popSample (size_t channel, SampleType delayInSamples, bool updateReadPointer)
+{
+ if (delayInSamples >= 0)
+ setDelay(delayInSamples);
+
+ auto result = interpolateSample (channel);
+
+ if (updateReadPointer)
+ this->readPos[channel] = (this->readPos[channel] + totalSize - 1) % totalSize;
+
+ return result;
+}
+
+//==============================================================================
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+template class DelayLine;
+
+} // namespace chowdsp
diff --git a/src/common/dsp/effect/chowdsp/shared/DelayLine.h b/src/common/dsp/effect/chowdsp/shared/DelayLine.h
new file mode 100644
index 00000000000..2d527c418de
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/shared/DelayLine.h
@@ -0,0 +1,168 @@
+#pragma once
+
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2020 - Raw Material Software Limited
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ By using JUCE, you agree to the terms of both the JUCE 6 End-User License
+ Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
+
+ End User License Agreement: www.juce.com/juce-6-licence
+ Privacy Policy: www.juce.com/juce-privacy-policy
+
+ Or: You may also use this code under the terms of the GPL v3 (see
+ www.gnu.org/licenses).
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+#include
+#include "DelayInterpolation.h"
+
+namespace chowdsp
+{
+
+/** Base class for delay lines with any interpolation type */
+template
+class DelayLineBase
+{
+public:
+ DelayLineBase() = default;
+
+ virtual void setDelay (SampleType /* newDelayInSamples */) = 0;
+ virtual SampleType getDelay() const = 0;
+
+ virtual void prepare (double sampleRate, size_t blockSize, size_t nChannels) = 0;
+ virtual void reset() = 0;
+
+ virtual void pushSample (size_t /* channel */, SampleType /* sample */) = 0;
+ virtual SampleType popSample (size_t /* channel */, SampleType /* delayInSamples */, bool /* updateReadPointer */) = 0;
+
+ void copyState (const DelayLineBase& other)
+ {
+ for (size_t ch = 0; ch < bufferData.size(); ++ch)
+ std::copy (other.bufferData[ch].begin(), other.bufferData[ch].end(), bufferData[ch].begin());
+
+ std::copy (other.v.begin(), other.v.end(), v.begin());
+ std::copy (other.writePos.begin(), other.writePos.end(), writePos.begin());
+ std::copy (other.readPos.begin(), other.readPos.end(), readPos.begin());
+ }
+
+protected:
+ std::vector> bufferData;
+ std::vector v;
+ std::vector writePos, readPos;
+};
+
+//==============================================================================
+/**
+ A delay line processor featuring several algorithms for the fractional delay
+ calculation, block processing, and sample-by-sample processing useful when
+ modulating the delay in real time or creating a standard delay effect with
+ feedback.
+
+ This implementation has been modified from the original JUCE implementation
+ to include 5th-order Lagrange Interpolation.
+
+ Note: If you intend to change the delay in real time, you may want to smooth
+ changes to the delay systematically using either a ramp or a low-pass filter.
+
+ @see SmoothedValue, FirstOrderTPTFilter
+*/
+template
+class DelayLine : public DelayLineBase
+{
+public:
+ //==============================================================================
+ /** Default constructor. */
+ DelayLine();
+
+ /** Constructor. */
+ explicit DelayLine (size_t maximumDelayInSamples);
+
+ //==============================================================================
+ /** Sets the delay in samples. */
+ void setDelay (SampleType newDelayInSamples) override;
+
+ /** Returns the current delay in samples. */
+ SampleType getDelay() const override;
+
+ //==============================================================================
+ /** Initialises the processor. */
+ void prepare (double sampleRate, size_t blockSize, size_t nChannels) override;
+
+ /** Resets the internal state variables of the processor. */
+ void reset() override;
+
+ //==============================================================================
+ /** Pushes a single sample into one channel of the delay line.
+
+ Use this function and popSample instead of process if you need to modulate
+ the delay in real time instead of using a fixed delay value, or if you want
+ to code a delay effect with a feedback loop.
+
+ @see setDelay, popSample, process
+ */
+ void pushSample (size_t channel, SampleType sample) override;
+
+ /** Pops a single sample from one channel of the delay line.
+
+ Use this function to modulate the delay in real time or implement standard
+ delay effects with feedback.
+
+ @param channel the target channel for the delay line.
+
+ @param delayInSamples sets the wanted fractional delay in samples, or -1
+ to use the value being used before or set with
+ setDelay function.
+
+ @param updateReadPointer should be set to true if you use the function
+ once for each sample, or false if you need
+ multi-tap delay capabilities.
+
+ @see setDelay, pushSample, process
+ */
+ SampleType popSample (size_t channel, SampleType delayInSamples = -1, bool updateReadPointer = true) override;
+
+ //==============================================================================
+ /** Processes the input and output samples supplied in the processing context.
+
+ Can be used for block processing when the delay is not going to change
+ during processing. The delay must first be set by calling setDelay.
+
+ @see setDelay
+ */
+ void process (const SampleType* inputSamples, SampleType* outputSamples, const size_t numSamples, size_t channel)
+ {
+ for (size_t i = 0; i < numSamples; ++i)
+ {
+ pushSample (channel, inputSamples[i]);
+ outputSamples[i] = popSample (channel);
+ }
+ }
+
+private:
+ inline SampleType interpolateSample (size_t channel) noexcept
+ {
+ auto index = (this->readPos[channel] + delayInt);
+ return interpolator.call (this->bufferData[channel].data(),
+ index, delayFrac, this->v[channel]);
+ }
+
+ //==============================================================================
+ InterpolationType interpolator;
+ SampleType delay = 0.0, delayFrac = 0.0;
+ int delayInt = 0;
+ size_t totalSize = 4;
+};
+
+} // namespace chowdsp
diff --git a/src/common/dsp/effect/chowdsp/shared/Oversampling.h b/src/common/dsp/effect/chowdsp/shared/Oversampling.h
new file mode 100644
index 00000000000..ee7a2f88dba
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/shared/Oversampling.h
@@ -0,0 +1,117 @@
+/*
+** Surge Synthesizer is Free and Open Source Software
+**
+** Surge is made available under the Gnu General Public License, v3.0
+** https://www.gnu.org/licenses/gpl-3.0.en.html
+**
+** Copyright 2004-2020 by various individuals as described by the Git transaction log
+**
+** All source at: https://github.com/surge-synthesizer/surge.git
+**
+** Surge was a commercial product from 2004-2018, with Copyright and ownership
+** in that period held by Claes Johanson at Vember Audio. Claes made Surge
+** open source in September 2018.
+*/
+
+#pragma once
+#include
+#include
+#include
+#include
+
+namespace chowdsp
+{
+
+/*
+** Generalised oversampling class for use in effects processing.
+** Uses anti-imaging filters for upsampling, and anti-aliasing
+** filters for downsampling. Most of the oversampling parameters
+** are set as template parameters:
+**
+** @param: OSFactor sets the oversampling ratio as a power of two. i.e. Ratio = 2^OSFactor
+** @param: block_size size of the blocks of audio before upsampling
+** @param: FilterOrd sets the order of the anti-imaging/anti-aliasing filters
+** @param: steep sets whether to use the filters in "steep" mode (see vt_dsp/halfratefilter.h)
+**
+** The class should be used in the processign callback as follows:
+** Then use the following code to process samples:
+** @code
+** Ovsampling<2, BLOCK_SIZE> os;
+** void process(float* dataL, float* dataR)
+** {
+** os.upsample(dataL, dataR);
+** process_samples(os.leftUp, os.rightUp, os.getUpBLockSize());
+** os.downsample(dataL, dataR);
+** }
+** @endcode
+*/
+template
+class Oversampling
+{
+ std::unique_ptr hr_filts_up alignas(16)[OSFactor];
+ std::unique_ptr hr_filts_down alignas(16)[OSFactor];
+
+ static constexpr size_t osRatio = 1 << OSFactor;
+ static constexpr size_t up_block_size = block_size * osRatio;
+ static constexpr size_t block_size_quad = block_size / 4;
+
+public:
+ Oversampling()
+ {
+ for (size_t i = 0; i < OSFactor; ++i)
+ {
+ hr_filts_up[i] = std::make_unique (FilterOrd, steep);
+ hr_filts_down[i] = std::make_unique (FilterOrd, steep);
+ }
+ }
+
+ /** Resets the processing pipeline */
+ void reset()
+ {
+ for(size_t i = 0; i < OSFactor; ++i)
+ {
+ hr_filts_up[i]->reset();
+ hr_filts_down[i]->reset();
+ }
+
+ std::fill(leftUp, &leftUp[up_block_size], 0.0f);
+ std::fill(rightUp, &rightUp[up_block_size], 0.0f);
+ }
+
+ /** Upsamples the audio in the input arrays, and stores the upsampled audio internally */
+ inline void upsample(float* leftIn, float* rightIn) noexcept
+ {
+ copy_block(leftIn, leftUp, block_size_quad);
+ copy_block(rightIn, rightUp, block_size_quad);
+
+ for (size_t i = 0; i < OSFactor; ++i)
+ {
+ auto numSamples = block_size * (1 << (i + 1));
+ hr_filts_up[i]->process_block_U2(leftUp, rightUp, leftUp, rightUp, numSamples);
+ }
+ }
+
+ /** Downsamples that audio in the internal buffers, and stores the downsampled audio in the input arrays */
+ inline void downsample(float* leftOut, float* rightOut) noexcept
+ {
+ for (size_t i = OSFactor; i > 0; --i)
+ {
+ auto numSamples = block_size * (1 << i);
+ hr_filts_down[i - 1]->process_block_D2 (leftUp, rightUp, numSamples);
+ }
+
+ copy_block(leftUp, leftOut, block_size_quad);
+ copy_block(rightUp, rightOut, block_size_quad);
+ }
+
+ /** Returns the size of the upsampled blocks */
+ inline constexpr size_t getUpBlockSize() const noexcept { return up_block_size; }
+
+ /** Returns the oversampling ratio */
+ inline constexpr size_t getOSRatio() const noexcept { return osRatio; }
+
+ float leftUp alignas(16)[up_block_size];
+ float rightUp alignas(16)[up_block_size];
+};
+
+} // namespace chowdsp
diff --git a/src/common/dsp/effect/chowdsp/shared/SmoothedValue.h b/src/common/dsp/effect/chowdsp/shared/SmoothedValue.h
new file mode 100644
index 00000000000..01a08a50a60
--- /dev/null
+++ b/src/common/dsp/effect/chowdsp/shared/SmoothedValue.h
@@ -0,0 +1,333 @@
+#pragma once
+
+/*
+ ==============================================================================
+ This file is part of the JUCE library.
+ Copyright (c) 2020 - Raw Material Software Limited
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ To use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+ ==============================================================================
+*/
+
+#include "basic_dsp.h"
+
+namespace chowdsp
+{
+
+//==============================================================================
+/**
+ A base class for the smoothed value classes.
+ This class is used to provide common functionality to the SmoothedValue and
+ dsp::LogRampedValue classes.
+ @tags{Audio}
+*/
+template
+class SmoothedValueBase
+{
+private:
+ //==============================================================================
+ template struct FloatTypeHelper;
+
+ template class SmoothedValueClass, typename FloatType>
+ struct FloatTypeHelper >
+ {
+ using Type = FloatType;
+ };
+
+ template class SmoothedValueClass, typename FloatType, typename SmoothingType>
+ struct FloatTypeHelper >
+ {
+ using Type = FloatType;
+ };
+
+public:
+ using FloatType = typename FloatTypeHelper::Type;
+
+ //==============================================================================
+ /** Constructor. */
+ SmoothedValueBase() = default;
+
+ virtual ~SmoothedValueBase() {}
+
+ //==============================================================================
+ /** Returns true if the current value is currently being interpolated. */
+ bool isSmoothing() const noexcept { return countdown > 0; }
+
+ /** Returns the current value of the ramp. */
+ FloatType getCurrentValue() const noexcept { return currentValue; }
+
+ //==============================================================================
+ /** Returns the target value towards which the smoothed value is currently moving. */
+ FloatType getTargetValue() const noexcept { return target; }
+
+ /** Sets the current value and the target value.
+ @param newValue the new value to take
+ */
+ void setCurrentAndTargetValue (FloatType newValue)
+ {
+ target = currentValue = newValue;
+ countdown = 0;
+ }
+
+ //==============================================================================
+ /** Applies a smoothed gain to a stream of samples
+ S[i] *= gain
+ @param samples Pointer to a raw array of samples
+ @param numSamples Length of array of samples
+
+ N.B.: numSamples must be a multiple of 16
+ */
+ void applyGain (FloatType* samples, int numSamples) noexcept
+ {
+ if (isSmoothing())
+ {
+ for (int i = 0; i < numSamples; ++i)
+ samples[i] *= getNextSmoothedValue();
+ }
+ else
+ {
+ mul_block (samples, target, samples, numSamples / 4);
+ }
+ }
+
+ /** Computes output as a smoothed gain applied to a stream of samples.
+ Sout[i] = Sin[i] * gain
+ @param samplesOut A pointer to a raw array of output samples
+ @param samplesIn A pointer to a raw array of input samples
+ @param numSamples The length of the array of samples
+
+ N.B.: numSamples must be a multiple of 16
+ */
+ void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept
+ {
+ if (isSmoothing())
+ {
+ for (int i = 0; i < numSamples; ++i)
+ samplesOut[i] = samplesIn[i] * getNextSmoothedValue();
+ }
+ else
+ {
+ mul_block (samplesIn, target, samplesOut, numSamples / 4);
+ }
+ }
+
+private:
+ //==============================================================================
+ FloatType getNextSmoothedValue() noexcept
+ {
+ return static_cast (this)->getNextValue();
+ }
+
+protected:
+ //==============================================================================
+ FloatType currentValue = 0;
+ FloatType target = currentValue;
+ int countdown = 0;
+};
+
+//==============================================================================
+/**
+ A namespace containing a set of types used for specifying the smoothing
+ behaviour of the SmoothedValue class.
+ For example:
+ @code
+ SmoothedValue frequency (1.0f);
+ @endcode
+*/
+namespace ValueSmoothingTypes
+{
+ /**
+ Used to indicate a linear smoothing between values.
+ @tags{Audio}
+ */
+ struct Linear {};
+
+ /**
+ Used to indicate a smoothing between multiplicative values.
+ @tags{Audio}
+ */
+ struct Multiplicative {};
+}
+
+//==============================================================================
+/**
+ A utility class for values that need smoothing to avoid audio glitches.
+ A ValueSmoothingTypes::Linear template parameter selects linear smoothing,
+ which increments the SmoothedValue linearly towards its target value.
+ @code
+ SmoothedValue yourSmoothedValue;
+ @endcode
+ A ValueSmoothingTypes::Multiplicative template parameter selects
+ multiplicative smoothing increments towards the target value.
+ @code
+ SmoothedValue yourSmoothedValue;
+ @endcode
+ Multiplicative smoothing is useful when you are dealing with
+ exponential/logarithmic values like volume in dB or frequency in Hz. For
+ example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the
+ frequency with an equal temperament tuning across the octave. A 10 step
+ smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in
+ increments of 1 dB.
+ Note that when you are using multiplicative smoothing you cannot ever reach a
+ target value of zero!
+ @tags{Audio}
+*/
+template
+class SmoothedValue : public SmoothedValueBase >
+{
+public:
+ //==============================================================================
+ /** Constructor. */
+ SmoothedValue() noexcept
+ : SmoothedValue ((FloatType) (std::is_same::value ? 0 : 1))
+ {
+ }
+
+ /** Constructor. */
+ SmoothedValue (FloatType initialValue) noexcept
+ {
+ // Visual Studio can't handle base class initialisation with CRTP
+ this->currentValue = initialValue;
+ this->target = this->currentValue;
+ }
+
+ //==============================================================================
+ /** Reset to a new sample rate and ramp length.
+ @param sampleRate The sample rate
+ @param rampLengthInSeconds The duration of the ramp in seconds
+ */
+ void reset (double sampleRate, double rampLengthInSeconds) noexcept
+ {
+ reset ((int) std::floor (rampLengthInSeconds * sampleRate));
+ }
+
+ /** Set a new ramp length directly in samples.
+ @param numSteps The number of samples over which the ramp should be active
+ */
+ void reset (int numSteps) noexcept
+ {
+ stepsToTarget = numSteps;
+ this->setCurrentAndTargetValue (this->target);
+ }
+
+ //==============================================================================
+ /** Set the next value to ramp towards.
+ @param newValue The new target value
+ */
+ void setTargetValue (FloatType newValue) noexcept
+ {
+ if (newValue == this->target)
+ return;
+
+ if (stepsToTarget <= 0)
+ {
+ this->setCurrentAndTargetValue (newValue);
+ return;
+ }
+
+ this->target = newValue;
+ this->countdown = stepsToTarget;
+
+ setStepSize();
+ }
+
+ //==============================================================================
+ /** Compute the next value.
+ @returns Smoothed value
+ */
+ FloatType getNextValue() noexcept
+ {
+ if (! this->isSmoothing())
+ return this->target;
+
+ --(this->countdown);
+
+ if (this->isSmoothing())
+ setNextValue();
+ else
+ this->currentValue = this->target;
+
+ return this->currentValue;
+ }
+
+ //==============================================================================
+ /** Skip the next numSamples samples.
+ This is identical to calling getNextValue numSamples times. It returns
+ the new current value.
+ @see getNextValue
+ */
+ FloatType skip (int numSamples) noexcept
+ {
+ if (numSamples >= this->countdown)
+ {
+ this->setCurrentAndTargetValue (this->target);
+ return this->target;
+ }
+
+ skipCurrentValue (numSamples);
+
+ this->countdown -= numSamples;
+ return this->currentValue;
+ }
+
+private:
+ //==============================================================================
+ template
+ using LinearVoid = typename std::enable_if ::value, void>::type;
+
+ template
+ using MultiplicativeVoid = typename std::enable_if ::value, void>::type;
+
+ //==============================================================================
+ template
+ LinearVoid setStepSize() noexcept
+ {
+ step = (this->target - this->currentValue) / (FloatType) this->countdown;
+ }
+
+ template
+ MultiplicativeVoid setStepSize()
+ {
+ step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / (FloatType) this->countdown);
+ }
+
+ //==============================================================================
+ template
+ LinearVoid setNextValue() noexcept
+ {
+ this->currentValue += step;
+ }
+
+ template
+ MultiplicativeVoid setNextValue() noexcept
+ {
+ this->currentValue *= step;
+ }
+
+ //==============================================================================
+ template
+ LinearVoid skipCurrentValue (int numSamples) noexcept
+ {
+ this->currentValue += step * (FloatType) numSamples;
+ }
+
+ template
+ MultiplicativeVoid skipCurrentValue (int numSamples)
+ {
+ this->currentValue *= (FloatType) std::pow (step, numSamples);
+ }
+
+ //==============================================================================
+ FloatType step = FloatType();
+ int stepsToTarget = 0;
+};
+
+} // namespace chowdsp
diff --git a/src/common/vt_dsp/basic_dsp.cpp b/src/common/vt_dsp/basic_dsp.cpp
index 080f156a2e2..400021aa09f 100644
--- a/src/common/vt_dsp/basic_dsp.cpp
+++ b/src/common/vt_dsp/basic_dsp.cpp
@@ -412,6 +412,21 @@ void mul_block(float* __restrict src1,
}
}
+void mul_block(float* __restrict src1,
+ float scalar,
+ float* __restrict dst,
+ unsigned int nquads)
+{
+ auto scalar_mm = _mm_set1_ps(scalar);
+ for (unsigned int i = 0; i < nquads; i += 4)
+ {
+ ((__m128*)dst)[i] = _mm_mul_ps(((__m128*)src1)[i], scalar_mm);
+ ((__m128*)dst)[i + 1] = _mm_mul_ps(((__m128*)src1)[i + 1], scalar_mm);
+ ((__m128*)dst)[i + 2] = _mm_mul_ps(((__m128*)src1)[i + 2], scalar_mm);
+ ((__m128*)dst)[i + 3] = _mm_mul_ps(((__m128*)src1)[i + 3], scalar_mm);
+ }
+}
+
void encodeMS(float* __restrict L,
float* __restrict R,
float* __restrict M,
diff --git a/src/common/vt_dsp/basic_dsp.h b/src/common/vt_dsp/basic_dsp.h
index 73339c80e42..9aa4594c723 100644
--- a/src/common/vt_dsp/basic_dsp.h
+++ b/src/common/vt_dsp/basic_dsp.h
@@ -29,6 +29,7 @@ void copy_block_USUD(float* src,
float* dst,
unsigned int nquads); // copy block (unaligned source + destination)
void mul_block(float* src1, float* src2, float* dst, unsigned int nquads);
+void mul_block(float* src1, float scalar, float* dst, unsigned int nquads);
void add_block(float* src1, float* src2, float* dst, unsigned int nquads);
void subtract_block(float* src1, float* src2, float* dst, unsigned int nquads);
void encodeMS(float* L, float* R, float* M, float* S, unsigned int nquads);
diff --git a/src/python_bindings/surgepy.cpp b/src/python_bindings/surgepy.cpp
index a847d783ed8..1ebc7aa828d 100644
--- a/src/python_bindings/surgepy.cpp
+++ b/src/python_bindings/surgepy.cpp
@@ -539,6 +539,26 @@ class SurgeSynthesizerWithPythonExtensions : public SurgeSynthesizer
}
return "";
}
+ std::string getParamInfo( const SurgePyNamedParam &id )
+ {
+ const auto val = getParamVal( id );
+ const auto min = getParamMin( id );
+ const auto max = getParamMax( id );
+ const auto def = getParamDef( id );
+ const auto val_type = getParamValType( id );
+ const auto display = getParamDisplay( id );
+
+ std::ostringstream oss;
+ oss << "Parameter name: " << id.getName() << std::endl;
+ oss << "Parameter value: " << val << std::endl;
+ oss << "Parameter min: " << min << std::endl;
+ oss << "Parameter max: " << max << std::endl;
+ oss << "Parameter default: " << def << std::endl;
+ oss << "Parameter value type: " << val_type << std::endl;
+ oss << "Parameter display value: " << display << std::endl;
+
+ return oss.str();
+ }
void setParamVal( const SurgePyNamedParam &id, float f )
{
@@ -868,6 +888,8 @@ PYBIND11_MODULE(surgepy, m) {
.def("getParamDisplay", &SurgeSynthesizerWithPythonExtensions::getParamDisplay,
"Parameter value display (stringified and formatted)")
+ .def("getParamInfo", &SurgeSynthesizerWithPythonExtensions::getParamInfo,
+ "Parameter value info (formatted)")
.def("setParamVal", &SurgeSynthesizerWithPythonExtensions::setParamVal,
"Set a parameter value", py::arg("param"), py::arg("toThis"))
@@ -1043,6 +1065,7 @@ PYBIND11_MODULE(surgepy, m) {
C(fxt_flanger);
C(fxt_ringmod);
C(fxt_airwindows);
+ C(fxt_neuron);
C(fxslot_ains1);
C(fxslot_ains2);