mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
187 lines
5.5 KiB
C
187 lines
5.5 KiB
C
|
|
/*
|
||
|
|
==============================================================================
|
||
|
|
|
||
|
|
Phaser.h
|
||
|
|
Created: 24 Jul 2026
|
||
|
|
Author: Armin
|
||
|
|
|
||
|
|
==============================================================================
|
||
|
|
*/
|
||
|
|
|
||
|
|
#pragma once
|
||
|
|
#include <JuceHeader.h>
|
||
|
|
|
||
|
|
#include "Effect.h"
|
||
|
|
|
||
|
|
/** A phaser effect with configurable stages (pairs of all-pass filters) and LFO modulation. */
|
||
|
|
class PhaserEffect final : public Effect
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
void initialize(int numChannels, int fxSampleRate) override
|
||
|
|
{
|
||
|
|
sampleRate = fxSampleRate;
|
||
|
|
numChannelsUsed = numChannels;
|
||
|
|
|
||
|
|
for (auto& stage : stages)
|
||
|
|
{
|
||
|
|
stage[0].reset();
|
||
|
|
stage[1].reset();
|
||
|
|
}
|
||
|
|
feedbackSampleL = 0.f;
|
||
|
|
feedbackSampleR = 0.f;
|
||
|
|
|
||
|
|
currentCenterFreq = -1.f;
|
||
|
|
currentStages = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
void setCenterFrequency(float freq)
|
||
|
|
{
|
||
|
|
freq = juce::jlimit(50.f, float(sampleRate) / 4.f, freq);
|
||
|
|
if (std::abs(freq - currentCenterFreq) > 0.5f)
|
||
|
|
{
|
||
|
|
currentCenterFreq = freq;
|
||
|
|
updateAllPassCoefficients();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void setNumStages(int numStages)
|
||
|
|
{
|
||
|
|
numStages = juce::jlimit(1, 6, numStages);
|
||
|
|
if (numStages != currentStages)
|
||
|
|
{
|
||
|
|
currentStages = numStages;
|
||
|
|
updateAllPassCoefficients();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void setFeedback(float fb)
|
||
|
|
{
|
||
|
|
feedback = juce::jlimit(-0.95f, 0.95f, fb);
|
||
|
|
}
|
||
|
|
|
||
|
|
void setDepth(float d)
|
||
|
|
{
|
||
|
|
depth = juce::jlimit(0.f, 1.f, d);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Process the buffer with a given LFO modulation value [-1, 1].
|
||
|
|
This is called per block; the center frequency is modulated by the LFO depth.
|
||
|
|
*/
|
||
|
|
void processWithLFO(juce::AudioBuffer<float>& buffer, int numSamples, float lfoValue)
|
||
|
|
{
|
||
|
|
float modulatedFreq = currentCenterFreq * std::pow(2.f, lfoValue * depth * 3.f);
|
||
|
|
setCenterFrequency(modulatedFreq);
|
||
|
|
|
||
|
|
for (int ch = 0; ch < buffer.getNumChannels(); ch++)
|
||
|
|
{
|
||
|
|
auto* data = buffer.getWritePointer(ch);
|
||
|
|
float& fbSample = (ch == 0) ? feedbackSampleL : feedbackSampleR;
|
||
|
|
|
||
|
|
for (int i = 0; i < numSamples; i++)
|
||
|
|
{
|
||
|
|
float input = data[i] + fbSample * feedback;
|
||
|
|
|
||
|
|
float apOutput = input;
|
||
|
|
for (int s = 0; s < currentStages; s++)
|
||
|
|
{
|
||
|
|
auto& stage = stages[static_cast<size_t>(s)];
|
||
|
|
auto& filter = (ch == 0) ? stage[0] : stage[1];
|
||
|
|
apOutput = filter.process(apOutput);
|
||
|
|
}
|
||
|
|
|
||
|
|
fbSample = apOutput;
|
||
|
|
data[i] = input + apOutput * (-depth);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void updateParams(const SamplerParameters& samplerSound, bool /*modulating*/) override
|
||
|
|
{
|
||
|
|
setFeedback(samplerSound.modPhaserFeedback->get());
|
||
|
|
setNumStages(samplerSound.modPhaserStages->get());
|
||
|
|
setDepth(samplerSound.modPhaserDepth->get());
|
||
|
|
}
|
||
|
|
|
||
|
|
void process(juce::AudioBuffer<float>& buffer, int numSamples, int startSample = 0) override
|
||
|
|
{
|
||
|
|
// When called without LFO, process with no modulation (center freq stays)
|
||
|
|
for (int ch = 0; ch < buffer.getNumChannels(); ch++)
|
||
|
|
{
|
||
|
|
auto* data = buffer.getWritePointer(ch, startSample);
|
||
|
|
float& fbSample = (ch == 0) ? feedbackSampleL : feedbackSampleR;
|
||
|
|
|
||
|
|
for (int i = 0; i < numSamples; i++)
|
||
|
|
{
|
||
|
|
float input = data[i] + fbSample * feedback;
|
||
|
|
|
||
|
|
float apOutput = input;
|
||
|
|
for (int s = 0; s < currentStages; s++)
|
||
|
|
{
|
||
|
|
auto& stage = stages[static_cast<size_t>(s)];
|
||
|
|
auto& filter = (ch == 0) ? stage[0] : stage[1];
|
||
|
|
apOutput = filter.process(apOutput);
|
||
|
|
}
|
||
|
|
|
||
|
|
fbSample = apOutput;
|
||
|
|
data[i] = input + apOutput * (-depth);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
/** Simple 1st-order all-pass filter: y[n] = b0*x[n] + b1*x[n-1] - a1*y[n-1] */
|
||
|
|
struct AllPassFilter
|
||
|
|
{
|
||
|
|
void reset() { x1 = 0.f; y1 = 0.f; }
|
||
|
|
void setCoefficients(float a, float b) { a1 = a; b1 = b; }
|
||
|
|
float process(float input)
|
||
|
|
{
|
||
|
|
float output = b1 * input + b0 * x1 - a1 * y1;
|
||
|
|
y1 = output;
|
||
|
|
x1 = input;
|
||
|
|
return output;
|
||
|
|
}
|
||
|
|
|
||
|
|
float b0{ 1.f };
|
||
|
|
float b1{ 0.f };
|
||
|
|
float a1{ 0.f };
|
||
|
|
float x1{ 0.f };
|
||
|
|
float y1{ 0.f };
|
||
|
|
};
|
||
|
|
|
||
|
|
void updateAllPassCoefficients()
|
||
|
|
{
|
||
|
|
if (sampleRate <= 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (int s = 0; s < currentStages; s++)
|
||
|
|
{
|
||
|
|
float freq = currentCenterFreq * (1.f + float(s) * 0.3f);
|
||
|
|
freq = juce::jlimit(50.f, float(sampleRate) / 4.f - 10.f, freq);
|
||
|
|
|
||
|
|
float angle = juce::MathConstants<float>::pi * freq / float(sampleRate);
|
||
|
|
float t = std::tan(angle);
|
||
|
|
float a = (t - 1.f) / (t + 1.f);
|
||
|
|
|
||
|
|
allPassA[static_cast<size_t>(s)] = a;
|
||
|
|
allPassB[static_cast<size_t>(s)] = 1.f;
|
||
|
|
|
||
|
|
stages[static_cast<size_t>(s)][0].setCoefficients(a, 1.f);
|
||
|
|
stages[static_cast<size_t>(s)][1].setCoefficients(a, 1.f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static constexpr int MAX_STAGES{ 6 };
|
||
|
|
std::array<std::array<AllPassFilter, 2>, MAX_STAGES> stages;
|
||
|
|
std::array<float, MAX_STAGES> allPassA{};
|
||
|
|
std::array<float, MAX_STAGES> allPassB{};
|
||
|
|
|
||
|
|
int sampleRate{ 0 };
|
||
|
|
int numChannelsUsed{ 2 };
|
||
|
|
int currentStages{ 3 };
|
||
|
|
float currentCenterFreq{ 1000.f };
|
||
|
|
float feedback{ 0.f };
|
||
|
|
float depth{ 0.5f };
|
||
|
|
float feedbackSampleL{ 0.f };
|
||
|
|
float feedbackSampleR{ 0.f };
|
||
|
|
};
|