From 6ecf1be09114e08cd025c4caee51c64ee9468812 Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Sun, 22 Jan 2023 06:48:34 -0800 Subject: [PATCH 1/7] add Chew, Degrade, Loss --- plugins/ChowDSP/AnalogChew.cpp | 108 +++++++++ plugins/ChowDSP/AnalogChew.hpp | 64 ++++++ plugins/ChowDSP/AnalogChew.sc | 11 + plugins/ChowDSP/AnalogChew.schelp | 37 +++ plugins/ChowDSP/AnalogDegrade.cpp | 78 +++++++ plugins/ChowDSP/AnalogDegrade.hpp | 39 ++++ plugins/ChowDSP/AnalogDegrade.sc | 11 + plugins/ChowDSP/AnalogDegrade.schelp | 40 ++++ plugins/ChowDSP/AnalogLoss.cpp | 146 ++++++++++++ plugins/ChowDSP/AnalogLoss.hpp | 52 +++++ plugins/ChowDSP/AnalogLoss.sc | 11 + plugins/ChowDSP/AnalogLoss.schelp | 58 +++++ plugins/ChowDSP/DegradeFilter.h | 62 +++++ plugins/ChowDSP/DegradeNoise.h | 28 +++ plugins/ChowDSP/Dropout.h | 57 +++++ plugins/ChowDSP/FIRFilter.h | 52 +++++ plugins/ChowDSP/LevelDetector.hpp | 58 +++++ plugins/ChowDSP/SmoothedValue.h | 332 +++++++++++++++++++++++++++ 18 files changed, 1244 insertions(+) create mode 100644 plugins/ChowDSP/AnalogChew.cpp create mode 100644 plugins/ChowDSP/AnalogChew.hpp create mode 100644 plugins/ChowDSP/AnalogChew.sc create mode 100644 plugins/ChowDSP/AnalogChew.schelp create mode 100644 plugins/ChowDSP/AnalogDegrade.cpp create mode 100644 plugins/ChowDSP/AnalogDegrade.hpp create mode 100644 plugins/ChowDSP/AnalogDegrade.sc create mode 100644 plugins/ChowDSP/AnalogDegrade.schelp create mode 100644 plugins/ChowDSP/AnalogLoss.cpp create mode 100644 plugins/ChowDSP/AnalogLoss.hpp create mode 100644 plugins/ChowDSP/AnalogLoss.sc create mode 100644 plugins/ChowDSP/AnalogLoss.schelp create mode 100644 plugins/ChowDSP/DegradeFilter.h create mode 100644 plugins/ChowDSP/DegradeNoise.h create mode 100644 plugins/ChowDSP/Dropout.h create mode 100644 plugins/ChowDSP/FIRFilter.h create mode 100644 plugins/ChowDSP/LevelDetector.hpp create mode 100644 plugins/ChowDSP/SmoothedValue.h diff --git a/plugins/ChowDSP/AnalogChew.cpp b/plugins/ChowDSP/AnalogChew.cpp new file mode 100644 index 0000000..7d0f1ce --- /dev/null +++ b/plugins/ChowDSP/AnalogChew.cpp @@ -0,0 +1,108 @@ +// AnalogChew.cpp +// Mads Kjeldgaard (mail@madskjeldgaard.dk) + +#include "AnalogChew.hpp" + +#include "SC_PlugIn.hpp" + +static InterfaceTable *ft; + +namespace AnalogChew { + +AnalogChew::AnalogChew() { + samplerate = sampleRate(); + + filt.reset(samplerate, int(samplerate * 0.02f)); + dropout.prepare((double) samplerate); + cookParams(samplerate,0.5,0.5,0.5); + + mCalcFunc = make_calc_function(); + next(1); +} + +AnalogChew::~AnalogChew() {} + + +float AnalogChew::uniform() { + return ((float) rand() / (RAND_MAX)); +} + +void AnalogChew::cookParams(float fs, float depthParam, float freqParam, float varParam) { + const float highFreq = std::min(22000.0f, 0.49f * fs); + const float freqChange = highFreq - 5000.0f; + + auto depth = depthParam; + auto freq = freqParam; + if (freq == 0.0f) + { + mix = 0.0f; + filt.setFreq (highFreq); + } + else if (freq == 1.0f) + { + mix = 1.0f; + power = 3.0f * depth; + filt.setFreq (highFreq - freqChange * depth); + } + else if (sampleCounter >= samplesUntilChange) + { + sampleCounter = 0; + isCrinkled = ! isCrinkled; + + if (isCrinkled) // start crinkle + { + mix = 1.0f; + power = (1.0f + 2.0f * uniform()) * depth; + filt.setFreq (highFreq - freqChange * depth); + samplesUntilChange = getWetTime(freq, depth, varParam); + } + else // end crinkle + { + mix = 0.0f; + filt.setFreq (highFreq); + samplesUntilChange = getDryTime(freq, varParam); + } + } + else + { + power = (1.0f + 2.0f * uniform()) * depth; + if (isCrinkled) + { + filt.setFreq (highFreq - freqChange * depth); + filt.setFreq (highFreq - freqChange * depth); + } + } + + dropout.setMix (mix); + dropout.setPower (1.0f + power); +} + +void AnalogChew::next(int nSamples) { + float depth=in0(Depth); + float freq=in0(Frequency); + float var=in0(Variance); + + cookParams(samplerate,depth,freq,var); + + const float *input = in(Input); + float *outbuf = out(Out1); + + for (int i = 0; i < nSamples; ++i) { + // get input + float x = mkutils::constrain(input[i], -1.0f, 1.0f); + x = dropout.processSample(x); + x = filt.processSample(x); + outbuf[i] = x; + sampleCounter++; + } +} + +void AnalogChew::clear(int nSamples) { ClearUnitOutputs(this, nSamples); } + +} // namespace AnalogChew + +PluginLoad(AnalogChewUGens) { + // Plugin magic + ft = inTable; + registerUnit(ft, "AnalogChew", false); +} diff --git a/plugins/ChowDSP/AnalogChew.hpp b/plugins/ChowDSP/AnalogChew.hpp new file mode 100644 index 0000000..27828fb --- /dev/null +++ b/plugins/ChowDSP/AnalogChew.hpp @@ -0,0 +1,64 @@ +// AnalogChew.hpp +// Mads Kjeldgaard (mail@madskjeldgaard.dk) + +#pragma once + +#include "SC_PlugIn.hpp" +#include "../mkutils.hpp" +#include "DegradeFilter.h" +#include "Dropout.h" + +namespace AnalogChew { + +class AnalogChew : public SCUnit { +public: + AnalogChew(); + + // Destructor + ~AnalogChew(); + + inline int getDryTime(float freq, float var) + { + auto tScale = std::pow(freq, 0.1f); + auto varScale = std::pow(((float) rand() / (RAND_MAX))* 2.0f, var); + + auto minVal = (int) ((1.0f - tScale) * samplerate * varScale); + auto maxVal = (int) ((2.0f - 1.99f * tScale) * samplerate * varScale); + return (int) ((int) rand()) % (maxVal - minVal) + minVal; + } + + inline int getWetTime(float freq, float depth, float var) + { + auto tScale = std::pow(freq, 0.1f); + auto start = 0.2f + 0.8f * depth; + auto end = start - (0.001f + 0.01f * depth); + auto varScale = std::pow(((float) rand() / (RAND_MAX))* 2.0f, var); + + auto minVal = (int) ((1.0f - tScale) * samplerate * varScale); + auto maxVal = (int) (((1.0f - tScale) + start - end * tScale) * samplerate * varScale); + return (int) ((int) rand()) % (maxVal - minVal) + minVal; + } +private: + // Calc function + void next(int nSamples); + void clear(int nSamples); + float uniform(); + void cookParams(float fs, float depthParam, float freqParam, float varParam); + + enum InputParams { Input, Depth, Frequency, Variance, NumInputParams }; + enum Outputs { Out1, NumOutputParams }; + + float samplerate = 44100.0f; + float mix = 0.0f; + float power = 0.0f; + int samplesUntilChange = 1000; + bool isCrinkled = false; + int sampleCounter = 0; + int nextCounter = 19; + + Dropout dropout; + DegradeFilter filt; + +}; + +} // namespace AnalogChew diff --git a/plugins/ChowDSP/AnalogChew.sc b/plugins/ChowDSP/AnalogChew.sc new file mode 100644 index 0000000..5f878bc --- /dev/null +++ b/plugins/ChowDSP/AnalogChew.sc @@ -0,0 +1,11 @@ +AnalogChew : UGen { + *ar { |input, depth=0.5, freq=0.5, variance=0.5| + ^this.multiNew('audio', input, depth, freq, variance); + } + + checkInputs { + /* TODO */ + ^this.checkValidInputs; + } +} + diff --git a/plugins/ChowDSP/AnalogChew.schelp b/plugins/ChowDSP/AnalogChew.schelp new file mode 100644 index 0000000..0576bbd --- /dev/null +++ b/plugins/ChowDSP/AnalogChew.schelp @@ -0,0 +1,37 @@ +CLASS:: AnalogChew +SUMMARY:: Analog tape emulation +RELATED::HelpSource/Overview/PortedPlugins +CATEGORIES::UGens>VirtualAnalog + +DESCRIPTION:: + +This plugin is an analog tape emulation algorithm by Jatin Chowdhury, a mini version of the mindblowing link::https://github.com/jatinchowdhury18/AnalogChewModel##AnalogChewModel vst plugin by the same::. For a deep dive, link::https://ccrma.stanford.edu/~jatin/420/tape/TapeModel_DAFx.pdf##see Chowdhury's paper on analog tape modelling::. This smaller version is mostly useful as a tape saturation/distortion. + +The plugin's guts feature variable oversampling and anti aliasing filters to achieve high quality distortion. + +CLASSMETHODS:: + +METHOD::ar + +ARGUMENT::input +Audio input + +ARGUMENT::depth +Tape bias. 0.0 to 1.0. + +ARGUMENT::freq +Tape saturation. 0.0 to 1.0 but may be pushed harder. + +ARGUMENT::variance +Tape drive. 0.0 to 1.0 but may be pushed harder. + + +EXAMPLES:: + +code:: +Ndef(\notam, {|freq=110, width=0.5| + var sig = VarSaw.ar(freq, width: width); + AnalogChew.ar(sig); +}).play; +:: + diff --git a/plugins/ChowDSP/AnalogDegrade.cpp b/plugins/ChowDSP/AnalogDegrade.cpp new file mode 100644 index 0000000..541e14f --- /dev/null +++ b/plugins/ChowDSP/AnalogDegrade.cpp @@ -0,0 +1,78 @@ +// AnalogDegrade.cpp +// Mads Kjeldgaard (mail@madskjeldgaard.dk) + +#include "AnalogDegrade.hpp" + +#include "SC_PlugIn.hpp" + +static InterfaceTable *ft; + +namespace AnalogDegrade { + +AnalogDegrade::AnalogDegrade() { + samplerate = sampleRate(); + + noiseProc.prepare(samplerate); + filterProc.reset(samplerate, int(samplerate * 0.05f)); + levelDetector.prepare(samplerate); + gainProc.reset((double) samplerate, 0.05f); + cookParams(samplerate,0.5f,0.5f,0.5f,0.5f); + + mCalcFunc = make_calc_function(); + next(1); +} + +AnalogDegrade::~AnalogDegrade() {} + + +float AnalogDegrade::uniform() { + return ((float) rand() / (RAND_MAX)); +} + +void AnalogDegrade::cookParams(float fs, float depthParam, float amtParam, float varParam, float envParam) { + const auto freqHz = 200.0f * std::pow(20000.0f / 200.0f, 1.0f - amtParam); + const auto gainDB = -24.0f * depthParam; + + noiseProc.setGain(0.33f * depthParam * amtParam); + filterProc.setFreq(std::min(freqHz + (varParam * (freqHz / 0.6f) * (uniform() - 0.5f)), 0.49f * fs)); + + const auto envSkew = 1.0f - std::pow(envParam, 0.8f); + levelDetector.setParameters(10.0f, 20.0f * std::pow(5000.0f / 20.0f, envSkew)); + gainProc.setTargetValue(std::pow(10.0f,0.05f*std::min(gainDB + (varParam * 36.0f * (uniform() - 0.5f)), 3.0f))); +} + +void AnalogDegrade::next(int nSamples) { + float depth=in0(Depth); + float amt=in0(Amount); + float var=in0(Variance); + float env=in0(Envelope); + + cookParams(samplerate,depth,amt,var,env); + + const float *input = in(Input); + float *outbuf = out(Out1); + + for (int i = 0; i < nSamples; ++i) { + // get input + float x = mkutils::constrain(input[i], -1.0f, 1.0f); + auto level=levelDetector.processSample(x); + auto noise=noiseProc.processSample(0.0f); + if (env>0.0f) { + noise *= level; + } + x += noise; + x = filterProc.processSample(x); + x *= gainProc.getNextValue(); + outbuf[i] = x; + } +} + +void AnalogDegrade::clear(int nSamples) { ClearUnitOutputs(this, nSamples); } + +} // namespace AnalogDegrade + +PluginLoad(AnalogDegradeUGens) { + // Plugin magic + ft = inTable; + registerUnit(ft, "AnalogDegrade", false); +} diff --git a/plugins/ChowDSP/AnalogDegrade.hpp b/plugins/ChowDSP/AnalogDegrade.hpp new file mode 100644 index 0000000..e87118d --- /dev/null +++ b/plugins/ChowDSP/AnalogDegrade.hpp @@ -0,0 +1,39 @@ +// AnalogDegrade.hpp +// Mads Kjeldgaard (mail@madskjeldgaard.dk) + +#pragma once + +#include "SC_PlugIn.hpp" +#include "../mkutils.hpp" +#include "DegradeFilter.h" +#include "DegradeNoise.h" +#include "LevelDetector.hpp" + +namespace AnalogDegrade { + +class AnalogDegrade : public SCUnit { +public: + AnalogDegrade(); + + // Destructor + ~AnalogDegrade(); + +private: + // Calc function + void next(int nSamples); + void clear(int nSamples); + float uniform(); + void cookParams(float fs, float depthParam, float amtParam, float varParam, float envParam); + + enum InputParams { Input, Depth, Amount, Variance, Envelope, NumInputParams }; + enum Outputs { Out1, NumOutputParams }; + + float samplerate = 44100; + DegradeFilter filterProc; + DegradeNoise noiseProc; + LevelDetector levelDetector; + SmoothedValue gainProc; + +}; + +} // namespace AnalogDegrade diff --git a/plugins/ChowDSP/AnalogDegrade.sc b/plugins/ChowDSP/AnalogDegrade.sc new file mode 100644 index 0000000..29c26d4 --- /dev/null +++ b/plugins/ChowDSP/AnalogDegrade.sc @@ -0,0 +1,11 @@ +AnalogDegrade : UGen { + *ar { |input, depth=0.5, amount=0.5, variance=0.5, envelope=0.5| + ^this.multiNew('audio', input, depth, amount, variance, envelope); + } + + checkInputs { + /* TODO */ + ^this.checkValidInputs; + } +} + diff --git a/plugins/ChowDSP/AnalogDegrade.schelp b/plugins/ChowDSP/AnalogDegrade.schelp new file mode 100644 index 0000000..15065eb --- /dev/null +++ b/plugins/ChowDSP/AnalogDegrade.schelp @@ -0,0 +1,40 @@ +CLASS:: AnalogDegrade +SUMMARY:: Analog tape emulation +RELATED::HelpSource/Overview/PortedPlugins +CATEGORIES::UGens>VirtualAnalog + +DESCRIPTION:: + +This plugin is an analog tape emulation algorithm by Jatin Chowdhury, a mini version of the mindblowing link::https://github.com/jatinchowdhury18/AnalogDegradeModel##AnalogDegradeModel vst plugin by the same::. For a deep dive, link::https://ccrma.stanford.edu/~jatin/420/tape/TapeModel_DAFx.pdf##see Chowdhury's paper on analog tape modelling::. This smaller version is mostly useful as a tape saturation/distortion. + +The plugin's guts feature variable oversampling and anti aliasing filters to achieve high quality distortion. + +CLASSMETHODS:: + +METHOD::ar + +ARGUMENT::input +Audio input + +ARGUMENT::depth +Tape bias. 0.0 to 1.0. + +ARGUMENT::amount +Tape saturation. 0.0 to 1.0 but may be pushed harder. + +ARGUMENT::variance +Tape drive. 0.0 to 1.0 but may be pushed harder. + +ARGUMENT::envelope +0 to 1.0 + + +EXAMPLES:: + +code:: +Ndef(\notam, {|freq=110, width=0.5| + var sig = VarSaw.ar(freq, width: width); + AnalogDegrade.ar(sig); +}).play; +:: + diff --git a/plugins/ChowDSP/AnalogLoss.cpp b/plugins/ChowDSP/AnalogLoss.cpp new file mode 100644 index 0000000..460687e --- /dev/null +++ b/plugins/ChowDSP/AnalogLoss.cpp @@ -0,0 +1,146 @@ +// AnalogLoss.cpp + +#include "AnalogLoss.hpp" + +#include "SC_PlugIn.hpp" + + +static InterfaceTable *ft; + +namespace +{ +constexpr float speedBase = 25.0f / 4.0f; +constexpr float speedMult = 28.0f / 3.0f; +constexpr float speedOff = -25.0f / 3.0f; +inline float getSpeed(float param) { + return std::pow(speedBase, param) * speedMult + speedOff; +} + +constexpr float spaceBase = 10000.0f / 9801.0f; +constexpr float spaceMult = 9801.0f / 10.0f; +constexpr float spaceOff = -980.0f; +inline float getSpacing(float param) { + return std::pow(spaceBase, param) * spaceMult + spaceOff; +} + +constexpr float thickBase = 122500.0f / 22201.0f; +constexpr float thickMult = 22201.0f / 2010.0f; +constexpr float thickOff = -2200.0f / 201.0f; +inline float getThickness(float param) { + return std::pow(thickBase, param) * thickMult + thickOff; +} + +constexpr float gapBase = 1600.0f / 81.0f; +constexpr float gapMult = 81.0f / 31.0f; +constexpr float gapOff = -50.0f / 31.0f; +inline float getGap(float param) { + return std::pow(gapBase, param) * gapMult + gapOff; +} +} + +namespace AnalogLoss { + +AnalogLoss::AnalogLoss() { + fs = sampleRate(); + + fsFactor = (float) fs / 44100.0f; + curOrder = int (order * fsFactor); + currentCoefs.resize (curOrder, 0.0f); + Hcoefs.resize (curOrder, 0.0f); + calcCoefs(prevGap,prevThickness,prevSpacing,prevSpeed); + + filter.reset(new FIRFilter (curOrder)); + filter->setCoefs(currentCoefs.data()); + filter->reset(); + + mCalcFunc = make_calc_function(); + next(1); +} + +AnalogLoss::~AnalogLoss() {} + +void AnalogLoss::calcCoefs(float speedArg, float thicknessArg, float gapArg, float spacingArg) +{ + const auto speed = getSpeed(speedArg); + const auto thickness = getThickness(thicknessArg); + const auto gap = getGap(gapArg); + const auto spacing = getSpacing(spacingArg); + + // Set freq domain multipliers + binWidth = fs / (float) curOrder; + auto H = Hcoefs.data(); + for (int k = 0; k < curOrder / 2; k++) + { + const auto freq = (float) k * binWidth; + const auto waveNumber = 2.0f * M_PI * std::max(freq, 20.0f) / (speed * 0.0254f); + const auto thickTimesK = waveNumber * (thickness * (float) 1.0e-6); + const auto kGapOverTwo = waveNumber * (gap * (float) 1.0e-6) / 2.0f; + + H[k] = expf (-waveNumber * (spacing * (float) 1.0e-6)); // Spacing loss + H[k] *= (1.0f - expf (-thickTimesK)) / thickTimesK; // Thickness loss + H[k] *= sinf (kGapOverTwo) / kGapOverTwo; // Gap loss + H[curOrder - k - 1] = H[k]; + } + + // Create time domain filter signal + auto h = currentCoefs.data(); + for (int n = 0; n < curOrder / 2; n++) + { + const auto idx = (size_t) curOrder / 2 + (size_t) n; + for (int k = 0; k < curOrder; k++) + h[idx] += Hcoefs[k] * cosf (2.0f * M_PI * (float) k * (float) n / (float) curOrder); + + h[idx] /= (float) curOrder; + h[curOrder / 2 - n] = h[idx]; + } + + // compute head bump filters + calcHeadBumpFilter(speed, gap * 1.0e-6f, fs); +} + +void AnalogLoss::calcHeadBumpFilter(float speedIps, float gapMeters, float fs) +{ + auto bumpFreq = speedIps * 0.0254f / (gapMeters * 500.0f); + auto gain = std::max(1.5f * (1000.0f - std::abs(bumpFreq - 100.0f)) / 1000.0f, 1.0f); + headBumpFilter.setParameters(BiquadFilter::PEAK, bumpFreq / fs, 2.0f, gain); +} + +void AnalogLoss::next(int nSamples) { + const float gap = in0(Gap); + const float thick = in0(Thick); + const float space = in0(Space); + const float speed = in0(Speed); + const float *input = in(Input); + float *outbuf = out(Out1); + + if(speed == prevSpeed && space == prevSpacing + && thick == prevThickness && gap == prevGap) { + } else { + calcCoefs(gap,thick,space,speed); + filter->setCoefs(currentCoefs.data()); + prevGap = gap; + prevThickness = thick; + prevSpacing = space; + prevSpeed = speed; + } + + // set hysteresis params + for (int i = 0; i < nSamples; ++i) { + // get input + float x = mkutils::constrain(input[i], -1.0f, 1.0f); + x = filter->process(x); + x = headBumpFilter.process(x); + outbuf[i] = x; + } + +} + +void AnalogLoss::clear(int nSamples) { ClearUnitOutputs(this, nSamples); } + +} // namespace AnalogLoss + +PluginLoad(AnalogLossUGens) { + // Plugin magic + ft = inTable; + registerUnit(ft, "AnalogLoss", false); +} diff --git a/plugins/ChowDSP/AnalogLoss.hpp b/plugins/ChowDSP/AnalogLoss.hpp new file mode 100644 index 0000000..4f75bc4 --- /dev/null +++ b/plugins/ChowDSP/AnalogLoss.hpp @@ -0,0 +1,52 @@ +// AnalogLoss.hpp + + +#include "HysteresisProcessing.cpp" +#include "SC_PlugIn.hpp" +#include "VariableOversampling.hpp" +#include "../mkutils.hpp" +#include "../dcblocker.h" +#include "FIRFilter.h" +#include "iir.hpp" +#include "wdf.h" + +namespace AnalogLoss { + +class AnalogLoss : public SCUnit { +public: + AnalogLoss(); + + // Destructor + ~AnalogLoss(); + +private: + // Calc function + void next(int nSamples); + void clear(int nSamples); + void calcHeadBumpFilter(float speedIps, float gapMeters, float fs); + void calcCoefs(float speed, float thickness, float gap, float spacing); + + enum InputParams { Input, Gap, Thick, Space, Speed, NumInputParams }; + enum Outputs { Out1, NumOutputParams }; + HysteresisProcessing hysteresis; + + float prevSpeed = 0.5f; + float prevSpacing = 0.5f; + float prevThickness = 0.5f; + float prevGap = 0.5f; + + float fs = 44100.0f; + float fsFactor = 1.0f; + float binWidth = fs / 100.0f; + + const int order = 64; + int curOrder = order; + std::vector currentCoefs; + std::vector Hcoefs; + + std::unique_ptr filter; + BiquadFilter headBumpFilter; + +}; + +} // namespace AnalogLoss diff --git a/plugins/ChowDSP/AnalogLoss.sc b/plugins/ChowDSP/AnalogLoss.sc new file mode 100644 index 0000000..b69cee4 --- /dev/null +++ b/plugins/ChowDSP/AnalogLoss.sc @@ -0,0 +1,11 @@ +AnalogLoss : UGen { + *ar { |input, gap=0.5, thick=0.5, space=0.5, speed=1| + ^this.multiNew('audio', input, gap, thick, space, speed); + } + + checkInputs { + /* TODO */ + ^this.checkValidInputs; + } +} + diff --git a/plugins/ChowDSP/AnalogLoss.schelp b/plugins/ChowDSP/AnalogLoss.schelp new file mode 100644 index 0000000..e332608 --- /dev/null +++ b/plugins/ChowDSP/AnalogLoss.schelp @@ -0,0 +1,58 @@ +CLASS:: AnalogLoss +SUMMARY:: Analog tape emulation +RELATED::HelpSource/Overview/PortedPlugins +CATEGORIES::UGens>VirtualAnalog + +DESCRIPTION:: + +This plugin is an analog tape emulation algorithm by Jatin Chowdhury, a mini version of the mindblowing link::https://github.com/jatinchowdhury18/AnalogLossModel##AnalogLossModel vst plugin by the same::. For a deep dive, link::https://ccrma.stanford.edu/~jatin/420/tape/TapeModel_DAFx.pdf##see Chowdhury's paper on analog tape modelling::. This smaller version is mostly useful as a tape saturation/distortion. + +The plugin's guts feature variable oversampling and anti aliasing filters to achieve high quality distortion. + +CLASSMETHODS:: + +METHOD::ar + +ARGUMENT::input +Audio input + +ARGUMENT::gap +Tape bias. 0.0 to 1.0. + +ARGUMENT::thick +Tape saturation. 0.0 to 1.0 but may be pushed harder. + +ARGUMENT::space +Tape drive. 0.0 to 1.0 but may be pushed harder. + +ARGUMENT::speed +Set amount of oversampling + +0 = No oversampling, +1 = x2, +2 = x4, +3 = x8, +4 = x16 + +ARGUMENT::mode +Change the mode (solver type) of the tape algorithm: +0 = RK2 (2nd order Runge Kutta) +1 = RK4 (4th order Runge Kutta) +2 = NR4 (4-iteration Newton Raphson) +3 = NR8 (8-iteration Newton Raphson) + +The Runge-Kutta solvers are computationally cheaper, but +somewhat less accurate than the Newton-Raphson solvers. +Similarly, the higher-order solvers will be more accurate, +but will also consume more compute resources. + + +EXAMPLES:: + +code:: +Ndef(\notam, {|freq=110, width=0.5| + var sig = VarSaw.ar(freq, width: width); + AnalogLoss.ar(sig); +}).play; +:: + diff --git a/plugins/ChowDSP/DegradeFilter.h b/plugins/ChowDSP/DegradeFilter.h new file mode 100644 index 0000000..dcb41dc --- /dev/null +++ b/plugins/ChowDSP/DegradeFilter.h @@ -0,0 +1,62 @@ +#ifndef DEGRADEFILTER_H_INCLUDED +#define DEGRADEFILTER_H_INCLUDED + +#include "SmoothedValue.h" + +/** Lowpass filter for tape degrade effect */ +class DegradeFilter +{ +public: + DegradeFilter() { freq.reset (numSteps); } + ~DegradeFilter() {} + + void reset (float sampleRate, int steps = 0) + { + fs = sampleRate; + for (int n = 0; n < 2; ++n) + z[n] = 0.0f; + + if (steps > 0) + freq.reset (steps); + + freq.setCurrentAndTargetValue (freq.getTargetValue()); + calcCoefs (freq.getCurrentValue()); + } + + inline void calcCoefs (float fc) + { + float wc = 2.0f * M_PI * fc / fs; + float c = 1.0f / std::tan (wc / 2.0f); + float a0 = c + 1.0f; + + b[0] = 1 / a0; + b[1] = b[0]; + a[1] = (1.0f - c) / a0; + } + + inline float processSample (float x) + { + if (freq.isSmoothing()) + calcCoefs (freq.getNextValue()); + + float y = z[1] + x * b[0]; + z[1] = x * b[1] - y * a[1]; + return y; + } + + void setFreq (float newFreq) + { + freq.setTargetValue (newFreq); + } + +private: + SmoothedValue freq = 20000.0f; + float fs = 44100.0f; + const int numSteps = 200; + + float a[2] = { 1.0f, 0.0f }; + float b[2] = { 1.0f, 0.0f }; + float z[2] = { 1.0f, 0.0f }; +}; + +#endif // DEGRADEFILTER_H_INCLUDED diff --git a/plugins/ChowDSP/DegradeNoise.h b/plugins/ChowDSP/DegradeNoise.h new file mode 100644 index 0000000..f330cb3 --- /dev/null +++ b/plugins/ChowDSP/DegradeNoise.h @@ -0,0 +1,28 @@ +#pragma once + +#include "SmoothedValue.h" + +class DegradeNoise +{ +public: + DegradeNoise() = default; + ~DegradeNoise() = default; + + void setGain (float newGain) + { + gain.setTargetValue(newGain); + } + + void prepare (float sampleRate) + { + gain.reset((double) sampleRate, 0.05); + } + + inline float processSample(float x) + { + return x + ((((float) rand() / (RAND_MAX))) - 0.5f) * gain.getNextValue(); + } + +private: + SmoothedValue gain; +}; diff --git a/plugins/ChowDSP/Dropout.h b/plugins/ChowDSP/Dropout.h new file mode 100644 index 0000000..5eece5e --- /dev/null +++ b/plugins/ChowDSP/Dropout.h @@ -0,0 +1,57 @@ +#ifndef DROPOUT_H_INCLUDED +#define DROPOUT_H_INCLUDED + +#include "SmoothedValue.h" + +class Dropout +{ +public: + Dropout() {} + + void setMix (float newMix) + { + mixSmooth.setTargetValue (newMix); + } + + void setPower (float newPow) + { + powerSmooth.setTargetValue (newPow); + } + + void prepare (double sr) + { + mixSmooth.reset (sr, 0.01); + mixSmooth.setCurrentAndTargetValue (mixSmooth.getTargetValue()); + + powerSmooth.reset (sr, 0.005); + powerSmooth.setCurrentAndTargetValue (powerSmooth.getTargetValue()); + } + + inline float processSample (float x) + { + auto mix = mixSmooth.getNextValue(); + if (mix == 0.0f) + return x; + + return mix * dropout (x) + (1.0f - mix) * x; + } + + /** Signum function to determine the sign of the input. */ + template + inline int signum (T val) const + { + return (T (0) < val) - (val < T (0)); + } + + inline float dropout (float x) + { + auto sign = (float) signum (x); + return pow (abs (x), powerSmooth.getNextValue()) * sign; + } + +private: + SmoothedValue mixSmooth; + SmoothedValue powerSmooth; +}; + +#endif // DROPOUT_H_INCLUDED diff --git a/plugins/ChowDSP/FIRFilter.h b/plugins/ChowDSP/FIRFilter.h new file mode 100644 index 0000000..16b7ebe --- /dev/null +++ b/plugins/ChowDSP/FIRFilter.h @@ -0,0 +1,52 @@ +#ifndef FIRFILTER_H_INCLUDED +#define FIRFILTER_H_INCLUDED + +#include +#include +#include + +/** FIR filter using a double-buffer and std::inner_product */ +class FIRFilter +{ +public: + FIRFilter (int filter_order) : order ((size_t) filter_order) + { + h.resize (order); + z.resize (2 * order); + } + + void reset() + { + zPtr = 0; + std::fill(z.begin(), z.end(), 0.0f); + } + + void setCoefs (const float* coefs) + { + std::copy(coefs, coefs + order, h.begin()); + } + + inline float process (float x) + { + float y = 0.0f; + + // insert input into double-buffered state + z[zPtr] = x; + z[zPtr + order] = x; + + y = std::inner_product(z.data() + zPtr, z.data() + zPtr + order, h.data(), 0.0f); // comput inner product + + zPtr = (zPtr == 0 ? order - 1 : zPtr - 1); // iterate state pointer in reverse + return y; + } + +protected: + std::vector h; + const size_t order; + +private: + std::vector z; + size_t zPtr = 0; +}; + +#endif //FIRFILTER_H_INCLUDED diff --git a/plugins/ChowDSP/LevelDetector.hpp b/plugins/ChowDSP/LevelDetector.hpp new file mode 100644 index 0000000..e184e62 --- /dev/null +++ b/plugins/ChowDSP/LevelDetector.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +/** A simple level detector, that I like better than the JUCE one */ +template +class LevelDetector +{ +public: + LevelDetector() = default; + + /** Sets the current time constants, in milliseconds. */ + void setParameters (float attackTimeMs, float releaseTimeMs) + { + tauAtt = (SampleType) calcTimeConstant (attackTimeMs, expFactor); + tauRel = (SampleType) calcTimeConstant (releaseTimeMs, expFactor); + } + + /** Initialises the processor. */ + void prepare (float sampleRate) + { + expFactor = -1000.0f / sampleRate; + reset(); + } + + /** Resets the internal state variables of the processor. */ + void reset() + { + yOld = (SampleType) 0; + increasing = true; + } + + /** Processes a single sample. Note that this function expects the input to be non-negative */ + virtual inline SampleType processSample (SampleType x) noexcept + { + auto tau = increasing ? tauAtt : tauRel; + x = yOld + tau * (x - yOld); + + // update for next sample + increasing = x > yOld; + yOld = x; + + return x; + } + +protected: + inline float calcTimeConstant (float timeMs, float expFactor) + { + return timeMs < 1.0e-3f ? 0.0f : 1.0f - std::exp (expFactor / timeMs); + } + + float expFactor; + SampleType yOld; + bool increasing; + + SampleType tauAtt = (SampleType) 1; + SampleType tauRel = (SampleType) 1; +}; diff --git a/plugins/ChowDSP/SmoothedValue.h b/plugins/ChowDSP/SmoothedValue.h new file mode 100644 index 0000000..db5d0d7 --- /dev/null +++ b/plugins/ChowDSP/SmoothedValue.h @@ -0,0 +1,332 @@ +#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 + +//============================================================================== +/** + 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