#pragma once #include #include namespace monostep { // Tempo-synced delay + Schroeder reverb, applied after the voice. class FxProcessor { public: FxProcessor() = default; void prepare (double sampleRate, int numChannels); void reset(); struct Params { float delayTime = 0.35f; // normalized knob 0..1 float delayFeedback = 0.45f; // 0..0.9 feedback (repeat density) bool delaySync = true; // tempo-synced vs. free milliseconds float delayMix = 0.0f; // 0..1 dry/wet float reverbRoom = 0.5f; // 0..1 float reverbLevel = 1.0f; // 0..1 wet send gain float reverbMix = 0.0f; // 0..1 dry/wet float reverbDiff = 0.6f; // 0..1 allpass diffusion double bpm = 120.0; }; void setParams (const Params& p); void process (juce::AudioBuffer& buffer, int numSamples); // Shared knob-to-time mapping so the editor displays the same values the DSP uses. static float delayTimeSeconds (float knobNormalized, bool sync, double bpm); static juce::String delayTimeLabel (float knobNormalized, bool sync, double bpm); private: struct DelayChannel { std::vector memory; int writeIndex = 0; float smoothedDelay = 0.0f; float feedbackLp = 0.0f; }; struct ReverbChannel { std::vector combs[4]; std::vector allpasses[2]; int combIndex[4] = {}; int allpassIndex[2] = {}; }; void processDelay (juce::AudioBuffer& buffer, int numSamples); void processReverb (const juce::AudioBuffer& dryIn, juce::AudioBuffer& buffer, int numSamples); double sampleRate = 44100.0; int numChannels = 2; int maxDelaySamples = 0; int combDelaySamples[2][4] = {}; int allpassDelaySamples[2][2] = {}; Params params; float currentDelaySeconds = 0.0f; // Pre-delay snapshot: the reverb is fed the dry signal (before the delay), // so delay and reverb run in parallel instead of in series. juce::AudioBuffer dryScratch; std::vector delays; std::vector reverbs; }; } // namespace monostep