#pragma once #include #include "Params.h" #include "Wavetable.h" namespace serum { // =========================================================================== // Per-oscillator parameters (snapshot read from the APVTS each block). // =========================================================================== struct OscParams { bool enabled = true; int wave = 0; float wtPos = 0.0f; int warp = 0; float warpAmt = 0.0f; int coarse = 0; int fine = 0; float level = 1.0f; float pan = 0.0f; int unison = 1; float detune = 0.0f; float spread = 0.0f; float phase = 0.0f; float randPhase = 0.0f; }; // =========================================================================== // A polyphonic oscillator with wavetable morphing, warp modes and unison // (1..16 sub-voices). Per-voice state (phase) lives here; params are read each // block from the OscParams snapshot. // =========================================================================== class Oscillator { public: Oscillator() = default; void prepare (double sampleRate) { sr = sampleRate; reset(); } void reset(); // Initialise a fresh note's unison sub-voices: phase offsets, detune, pan and gain. void noteOn (double freqHz, const OscParams& p, juce::uint32 seed); void setParams (const OscParams& p) noexcept; // Accumulate this oscillator's contribution into outL/outR. void processAdd (const Wavetable& wt, const OscParams& p, double freqHz, float& outL, float& outR) noexcept; int getActiveUnison() const noexcept { return activeUnison; } private: struct SubVoice { double phase = 0.0; double detuneRatio = 1.0; float panL = 0.70710678f; float panR = 0.70710678f; float level = 1.0f; }; std::array voices; int activeUnison = 1; int initializedUnison = 0; bool paramsValid = false; OscParams cachedParams; double sr = 44100.0; juce::Random rng; static constexpr double kTwoPi = juce::MathConstants::twoPi; float warpPhase (float phase, const OscParams& p) const noexcept; float warpSample (float sample, const OscParams& p) const noexcept; }; } // namespace serum