242 lines
8.2 KiB
C++
242 lines
8.2 KiB
C++
#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();
|
||
|