Files
NeuralSynthEd/PluginProcessor.h
2025-10-21 17:22:44 +01:00

242 lines
8.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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();