Initial commit
This commit is contained in:
241
PluginProcessor.h
Normal file
241
PluginProcessor.h
Normal 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();
|
||||
|
||||
Reference in New Issue
Block a user