mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 04:10:48 +02:00
fixes, changes to gitignore
This commit is contained in:
parent
2ab046489f
commit
11d81cf1fd
17 changed files with 1160 additions and 16 deletions
|
|
@ -15,6 +15,10 @@
|
|||
#include "Effects/Chorus.h"
|
||||
#include "Effects/Distortion.h"
|
||||
#include "Effects/Reverb.h"
|
||||
#include "Effects/ModFilter.h"
|
||||
#include "Effects/Phaser.h"
|
||||
#include "Effects/RingMod.h"
|
||||
#include "LFO.h"
|
||||
|
||||
CustomSamplerVoice::CustomSamplerVoice(const SamplerParameters& samplerSound, MTSClient* client, double applicationSampleRate, int expectedBlockSize, bool initSample) :
|
||||
expectedBlockSize(expectedBlockSize), sampleSound(samplerSound),
|
||||
|
|
@ -66,6 +70,13 @@ void CustomSamplerVoice::initializeSample()
|
|||
loopLowpass.emplace_back(std::make_unique<LowpassStream>(2 * LANCZOS_WINDOW_SIZE));
|
||||
endLowpass.emplace_back(std::make_unique<LowpassStream>(2 * LANCZOS_WINDOW_SIZE));
|
||||
}
|
||||
|
||||
// Prepare modulation section
|
||||
modLFO.prepare(getSampleRate());
|
||||
modFilter.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modPhaser.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modRingMod.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modLfoBuffer.setSize(1, expectedBlockSize * 2);
|
||||
}
|
||||
|
||||
void CustomSamplerVoice::startNote(int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition)
|
||||
|
|
@ -131,7 +142,17 @@ void CustomSamplerVoice::startNote(int midiNoteNumber, float velocity, juce::Syn
|
|||
effect.fx->initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
effect.fx->updateParams(sampleSound);
|
||||
}
|
||||
|
||||
|
||||
// Initialize modulation section
|
||||
modLFO.reset();
|
||||
modLFO.prepare(getSampleRate());
|
||||
modFilter.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modFilter.updateParams(sampleSound, false);
|
||||
modPhaser.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modPhaser.updateParams(sampleSound, false);
|
||||
modRingMod.initialize(sampleSound.sample.getNumChannels(), int(getSampleRate()));
|
||||
modRingMod.updateParams(sampleSound, false);
|
||||
|
||||
updateFXParamsTimer = 0;
|
||||
|
||||
// Set the initial state (vc.currentPosition is set before updateSpeedAndPitch)
|
||||
|
|
@ -279,6 +300,23 @@ void CustomSamplerVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
|
|||
someFXEnabled = someFXEnabled || effect.enablementSource->get();
|
||||
}
|
||||
|
||||
// Pre-compute LFO values for the block
|
||||
bool modEnabled = sampleSound.modLfoFilterDepth->get() > 0.f || sampleSound.modLfoSpeedDepth->get() > 0.f
|
||||
|| sampleSound.modRingModEnabled->get() || sampleSound.modPhaserEnabled->get();
|
||||
if (modEnabled)
|
||||
{
|
||||
float bpm = sampleSound.currentBpm.load();
|
||||
modLFO.setSync(sampleSound.modLfoSync->get(), bpm,
|
||||
sampleSound.modLfoSyncDiv->getIndex() == 0 ? 2 : (sampleSound.modLfoSyncDiv->getIndex() == 1 ? 3 : 4));
|
||||
modLFO.setRate(sampleSound.modLfoRate->get());
|
||||
modLFO.setWaveform(static_cast<LFOWaveform>(sampleSound.modLfoWaveform->getIndex()));
|
||||
|
||||
if (modLfoBuffer.getNumSamples() < numSamples)
|
||||
modLfoBuffer.setSize(1, numSamples);
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
modLfoBuffer.setSample(0, i, modLFO.getNextSample());
|
||||
}
|
||||
|
||||
// Main processing loop
|
||||
VoiceContext con;
|
||||
for (auto ch = 0; ch < sampleSound.sample.getNumChannels(); ch++)
|
||||
|
|
@ -352,8 +390,10 @@ void CustomSamplerVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
|
|||
if (con.isReleasing)
|
||||
envelopeBuffer.setSample(ch, i, envelopeBuffer.getSample(ch, i) * exponentialCurve(releaseShape, 1 - con.speedMovedSinceRelease / releaseSmoothing));
|
||||
|
||||
// Update the position
|
||||
con.currentPosition += speed;
|
||||
// Update the position with optional LFO speed modulation
|
||||
float lfoVal = modEnabled ? modLfoBuffer.getSample(0, i) : 0.f;
|
||||
float modulatedSpeed = speed * (1.f + lfoVal * sampleSound.modLfoSpeedDepth->get());
|
||||
con.currentPosition += modulatedSpeed;
|
||||
con.speedMovedSinceStart += 1;
|
||||
if (con.isReleasing)
|
||||
con.speedMovedSinceRelease += 1;
|
||||
|
|
@ -430,6 +470,43 @@ void CustomSamplerVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
|
|||
}
|
||||
vc = con;
|
||||
|
||||
// Apply modulation section (filter, ring mod, phaser) before FX chain
|
||||
if (modEnabled)
|
||||
{
|
||||
float lfoFilterDepth = sampleSound.modLfoFilterDepth->get();
|
||||
if (lfoFilterDepth > 0.f)
|
||||
{
|
||||
float baseCutoff = sampleSound.modFilterCutoff->get();
|
||||
float avgLfo = 0.f;
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
avgLfo += modLfoBuffer.getSample(0, i);
|
||||
avgLfo /= float(numSamples);
|
||||
float modulatedCutoff = baseCutoff * std::pow(2.f, avgLfo * lfoFilterDepth * 4.f);
|
||||
modFilter.setCutoff(modulatedCutoff);
|
||||
modFilter.setResonance(sampleSound.modFilterResonance->get());
|
||||
modFilter.process(tempOutputBuffer, numSamples);
|
||||
}
|
||||
else
|
||||
{
|
||||
modFilter.setCutoff(sampleSound.modFilterCutoff->get());
|
||||
modFilter.setResonance(sampleSound.modFilterResonance->get());
|
||||
modFilter.process(tempOutputBuffer, numSamples);
|
||||
}
|
||||
|
||||
if (sampleSound.modRingModEnabled->get())
|
||||
modRingMod.processWithLFO(tempOutputBuffer, numSamples, modLfoBuffer.getReadPointer(0));
|
||||
|
||||
if (sampleSound.modPhaserEnabled->get())
|
||||
{
|
||||
float avgLfo = 0.f;
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
avgLfo += modLfoBuffer.getSample(0, i);
|
||||
avgLfo /= float(numSamples);
|
||||
modPhaser.setDepth(sampleSound.modPhaserDepth->get());
|
||||
modPhaser.processWithLFO(tempOutputBuffer, numSamples, avgLfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for updated FX order
|
||||
if (updateFXParamsTimer == UPDATE_PARAMS_LENGTH)
|
||||
initializeFx();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
|
||||
#include "SamplerParameters.h"
|
||||
#include "Effects/Effect.h"
|
||||
#include "Effects/ModFilter.h"
|
||||
#include "Effects/Phaser.h"
|
||||
#include "Effects/RingMod.h"
|
||||
#include "LFO.h"
|
||||
#include "Stretcher.h"
|
||||
#include <libMTSClient.h>
|
||||
|
||||
|
|
@ -257,6 +261,13 @@ private:
|
|||
int updateFXParamsTimer{ 0 };
|
||||
std::vector<Fx> effects;
|
||||
|
||||
// Modulation section
|
||||
ModLFO modLFO;
|
||||
ModFilter modFilter;
|
||||
PhaserEffect modPhaser;
|
||||
RingModEffect modRingMod;
|
||||
juce::AudioBuffer<float> modLfoBuffer;
|
||||
|
||||
MTSClient* mtsClient{ nullptr };
|
||||
};
|
||||
|
||||
|
|
|
|||
150
Source/Sampler/Effects/ModFilter.h
Normal file
150
Source/Sampler/Effects/ModFilter.h
Normal 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 };
|
||||
};
|
||||
187
Source/Sampler/Effects/Phaser.h
Normal file
187
Source/Sampler/Effects/Phaser.h
Normal 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 };
|
||||
};
|
||||
56
Source/Sampler/Effects/RingMod.h
Normal file
56
Source/Sampler/Effects/RingMod.h
Normal 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 };
|
||||
};
|
||||
97
Source/Sampler/LFO.h
Normal file
97
Source/Sampler/LFO.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
LFO.h
|
||||
Created: 24 Jul 2026
|
||||
Author: Armin
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
|
||||
enum class LFOWaveform : std::uint8_t
|
||||
{
|
||||
Sine,
|
||||
Triangle,
|
||||
Saw,
|
||||
Square
|
||||
};
|
||||
|
||||
/** A simple polyphonic LFO for modulation purposes. Outputs values in [-1, 1]. */
|
||||
class ModLFO
|
||||
{
|
||||
public:
|
||||
void prepare(double newSampleRate)
|
||||
{
|
||||
sampleRate = newSampleRate;
|
||||
phase = 0.f;
|
||||
}
|
||||
|
||||
void setRate(float hz)
|
||||
{
|
||||
rate = hz;
|
||||
if (!synced)
|
||||
currentHz = rate;
|
||||
}
|
||||
|
||||
void setWaveform(LFOWaveform wf) { waveform = wf; }
|
||||
|
||||
void setSync(bool shouldSync, double bpm, int syncDiv)
|
||||
{
|
||||
synced = shouldSync;
|
||||
if (synced && bpm > 0 && syncDiv > 0)
|
||||
currentHz = float(bpm) * float(syncDiv) / (60.f * 4.f);
|
||||
else
|
||||
currentHz = rate;
|
||||
}
|
||||
|
||||
float getNextSample()
|
||||
{
|
||||
float increment = currentHz / float(sampleRate);
|
||||
float value = evaluate(phase);
|
||||
|
||||
phase += increment;
|
||||
if (phase >= 1.f)
|
||||
phase -= 1.f;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
phase = 0.f;
|
||||
}
|
||||
|
||||
float getCurrentHz() const { return currentHz; }
|
||||
|
||||
private:
|
||||
float evaluate(float p) const
|
||||
{
|
||||
switch (waveform)
|
||||
{
|
||||
case LFOWaveform::Sine:
|
||||
return std::sin(2.f * juce::MathConstants<float>::pi * p);
|
||||
|
||||
case LFOWaveform::Triangle:
|
||||
return 2.f * std::abs(2.f * p - 1.f) - 1.f;
|
||||
|
||||
case LFOWaveform::Saw:
|
||||
return 2.f * p - 1.f;
|
||||
|
||||
case LFOWaveform::Square:
|
||||
return p < 0.5f ? 1.f : -1.f;
|
||||
|
||||
default:
|
||||
return std::sin(2.f * juce::MathConstants<float>::pi * p);
|
||||
}
|
||||
}
|
||||
|
||||
double sampleRate{ 44100 };
|
||||
float phase{ 0.f };
|
||||
float rate{ 1.f };
|
||||
float currentHz{ 1.f };
|
||||
LFOWaveform waveform{ LFOWaveform::Sine };
|
||||
bool synced{ false };
|
||||
};
|
||||
|
|
@ -74,6 +74,22 @@ SamplerParameters::SamplerParameters(const juce::AudioProcessorValueTreeState& a
|
|||
chorusFeedback(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::CHORUS_FEEDBACK))),
|
||||
chorusCenterDelay(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::CHORUS_CENTER_DELAY))),
|
||||
|
||||
modFilterType(dynamic_cast<juce::AudioParameterChoice*>(apvts.getParameter(PluginParameters::MOD_FILTER_TYPE))),
|
||||
modFilterCutoff(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_FILTER_CUTOFF))),
|
||||
modFilterResonance(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_FILTER_RESONANCE))),
|
||||
modLfoRate(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_LFO_RATE))),
|
||||
modLfoSync(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::MOD_LFO_SYNC))),
|
||||
modLfoSyncDiv(dynamic_cast<juce::AudioParameterChoice*>(apvts.getParameter(PluginParameters::MOD_LFO_SYNC_DIV))),
|
||||
modLfoWaveform(dynamic_cast<juce::AudioParameterChoice*>(apvts.getParameter(PluginParameters::MOD_LFO_WAVEFORM))),
|
||||
modLfoFilterDepth(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_LFO_FILTER_DEPTH))),
|
||||
modLfoSpeedDepth(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_LFO_SPEED_DEPTH))),
|
||||
modPhaserEnabled(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::MOD_PHASER_ENABLED))),
|
||||
modPhaserDepth(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_PHASER_DEPTH))),
|
||||
modPhaserFeedback(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_PHASER_FEEDBACK))),
|
||||
modPhaserStages(dynamic_cast<juce::AudioParameterInt*>(apvts.getParameter(PluginParameters::MOD_PHASER_STAGES))),
|
||||
modRingModEnabled(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::MOD_RINGMOD_ENABLED))),
|
||||
modRingModMix(dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter(PluginParameters::MOD_RINGMOD_MIX))),
|
||||
|
||||
playbackMode(dynamic_cast<juce::AudioParameterChoice*>(apvts.getParameter(PluginParameters::PLAYBACK_MODE))),
|
||||
fxOrder(dynamic_cast<juce::AudioParameterInt*>(apvts.getParameter(PluginParameters::FX_PERM)))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,6 +47,22 @@ public:
|
|||
juce::AudioParameterFloat* eqLowGain, * eqMidGain, * eqHighGain, * eqLowFreq, * eqHighFreq;
|
||||
juce::AudioParameterFloat* chorusMix, * chorusRate, * chorusDepth, * chorusFeedback, * chorusCenterDelay;
|
||||
|
||||
/** Modulation parameters */
|
||||
juce::AudioParameterChoice* modFilterType;
|
||||
juce::AudioParameterFloat* modFilterCutoff, * modFilterResonance;
|
||||
juce::AudioParameterFloat* modLfoRate;
|
||||
juce::AudioParameterBool* modLfoSync;
|
||||
juce::AudioParameterChoice* modLfoSyncDiv, * modLfoWaveform;
|
||||
juce::AudioParameterFloat* modLfoFilterDepth, * modLfoSpeedDepth;
|
||||
juce::AudioParameterBool* modPhaserEnabled;
|
||||
juce::AudioParameterFloat* modPhaserDepth, * modPhaserFeedback;
|
||||
juce::AudioParameterInt* modPhaserStages;
|
||||
juce::AudioParameterBool* modRingModEnabled;
|
||||
juce::AudioParameterFloat* modRingModMix;
|
||||
|
||||
/** Host tempo for LFO sync */
|
||||
std::atomic<float> currentBpm{ 120.f };
|
||||
|
||||
private:
|
||||
juce::AudioParameterChoice* playbackMode;
|
||||
juce::AudioParameterInt* fxOrder;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue