Skip to content

Commit

Permalink
Muff Clipper (#252)
Browse files Browse the repository at this point in the history
* Copied the BigMuffDrive with a new name

* Register the new drive

* Rename without the drive

* Make the MuffClipper single-stage

* Optimistically add parameters

* Implement asymmetric clipping

* Rescaled the smoothing parameter

* cosh as true derivative of sinh

* Add myself to the authors for this module

* Added a preset with the new module

* make the metadata more like the things already there

* Listed a Violet Mist in the preset manager

* Another preset

* Metadata for the new new preset

* Small cleanup and optimizations

* Fixed +/- clips for the signal getting inverted

* rebrand the new presets

Co-authored-by: Jatin Chowdhury <[email protected]>
  • Loading branch information
x31eq and jatinchowdhury18 authored Jan 8, 2023
1 parent f8f673e commit d369246
Show file tree
Hide file tree
Showing 10 changed files with 534 additions and 1 deletion.
56 changes: 56 additions & 0 deletions res/presets/Gainful Clipper.chowpreset
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>

<Preset name="Gainful Clipper" plugin="BYOD" vendor="x31eq" category="Pedals"
version="1.1.0">
<proc_chain state_plugin_version="1.1.0">
<Muff_Tone>
<Parameters x_pos="0.6112957000732422" y_pos="0.2140151560306549">
<PARAM id="mids" value="0.3120000660419464"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="tone" value="0.5560000538825989"/>
<PARAM id="type" value="8.0"/>
</Parameters>
<port_0 connection_0="-1" connection_end_0="0"/>
</Muff_Tone>
<Muff_Clipper>
<Parameters x_pos="0.1436877101659775" y_pos="0.5151515007019043">
<PARAM id="clip1" value="0.355555534362793"/>
<PARAM id="clip2" value="0.644444465637207"/>
<PARAM id="harmonics" value="0.7555555701255798"/>
<PARAM id="high_q" value="1.0"/>
<PARAM id="level" value="0.6499999761581421"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="scream" value="0.0"/>
<PARAM id="smoothing" value="-0.199999988079071"/>
<PARAM id="sustain" value="0.7888888716697693"/>
</Parameters>
<port_0 connection_0="2" connection_end_0="0"/>
</Muff_Clipper>
<Muff_Clipper>
<Parameters x_pos="0.3554817140102386" y_pos="0.07196969538927078">
<PARAM id="clip1" value="0.2666666507720947"/>
<PARAM id="clip2" value="0.08888888359069824"/>
<PARAM id="harmonics" value="0.6555555462837219"/>
<PARAM id="high_q" value="1.0"/>
<PARAM id="level" value="0.7222222089767456"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="scream" value="0.0"/>
<PARAM id="smoothing" value="0.4666666984558105"/>
<PARAM id="sustain" value="0.8333333134651184"/>
</Parameters>
<port_0 connection_0="0" connection_end_0="0"/>
</Muff_Clipper>
<Input>
<Parameters x_pos="0.01197604835033417" y_pos="0.1228070184588432">
<PARAM id="on_off" value="1.0"/>
</Parameters>
<port_0 connection_0="1" connection_end_0="0"/>
</Input>
<Output>
<Parameters x_pos="0.8999999761581421" y_pos="0.112648218870163">
<PARAM id="on_off" value="1.0"/>
</Parameters>
</Output>
</proc_chain>
<extra_info/>
</Preset>
54 changes: 54 additions & 0 deletions res/presets/Violet Mist.chowpreset
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>

<Preset name="Violet Mist" plugin="BYOD" vendor="x31eq" category="Pedals"
version="1.1.0">
<proc_chain state_plugin_version="1.1.0">
<Muff_Tone>
<Parameters x_pos="0.579734206199646" y_pos="0.3162878751754761">
<PARAM id="mids" value="0.5"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="tone" value="0.5"/>
<PARAM id="type" value="1.0"/>
</Parameters>
<port_0 connection_0="-1" connection_end_0="0"/>
</Muff_Tone>
<Muff_Clipper>
<Parameters x_pos="0.1644518226385117" y_pos="0.1780302971601486">
<PARAM id="clip1" value="-0.3777777552604675"/>
<PARAM id="clip2" value="-0.3111110925674438"/>
<PARAM id="harmonics" value="0.8888888955116272"/>
<PARAM id="high_q" value="1.0"/>
<PARAM id="level" value="0.4888888895511627"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="smoothing" value="-0.04444444179534912"/>
<PARAM id="sustain" value="0.5"/>
</Parameters>
<port_0 connection_0="2" connection_end_0="0"/>
</Muff_Clipper>
<Muff_Clipper>
<Parameters x_pos="0.3197674453258514" y_pos="0.564393937587738">
<PARAM id="clip1" value="0.4444444179534912"/>
<PARAM id="clip2" value="0.2666666507720947"/>
<PARAM id="harmonics" value="0.2666666805744171"/>
<PARAM id="high_q" value="1.0"/>
<PARAM id="level" value="0.7444444298744202"/>
<PARAM id="on_off" value="1.0"/>
<PARAM id="smoothing" value="0.355555534362793"/>
<PARAM id="sustain" value="0.4777777791023254"/>
</Parameters>
<port_0 connection_0="0" connection_end_0="0"/>
</Muff_Clipper>
<Input>
<Parameters x_pos="0.005020080134272575" y_pos="0.3221343755722046">
<PARAM id="on_off" value="1.0"/>
</Parameters>
<port_0 connection_0="1" connection_end_0="0"/>
</Input>
<Output>
<Parameters x_pos="0.8594377636909485" y_pos="0.3221343755722046">
<PARAM id="on_off" value="1.0"/>
</Parameters>
</Output>
</proc_chain>
<extra_info/>
</Preset>
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ target_sources(BYOD PRIVATE
processors/drive/hysteresis/HysteresisProcessing.cpp
processors/drive/junior_b/JuniorB.cpp
processors/drive/king_of_tone/KingOfToneDrive.cpp
processors/drive/muff_clipper/MuffClipper.cpp
processors/drive/muff_clipper/MuffClipperStage.cpp
processors/drive/mxr_distortion/MXRDistortion.cpp
processors/drive/neural_utils/ResampledRNN.cpp
processors/drive/tube_amp/TubeAmp.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/headless/tests/PreBufferTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class PreBufferTest : public UnitTest
}(proc->getName());
testBuffer (buffer.getReadPointer (0), steadyStateMax);
},
StringArray { "Muff Drive", "Trumble Drive" });
StringArray { "Muff Drive", "Muff Clipper", "Trumble Drive" });
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/processors/ProcessorStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "drive/hysteresis/Hysteresis.h"
#include "drive/junior_b/JuniorB.h"
#include "drive/king_of_tone/KingOfToneDrive.h"
#include "drive/muff_clipper/MuffClipper.h"
#include "drive/mxr_distortion/MXRDistortion.h"
#include "drive/tube_amp/TubeAmp.h"
#include "drive/tube_screamer/TubeScreamer.h"
Expand Down Expand Up @@ -85,6 +86,7 @@ ProcessorStore::StoreMap ProcessorStore::store = {
{ "Junior B", &processorFactory<JuniorB> },
{ "Tone King", &processorFactory<KingOfToneDrive> },
{ "Metal Face", &processorFactory<MetalFace> },
{ "Muff Clipper", &processorFactory<MuffClipper> },
{ "Muff Drive", &processorFactory<BigMuffDrive> },
{ "Range Booster", &processorFactory<RangeBooster> },
{ "RONN", &processorFactory<RONN> },
Expand Down
192 changes: 192 additions & 0 deletions src/processors/drive/muff_clipper/MuffClipper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#include "MuffClipper.h"
#include "../../ParameterHelpers.h"

namespace
{
const auto cutoffRange = ParameterHelpers::createNormalisableRange (500.0f, 22000.0f, 1200.0f);
float harmParamToCutoffHz (float harmParam)
{
return cutoffRange.convertFrom0to1 (harmParam);
}

const auto sustainRange = ParameterHelpers::createNormalisableRange (0.4f, 2.0f, 1.0f);
const auto levelRange = ParameterHelpers::createNormalisableRange (-60.0f, 0.0f, -9.0f);
} // namespace

MuffClipper::MuffClipper (UndoManager* um) : BaseProcessor ("Muff Clipper", createParameterLayout(), um)
{
using namespace ParameterHelpers;
loadParameterPointer (sustainParam, vts, "sustain");
loadParameterPointer (harmParam, vts, "harmonics");
loadParameterPointer (levelParam, vts, "level");
clip1Param.setParameterHandle (getParameterPointer<chowdsp::FloatParameter*> (vts, "clip1"));
clip2Param.setParameterHandle (getParameterPointer<chowdsp::FloatParameter*> (vts, "clip2"));
smoothingParam.setParameterHandle (getParameterPointer<chowdsp::FloatParameter*> (vts, "smoothing"));
hiQParam = vts.getRawParameterValue ("high_q");

addPopupMenuParameter ("high_q");

uiOptions.backgroundColour = Colours::darkgrey.brighter (0.3f).withRotatedHue (0.2f);
uiOptions.powerColour = Colours::red.brighter (0.15f);
uiOptions.info.description = "Fuzz effect based on a single drive stage from the Electro-Harmonix Big Muff Pi.";
uiOptions.info.authors = StringArray { "Jatin Chowdhury", "Graham Breed" };
}

ParamLayout MuffClipper::createParameterLayout()
{
using namespace ParameterHelpers;

auto params = createBaseParams();

createPercentParameter (params, "sustain", "Gain", 0.5f);
createPercentParameter (params, "harmonics", "Harm.", 0.65f);
createBipolarPercentParameter (params, "smoothing", "Smooth", 0.0f);
createBipolarPercentParameter (params, "clip2", "+Clip", 0.0f);
createBipolarPercentParameter (params, "clip1", "-Clip", 0.0f);
createPercentParameter (params, "level", "Level", 0.65f);

emplace_param<AudioParameterBool> (params, "high_q", "High Quality", true);

return { params.begin(), params.end() };
}

void MuffClipper::prepare (double sampleRate, int samplesPerBlock)
{
fs = (float) sampleRate;

cutoffSmooth.reset (sampleRate, 0.02);
cutoffSmooth.setCurrentAndTargetValue (harmParamToCutoffHz (*harmParam));
for (auto& filt : inputFilter)
{
filt.calcCoefs (cutoffSmooth.getTargetValue(), fs);
filt.reset();
}

clip1Param.setRampLength (0.05);
clip1Param.mappingFunction = [] (float val)
{
return MuffClipperStage::getClipV (val);
};
clip1Param.prepare (sampleRate, samplesPerBlock);

clip2Param.setRampLength (0.05);
clip2Param.mappingFunction = [] (float val)
{
return MuffClipperStage::getClipV (val);
};
clip2Param.prepare (sampleRate, samplesPerBlock);

smoothingParam.setRampLength (0.05);
smoothingParam.mappingFunction = [fs = this->fs] (float val)
{
return MuffClipperStage::getGC12 (fs, val);
};
smoothingParam.prepare (sampleRate, samplesPerBlock);

stage.prepare (sampleRate);

auto spec = dsp::ProcessSpec { sampleRate, (uint32) samplesPerBlock, 2 };

sustainGain.prepare (spec);
sustainGain.setRampDurationSeconds (0.02);

outLevel.prepare (spec);
outLevel.setRampDurationSeconds (0.02);

for (auto& filt : dcBlocker)
{
filt.calcCoefs (16.0f, fs);
filt.reset();
}

maxBlockSize = samplesPerBlock;
doPrebuffering();
}

void MuffClipper::doPrebuffering()
{
AudioBuffer<float> buffer (2, maxBlockSize);
for (int i = 0; i < 10000; i += maxBlockSize)
{
buffer.clear();
processAudio (buffer);
}
}

void MuffClipper::processInputStage (AudioBuffer<float>& buffer)
{
const auto numChannels = buffer.getNumChannels();
const auto numSamples = buffer.getNumSamples();

cutoffSmooth.setTargetValue (harmParamToCutoffHz (*harmParam));
if (cutoffSmooth.isSmoothing())
{
if (numChannels == 1)
{
auto* x = buffer.getWritePointer (0);
for (int n = 0; n < numSamples; ++n)
{
inputFilter[0].calcCoefs (cutoffSmooth.getNextValue(), fs);
x[n] = inputFilter[0].processSample (x[n]);
}
}
else if (numChannels == 2)
{
auto* xL = buffer.getWritePointer (0);
auto* xR = buffer.getWritePointer (1);
for (int n = 0; n < numSamples; ++n)
{
auto cutoffHz = cutoffSmooth.getNextValue();

inputFilter[0].calcCoefs (cutoffHz, fs);
xL[n] = inputFilter[0].processSample (xL[n]);

inputFilter[1].calcCoefs (cutoffHz, fs);
xR[n] = inputFilter[1].processSample (xR[n]);
}
}
}
else
{
for (int ch = 0; ch < numChannels; ++ch)
{
inputFilter[ch].calcCoefs (cutoffSmooth.getNextValue(), fs);
inputFilter[ch].processBlock (buffer.getWritePointer (ch), numSamples);
}
}

sustainGain.setGainLinear (sustainRange.convertFrom0to1 (*sustainParam));
dsp::AudioBlock<float> block { buffer };
sustainGain.process (dsp::ProcessContextReplacing<float> { block });
}

void MuffClipper::processAudio (AudioBuffer<float>& buffer)
{
const auto numChannels = buffer.getNumChannels();
const auto numSamples = buffer.getNumSamples();

processInputStage (buffer);

clip1Param.process (numSamples);
clip2Param.process (numSamples);
smoothingParam.process (numSamples);
const auto useHighQualityMode = hiQParam->load() == 1.0f;
if (useHighQualityMode)
{
stage.processBlock<true> (buffer, clip1Param, clip2Param, smoothingParam);
}
else
{
stage.processBlock<false> (buffer, clip1Param, clip2Param, smoothingParam);
}

for (int ch = 0; ch < numChannels; ++ch)
dcBlocker[ch].processBlock (buffer.getWritePointer (ch), numSamples);

auto outGain = Decibels::decibelsToGain (levelRange.convertFrom0to1 (*levelParam), levelRange.start);
outGain *= Decibels::decibelsToGain (13.0f); // makeup from level lost in clipping stage
outGain *= -1.0f;
outLevel.setGainLinear (outGain);
dsp::AudioBlock<float> block { buffer };
outLevel.process (dsp::ProcessContextReplacing<float> { block });
}
42 changes: 42 additions & 0 deletions src/processors/drive/muff_clipper/MuffClipper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include "../../BaseProcessor.h"
#include "MuffClipperStage.h"

class MuffClipper : public BaseProcessor
{
public:
explicit MuffClipper (UndoManager* um);

ProcessorType getProcessorType() const override { return Drive; }
static ParamLayout createParameterLayout();

void prepare (double sampleRate, int samplesPerBlock) override;
void processAudio (AudioBuffer<float>& buffer) override;

private:
void doPrebuffering();
void processInputStage (AudioBuffer<float>& buffer);

chowdsp::FloatParameter* sustainParam = nullptr;
chowdsp::FloatParameter* harmParam = nullptr;
chowdsp::FloatParameter* levelParam = nullptr;
chowdsp::SmoothedBufferValue<float> clip1Param;
chowdsp::SmoothedBufferValue<float> clip2Param;
chowdsp::SmoothedBufferValue<float> smoothingParam;
std::atomic<float>* hiQParam = nullptr;

chowdsp::FirstOrderLPF<float> inputFilter[2];
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> cutoffSmooth;
dsp::Gain<float> sustainGain;

MuffClipperStage stage;

chowdsp::FirstOrderHPF<float> dcBlocker[2];
dsp::Gain<float> outLevel;

float fs = 48000.0f;
int maxBlockSize = 0;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MuffClipper)
};
Loading

0 comments on commit d369246

Please sign in to comment.