#pragma once #include #include #include "xtc/xtc_plugin.h" #include #include // JUCE owns host integration/UI/DSP; synthesis remains behind xtc's C API. class XtcJuceProcessor final : public juce::AudioProcessor { public: XtcJuceProcessor(); ~XtcJuceProcessor() override; const juce::String getName() const override { return "xtc JUCE Synth"; } void prepareToPlay(double, int) override; void releaseResources() override; bool isBusesLayoutSupported(const BusesLayout&) const override; void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override; using juce::AudioProcessor::processBlock; void reset() override; bool acceptsMidi() const override { return true; } bool producesMidi() const override { return false; } double getTailLengthSeconds() const override { return 3.0; } bool hasEditor() const override { return true; } juce::AudioProcessorEditor* createEditor() override; int getNumPrograms() override { return 1; } int getCurrentProgram() override { return 0; } void setCurrentProgram(int) override {} const juce::String getProgramName(int) override { return "Default"; } void changeProgramName(int, const juce::String&) override {} void getStateInformation(juce::MemoryBlock&) override; void setStateInformation(const void*, int) override; bool restorePreset(const void*, int); juce::AudioProcessorValueTreeState parameters; // One GUI producer; one processBlock consumer. Never calls MidiKeyboardState // or its locks on the audio thread. Overflow requests panic instead of stuck notes. void enqueueNote(bool on, int channel, int note, float velocity) noexcept; void panic() noexcept { panicRequested.store(true, std::memory_order_release); } float peak() const noexcept { return meter.load(std::memory_order_relaxed); } unsigned int droppedEvents() const noexcept { return dropped.load(std::memory_order_relaxed); } bool ready() const noexcept { return prepared.load(std::memory_order_acquire); } bool processingFailed() const noexcept { return failed.load(std::memory_order_relaxed); } private: static juce::AudioProcessorValueTreeState::ParameterLayout layout(); static constexpr int chunkFrames = 256, queueCapacity = 128; xtc_plugin* synth = nullptr; std::array scratch{}; std::array events{}; std::array keyboardEvents{}; juce::AbstractFifo keyboardQueue{queueCapacity}; juce::dsp::StateVariableTPTFilter filter; juce::dsp::Reverb reverb; juce::dsp::Gain gain; std::atomic* gainDb = nullptr; std::atomic* cutoff = nullptr; std::atomic* room = nullptr; std::atomic* wet = nullptr; std::atomic* effects = nullptr; std::atomic meter{0}; std::atomic dropped{0}; std::atomic panicRequested{false}, prepared{false}, failed{false}; double rate = 48000; uint64_t position = 0; void discardKeyboard() noexcept; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(XtcJuceProcessor) };