refactor(engine): add thread-safe control capture and sub-block MIDI
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.
This commit is contained in:
+22
-75
@@ -1,90 +1,37 @@
|
||||
#include "RAVEButton.h"
|
||||
#include "SynthVoice.h"
|
||||
#include "FXProcessor.h"
|
||||
|
||||
namespace serum
|
||||
{
|
||||
|
||||
void RaveController::resetSnapshot()
|
||||
void RaveController::apply (RenderContext& context, FxSlotParams* slots, int numSlots) noexcept
|
||||
{
|
||||
snapshot.clear();
|
||||
enabled = false;
|
||||
}
|
||||
// Boost the static rendering parameters.
|
||||
context.oscA.unison = juce::jlimit (8, kMaxUnison, context.oscA.unison);
|
||||
context.oscB.unison = juce::jlimit (8, kMaxUnison, context.oscB.unison);
|
||||
context.oscA.spread = 1.0f;
|
||||
context.oscB.spread = 1.0f;
|
||||
context.oscA.detune = 1.0f;
|
||||
context.oscB.detune = 0.7f;
|
||||
|
||||
void RaveController::snapshotParam (const juce::String& id, juce::AudioProcessorValueTreeState& apvts)
|
||||
{
|
||||
if (auto* p = apvts.getParameter (id))
|
||||
snapshot.emplace_back (id, p->getValue());
|
||||
}
|
||||
// Apply drive boosts.
|
||||
context.filters.f1Drive = 0.6f;
|
||||
context.filters.f2Drive = 0.6f;
|
||||
|
||||
void RaveController::setParam (const juce::String& id, float value, juce::AudioProcessorValueTreeState& apvts)
|
||||
{
|
||||
if (auto* p = apvts.getParameter (id))
|
||||
p->setValueNotifyingHost (juce::jlimit (0.0f, 1.0f, value));
|
||||
}
|
||||
|
||||
void RaveController::setEnabled (bool shouldEnable, juce::AudioProcessorValueTreeState& apvts)
|
||||
{
|
||||
if (shouldEnable == enabled)
|
||||
// Boost FX-specific params (Hyper intensity / Reverb mix).
|
||||
if (slots == nullptr)
|
||||
return;
|
||||
|
||||
if (shouldEnable)
|
||||
for (int i = 0; i < juce::jlimit (0, kNumFxSlots, numSlots); ++i)
|
||||
{
|
||||
snapshot.clear();
|
||||
|
||||
// Snapshot the static "boost" parameters.
|
||||
const char* staticParams[] =
|
||||
auto& slot = slots[i];
|
||||
if (slot.type == (int) FxType::Hyper)
|
||||
slot.p[0] = 1.0f; // OTT intensity 100%
|
||||
else if (slot.type == (int) FxType::Reverb)
|
||||
{
|
||||
ids::oscAUnison, ids::oscBUnison,
|
||||
ids::oscASpread, ids::oscBSpread,
|
||||
ids::oscADetune, ids::oscBDetune,
|
||||
ids::f1Drive, ids::f2Drive
|
||||
};
|
||||
for (auto id : staticParams)
|
||||
snapshotParam (id, apvts);
|
||||
|
||||
// Snapshot + boost FX-specific params (Hyper intensity / Reverb mix).
|
||||
const char* fxMixIds[kNumFxSlots] = { ids::fx1Mix, ids::fx2Mix, ids::fx3Mix, ids::fx4Mix,
|
||||
ids::fx5Mix, ids::fx6Mix, ids::fx7Mix, ids::fx8Mix };
|
||||
const char* fxP1Ids[kNumFxSlots] = { ids::fx1P1, ids::fx2P1, ids::fx3P1, ids::fx4P1,
|
||||
ids::fx5P1, ids::fx6P1, ids::fx7P1, ids::fx8P1 };
|
||||
const char* fxTypeIds[kNumFxSlots] = { ids::fx1Type, ids::fx2Type, ids::fx3Type, ids::fx4Type,
|
||||
ids::fx5Type, ids::fx6Type, ids::fx7Type, ids::fx8Type };
|
||||
|
||||
for (int i = 0; i < kNumFxSlots; ++i)
|
||||
{
|
||||
const auto* typeParam = apvts.getParameter (fxTypeIds[i]);
|
||||
const int type = typeParam ? (int) (typeParam->getValue() * (int) FxType::Count) : 0;
|
||||
|
||||
if (type == (int) FxType::Hyper)
|
||||
{
|
||||
snapshotParam (fxP1Ids[i], apvts);
|
||||
setParam (fxP1Ids[i], 1.0f, apvts); // OTT intensity 100%
|
||||
}
|
||||
else if (type == (int) FxType::Reverb)
|
||||
{
|
||||
snapshotParam (fxMixIds[i], apvts);
|
||||
const float current = apvts.getParameter (fxMixIds[i])->getValue();
|
||||
setParam (fxMixIds[i], current + 0.4f, apvts); // reverb send +6dB-ish
|
||||
}
|
||||
const float mix = std::isfinite (slot.mix) ? slot.mix : 0.0f;
|
||||
slot.mix = juce::jlimit (0.0f, 1.0f, mix + 0.4f); // reverb send +6dB-ish
|
||||
}
|
||||
|
||||
// Apply boosts.
|
||||
setParam (ids::oscAUnison, 8.0f / 16.0f, apvts);
|
||||
setParam (ids::oscBUnison, 8.0f / 16.0f, apvts);
|
||||
setParam (ids::oscASpread, 1.0f, apvts);
|
||||
setParam (ids::oscBSpread, 1.0f, apvts);
|
||||
setParam (ids::oscADetune, 1.0f, apvts);
|
||||
setParam (ids::oscBDetune, 0.7f, apvts);
|
||||
setParam (ids::f1Drive, 0.6f, apvts);
|
||||
setParam (ids::f2Drive, 0.6f, apvts);
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& entry : snapshot)
|
||||
setParam (entry.first, entry.second, apvts);
|
||||
snapshot.clear();
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user