66 lines
3.2 KiB
C
66 lines
3.2 KiB
C
|
|
#pragma once
|
||
|
|
#include <juce_audio_utils/juce_audio_utils.h>
|
||
|
|
#include <juce_dsp/juce_dsp.h>
|
||
|
|
#include "xtc/xtc_plugin.h"
|
||
|
|
#include <array>
|
||
|
|
#include <atomic>
|
||
|
|
|
||
|
|
// 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<float>&, 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<float, chunkFrames> scratch{};
|
||
|
|
std::array<xtc_midi_event, XTC_MIDI_MAX_EVENTS> events{};
|
||
|
|
std::array<xtc_midi_event, queueCapacity> keyboardEvents{};
|
||
|
|
juce::AbstractFifo keyboardQueue{queueCapacity};
|
||
|
|
juce::dsp::StateVariableTPTFilter<float> filter;
|
||
|
|
juce::dsp::Reverb reverb;
|
||
|
|
juce::dsp::Gain<float> gain;
|
||
|
|
std::atomic<float>* gainDb = nullptr;
|
||
|
|
std::atomic<float>* cutoff = nullptr;
|
||
|
|
std::atomic<float>* room = nullptr;
|
||
|
|
std::atomic<float>* wet = nullptr;
|
||
|
|
std::atomic<float>* effects = nullptr;
|
||
|
|
std::atomic<float> meter{0};
|
||
|
|
std::atomic<unsigned int> dropped{0};
|
||
|
|
std::atomic<bool> panicRequested{false}, prepared{false}, failed{false};
|
||
|
|
double rate = 48000;
|
||
|
|
uint64_t position = 0;
|
||
|
|
void discardKeyboard() noexcept;
|
||
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(XtcJuceProcessor)
|
||
|
|
};
|