fixes, changes to gitignore

This commit is contained in:
Armin 2026-07-24 18:03:15 +02:00
commit 11d81cf1fd
17 changed files with 1160 additions and 16 deletions

View file

@ -0,0 +1,150 @@
/*
==============================================================================
ModFilter.h
Created: 24 Jul 2026
Author: Armin
==============================================================================
*/
#pragma once
#include <JuceHeader.h>
#include "Effect.h"
enum class ModFilterType : std::uint8_t
{
LP12,
LP24,
HP,
BP,
Notch
};
/** A multi-mode filter effect for the modulation section. Supports LP12, LP24, HP, BP, Notch. */
class ModFilter final : public Effect
{
public:
void initialize(int numChannels, int fxSampleRate) override
{
sampleRate = fxSampleRate;
juce::dsp::ProcessSpec spec{};
spec.numChannels = static_cast<juce::uint32>(numChannels);
spec.sampleRate = sampleRate;
filterChain.reset();
filterChain.prepare(spec);
currentCutoff = -1.f;
currentResonance = -1.f;
currentType = static_cast<ModFilterType>(255); // Force update
}
void setFilterType(ModFilterType type)
{
if (type != currentType)
{
currentType = type;
needsUpdate = true;
}
}
void setCutoff(float cutoff)
{
cutoff = juce::jlimit(20.f, float(sampleRate) / 2.f - 10.f, cutoff);
if (std::abs(cutoff - currentCutoff) > 0.1f)
{
currentCutoff = cutoff;
needsUpdate = true;
}
}
void setResonance(float resonance)
{
resonance = juce::jlimit(0.f, 1.f, resonance);
if (std::abs(resonance - currentResonance) > 0.001f)
{
currentResonance = resonance;
needsUpdate = true;
}
}
void updateParams(const SamplerParameters& samplerSound, bool /*modulating*/) override
{
setFilterType(static_cast<ModFilterType>(samplerSound.modFilterType->getIndex()));
setCutoff(samplerSound.modFilterCutoff->get());
setResonance(samplerSound.modFilterResonance->get());
}
void process(juce::AudioBuffer<float>& buffer, int numSamples, int startSample = 0) override
{
if (needsUpdate)
updateCoefficients();
juce::dsp::AudioBlock block{ buffer.getArrayOfWritePointers(), size_t(buffer.getNumChannels()), size_t(startSample), size_t(numSamples) };
juce::dsp::ProcessContextReplacing context{ block };
filterChain.process(context);
}
private:
void updateCoefficients()
{
needsUpdate = false;
float q = 0.707f / (1.f - currentResonance * 0.9f); // Map 0-1 resonance to Q
q = juce::jlimit(0.5f, 30.f, q);
switch (currentType)
{
case ModFilterType::LP12:
{
auto coeff = juce::dsp::IIR::Coefficients<float>::makeLowPass(sampleRate, currentCutoff, q);
*filterChain.get<0>().state = *coeff;
*filterChain.get<1>().state = *coeff;
break;
}
case ModFilterType::LP24:
{
auto coeff = juce::dsp::IIR::Coefficients<float>::makeLowPass(sampleRate, currentCutoff, q);
*filterChain.get<0>().state = *coeff;
*filterChain.get<1>().state = *coeff;
*filterChain.get<2>().state = *coeff;
*filterChain.get<3>().state = *coeff;
break;
}
case ModFilterType::HP:
{
auto coeff = juce::dsp::IIR::Coefficients<float>::makeHighPass(sampleRate, currentCutoff, q);
*filterChain.get<0>().state = *coeff;
*filterChain.get<1>().state = *coeff;
break;
}
case ModFilterType::BP:
{
auto coeff = juce::dsp::IIR::Coefficients<float>::makeBandPass(sampleRate, currentCutoff, q);
*filterChain.get<0>().state = *coeff;
*filterChain.get<1>().state = *coeff;
break;
}
case ModFilterType::Notch:
{
auto coeff = juce::dsp::IIR::Coefficients<float>::makeNotch(sampleRate, currentCutoff, q);
*filterChain.get<0>().state = *coeff;
*filterChain.get<1>().state = *coeff;
break;
}
}
}
using Filter = juce::dsp::ProcessorDuplicator<juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Coefficients<float>>;
using FilterChain = juce::dsp::ProcessorChain<Filter, Filter, Filter, Filter>;
int sampleRate{ 0 };
FilterChain filterChain;
ModFilterType currentType{ ModFilterType::LP12 };
float currentCutoff{ -1.f };
float currentResonance{ -1.f };
bool needsUpdate{ true };
};

View file

@ -0,0 +1,187 @@
/*
==============================================================================
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 };
};

View file

@ -0,0 +1,56 @@
/*
==============================================================================
RingMod.h
Created: 24 Jul 2026
Author: Armin
==============================================================================
*/
#pragma once
#include <JuceHeader.h>
#include "Effect.h"
/** Ring modulation effect driven by the LFO waveform. */
class RingModEffect final : public Effect
{
public:
void initialize(int /*numChannels*/, int /*fxSampleRate*/) override
{
}
void setMix(float m)
{
mix = juce::jlimit(0.f, 1.f, m);
}
void updateParams(const SamplerParameters& samplerSound, bool /*modulating*/) override
{
setMix(samplerSound.modRingModMix->get());
}
/** Process with a pre-computed LFO buffer for ring modulation. */
void processWithLFO(juce::AudioBuffer<float>& buffer, int numSamples, const float* lfoBuffer)
{
for (int ch = 0; ch < buffer.getNumChannels(); ch++)
{
auto* data = buffer.getWritePointer(ch);
for (int i = 0; i < numSamples; i++)
{
float dry = data[i];
float modulated = dry * lfoBuffer[i];
data[i] = dry + (modulated - dry) * mix;
}
}
}
void process(juce::AudioBuffer<float>& /*buffer*/, int /*numSamples*/, int /*startSample*/ = 0) override
{
// No-op without LFO buffer
}
private:
float mix{ 0.5f };
};