Introduce a CriticalSection controlLock separating control-thread state (matrix, macros, controlLfos) from audio-thread snapshots (audioMatrix, audioMacros, lfos). captureControls() copies under lock at the start of each processBlock; the audio thread never touches control state directly. Cache parameter values in an unordered_map keyed by string_view to avoid per-sample APVTS lookups. Prebuild wavetables in the constructor instead of lazy allocation. Resize mixBuffer only in prepare(), not per block. Render MIDI events at their sample positions using sub-block rendering with renderUntil(), so notes start at the correct sub-sample offset. Make RaveController non-destructive: replace APVTS mutation with a static apply() that boosts the RenderContext and FX slots for the current block only. Remove the snapshot/restore machinery. Make currentProgram atomic. Validate state I/O: check XML tag, clamp program index, sanitize LFO shape data, and use beginChangeGesture/endChangeGesture for RAVE toggle. Reset parameters to defaults before loading preset values.
83 lines
2.6 KiB
C++
83 lines
2.6 KiB
C++
#pragma once
|
|
|
|
#include <JuceHeader.h>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include "Params.h"
|
|
#include "Wavetable.h"
|
|
#include "SynthVoice.h"
|
|
#include "LFO.h"
|
|
#include "FXProcessor.h"
|
|
#include "ModulationMatrix.h"
|
|
#include "MacroControls.h"
|
|
|
|
namespace serum
|
|
{
|
|
|
|
// ===========================================================================
|
|
// The synthesis engine: a fixed pool of voices, four global LFOs, a wavetable
|
|
// library and the FX rack. Owns all per-block DSP and MIDI handling.
|
|
// ===========================================================================
|
|
class Engine
|
|
{
|
|
public:
|
|
Engine();
|
|
void prepare (double sampleRate, int blockSize, juce::AudioProcessorValueTreeState& apvts);
|
|
void reset();
|
|
|
|
void processBlock (juce::AudioBuffer<float>& buffer,
|
|
juce::MidiBuffer& midi,
|
|
juce::AudioProcessorValueTreeState& apvts,
|
|
juce::AudioPlayHead* playhead);
|
|
|
|
// MIDI
|
|
void noteOn (int noteNumber, float velocity01);
|
|
void noteOff (int noteNumber);
|
|
void allNotesOff();
|
|
|
|
// Control-state accessors: hold getControlLock() while reading or editing.
|
|
const juce::CriticalSection& getControlLock() const { return controlLock; }
|
|
ModulationMatrix& getMatrix() { return matrix; }
|
|
MacroControls& getMacros() { return macros; }
|
|
const std::array<LFO, kNumLfos>& getLfos() const { return controlLfos; }
|
|
const WavetableLibrary& getWavetables() const { return wavetables; }
|
|
void setLfoShapeData (int index, const std::vector<float>& data, int steps);
|
|
|
|
int getActiveVoiceCount() const { return activeVoiceCount.load (std::memory_order_relaxed); }
|
|
|
|
private:
|
|
std::array<SynthVoice, kNumVoices> voices;
|
|
std::array<LFO, kNumLfos> lfos, controlLfos;
|
|
WavetableLibrary wavetables;
|
|
FXProcessor fx;
|
|
ModulationMatrix matrix, audioMatrix;
|
|
MacroControls macros, audioMacros;
|
|
juce::CriticalSection controlLock;
|
|
|
|
struct ParameterValue
|
|
{
|
|
std::atomic<float>* source;
|
|
float value;
|
|
};
|
|
std::unordered_map<std::string_view, ParameterValue> parameterValues;
|
|
|
|
double sr = 44100.0;
|
|
int blockSize = 512;
|
|
double bpm = 120.0;
|
|
float pitchBend = 0.0f;
|
|
float modWheel = 0.0f;
|
|
juce::uint64 noteCounter = 0;
|
|
std::atomic<int> activeVoiceCount { 0 };
|
|
|
|
juce::AudioBuffer<float> mixBuffer;
|
|
|
|
SynthVoice* findFreeVoice();
|
|
SynthVoice* stealVoice();
|
|
void captureControls();
|
|
float v (const char* id) const;
|
|
int vic (const char* id, int maxValue) const;
|
|
void readOscParams (const paramIds::Oscillator& ids, OscParams& out) const;
|
|
};
|
|
|
|
} // namespace serum
|