Initial commit

This commit is contained in:
Roboboffin
2025-10-21 17:22:44 +01:00
commit 45183c21a4
5 changed files with 1922 additions and 0 deletions

241
PluginProcessor.h Normal file
View File

@@ -0,0 +1,241 @@
#pragma once
#include <JuceHeader.h>
#include <cmath>
//==============================================================================
// Forward declaration
class TwoOscAudioProcessor;
//==============================================================================
// SimpleSound: a basic sound object (always valid for poly synth)
class SimpleSound : public juce::SynthesiserSound
{
public:
bool appliesToNote (int) override;
bool appliesToChannel (int) override;
};
//==============================================================================
// SimpleVoice: single voice instance for the TwoOsc synth
//==============================================================================
class SimpleVoice : public juce::SynthesiserVoice
{
public:
SimpleVoice() = default;
bool canPlaySound (juce::SynthesiserSound*) override;
void startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound*, int) override;
void stopNote (float velocity, bool allowTailOff) override;
void pitchWheelMoved (int) override {}
void controllerMoved (int, int) override {}
void renderNextBlock (juce::AudioBuffer<float>&, int startSample, int numSamples) override;
void reset();
// Pull the latest APVTS values into this voice
void setParameters (juce::AudioProcessorValueTreeState& apvts);
private:
// ===== Helpers =====
static inline float wrap01 (float x) { return x - std::floor (x); }
// PolyBLEP step correction
static inline float polyBLEP (float t, float dt)
{
if (t < dt)
{
float x = t / dt;
return x + x - x * x - 1.0f; // 2x - x^2 - 1
}
else if (t > 1.0f - dt)
{
float x = (t - 1.0f) / dt;
return x * x + 2.0f * x + 1.0f; // x^2 + 2x + 1
}
return 0.0f;
}
// Anti-aliased oscillators using PolyBLEP + DPW tri
inline float oscSaw (float t, float dt) const
{
float y = 2.0f * t - 1.0f;
y -= polyBLEP (t, dt);
return y;
}
inline float oscSquare (float t, float dt) const
{
float y = (t < 0.5f ? 1.0f : -1.0f);
y += polyBLEP (t, dt);
y -= polyBLEP (wrap01 (t + 0.5f), dt);
return y;
}
inline float oscTriangle (float t, float dt)
{
// DPW via BL square -> leaky integrator (SR-aware leak computed in startNote)
float sq = (t < 0.5f ? 1.0f : -1.0f);
sq += polyBLEP (t, dt);
sq -= polyBLEP (wrap01 (t + 0.5f), dt);
// One-pole leaky integrator (avoid denormals)
const float k = 2.0f * dt; // integration step
triState += k * sq - triLeakCoeff * triState;
if (! std::isfinite (triState))
triState = 0.0f;
return juce::jlimit (-1.0f, 1.0f, triState);
}
inline float oscSine (float t) const
{
return std::sin (juce::MathConstants<float>::twoPi * t);
}
inline float oscBLEP (int mode, float t, float dt)
{
switch (mode)
{
case 1: return oscSaw (t, dt);
case 2: return oscSquare (t, dt);
case 3: return oscTriangle(t, dt);
default: return oscSine (t);
}
}
// ===== parameters pushed from APVTS =====
int oscAChoice = 1, oscBChoice = 2;
float mix = 0.35f;
float detune = -5.0f; // cents
float cutoff = 180.0f; // Hz
float reso = 0.18f;
float envAmt = 0.25f;
bool filterBypass = false;
// --- LFO (per-voice, sine) ---
float lfoRateHz = 0.0f; // Hz
float lfoToCutOct = 0.0f; // octaves
float lfoToPitchCt = 0.0f; // cents
float lfoPhase = 0.0f; // 0..1
// ADSR
juce::ADSR adsrAmp, adsrFilter;
juce::ADSR::Parameters ampParams, filterParams;
// State-variable filter (per-voice) set in startNote()
juce::dsp::StateVariableTPTFilter<float> lpFilter;
// Free-running phases (0..1)
float phaseA = 0.0f, phaseB = 0.0f;
// Voice state
float currentNoteHz = 440.0f;
float velocityGain = 1.0f;
// Triangle integrator state & leak coefficient
float triState = 0.0f;
float triLeakCoeff = 0.0f;
// Start/stop ramps
int rampSamples = 0;
int rampCounter = 0;
bool forcedOffActive = false;
int forcedOffSamples = 0;
int forcedOffCounter = 0;
// Smoothed params
juce::SmoothedValue<float> detuneSmoothed { 0.0f };
juce::SmoothedValue<float> cutoffSmoothed { 1200.0f };
juce::SmoothedValue<float> resoSmoothed { 0.3f };
juce::SmoothedValue<float> envAmtSmoothed { 0.2f };
juce::SmoothedValue<float> cutoffModSmooth{ 1000.0f };
// LFO smoothed depths
juce::SmoothedValue<float> lfoCutSmoothed { 0.0f };
juce::SmoothedValue<float> lfoPitchSmoothed { 0.0f };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleVoice)
};
//==============================================================================
// Main processor
//==============================================================================
class TwoOscAudioProcessor : public juce::AudioProcessor,
public juce::AudioProcessorValueTreeState::Listener
{
public:
TwoOscAudioProcessor();
~TwoOscAudioProcessor() override;
//==========================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override {}
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
//==========================================================================
juce::AudioProcessorEditor* createEditor() override;
bool hasEditor() const override;
//==========================================================================
const juce::String getName() const override;
bool acceptsMidi() const override;
bool producesMidi() const override;
bool isMidiEffect() const override;
double getTailLengthSeconds() const override;
//==========================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const juce::String getProgramName (int) override { return {}; }
void changeProgramName (int, const juce::String&) override {}
//==========================================================================
void getStateInformation (juce::MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
//==========================================================================
void parameterChanged (const juce::String& paramID, float newValue) override;
// Preset helpers
juce::StringArray getPresetCategories() const;
juce::Array<int> getPresetIndicesForCategory (const juce::String& category) const;
juce::String getPresetLabel (int index) const;
juce::String getCurrentPresetLabel() const;
void applyPresetByIndex (int index, bool setParamToo);
// APVTS
juce::AudioProcessorValueTreeState apvts;
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
private:
//==========================================================================
void updateFilter();
juce::Synthesiser synth;
double lastSampleRate = 44100.0;
// Hidden HPF
juce::dsp::ProcessorDuplicator<
juce::dsp::IIR::Filter<float>,
juce::dsp::IIR::Coefficients<float>> hpFilter;
// Global smoothers
juce::SmoothedValue<float> smoothedCutoff;
juce::SmoothedValue<float> smoothedReso;
juce::SmoothedValue<float> smoothedEnvAmt;
std::atomic<bool> isApplyingPreset { false };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TwoOscAudioProcessor)
};
//==============================================================================
// Factory function
//==============================================================================
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter();