feat(fx): add effect rack and effect units
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Small RBJ-cookbook biquad (direct form 1) used by EQ and the Hyper crossover.
|
||||||
|
// ===========================================================================
|
||||||
|
class Biquad
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void setSampleRate (double sr) noexcept { sampleRate = sr; }
|
||||||
|
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
x1 = x2 = y1 = y2 = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float process (float in) noexcept
|
||||||
|
{
|
||||||
|
const double out = b0 * in + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
||||||
|
x2 = x1; x1 = in;
|
||||||
|
y2 = y1; y1 = out;
|
||||||
|
return (float) out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLowpass (double freq, double q)
|
||||||
|
{
|
||||||
|
const double w0 = 2.0 * juce::MathConstants<double>::pi * freq / sampleRate;
|
||||||
|
const double cw = std::cos (w0);
|
||||||
|
const double sw = std::sin (w0);
|
||||||
|
const double alpha = sw / (2.0 * q);
|
||||||
|
const double a0 = 1.0 + alpha;
|
||||||
|
b0 = ((1.0 - cw) / 2.0) / a0;
|
||||||
|
b1 = (1.0 - cw) / a0;
|
||||||
|
b2 = b0;
|
||||||
|
a1 = (-2.0 * cw) / a0;
|
||||||
|
a2 = (1.0 - alpha) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHighpass (double freq, double q)
|
||||||
|
{
|
||||||
|
const double w0 = 2.0 * juce::MathConstants<double>::pi * freq / sampleRate;
|
||||||
|
const double cw = std::cos (w0);
|
||||||
|
const double sw = std::sin (w0);
|
||||||
|
const double alpha = sw / (2.0 * q);
|
||||||
|
const double a0 = 1.0 + alpha;
|
||||||
|
b0 = ((1.0 + cw) / 2.0) / a0;
|
||||||
|
b1 = -(1.0 + cw) / a0;
|
||||||
|
b2 = b0;
|
||||||
|
a1 = (-2.0 * cw) / a0;
|
||||||
|
a2 = (1.0 - alpha) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLowShelf (double freq, double gainDb)
|
||||||
|
{
|
||||||
|
const double a = std::pow (10.0, gainDb / 40.0);
|
||||||
|
const double w0 = 2.0 * juce::MathConstants<double>::pi * freq / sampleRate;
|
||||||
|
const double cw = std::cos (w0);
|
||||||
|
const double sw = std::sin (w0);
|
||||||
|
const double alpha = sw / 2.0 * std::sqrt (2.0);
|
||||||
|
const double a0 = (a + 1.0) + (a - 1.0) * cw + 2.0 * std::sqrt (a) * alpha;
|
||||||
|
b0 = (a * ((a + 1.0) - (a - 1.0) * cw + 2.0 * std::sqrt (a) * alpha)) / a0;
|
||||||
|
b1 = (2.0 * a * ((a - 1.0) - (a + 1.0) * cw)) / a0;
|
||||||
|
b2 = (a * ((a + 1.0) - (a - 1.0) * cw - 2.0 * std::sqrt (a) * alpha)) / a0;
|
||||||
|
a1 = (-2.0 * ((a - 1.0) + (a + 1.0) * cw)) / a0;
|
||||||
|
a2 = ((a + 1.0) + (a - 1.0) * cw - 2.0 * std::sqrt (a) * alpha) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHighShelf (double freq, double gainDb)
|
||||||
|
{
|
||||||
|
const double a = std::pow (10.0, gainDb / 40.0);
|
||||||
|
const double w0 = 2.0 * juce::MathConstants<double>::pi * freq / sampleRate;
|
||||||
|
const double cw = std::cos (w0);
|
||||||
|
const double sw = std::sin (w0);
|
||||||
|
const double alpha = sw / 2.0 * std::sqrt (2.0);
|
||||||
|
const double a0 = (a + 1.0) - (a - 1.0) * cw + 2.0 * std::sqrt (a) * alpha;
|
||||||
|
b0 = (a * ((a + 1.0) + (a - 1.0) * cw + 2.0 * std::sqrt (a) * alpha)) / a0;
|
||||||
|
b1 = (-2.0 * a * ((a - 1.0) + (a + 1.0) * cw)) / a0;
|
||||||
|
b2 = (a * ((a + 1.0) + (a - 1.0) * cw - 2.0 * std::sqrt (a) * alpha)) / a0;
|
||||||
|
a1 = (2.0 * ((a - 1.0) - (a + 1.0) * cw)) / a0;
|
||||||
|
a2 = ((a + 1.0) - (a - 1.0) * cw - 2.0 * std::sqrt (a) * alpha) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPeak (double freq, double gainDb, double q)
|
||||||
|
{
|
||||||
|
const double a = std::pow (10.0, gainDb / 40.0);
|
||||||
|
const double w0 = 2.0 * juce::MathConstants<double>::pi * freq / sampleRate;
|
||||||
|
const double cw = std::cos (w0);
|
||||||
|
const double sw = std::sin (w0);
|
||||||
|
const double alpha = sw / (2.0 * q);
|
||||||
|
const double a0 = 1.0 + alpha / a;
|
||||||
|
b0 = (1.0 + alpha * a) / a0;
|
||||||
|
b1 = (-2.0 * cw) / a0;
|
||||||
|
b2 = (1.0 - alpha * a) / a0;
|
||||||
|
a1 = (-2.0 * cw) / a0;
|
||||||
|
a2 = (1.0 - alpha / a) / a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sampleRate = 44100.0;
|
||||||
|
double b0 = 1.0, b1 = 0.0, b2 = 0.0, a1 = 0.0, a2 = 0.0;
|
||||||
|
double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include "Chorus.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void ChorusUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
const int maxDelay = (int) (sr * 0.05); // 50ms
|
||||||
|
delayL.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
delayR.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChorusUnit::reset()
|
||||||
|
{
|
||||||
|
std::fill (delayL.begin(), delayL.end(), 0.0f);
|
||||||
|
std::fill (delayR.begin(), delayR.end(), 0.0f);
|
||||||
|
writePos = 0;
|
||||||
|
lfoPhase = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChorusUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const int len = (int) delayL.size();
|
||||||
|
if (len < 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double rate = 0.1 + p[0] * 4.9; // 0.1..5 Hz
|
||||||
|
const double depth = (0.5 + p[1] * 5.5) * sr / 1000.0; // 0.5..6 ms
|
||||||
|
const double base = 8.0 * sr / 1000.0; // 8 ms centre
|
||||||
|
const float width = p[2];
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float inL = l[i], inR = r[i];
|
||||||
|
delayL[(size_t) writePos] = inL;
|
||||||
|
delayR[(size_t) writePos] = inR;
|
||||||
|
|
||||||
|
lfoPhase += 6.28318530717958647692 * rate / sr;
|
||||||
|
if (lfoPhase > 6.28318530717958647692) lfoPhase -= 6.28318530717958647692;
|
||||||
|
|
||||||
|
float sumL = 0.0f, sumR = 0.0f;
|
||||||
|
for (int v = 0; v < 2; ++v)
|
||||||
|
{
|
||||||
|
const double mod = std::sin (lfoPhase + v * 3.14159265358979323846) * depth;
|
||||||
|
const double d = base + mod + (v == 1 ? width * depth * 0.5 : 0.0);
|
||||||
|
double readPos = writePos - d;
|
||||||
|
if (readPos < 0.0) readPos += len;
|
||||||
|
const int i0 = (int) readPos;
|
||||||
|
const int i1 = (i0 + 1) % len;
|
||||||
|
const float frac = (float) (readPos - std::floor (readPos));
|
||||||
|
sumL += delayL[(size_t) i0] * (1.0f - frac) + delayL[(size_t) i1] * frac;
|
||||||
|
sumR += delayR[(size_t) i0] * (1.0f - frac) + delayR[(size_t) i1] * frac;
|
||||||
|
}
|
||||||
|
|
||||||
|
l[i] = sumL * 0.5f;
|
||||||
|
r[i] = sumR * 0.5f;
|
||||||
|
writePos = (writePos + 1) % len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Chorus — two LFO-modulated delay taps per channel. p[0]=rate, p[1]=depth,
|
||||||
|
// p[2]=width, p[3]=unused.
|
||||||
|
// ===========================================================================
|
||||||
|
class ChorusUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
std::vector<float> delayL, delayR;
|
||||||
|
int writePos = 0;
|
||||||
|
double lfoPhase = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#include "Compressor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void CompressorUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorUnit::reset()
|
||||||
|
{
|
||||||
|
envL = envR = 0.0f;
|
||||||
|
gainL = gainR = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressorUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const double thresholdDb = -40.0 + p[0] * 40.0; // -40..0 dB
|
||||||
|
const double ratio = 2.0 + p[1] * 18.0; // 2..20
|
||||||
|
const double attackMs = 2.0 + p[2] * 98.0;
|
||||||
|
const double releaseMs = 30.0 + p[3] * 470.0;
|
||||||
|
const double attackCoef = std::exp (-1.0 / (attackMs / 1000.0 * sr));
|
||||||
|
const double releaseCoef = std::exp (-1.0 / (releaseMs / 1000.0 * sr));
|
||||||
|
const double threshold = std::pow (10.0, thresholdDb / 20.0);
|
||||||
|
const double makeup = std::pow (10.0, -thresholdDb * 0.5 / 20.0);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float aL = std::abs (l[i]);
|
||||||
|
const float aR = std::abs (r[i]);
|
||||||
|
|
||||||
|
envL = aL > envL ? (float) (attackCoef * envL + (1.0 - attackCoef) * aL)
|
||||||
|
: (float) (releaseCoef * envL + (1.0 - releaseCoef) * aL);
|
||||||
|
envR = aR > envR ? (float) (attackCoef * envR + (1.0 - attackCoef) * aR)
|
||||||
|
: (float) (releaseCoef * envR + (1.0 - releaseCoef) * aR);
|
||||||
|
|
||||||
|
const double targetL = (envL > threshold) ? threshold * std::pow (envL / threshold, 1.0 / ratio) : envL;
|
||||||
|
const double targetR = (envR > threshold) ? threshold * std::pow (envR / threshold, 1.0 / ratio) : envR;
|
||||||
|
const float desiredL = (float) juce::jlimit (0.05, 1.0, (envL > 1e-6) ? targetL / envL : 1.0);
|
||||||
|
const float desiredR = (float) juce::jlimit (0.05, 1.0, (envR > 1e-6) ? targetR / envR : 1.0);
|
||||||
|
|
||||||
|
gainL += 0.01f * (desiredL - gainL);
|
||||||
|
gainR += 0.01f * (desiredR - gainR);
|
||||||
|
|
||||||
|
l[i] = (float) (l[i] * gainL * makeup);
|
||||||
|
r[i] = (float) (r[i] * gainR * makeup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Compressor — soft-knee RMS compressor with makeup gain. p[0]=threshold,
|
||||||
|
// p[1]=ratio, p[2]=attack, p[3]=release.
|
||||||
|
// ===========================================================================
|
||||||
|
class CompressorUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
float envL = 0.0f, envR = 0.0f;
|
||||||
|
float gainL = 1.0f, gainR = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include "Delay.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void DelayUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
const int maxDelay = (int) (sr * 2.1); // 2.1 seconds
|
||||||
|
delayL.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
delayR.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DelayUnit::reset()
|
||||||
|
{
|
||||||
|
std::fill (delayL.begin(), delayL.end(), 0.0f);
|
||||||
|
std::fill (delayR.begin(), delayR.end(), 0.0f);
|
||||||
|
writePos = 0;
|
||||||
|
dampL = dampR = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DelayUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const int len = (int) delayL.size();
|
||||||
|
if (len < 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double delaySamples = juce::jlimit (2.0, (double) len - 2.0, maps::delayToMs (p[0]) / 1000.0 * sr);
|
||||||
|
const float feedback = p[1] * 0.92f;
|
||||||
|
const float dampCoef = 1.0f - p[2] * 0.95f; // 1 = no damping
|
||||||
|
const float pingpong = p[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
double readPos = writePos - delaySamples;
|
||||||
|
if (readPos < 0.0) readPos += len;
|
||||||
|
const int i0 = (int) readPos;
|
||||||
|
const int i1 = (i0 + 1) % len;
|
||||||
|
const float frac = (float) (readPos - std::floor (readPos));
|
||||||
|
|
||||||
|
float dl = delayL[(size_t) i0] * (1.0f - frac) + delayL[(size_t) i1] * frac;
|
||||||
|
float dr = delayR[(size_t) i0] * (1.0f - frac) + delayR[(size_t) i1] * frac;
|
||||||
|
|
||||||
|
// Damping lowpass in the feedback path.
|
||||||
|
dampL = dl * (1.0f - dampCoef) + dampL * dampCoef;
|
||||||
|
dampR = dr * (1.0f - dampCoef) + dampR * dampCoef;
|
||||||
|
|
||||||
|
// Ping-pong: cross-feed between channels.
|
||||||
|
const float feedL = dampR * pingpong + dampL * (1.0f - pingpong);
|
||||||
|
const float feedR = dampL * pingpong + dampR * (1.0f - pingpong);
|
||||||
|
|
||||||
|
delayL[(size_t) writePos] = l[i] + feedL * feedback;
|
||||||
|
delayR[(size_t) writePos] = r[i] + feedR * feedback;
|
||||||
|
|
||||||
|
l[i] = dl;
|
||||||
|
r[i] = dr;
|
||||||
|
writePos = (writePos + 1) % len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Delay — stereo delay with feedback, damping and ping-pong. p[0]=time,
|
||||||
|
// p[1]=feedback, p[2]=damping, p[3]=ping-pong/width.
|
||||||
|
// ===========================================================================
|
||||||
|
class DelayUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
std::vector<float> delayL, delayR;
|
||||||
|
int writePos = 0;
|
||||||
|
float dampL = 0.0f, dampR = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include "Distortion.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void DistortionUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
lpL.setSampleRate (sr); lpR.setSampleRate (sr);
|
||||||
|
lpL.setLowpass (8000.0, 0.7071); lpR.setLowpass (8000.0, 0.7071);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DistortionUnit::reset()
|
||||||
|
{
|
||||||
|
lpL.clear(); lpR.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
float DistortionUnit::shape (float x, float shapeParam) noexcept
|
||||||
|
{
|
||||||
|
if (shapeParam < 0.33f) return std::tanh (x);
|
||||||
|
if (shapeParam < 0.66f) return juce::jlimit (-1.0f, 1.0f, x);
|
||||||
|
return std::sin (x * 1.5707963267948966f); // sine fold
|
||||||
|
}
|
||||||
|
|
||||||
|
void DistortionUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const float drive = 1.0f + p[0] * 24.0f;
|
||||||
|
const float shapeP = p[1];
|
||||||
|
const float output = 0.4f + p[3] * 1.2f;
|
||||||
|
|
||||||
|
// Tone: re-tune the lowpass from 800..16000 Hz.
|
||||||
|
const double toneHz = 800.0 + p[2] * 15200.0;
|
||||||
|
lpL.setLowpass (toneHz, 0.7071);
|
||||||
|
lpR.setLowpass (toneHz, 0.7071);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
l[i] = lpL.process (shape (l[i] * drive, shapeP) * output);
|
||||||
|
r[i] = lpR.process (shape (r[i] * drive, shapeP) * output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
#include "Biquad.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Distortion — waveshaper with drive and a post lowpass tone. p[0]=drive,
|
||||||
|
// p[1]=shape (soft/hard/fold), p[2]=tone, p[3]=output.
|
||||||
|
// ===========================================================================
|
||||||
|
class DistortionUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
Biquad lpL, lpR;
|
||||||
|
|
||||||
|
static float shape (float x, float shapeParam) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "EQ.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void EQUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
lowL.setSampleRate (sr); lowR.setSampleRate (sr);
|
||||||
|
midL.setSampleRate (sr); midR.setSampleRate (sr);
|
||||||
|
highL.setSampleRate (sr); highR.setSampleRate (sr);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EQUnit::reset()
|
||||||
|
{
|
||||||
|
lowL.clear(); lowR.clear(); midL.clear(); midR.clear(); highL.clear(); highR.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EQUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const double lowGain = (p[0] - 0.5f) * 2.0f * 15.0;
|
||||||
|
const double midGain = (p[1] - 0.5f) * 2.0f * 12.0;
|
||||||
|
const double highGain = (p[2] - 0.5f) * 2.0f * 15.0;
|
||||||
|
const double midFreq = 200.0 + p[3] * 4800.0;
|
||||||
|
|
||||||
|
lowL.setLowShelf (200.0, lowGain); lowR.setLowShelf (200.0, lowGain);
|
||||||
|
midL.setPeak (midFreq, midGain, 0.8); midR.setPeak (midFreq, midGain, 0.8);
|
||||||
|
highL.setHighShelf (4000.0, highGain); highR.setHighShelf (4000.0, highGain);
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float xL = l[i], xR = r[i];
|
||||||
|
l[i] = highL.process (midL.process (lowL.process (xL)));
|
||||||
|
r[i] = highR.process (midR.process (lowR.process (xR)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
#include "Biquad.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// EQ — three-band (low shelf / peak / high shelf). p[0]=low gain, p[1]=mid
|
||||||
|
// gain, p[2]=high gain, p[3]=mid frequency.
|
||||||
|
// ===========================================================================
|
||||||
|
class EQUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
Biquad lowL, lowR, midL, midR, highL, highR;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include "Flanger.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void FlangerUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
const int maxDelay = (int) (sr * 0.03); // 30ms
|
||||||
|
delayL.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
delayR.assign ((size_t) maxDelay, 0.0f);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlangerUnit::reset()
|
||||||
|
{
|
||||||
|
std::fill (delayL.begin(), delayL.end(), 0.0f);
|
||||||
|
std::fill (delayR.begin(), delayR.end(), 0.0f);
|
||||||
|
writePos = 0;
|
||||||
|
lfoPhase = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlangerUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const int len = (int) delayL.size();
|
||||||
|
if (len < 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double rate = 0.05 + p[0] * 2.0;
|
||||||
|
const double depth = (0.3 + p[1] * 4.7) * sr / 1000.0; // 0.3..5 ms
|
||||||
|
const double base = 1.5 * sr / 1000.0;
|
||||||
|
const float feedback = p[2] * 0.9f;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float inL = l[i], inR = r[i];
|
||||||
|
|
||||||
|
lfoPhase += 6.28318530717958647692 * rate / sr;
|
||||||
|
if (lfoPhase > 6.28318530717958647692) lfoPhase -= 6.28318530717958647692;
|
||||||
|
const double mod = std::sin (lfoPhase) * depth;
|
||||||
|
|
||||||
|
double readPos = writePos - (base + mod);
|
||||||
|
if (readPos < 0.0) readPos += len;
|
||||||
|
const int i0 = (int) readPos;
|
||||||
|
const int i1 = (i0 + 1) % len;
|
||||||
|
const float frac = (float) (readPos - std::floor (readPos));
|
||||||
|
const float outL = delayL[(size_t) i0] * (1.0f - frac) + delayL[(size_t) i1] * frac;
|
||||||
|
const float outR = delayR[(size_t) i0] * (1.0f - frac) + delayR[(size_t) i1] * frac;
|
||||||
|
|
||||||
|
delayL[(size_t) writePos] = inL + outL * feedback;
|
||||||
|
delayR[(size_t) writePos] = inR + outR * feedback;
|
||||||
|
|
||||||
|
l[i] = outL;
|
||||||
|
r[i] = outR;
|
||||||
|
writePos = (writePos + 1) % len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Flanger — LFO-modulated delay with feedback. p[0]=rate, p[1]=depth,
|
||||||
|
// p[2]=feedback, p[3]=unused.
|
||||||
|
// ===========================================================================
|
||||||
|
class FlangerUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
std::vector<float> delayL, delayR;
|
||||||
|
int writePos = 0;
|
||||||
|
double lfoPhase = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include "Hyper.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void HyperUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
lpL.setSampleRate (sr); lpR.setSampleRate (sr);
|
||||||
|
hpL.setSampleRate (sr); hpR.setSampleRate (sr);
|
||||||
|
lpL.setLowpass (150.0, 0.7071); lpR.setLowpass (150.0, 0.7071);
|
||||||
|
hpL.setHighpass (2500.0, 0.7071); hpR.setHighpass (2500.0, 0.7071);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyperUnit::reset()
|
||||||
|
{
|
||||||
|
lpL.clear(); lpR.clear(); hpL.clear(); hpR.clear();
|
||||||
|
for (auto& b : bL) { b.env = 0.0f; b.gain = 1.0f; }
|
||||||
|
for (auto& b : bR) { b.env = 0.0f; b.gain = 1.0f; }
|
||||||
|
}
|
||||||
|
|
||||||
|
float HyperUnit::computeGain (Band& band, float absIn, float intensity) noexcept
|
||||||
|
{
|
||||||
|
// Peak envelope follower with fast attack, slower release.
|
||||||
|
const float attack = (absIn > band.env) ? 0.3f : 0.003f;
|
||||||
|
band.env += attack * (absIn - band.env);
|
||||||
|
|
||||||
|
const float env = band.env + 1e-6f;
|
||||||
|
const float ratio = 4.0f + 8.0f * intensity;
|
||||||
|
const float upAmount = intensity * 0.7f;
|
||||||
|
|
||||||
|
// Downward compression above the reference level, upward expansion below.
|
||||||
|
float target;
|
||||||
|
if (env > 1.0f)
|
||||||
|
target = 1.0f + (env - 1.0f) / ratio;
|
||||||
|
else
|
||||||
|
target = 1.0f - (1.0f - env) * (1.0f - upAmount);
|
||||||
|
|
||||||
|
float desired = target / env;
|
||||||
|
desired = juce::jlimit (0.2f, 4.0f, desired);
|
||||||
|
band.gain += 0.01f * (desired - band.gain);
|
||||||
|
return band.gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyperUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const float intensity = p[0];
|
||||||
|
const float lowAmt = 0.4f + 0.6f * p[1];
|
||||||
|
const float highAmt = 0.4f + 0.6f * p[2];
|
||||||
|
const float output = 0.5f + p[3] * 1.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
const float inL = l[i], inR = r[i];
|
||||||
|
|
||||||
|
const float loL = lpL.process (inL);
|
||||||
|
const float restL = inL - loL;
|
||||||
|
const float hiL = hpL.process (restL);
|
||||||
|
const float midL = restL - hiL;
|
||||||
|
|
||||||
|
const float loR = lpR.process (inR);
|
||||||
|
const float restR = inR - loR;
|
||||||
|
const float hiR = hpR.process (restR);
|
||||||
|
const float midR = restR - hiR;
|
||||||
|
|
||||||
|
const float gLL = computeGain (bL[0], std::abs (loL), intensity);
|
||||||
|
const float gML = computeGain (bL[1], std::abs (midL), intensity);
|
||||||
|
const float gHL = computeGain (bL[2], std::abs (hiL), intensity);
|
||||||
|
|
||||||
|
const float gLR = computeGain (bR[0], std::abs (loR), intensity);
|
||||||
|
const float gMR = computeGain (bR[1], std::abs (midR), intensity);
|
||||||
|
const float gHR = computeGain (bR[2], std::abs (hiR), intensity);
|
||||||
|
|
||||||
|
l[i] = (loL * gLL * lowAmt + midL * gML + hiL * gHL * highAmt) * output;
|
||||||
|
r[i] = (loR * gLR * lowAmt + midR * gMR + hiR * gHR * highAmt) * output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
#include "Biquad.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Hyper — an OTT-style three-band upward/downward compressor. p[0]=intensity,
|
||||||
|
// p[1]=low amount, p[2]=high amount, p[3]=output.
|
||||||
|
// ===========================================================================
|
||||||
|
class HyperUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Band
|
||||||
|
{
|
||||||
|
float env = 0.0f;
|
||||||
|
float gain = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
double sr = 44100.0;
|
||||||
|
Biquad lpL, lpR, hpL, hpR;
|
||||||
|
Band bL[3], bR[3];
|
||||||
|
|
||||||
|
float computeGain (Band& band, float absIn, float intensity) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "Phaser.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
void PhaserUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaserUnit::reset()
|
||||||
|
{
|
||||||
|
lfoPhase = 0.0;
|
||||||
|
for (int i = 0; i < 6; ++i)
|
||||||
|
{
|
||||||
|
xL[i] = xR[i] = yL[i] = yR[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaserUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const double rate = 0.05 + p[0] * 2.0;
|
||||||
|
const float depth = 0.3f + p[1] * 0.6f;
|
||||||
|
const float feedback = p[2] * 0.7f;
|
||||||
|
const int stages = juce::jlimit (2, 6, 2 + (int) (p[3] * 4.0f + 0.5f));
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
lfoPhase += 6.28318530717958647692 * rate / sr;
|
||||||
|
if (lfoPhase > 6.28318530717958647692) lfoPhase -= 6.28318530717958647692;
|
||||||
|
|
||||||
|
const float sweep = (float) (0.5 + 0.5 * std::sin (lfoPhase));
|
||||||
|
const float a = 0.3f + depth * sweep; // allpass coefficient
|
||||||
|
|
||||||
|
float outL = l[i] + yL[5] * feedback;
|
||||||
|
float outR = r[i] + yR[5] * feedback;
|
||||||
|
|
||||||
|
for (int s = 0; s < stages; ++s)
|
||||||
|
{
|
||||||
|
// y = a*x + x_prev - a*y_prev
|
||||||
|
const float inL = outL, inR = outR;
|
||||||
|
outL = a * inL + xL[s] - a * yL[s];
|
||||||
|
outR = a * inR + xR[s] - a * yR[s];
|
||||||
|
xL[s] = inL; xR[s] = inR;
|
||||||
|
yL[s] = outL; yR[s] = outR;
|
||||||
|
}
|
||||||
|
|
||||||
|
l[i] = outL;
|
||||||
|
r[i] = outR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Phaser — cascade of four first-order allpass stages with an LFO-modulated
|
||||||
|
// coefficient and feedback. p[0]=rate, p[1]=depth, p[2]=feedback, p[3]=stages.
|
||||||
|
// ===========================================================================
|
||||||
|
class PhaserUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
double lfoPhase = 0.0;
|
||||||
|
float xL[6] {}, xR[6] {}, yL[6] {}, yR[6] {};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#include "Reverb.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr int combTuning[8] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 };
|
||||||
|
constexpr int allpassTuning[4] = { 556, 441, 341, 225 };
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbUnit::prepare (double sampleRate, int)
|
||||||
|
{
|
||||||
|
sr = sampleRate;
|
||||||
|
const double scale = sr / 44100.0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
const int len = (int) (combTuning[i] * scale) + 1;
|
||||||
|
combL[(size_t) i].assign ((size_t) len, 0.0f);
|
||||||
|
combR[(size_t) i].assign ((size_t) len, 0.0f);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const int len = (int) (allpassTuning[i] * scale) + 1;
|
||||||
|
apL[(size_t) i].assign ((size_t) len, 0.0f);
|
||||||
|
apR[(size_t) i].assign ((size_t) len, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int preLen = (int) (sr * 0.2);
|
||||||
|
preL.assign ((size_t) preLen, 0.0f);
|
||||||
|
preR.assign ((size_t) preLen, 0.0f);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbUnit::reset()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
std::fill (combL[(size_t) i].begin(), combL[(size_t) i].end(), 0.0f);
|
||||||
|
std::fill (combR[(size_t) i].begin(), combR[(size_t) i].end(), 0.0f);
|
||||||
|
combPosL[(size_t) i] = combPosR[(size_t) i] = 0;
|
||||||
|
combFiltL[(size_t) i] = combFiltR[(size_t) i] = 0.0f;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
std::fill (apL[(size_t) i].begin(), apL[(size_t) i].end(), 0.0f);
|
||||||
|
std::fill (apR[(size_t) i].begin(), apR[(size_t) i].end(), 0.0f);
|
||||||
|
apPosL[(size_t) i] = apPosR[(size_t) i] = 0;
|
||||||
|
}
|
||||||
|
std::fill (preL.begin(), preL.end(), 0.0f);
|
||||||
|
std::fill (preR.begin(), preR.end(), 0.0f);
|
||||||
|
prePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReverbUnit::process (float* l, float* r, int numSamples, const float p[4])
|
||||||
|
{
|
||||||
|
const float feedback = maps::sizeToValue (p[0]);
|
||||||
|
const float damping = p[1];
|
||||||
|
const float width = p[2];
|
||||||
|
const int preLen = (int) preL.size();
|
||||||
|
const float preDelay = (preLen > 0) ? p[3] * 0.15f * sr : 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; ++i)
|
||||||
|
{
|
||||||
|
// Predelay.
|
||||||
|
float inL = l[i], inR = r[i];
|
||||||
|
if (preLen > 0)
|
||||||
|
{
|
||||||
|
const int read = (prePos - (int) preDelay + preLen) % preLen;
|
||||||
|
preL[(size_t) prePos] = inL;
|
||||||
|
preR[(size_t) prePos] = inR;
|
||||||
|
inL = preL[(size_t) read];
|
||||||
|
inR = preR[(size_t) read];
|
||||||
|
prePos = (prePos + 1) % preLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combs.
|
||||||
|
float sumL = 0.0f, sumR = 0.0f;
|
||||||
|
for (int c = 0; c < 8; ++c)
|
||||||
|
{
|
||||||
|
auto& buf = combL[(size_t) c];
|
||||||
|
const int pos = combPosL[(size_t) c];
|
||||||
|
float out = buf[(size_t) pos];
|
||||||
|
combFiltL[(size_t) c] = out * (1.0f - damping) + combFiltL[(size_t) c] * damping;
|
||||||
|
buf[(size_t) pos] = inL + combFiltL[(size_t) c] * feedback;
|
||||||
|
combPosL[(size_t) c] = (pos + 1) % (int) buf.size();
|
||||||
|
sumL += out;
|
||||||
|
}
|
||||||
|
for (int c = 0; c < 8; ++c)
|
||||||
|
{
|
||||||
|
auto& buf = combR[(size_t) c];
|
||||||
|
const int pos = combPosR[(size_t) c];
|
||||||
|
float out = buf[(size_t) pos];
|
||||||
|
combFiltR[(size_t) c] = out * (1.0f - damping) + combFiltR[(size_t) c] * damping;
|
||||||
|
buf[(size_t) pos] = inR + combFiltR[(size_t) c] * feedback;
|
||||||
|
combPosR[(size_t) c] = (pos + 1) % (int) buf.size();
|
||||||
|
sumR += out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allpasses.
|
||||||
|
float aL = sumL, aR = sumR;
|
||||||
|
for (int a = 0; a < 4; ++a)
|
||||||
|
{
|
||||||
|
auto& buf = apL[(size_t) a];
|
||||||
|
const int pos = apPosL[(size_t) a];
|
||||||
|
const float stored = buf[(size_t) pos];
|
||||||
|
buf[(size_t) pos] = aL + stored * 0.5f;
|
||||||
|
aL = stored - aL;
|
||||||
|
apPosL[(size_t) a] = (pos + 1) % (int) buf.size();
|
||||||
|
}
|
||||||
|
for (int a = 0; a < 4; ++a)
|
||||||
|
{
|
||||||
|
auto& buf = apR[(size_t) a];
|
||||||
|
const int pos = apPosR[(size_t) a];
|
||||||
|
const float stored = buf[(size_t) pos];
|
||||||
|
buf[(size_t) pos] = aR + stored * 0.5f;
|
||||||
|
aR = stored - aR;
|
||||||
|
apPosR[(size_t) a] = (pos + 1) % (int) buf.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width (stereo cross-mix).
|
||||||
|
const float w = 1.0f - width * 0.5f;
|
||||||
|
const float outL = aL * w + aR * (1.0f - w);
|
||||||
|
const float outR = aR * w + aL * (1.0f - w);
|
||||||
|
|
||||||
|
l[i] = outL;
|
||||||
|
r[i] = outR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../FXProcessor.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Reverb — Freeverb-style Schroeder reverb (8 combs + 4 allpasses per channel)
|
||||||
|
// with predelay. p[0]=size, p[1]=damping, p[2]=width, p[3]=predelay.
|
||||||
|
// ===========================================================================
|
||||||
|
class ReverbUnit : public FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void prepare (double sampleRate, int maxBlockSize) override;
|
||||||
|
void reset() override;
|
||||||
|
void process (float* l, float* r, int numSamples, const float p[4]) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
double sr = 44100.0;
|
||||||
|
|
||||||
|
std::array<std::vector<float>, 8> combL, combR;
|
||||||
|
std::array<int, 8> combPosL {}, combPosR {};
|
||||||
|
std::array<float, 8> combFiltL {}, combFiltR {};
|
||||||
|
|
||||||
|
std::array<std::vector<float>, 4> apL, apR;
|
||||||
|
std::array<int, 4> apPosL {}, apPosR {};
|
||||||
|
|
||||||
|
std::vector<float> preL, preR;
|
||||||
|
int prePos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#include "FXProcessor.h"
|
||||||
|
#include "EffectUnits/Hyper.h"
|
||||||
|
#include "EffectUnits/Chorus.h"
|
||||||
|
#include "EffectUnits/Flanger.h"
|
||||||
|
#include "EffectUnits/Phaser.h"
|
||||||
|
#include "EffectUnits/Distortion.h"
|
||||||
|
#include "EffectUnits/EQ.h"
|
||||||
|
#include "EffectUnits/Compressor.h"
|
||||||
|
#include "EffectUnits/Delay.h"
|
||||||
|
#include "EffectUnits/Reverb.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
FXProcessor::FXProcessor()
|
||||||
|
{
|
||||||
|
units[0] = std::make_unique<HyperUnit>();
|
||||||
|
units[1] = std::make_unique<ChorusUnit>();
|
||||||
|
units[2] = std::make_unique<FlangerUnit>();
|
||||||
|
units[3] = std::make_unique<PhaserUnit>();
|
||||||
|
units[4] = std::make_unique<DistortionUnit>();
|
||||||
|
units[5] = std::make_unique<EQUnit>();
|
||||||
|
units[6] = std::make_unique<CompressorUnit>();
|
||||||
|
units[7] = std::make_unique<DelayUnit>();
|
||||||
|
units[8] = std::make_unique<ReverbUnit>();
|
||||||
|
}
|
||||||
|
|
||||||
|
FXProcessor::~FXProcessor() = default;
|
||||||
|
|
||||||
|
void FXProcessor::prepare (double sampleRate, int maxBlockSize)
|
||||||
|
{
|
||||||
|
for (auto& u : units)
|
||||||
|
u->prepare (sampleRate, maxBlockSize);
|
||||||
|
dry.setSize (2, maxBlockSize, false, false, true);
|
||||||
|
wet.setSize (2, maxBlockSize, false, false, true);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FXProcessor::reset()
|
||||||
|
{
|
||||||
|
for (auto& u : units)
|
||||||
|
u->reset();
|
||||||
|
dry.clear();
|
||||||
|
wet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::String FXProcessor::fxTypeName (int type)
|
||||||
|
{
|
||||||
|
switch ((FxType) type)
|
||||||
|
{
|
||||||
|
case FxType::Off: return "Off";
|
||||||
|
case FxType::Hyper: return "Hyper";
|
||||||
|
case FxType::Chorus: return "Chorus";
|
||||||
|
case FxType::Flanger: return "Flanger";
|
||||||
|
case FxType::Phaser: return "Phaser";
|
||||||
|
case FxType::Distortion: return "Distortion";
|
||||||
|
case FxType::EQ: return "EQ";
|
||||||
|
case FxType::Compressor: return "Compressor";
|
||||||
|
case FxType::Delay: return "Delay";
|
||||||
|
case FxType::Reverb: return "Reverb";
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FXProcessor::process (juce::AudioBuffer<float>& buffer, const FxSlotParams* slots, int numSlots)
|
||||||
|
{
|
||||||
|
const int n = buffer.getNumSamples();
|
||||||
|
if (n <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int s = 0; s < numSlots; ++s)
|
||||||
|
{
|
||||||
|
const FxSlotParams& slot = slots[s];
|
||||||
|
if (slot.type == (int) FxType::Off)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const int unitIdx = slot.type - 1;
|
||||||
|
if (unitIdx < 0 || unitIdx >= (int) units.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
dry.copyFrom (0, 0, buffer, 0, 0, n);
|
||||||
|
dry.copyFrom (1, 1, buffer, 1, 0, n);
|
||||||
|
wet.copyFrom (0, 0, buffer, 0, 0, n);
|
||||||
|
wet.copyFrom (1, 1, buffer, 1, 0, n);
|
||||||
|
|
||||||
|
units[(size_t) unitIdx]->process (wet.getWritePointer (0), wet.getWritePointer (1), n, slot.p);
|
||||||
|
|
||||||
|
const float mix = juce::jlimit (0.0f, 1.0f, slot.mix);
|
||||||
|
if (mix >= 1.0f)
|
||||||
|
{
|
||||||
|
buffer.copyFrom (0, 0, wet, 0, 0, n);
|
||||||
|
buffer.copyFrom (1, 0, wet, 1, 0, n);
|
||||||
|
}
|
||||||
|
else if (mix > 0.0f)
|
||||||
|
{
|
||||||
|
const float* dL = dry.getReadPointer (0);
|
||||||
|
const float* dR = dry.getReadPointer (1);
|
||||||
|
const float* wL = wet.getReadPointer (0);
|
||||||
|
const float* wR = wet.getReadPointer (1);
|
||||||
|
float* oL = buffer.getWritePointer (0);
|
||||||
|
float* oR = buffer.getWritePointer (1);
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
oL[i] = dL[i] * (1.0f - mix) + wL[i] * mix;
|
||||||
|
oR[i] = dR[i] * (1.0f - mix) + wR[i] * mix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
#include "Params.h"
|
||||||
|
|
||||||
|
namespace serum
|
||||||
|
{
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Base class for a stereo effect unit. Each unit processes a full-wet buffer.
|
||||||
|
// ===========================================================================
|
||||||
|
class FXUnit
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FXUnit() = default;
|
||||||
|
virtual void prepare (double sampleRate, int maxBlockSize) = 0;
|
||||||
|
virtual void reset() = 0;
|
||||||
|
virtual void process (float* l, float* r, int numSamples, const float p[4]) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Snapshot of one FX slot.
|
||||||
|
// ===========================================================================
|
||||||
|
struct FxSlotParams
|
||||||
|
{
|
||||||
|
int type = (int) FxType::Off;
|
||||||
|
float mix = 0.0f;
|
||||||
|
float p[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Reorderable effects rack. Slots are processed in list order; reordering is
|
||||||
|
// simply swapping FxSlotParams entries. Nine effect unit implementations are
|
||||||
|
// owned here and shared across slots.
|
||||||
|
// ===========================================================================
|
||||||
|
class FXProcessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FXProcessor();
|
||||||
|
~FXProcessor();
|
||||||
|
|
||||||
|
void prepare (double sampleRate, int maxBlockSize);
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void process (juce::AudioBuffer<float>& buffer, const FxSlotParams* slots, int numSlots);
|
||||||
|
|
||||||
|
static juce::String fxTypeName (int type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<std::unique_ptr<FXUnit>, 9> units;
|
||||||
|
juce::AudioBuffer<float> dry, wet;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serum
|
||||||
Reference in New Issue
Block a user