Skip to content

Commit

Permalink
1.8.2 MERGEABLE - Neuron, distortion effect based on Gated Recurrent …
Browse files Browse the repository at this point in the history
…Unit (surge-synthesizer#3656)

* Add "Neuron" distortion effect based on
Gated Recurrent Unit.

* Fix compilation on Linux/OSX

* Add stereo separation of combs, output gain/width adjustment, cleaner UI layout

* Neuron tweaks: adjust DC blocking filter, and correct delay line sample rate for oversampling factor

Co-authored-by: jatinchowdhury18 <[email protected]>
Co-authored-by: Mario Kruselj <[email protected]>
  • Loading branch information
3 people authored Jan 27, 2021
1 parent 9550679 commit de91746
Show file tree
Hide file tree
Showing 15 changed files with 1,408 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions doc/AddingAnFX.md
Original file line number Diff line number Diff line change
@@ -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!
4 changes: 4 additions & 0 deletions resources/data/configuration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@
<snapshot name="To Tape" p0="42" p1="0.5" p2="0.5" p3="0.5" p4="0.5" p5="0.5" p6="1" />
</type>
</type>
<separator/>
<type i="15" name="Neuron">
<snapshot name="Init" />
</type>
</fx>
<customctrl>
<entry p="0" ctrl="41" />
Expand Down
2 changes: 2 additions & 0 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ enum fx_type
fxt_flanger,
fxt_ringmod,
fxt_airwindows,
fxt_neuron,

n_fx_types,
};
Expand All @@ -300,6 +301,7 @@ const char fx_type_names[n_fx_types][16] =
"Flanger",
"Ring Mod",
"Airwindows",
"Neuron",
};

enum fx_bypass
Expand Down
3 changes: 3 additions & 0 deletions src/common/dsp/effect/Effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "FlangerEffect.h"
#include "RingModulatorEffect.h"
#include "airwindows/AirWindowsEffect.h"
#include "chowdsp/Neuron.h"
#include "DebugHelpers.h"

using namespace std;
Expand Down Expand Up @@ -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;
};
Expand Down
235 changes: 235 additions & 0 deletions src/common/dsp/effect/chowdsp/Neuron.cpp
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit de91746

Please sign in to comment.