S7: stereo channel mode — core channel dimension, v4 mode state, VST3 bus negotiation, editor toggle

Per-instance mono|stereo (default mono, byte-identical). Stereo grows SampleData a 2nd
channel + a per-channel VoiceEngine render; decodeChannels applies the cross-mode policy;
setBusArrangements pins the mode's arrangement and restartComponent(kIoChanged) re-negotiates.
This commit is contained in:
2026-07-26 21:26:21 -04:00
parent ee82b50fb7
commit 4a3551b3b9
11 changed files with 727 additions and 85 deletions
+32
View File
@@ -88,6 +88,16 @@ public:
Steinberg::tresult PLUGIN_API process(
Steinberg::Vst::ProcessData& data) override;
// S7 channel-mode bus negotiation. The instrument has ONE canonical output arrangement
// determined by its per-instance channel mode (mono -> kMono, stereo -> kStereo). We
// accept the host's proposal only when it matches that arrangement; otherwise we reject
// (kResultFalse) but keep the mode's arrangement, so getBusArrangement / getBusInfo always
// report the mode's channel count and REAPER routes accordingly. A runtime mode change
// updates the output bus + calls restartComponent(kIoChanged) to trigger re-negotiation.
Steinberg::tresult PLUGIN_API setBusArrangements(
Steinberg::Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns,
Steinberg::Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override;
//--- from IEditController -----------------------------------------------
// Hands the host our LICE IPlugView editor.
Steinberg::IPlugView* PLUGIN_API createView(Steinberg::FIDString name) override;
@@ -128,7 +138,23 @@ public:
PerformanceMap performanceMap();
void setPerformanceMap(const PerformanceMap& map);
// The per-instance channel mode (S7, D-E: mono | stereo). Read/written on the UI thread
// (the editor toggle) and read off-thread by getState/reloadFromBank; guarded by
// channelModeMutex_. NEVER read on the audio thread — process() renders against the host's
// negotiated output channel count, and reloadFromBank bakes the mode into the decode.
ChannelMode channelMode();
// Sets the mode. When it CHANGES, updates the output bus arrangement (mono->kMono /
// stereo->kStereo) and asks the host to re-negotiate I/O via restartComponent(kIoChanged),
// then reloads the instrument so the next block decodes the new channel count. A no-op set
// (same mode) does neither. UI thread only.
void setChannelMode(ChannelMode mode);
private:
// Apply `mode` to the output audio bus's SpeakerArrangement (kMono / kStereo). Called from
// initialize (topology) and setChannelMode (runtime change). Does NOT re-negotiate — the
// caller drives restartComponent when appropriate.
void applyOutputArrangement(ChannelMode mode);
ReaperBridge bridge_;
// --- The audio-thread handoff (S4 real-time discipline) -----------------
@@ -178,6 +204,12 @@ private:
std::mutex performanceMutex_;
PerformanceMap performanceMap_;
// The per-instance channel mode (S7). Off-thread only (UI + getState + reloadFromBank);
// guarded against a getState/editor race. Default Mono preserves pre-S7 behavior. NOT read
// on the audio thread — process renders against the host's negotiated output channel count.
std::mutex channelModeMutex_;
ChannelMode channelMode_ = ChannelMode::Mono;
// Latched from setupProcessing so setActive/reload can size against it. Read
// off-thread only.
double sampleRate_ = 44100.0;