AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit 241576e84798c1bbcdd4f8e9df583fd47a9ff923
parent 6143c902d78edbe343dcb8692eac6a78339dc981
Author: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Date:   Wed, 13 May 2020 13:20:23 -0700

Updates to Flutter algorithm

Diffstat:
MPlugin/CHOWTapeModel.jucer | 1+
APlugin/Source/Processors/Hysteresis/DCFilters.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 110++-----------------------------------------------------------------------------
MPlugin/Source/Processors/Timing_Effects/Flutter.cpp | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
MPlugin/Source/Processors/Timing_Effects/Flutter.h | 12++++++++++++
5 files changed, 217 insertions(+), 130 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -23,6 +23,7 @@ file="Source/Processors/Degrade/DegradeProcessor.h"/> </GROUP> <GROUP id="{6052B1B0-83EF-DBFA-991C-FC0B47A949C9}" name="Hysteresis"> + <FILE id="l6IKp3" name="DCFilters.h" compile="0" resource="0" file="Source/Processors/Hysteresis/DCFilters.h"/> <FILE id="Qe4tlV" name="HysteresisProcessing.cpp" compile="1" resource="0" file="Source/Processors/Hysteresis/HysteresisProcessing.cpp"/> <FILE id="OYS18C" name="HysteresisProcessing.h" compile="0" resource="0" diff --git a/Plugin/Source/Processors/Hysteresis/DCFilters.h b/Plugin/Source/Processors/Hysteresis/DCFilters.h @@ -0,0 +1,118 @@ +#ifndef DCFILTERS_H_INCLUDED +#define DCFILTERS_H_INCLUDED + +#include "JuceHeader.h" + +/* High-pass filter to compensate for low frequency noise of transformer */ +class TransformerHPF +{ +public: + TransformerHPF() {} + + void reset (double sampleRate) + { + for (int n = 0; n < 3; ++n) + z[n] = 0.0f; + + fs = (float) sampleRate; + } + + void calcCoefs (float fc, float Q) + { + float wc = MathConstants<float>::twoPi * fc / fs; + float c = 1.0f / dsp::FastMathApproximations::tan (wc / 2.0f); + float phi = c * c; + float K = c / Q; + float a0 = phi + K + 1.0f; + + b[0] = phi / a0; + b[1] = -2.0f * b[0]; + b[2] = b[0]; + a[1] = 2.0f * (1.0f - phi) / a0; + a[2] = (phi - K + 1.0f) / a0; + } + + void processBlock (float* buffer, const int numSamples) + { + for (int n = 0; n < numSamples; ++n) + buffer[n] = processSample (buffer[n]); + } + + inline float processSample (float x) + { + // direct form II transposed + float y = z[1] + x * b[0]; + + z[1] = z[2] + x*b[1] - y*a[1]; + z[2] = x*b[2] - y*a[2]; + + return y; + } + +private: + float a[3] = { 0.0f, 0.0f, 0.0f }; + float b[3] = { 1.0f, 0.0f, 0.0f }; + + float z[3] = { 0.0f, 0.0f, 0.0f }; + + float fs = 44100.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TransformerHPF) +}; + +/* Low-shelf filter to compensate for low frequency noise of transformer +class TransformerShelf +{ +public: +TransformerShelf() {} + +void reset (double sampleRate) +{ +for (int n = 0; n < 3; ++n) +z[n] = 0.0f; + +fs = (float) sampleRate; +} + +void calcCoefs (float fc, float Q, float gain) +{ +float A = sqrtf (gain); +float wc = MathConstants<float>::twoPi * fc / fs; +float wS = dsp::FastMathApproximations::sin (wc); +float wC = dsp::FastMathApproximations::cos (wc); +float beta = sqrtf (A) / Q; + +float a0 = ((A+1.0f) + ((A-1.0f) * wC) + (beta*wS)); + +b[0] = A*((A+1.0f) - ((A-1.0f)*wC) + (beta*wS)) / a0; +b[1] = 2.0f*A * ((A-1.0f) - ((A+1.0f)*wC)) / a0; +b[2] = A*((A+1.0f) - ((A-1.0f)*wC) - (beta*wS)) / a0; + +a[1] = -2.0f * ((A-1.0f) + ((A+1.0f)*wC)) / a0; +a[2] = ((A+1.0f) + ((A-1.0f)*wC)-(beta*wS)) / a0; +} + +inline float processSample (float x) +{ +// direct form II transposed +float y = z[1] + x * b[0]; + +z[1] = z[2] + x*b[1] - y*a[1]; +z[2] = x*b[2] - y*a[2]; + +return y; +} + +private: +float a[3] = { 0.0f, 0.0f, 0.0f }; +float b[3] = { 1.0f, 0.0f, 0.0f }; + +float z[3] = { 0.0f, 0.0f, 0.0f }; + +float fs = 44100.0f; + +JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TransformerShelf) +}; +*/ + +#endif // DCFILTERS_H_INCLUDED diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -2,115 +2,9 @@ #define HYSTERESISPROCESSOR_H_INCLUDED #include "HysteresisProcessing.h" +#include "DCFilters.h" -/* High-pass filter to compensate for low frequency noise of transformer */ -class TransformerHPF -{ -public: - TransformerHPF() {} - - void reset (double sampleRate) - { - for (int n = 0; n < 3; ++n) - z[n] = 0.0f; - - fs = (float) sampleRate; - } - - void calcCoefs (float fc, float Q) - { - float wc = MathConstants<float>::twoPi * fc / fs; - float c = 1.0f / dsp::FastMathApproximations::tan (wc / 2.0f); - float phi = c * c; - float K = c / Q; - float a0 = phi + K + 1.0f; - - b[0] = phi / a0; - b[1] = -2.0f * b[0]; - b[2] = b[0]; - a[1] = 2.0f * (1.0f - phi) / a0; - a[2] = (phi - K + 1.0f) / a0; - } - - inline float processSample (float x) - { - // direct form II transposed - float y = z[1] + x * b[0]; - - z[1] = z[2] + x*b[1] - y*a[1]; - z[2] = x*b[2] - y*a[2]; - - return y; - } - -private: - float a[3] = { 0.0f, 0.0f, 0.0f }; - float b[3] = { 1.0f, 0.0f, 0.0f }; - - float z[3] = { 0.0f, 0.0f, 0.0f }; - - float fs = 44100.0f; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TransformerHPF) -}; - -/* Low-shelf filter to compensate for low frequency noise of transformer -class TransformerShelf -{ -public: - TransformerShelf() {} - - void reset (double sampleRate) - { - for (int n = 0; n < 3; ++n) - z[n] = 0.0f; - - fs = (float) sampleRate; - } - - void calcCoefs (float fc, float Q, float gain) - { - float A = sqrtf (gain); - float wc = MathConstants<float>::twoPi * fc / fs; - float wS = dsp::FastMathApproximations::sin (wc); - float wC = dsp::FastMathApproximations::cos (wc); - float beta = sqrtf (A) / Q; - - float a0 = ((A+1.0f) + ((A-1.0f) * wC) + (beta*wS)); - - b[0] = A*((A+1.0f) - ((A-1.0f)*wC) + (beta*wS)) / a0; - b[1] = 2.0f*A * ((A-1.0f) - ((A+1.0f)*wC)) / a0; - b[2] = A*((A+1.0f) - ((A-1.0f)*wC) - (beta*wS)) / a0; - - a[1] = -2.0f * ((A-1.0f) + ((A+1.0f)*wC)) / a0; - a[2] = ((A+1.0f) + ((A-1.0f)*wC)-(beta*wS)) / a0; - } - - inline float processSample (float x) - { - // direct form II transposed - float y = z[1] + x * b[0]; - - z[1] = z[2] + x*b[1] - y*a[1]; - z[2] = x*b[2] - y*a[2]; - - return y; - } - -private: - float a[3] = { 0.0f, 0.0f, 0.0f }; - float b[3] = { 1.0f, 0.0f, 0.0f }; - - float z[3] = { 0.0f, 0.0f, 0.0f }; - - float fs = 44100.0f; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TransformerShelf) -}; -*/ - -//============================================================= -/* Hysteresis Processor for transformer. */ +/* Hysteresis Processor for tape. */ class HysteresisProcessor { public: diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.cpp b/Plugin/Source/Processors/Timing_Effects/Flutter.cpp @@ -19,26 +19,28 @@ void Flutter::prepareToPlay (double sampleRate, int samplesPerBlock) { fs = (float) sampleRate; - delay[0].prepareToPlay (sampleRate, samplesPerBlock); - delay[1].prepareToPlay (sampleRate, samplesPerBlock); - - delay[0].setLengthMs (0.f); - delay[1].setLengthMs (0.f); + for (int ch = 0; ch < 2; ++ch) + { + delay[ch].prepareToPlay (sampleRate, samplesPerBlock); + delay[ch].setLengthMs (0.f); + depthSlew[ch].reset (10000); + depthSlew[ch].setCurrentAndTargetValue (0.001f); - depthSlew[0].reset (100); - depthSlew[1].reset (100); + phase1[ch] = 0.0f; + phase2[ch] = 0.0f; + phase3[ch] = 0.0f; - phase1[0] = 0.0f; - phase1[1] = 0.0f; - phase2[0] = 0.0f; - phase2[1] = 0.0f; - phase3[0] = 0.0f; - phase3[1] = 0.0f; + dcBlocker[ch].reset (sampleRate); + dcBlocker[ch].calcCoefs (10.0f, 0.707f); + } amp1 = -230.0f * 1000.0f / (float) sampleRate; amp2 = -80.0f * 1000.0f / (float) sampleRate; amp3 = -99.0f * 1000.0f / (float) sampleRate; dcOffset = 350.0f * 1000.0f / (float) sampleRate; + + isOff = depthSlew[0].getTargetValue() == 0.0f; + dryBuffer.setSize (2, samplesPerBlock); } void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessages*/) @@ -46,14 +48,51 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag ScopedNoDenormals noDenormals; auto curDepth = powf (*depth * 81.0f / 625.0f, 0.5f); - depthSlew[0].setTargetValue (jmax ((float) 1.0e-12, curDepth)); - depthSlew[1].setTargetValue (jmax ((float) 1.0e-12, curDepth)); + depthSlew[0].setTargetValue (jmax (0.001f, curDepth)); + depthSlew[1].setTargetValue (jmax (0.001f, curDepth)); auto freq = 0.1f * powf (1000.0f, *rate); - auto angleDelta1 = MathConstants<float>::twoPi * 1.0f * freq / fs; - auto angleDelta2 = MathConstants<float>::twoPi * 2.0f * freq / fs; - auto angleDelta3 = MathConstants<float>::twoPi * 3.0f * freq / fs; + angleDelta1 = MathConstants<float>::twoPi * 1.0f * freq / fs; + angleDelta2 = MathConstants<float>::twoPi * 2.0f * freq / fs; + angleDelta3 = MathConstants<float>::twoPi * 3.0f * freq / fs; + + bool shouldTurnOff = depthSlew[0].getTargetValue() == 0.001f; + if (! isOff && ! shouldTurnOff) // process normally + { + processWetBuffer (buffer); + } + else if (! isOff && shouldTurnOff) // turn off + { + dryBuffer.makeCopyOf (buffer, true); + processWetBuffer (buffer); + + buffer.applyGainRamp (0, buffer.getNumSamples(), 1.0f, 0.0f); + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + buffer.addFromWithRamp (ch, 0, dryBuffer.getWritePointer (ch), buffer.getNumSamples(), 0.0f, 1.0f); + } + else if (isOff && ! shouldTurnOff) // turn on + { + dryBuffer.makeCopyOf (buffer, true); + processWetBuffer (buffer); + buffer.applyGainRamp (0, buffer.getNumSamples(), 0.0f, 1.0f); + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + buffer.addFromWithRamp (ch, 0, dryBuffer.getWritePointer (ch), buffer.getNumSamples(), 1.0f, 0.0f); + } + else // off + { + processBypassed (buffer); + } + + isOff = shouldTurnOff; + + // dc block + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + dcBlocker[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); +} + +void Flutter::processWetBuffer (AudioBuffer<float>& buffer) +{ for (int ch = 0; ch < buffer.getNumChannels(); ++ch) { auto* x = buffer.getWritePointer (ch); @@ -62,11 +101,11 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag phase1[ch] += angleDelta1; phase2[ch] += angleDelta2; phase3[ch] += angleDelta3; - + auto lfo = amp1 * cosf (phase1[ch] + phaseOff1) - + amp2 * cosf (phase2[ch] + phaseOff2) - + amp3 * cosf (phase3[ch] + phaseOff3) - + dcOffset; + + amp2 * cosf (phase2[ch] + phaseOff2) + + amp3 * cosf (phase3[ch] + phaseOff3) + + dcOffset; delay[ch].setLengthMs (depthSlew[ch].getNextValue() * lfo); x[n] = delay[ch].delay (x[n]); @@ -80,3 +119,26 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag phase2[ch] -= MathConstants<float>::twoPi; } } + +void Flutter::processBypassed (AudioBuffer<float>& buffer) +{ + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + { + delay[ch].setLengthMs (0.0f); + for (int n = 0; n < buffer.getNumSamples(); ++n) + { + phase1[ch] += angleDelta1; + phase2[ch] += angleDelta2; + phase3[ch] += angleDelta3; + + delay[ch].delay (0.0f); + } + + while (phase1[ch] >= MathConstants<float>::twoPi) + phase1[ch] -= MathConstants<float>::twoPi; + while (phase2[ch] >= MathConstants<float>::twoPi) + phase2[ch] -= MathConstants<float>::twoPi; + while (phase2[ch] >= MathConstants<float>::twoPi) + phase2[ch] -= MathConstants<float>::twoPi; + } +} diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.h b/Plugin/Source/Processors/Timing_Effects/Flutter.h @@ -3,6 +3,7 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "DelayProcessor.h" +#include "../Hysteresis/DCFilters.h" class Flutter { @@ -14,9 +15,15 @@ public: void prepareToPlay (double sampleRate, int samplesPerBlock); void processBlock (AudioBuffer<float>&, MidiBuffer&); + void processWetBuffer (AudioBuffer<float>& buffer); + void processBypassed (AudioBuffer<float>& buffer); + private: std::atomic<float>* rate = nullptr; std::atomic<float>* depth = nullptr; + + bool isOff = false; + AudioBuffer<float> dryBuffer; float phase1[2] = { 0.0f, 0.0f }; float phase2[2] = { 0.0f, 0.0f }; @@ -32,9 +39,14 @@ private: const float phaseOff2 = 13.0f * MathConstants<float>::pi / 4.0f; const float phaseOff3 = -MathConstants<float>::pi / 10.0f; + float angleDelta1 = 0.0f; + float angleDelta2 = 0.0f; + float angleDelta3 = 0.0f; + SmoothedValue<float, ValueSmoothingTypes::Multiplicative> depthSlew[2]; DelayProcessor delay[2]; + TransformerHPF dcBlocker[2]; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Flutter)