Goal
Extend the current Network UI (currently: Show Local, Show Remote, Broadcast) so remote waveforms can be enabled/disabled per channel (Ch 1–8).
Key behavior requirement: in Selected… mode, only selected channels are processed:
- non-selected channels are not stored, not displayed, and do not contribute to RMS or cancellation computations.
Selection is by channel index; the chooser should show label + color when available (from remote identity packets). Must respect plugin instance identity:
- packets are per
instanceID, but user selection is by instanceIndex (Ch 1–8)
- if multiple remote instances share a channel index, and that channel is enabled, they are all allowed through ingestion (unless we later add “pick one instance” behavior).
Current state (for reference)
src/PluginEditor.cpp: remoteDisplayToggle (“Show Remote”) currently:
- calls
audioProcessor.setReceiveEnabled(enabled)
- calls
scopeDisplay.setRemoteDisplayEnabled(enabled)
timerCallback() fetches remote infos + packets every frame and calls:
scopeDisplay.writeRemotePackets(m_remoteDataCache, m_remoteInfosCache);
ScopeDisplay draws remotes when m_showRemote is true.
UI changes
1) Replace “Show Remote” with Remote mode selector
Files
src/PluginEditor.h
src/PluginEditor.cpp
src/PluginEditor::resized() layout
UI
In Network group, replace:
juce::ToggleButton remoteDisplayToggle; // Show/hide remote waveforms
with:
juce::Label remoteModeLabel; text: "Remote:"
juce::ComboBox remoteModeCombo; items:
All
Selected…
None
juce::TextButton remoteChooseButton; text: "Choose…" (enabled only when mode == Selected…)
Behavior
- Selecting Selected… auto-opens the chooser (CallOutBox) anchored to either the combo or the Choose… button.
- Choose… reopens the chooser at any time in Selected mode.
Chooser implementation
2) Add a chooser component shown via CallOutBox
New file(s)
src/RemoteChannelChooser.h/.cpp (or keep local in PluginEditor.cpp if preferred)
Component
RemoteChannelChooserComponent : public juce::Component
- 8 rows (fixed): Ch 1..8
- Each row contains:
juce::ToggleButton enabled checkbox
- channel text (
Ch N)
- small colour swatch component (or a
TextButton with background colour)
- label text (read-only)
- Optional buttons:
All, None
Data in chooser
- Maintains/edits a
uint8_t remoteChannelMask (bits 0..7 => Ch1..Ch8).
- Shows label/color for each channel:
- Derived from the most recent
RemoteInstanceInfo for that channel (see “Identity mapping” below).
Identity mapping rule for display
- For each channel index N, pick the most recently seen online remote instance advertising N (by
RemoteInstanceInfo.lastSeenMs) and display its:
- label (
channelLabel)
- colour (
colourRGBA)
If no remote currently advertising that channel:
- display label as
"—" and swatch as neutral/dim.
State + storage
3) Add editor state fields
File: src/PluginEditor.h
Add:
enum class RemoteMode { All, Selected, None };
RemoteMode m_remoteMode = RemoteMode::All;
uint8_t m_remoteChannelMask = 0xFF; // default all enabled
uint8_t m_lastRemoteChannelMask = 0xFF;
(Optionally: persist via APVTS later; not required for first pass unless desired.)
Processing changes (core requirement)
4) Replace the old remoteEnabled toggle logic in timerCallback()
File: src/PluginEditor.cpp in timerCallback() near current block:
const bool remoteEnabled = remoteDisplayToggle.getToggleState();
if (remoteEnabled) {
...
scopeDisplay.writeRemotePackets(m_remoteDataCache, m_remoteInfosCache);
} else if (m_lastRemoteEnabled) {
scopeDisplay.clearRemoteInstances();
}
m_lastRemoteEnabled = remoteEnabled;
Replace with mode-based logic:
A) Mode == None
audioProcessor.setReceiveEnabled(false);
scopeDisplay.setRemoteDisplayEnabled(false);
- On transition into None:
scopeDisplay.clearRemoteInstances();
B) Mode == All
audioProcessor.setReceiveEnabled(true);
scopeDisplay.setRemoteDisplayEnabled(true);
- Ingest all as today:
- fetch infos + packets
scopeDisplay.writeRemotePackets(allPackets, allInfos);
C) Mode == Selected
audioProcessor.setReceiveEnabled(true); (multicast is global; we keep receiving)
scopeDisplay.setRemoteDisplayEnabled(true); (remote drawing enabled, but only selected will exist)
- Fetch infos + packets as today, but filter at ingestion:
Filtering
- Keep only packets whose
packet.instanceIndex (1..8) is enabled by m_remoteChannelMask
- Keep only
RemoteInstanceInfo whose info.instanceIndex is enabled by mask
- Pass filtered vectors to
writeRemotePackets()
Important: this ensures non-selected channels are never stored/processed and do not affect RMS/cancellation.
Clearing state when deselecting channels
5) Add ScopeDisplay API to clear by channel selection
Files
src/ScopeDisplay.h
src/ScopeDisplay.cpp
Add one of:
void clearRemoteChannelsNotInMask(uint8_t enabledMask);
- Deactivate/clear any remote instance slots whose channel index is not enabled.
- Also remove their entries from any remote-info map if applicable.
When to call
- Whenever
m_remoteChannelMask changes in Selected mode, call:
scopeDisplay.clearRemoteChannelsNotInMask(m_remoteChannelMask);
- Also call it when entering Selected mode (to ensure old non-selected data is purged).
This prevents stale data from lingering after a channel is turned off.
Instance identity considerations
6) Correctness rules
- Selection is by channel index (
instanceIndex), but packets/infos are per instanceID.
- Filtering MUST use:
RemoteRawPacket.instanceIndex
RemoteInstanceInfo.instanceIndex
- If multiple instances share a channel index and it’s enabled:
- allow them through ingestion (all processed).
- Self packets are already ignored in network receive (by
instanceID); keep that behavior.
Layout updates
7) Update resized() for the Network row
File: src/PluginEditor.cpp::resized()
Current row assigns bounds for:
- localDisplayToggle, remoteDisplayToggle, broadcastToggle
Update to include:
- localDisplayToggle
- remoteModeLabel + remoteModeCombo (+ remoteChooseButton if used)
- broadcastToggle
Keep overall width ~ similar to current remoteArea (currently 360px), adjust as needed.
Testing checklist
- Remote: None
- remote waveforms disappear
- remote slots cleared once (no lingering)
- CPU drops (no remote ingest)
- Remote: All
- behavior matches current “Show Remote ON”
- Remote: Selected…
- chooser opens
- unchecking a channel immediately removes it from display
- it does not return until re-enabled
- RMS/cancellation change as expected (deselected channels no longer influence them)
- Label/color in chooser
- when a remote is online on Ch N, chooser shows their label + colour
- if no remote online, shows placeholder
- Channel collisions
- if two remotes claim Ch 3 and Ch 3 enabled, both show/process (document this behavior)
additionall
- Persist
RemoteMode + remoteChannelMask in APVTS/state so it restores with the session.
- Add “All/None” buttons in chooser for quick toggling.
- Add tooltip text clarifying that filtering is by channel index.
Goal
Extend the current Network UI (currently: Show Local, Show Remote, Broadcast) so remote waveforms can be enabled/disabled per channel (Ch 1–8).
Key behavior requirement: in Selected… mode, only selected channels are processed:
Selection is by channel index; the chooser should show label + color when available (from remote identity packets). Must respect plugin instance identity:
instanceID, but user selection is byinstanceIndex(Ch 1–8)Current state (for reference)
src/PluginEditor.cpp:remoteDisplayToggle(“Show Remote”) currently:audioProcessor.setReceiveEnabled(enabled)scopeDisplay.setRemoteDisplayEnabled(enabled)timerCallback()fetches remote infos + packets every frame and calls:scopeDisplay.writeRemotePackets(m_remoteDataCache, m_remoteInfosCache);ScopeDisplaydraws remotes whenm_showRemoteis true.UI changes
1) Replace “Show Remote” with Remote mode selector
Files
src/PluginEditor.hsrc/PluginEditor.cppsrc/PluginEditor::resized()layoutUI
In Network group, replace:
juce::ToggleButton remoteDisplayToggle; // Show/hide remote waveformswith:
juce::Label remoteModeLabel;text:"Remote:"juce::ComboBox remoteModeCombo;items:AllSelected…Nonejuce::TextButton remoteChooseButton;text:"Choose…"(enabled only when mode == Selected…)Behavior
Chooser implementation
2) Add a chooser component shown via CallOutBox
New file(s)
src/RemoteChannelChooser.h/.cpp(or keep local in PluginEditor.cpp if preferred)Component
RemoteChannelChooserComponent : public juce::Componentjuce::ToggleButtonenabled checkboxCh N)TextButtonwith background colour)All,NoneData in chooser
uint8_t remoteChannelMask(bits 0..7 => Ch1..Ch8).RemoteInstanceInfofor that channel (see “Identity mapping” below).Identity mapping rule for display
RemoteInstanceInfo.lastSeenMs) and display its:channelLabel)colourRGBA)If no remote currently advertising that channel:
"—"and swatch as neutral/dim.State + storage
3) Add editor state fields
File:
src/PluginEditor.hAdd:
enum class RemoteMode { All, Selected, None };RemoteMode m_remoteMode = RemoteMode::All;uint8_t m_remoteChannelMask = 0xFF; // default all enableduint8_t m_lastRemoteChannelMask = 0xFF;(Optionally: persist via APVTS later; not required for first pass unless desired.)
Processing changes (core requirement)
4) Replace the old remoteEnabled toggle logic in timerCallback()
File:
src/PluginEditor.cppintimerCallback()near current block:Replace with mode-based logic:
A) Mode == None
audioProcessor.setReceiveEnabled(false);scopeDisplay.setRemoteDisplayEnabled(false);scopeDisplay.clearRemoteInstances();B) Mode == All
audioProcessor.setReceiveEnabled(true);scopeDisplay.setRemoteDisplayEnabled(true);scopeDisplay.writeRemotePackets(allPackets, allInfos);C) Mode == Selected
audioProcessor.setReceiveEnabled(true);(multicast is global; we keep receiving)scopeDisplay.setRemoteDisplayEnabled(true);(remote drawing enabled, but only selected will exist)Filtering
packet.instanceIndex(1..8) is enabled bym_remoteChannelMaskRemoteInstanceInfowhoseinfo.instanceIndexis enabled by maskwriteRemotePackets()Important: this ensures non-selected channels are never stored/processed and do not affect RMS/cancellation.
Clearing state when deselecting channels
5) Add ScopeDisplay API to clear by channel selection
Files
src/ScopeDisplay.hsrc/ScopeDisplay.cppAdd one of:
void clearRemoteChannelsNotInMask(uint8_t enabledMask);When to call
m_remoteChannelMaskchanges in Selected mode, call:scopeDisplay.clearRemoteChannelsNotInMask(m_remoteChannelMask);This prevents stale data from lingering after a channel is turned off.
Instance identity considerations
6) Correctness rules
instanceIndex), but packets/infos are perinstanceID.RemoteRawPacket.instanceIndexRemoteInstanceInfo.instanceIndexinstanceID); keep that behavior.Layout updates
7) Update resized() for the Network row
File:
src/PluginEditor.cpp::resized()Current row assigns bounds for:
Update to include:
Keep overall width ~ similar to current
remoteArea(currently 360px), adjust as needed.Testing checklist
additionall
RemoteMode+remoteChannelMaskin APVTS/state so it restores with the session.