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
+47 -2
View File
@@ -146,6 +146,7 @@ void ReaSamplerEditor::refreshFromBank() {
banks_ = banksJson ? listBanks(*banksJson) : std::vector<BankChoice>{};
selectedId_ = processor_->selectedSampleId();
map_ = processor_->performanceMap();
channelMode_ = processor_->channelMode();
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
// Drop a filter that names a bank no longer present.
if (!activeFilterBankId_.empty()) {
@@ -334,6 +335,22 @@ Rect zonesStripArea(const EditorBands& bands) {
return Rect{bands.content.left + pad, stripTop, bands.content.right - pad,
stripTop + kStripBandHeight};
}
// The S7 mono/stereo toggle, a two-segment control anchored to the RIGHT of the setup band's
// header row (same y as the sample-name header, so it reads as "this capture's output mode").
// `area` is the full setup Rect. Returns {mono-segment, stereo-segment}; each is kSegW wide,
// kSegH tall, side by side. Kept to a small fenced block (S11 owns the waveform region).
constexpr int kChanSegW = 52;
constexpr int kChanSegH = 18;
struct ChannelToggleRects { Rect mono; Rect stereo; };
ChannelToggleRects channelToggleRects(const Rect& area) {
constexpr int pad = 8;
const int top = area.top + 4;
const int right = area.right - pad;
const Rect stereo{right - kChanSegW, top, right, top + kChanSegH};
const Rect mono{stereo.left - kChanSegW, top, stereo.left, top + kChanSegH};
return {mono, stereo};
}
} // namespace
void ReaSamplerEditor::paint(HDC hdc) {
@@ -477,10 +494,22 @@ void ReaSamplerEditor::paintSetup(LICE_IBitmap* bmp, const Rect& area) {
}
const int pad = 8;
Rect headerR{area.left + pad, area.top + 4, area.right - pad, area.top + 22};
// The mono/stereo toggle sits at the right of the header row; keep the name text clear of it.
const ChannelToggleRects chan = channelToggleRects(area);
Rect headerR{area.left + pad, area.top + 4, chan.mono.left - 8, area.top + 22};
std::string header = sampleLabel(samples_, selectedId_) + " root " + noteLabel(root);
drawText(bmp, headerR, header.c_str(), kRgbText);
// S7 mono | stereo output-mode toggle. The active segment highlights (kColTabActiveBg),
// the inactive is kColTabBg — the same visual grammar as the Browser/Zones toggle.
const bool isStereo = (channelMode_ == ChannelMode::Stereo);
LICE_FillRect(bmp, chan.mono.left, chan.mono.top, chan.mono.width(), chan.mono.height(),
isStereo ? kColTabBg : kColTabActiveBg, 1.0f, 0);
LICE_FillRect(bmp, chan.stereo.left, chan.stereo.top, chan.stereo.width(),
chan.stereo.height(), isStereo ? kColTabActiveBg : kColTabBg, 1.0f, 0);
drawTextCentered(bmp, chan.mono, "Mono", kRgbText);
drawTextCentered(bmp, chan.stereo, "Stereo", kRgbText);
Rect hintR{area.left + pad, headerR.bottom, area.right - pad, headerR.bottom + 16};
drawText(bmp, hintR, "Drag on the keyboard to set the root note.", kRgbDim);
@@ -597,9 +626,25 @@ void ReaSamplerEditor::onMouseDown(int x, int y) {
commitAndReload(); // publishes the pick + reloads; process() plays it repitched
return;
}
// The setup strip: grab the root marker (drag to set the picked capture's root).
// The setup band: the mono/stereo toggle (header row), then the root-marker strip.
if (havePick) {
const Rect area{bands.content.left, setupTop, bands.content.right, bands.content.bottom};
// S7: a click on a channel-mode segment sets the instance mode (setChannelMode
// re-negotiates the bus + reloads; a no-op set for the already-active mode is ignored
// by the processor). Snapshot the new mode locally so the paint reflects it at once.
const ChannelToggleRects chan = channelToggleRects(area);
if (contains(chan.mono, x, y)) {
channelMode_ = ChannelMode::Mono;
processor_->setChannelMode(ChannelMode::Mono);
invalidate();
return;
}
if (contains(chan.stereo, x, y)) {
channelMode_ = ChannelMode::Stereo;
processor_->setChannelMode(ChannelMode::Stereo);
invalidate();
return;
}
const Rect stripArea = setupStripArea(area);
const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height());
const int note = keyAtPoint(sl, x - stripArea.left, y - stripArea.top);