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:
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)