Retarget overlay: temporarily narrow a strip's target to the active node #16
Labels
No labels
Agent
Ignore
Agent
In Progress
Agent
Planned
Agent
Planning
Agent
Ready
Agent
Triage
Compat
Breaking
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
lucas/nanoKontroller2#16
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
A strip's target is a
NodeMatcherpattern list, so one strip can cover a whole class of nodes — "all music players", say — and act on every match at once. That is the right default: the fader moves everything in the class.But sometimes you want the same strip to act on just the one that currently matters, without editing the config and without permanently giving up the broad target. "All music" should be able to become "only the music that is actually playing right now" for as long as you hold or latch a button, then go back.
This is also the cheapest answer to having more interesting nodes than physical strips: a strip can be pointed at something on demand instead of being statically assigned.
Scope
A button-driven overlay that temporarily replaces the set of nodes a strip acts on. While the overlay is active, the strip's other controls (fader, mute, …) operate on the overlaid set; when it is cleared, they return to the configured matcher.
Two modes of operation:
The second mode is the more clearly useful of the two and is strictly safer, since it can never bind the strip to something unrelated.
Surface area
NodeMatcherandAudioService::GetMatchingNodesalready produce the ranked match list; the overlay is a filter or replacement applied on top of that result (includes/NodeMatcher.h,includes/AudioService.h).IActionBase::MatchedNodes()/RepresentativeNode()are the single choke point every action reads its targets through (src/Actions/IAction.cpp), which makes them the natural place for an overlay to take effect.Open questions
AudioServiceneeds to expose and watch something new, is unverified.Acceptance
Out of scope
I think this might need thinking about a broader architectural scope, because the "it" in "retarget it" can mean different things in different use cases.
Currently, the entire config is effectively independent per control. E.g. "Mute1" is completely independent of "Fader1" and could target completely different applications. So in order for "retarget this strip" to work, we are lacking the ability to assign a single target to an entire strip.
Then another use case is "target the active one out of this group", where "this group" would likely be an already existing matcher. And it could be meant either to be scoped to a single control, e.g. "make Fader2 only target the active entity in its set", but could also mean "every control that uses the 'all music' set, target only the active one in that set".
But I think both can be accomplished reasonably well if the "FriendlyNames" feature was reworked conceptually. Rather than just making it aliases that are replaced in the config before evaluation, make them node groups as a living entity.
Then, if "retarget" was pointed at a node group, it would alter the group's state, and every control that uses the group would behave accordingly. E.g. retarget the 'all music' group to only the active one, then any control using that group would automatically also only target the active one.
Agreed, and I think the group reframe is stronger than the "strip" direction I wrote into the issue — it solves the coupling problem without inventing a strip concept at all.
The part that convinces me: one mechanism covers both scopes you describe. If every node-valued parameter resolves to a group, and an inline pattern list is just an anonymous group private to that action, then the scope of a retarget is decided purely by which group the button names:
retargetpointed at"all music"→ every control referencing that group narrows together;retargetpointed at Fader2's own (named, but used once) group → only Fader2 narrows.No
scope: control | groupparameter, no strip block. "Which controls move together" becomes a config-authoring decision — share a group name or don't — rather than a feature with two modes. That also makes the two modes in the issue collapse into one: "retarget to the system-wide active node" is just the same filter applied to a group whose candidate set is.*.What it means in the code
Today the alias is gone by the time anything runs:
ExpandFriendlyNamessplices alias entries into each action's parameter lists in-place, andIActionBase::Initthen builds aNodeMatchercopy from them (src/AppSettings.cpp,src/Actions/IAction.cpp:14-16). Making groups live means roughly:NodeMatcherplus mutable state (All/ActiveOnly/ pinned ids). It has to outlive the actions, so it sits next toAudioService, not in the parsedAppSettings; actions hold a non-owning handle resolved atInit.MatchedNodes()becomes "askAudioService::GetMatchingNodesfor the candidate set, then apply the group's current filter". Ranking and the representative stay where they are — the filter is a post-filter over the ranked id list, sofront()keeps its meaning.RegisterNodeListenersubscribes against the candidate matcher, which a state change does not alter. So no re-subscription churn — but a group state change does need a new notification edge (group → its subscriber actions) so every affected control re-reads and refreshes its LED, parallel to the existingon_node_available/on_node_removedpath.target:Action_Connectiontakesleft/right, andExpandFriendlyNamesalready expands every parameter indiscriminately.Mixed lists like
"target": ["all music", "firefox.*"]work today by splicing. Under groups the natural reading is "union of the resolved groups and the inline entries, each group contributing its filtered members, with expansion keeping its position so configured order still ranks" — worth confirming that's what you want, because it means one half of a target can be narrowed while the other isn't.This decouples #16 from #14
The issue text claims #16 and #14 should be designed together because both are blocked on actions being independent. With groups, that's only half true: they share a symptom, not a mechanism. #16's shared state is a target set, which groups own cleanly. #14's shared state is a continuous value (knob trim feeding the fader's mapping) — a group has nothing to say about it. So they want different solutions and can proceed independently.
What the reframe does not answer
Everything under "Open questions" that's about semantics rather than plumbing survives unchanged: what "active" means, whether node state transitions are observable through the current listener path, ties, momentary vs latched, and the nothing-active fallback.
It also adds some:
Proposal
Split it: the FriendlyNames → live node groups rework is a prerequisite with its own value (it's a config-semantics change with its own data model and compatibility story), and #16 becomes the retarget action on top of it. Want me to open that as a separate issue with the data model sketched out, and reduce #16's scope to the action plus the "active" semantics?