Knob as per-strip volume trim / headroom for its fader #14

Open
opened 2026-11-12 23:06:03 +01:00 by omp-deck · 0 comments
Collaborator

Why

Each channel strip has both a fader and a knob, but a node only needs one volume control. The fader is the natural volume control, which leaves the knob unused.

Applications have wildly different output levels: one app is barely audible at fader top, another is painfully loud at a quarter travel. Today the only fix is a per-action pow curve in the config, which has to be guessed, edited, and reloaded.

Giving the knob the role of trim turns that into a physical, per-strip calibration: set the knob once so the fader's full travel covers the useful range for that app, then forget about it. The strip still has exactly one volume control — the fader — and the knob becomes calibration, not a second thing to keep in sync.

Scope

A continuous action where the knob above a strip scales the range its fader maps onto, for the same target node.

Two candidate semantics, to be decided:

  • Headroom: the knob sets the fader's top end; the fader maps 0 .. knob instead of 0 .. 1.
  • Offset trim: the knob applies a symmetric gain offset (e.g. ±6 dB) around the fader value, with the knob centered meaning "no trim".

The offset variant has the advantage of a well-defined neutral resting position, which matters because the knobs are absolute pots with no LED: a centered pointer visibly means "untrimmed". The headroom variant is simpler to reason about and cannot boost beyond unity.

Surface area

  • A new or extended continuous action type, plus its config parameters (trim range / curve).

  • The main obstacle: actions are currently independent. Each is constructed from its own config entry, keyed by a single NanoKontrol2::Control, and the only input it receives is on_midi(int value) (includes/Actions/IAction.h). There is no mechanism for the knob's action to influence the fader's action on the same strip.

    Possible directions:

    • One action instance owning both controls (registered for a fader and a knob CC), computing volume from the two latest values.
    • A shared per-strip state object both actions read and write.
    • A "strip" concept in the config: one block per channel declaring the target node plus the roles of its fader, knob, and buttons — instead of up to 24 independent entries that merely happen to name the same target. This is the most invasive option and changes the config format, but it also addresses the general awkwardness of expressing "this whole strip is about one node".
  • Volume is written via AudioService::SetVolume(name, float), which should be sufficient; no new audio API is expected to be needed. Whichever control moves, the resulting volume must be recomputed from both values and pushed, so the trim takes effect immediately rather than on the next fader move.

Open questions

  • Headroom or centered offset trim? (affects whether boost above unity is possible at all)
  • Does the trim compose with the existing pow curve, or replace it?
  • Should the knob's effect apply before or after the curve?
  • Is the trim purely in-memory, or should it be persisted so a strip keeps its calibration across restarts? Persisting it reintroduces the pot-desync problem: a saved value will not match the physical knob position on the next start.
  • Backwards compatibility: configs that map a knob directly to volume today must keep working.

Acceptance

  • A strip can be configured so that its knob trims its fader for the same target node.
  • Moving either control updates the node volume immediately and consistently.
  • The neutral knob position is documented and behaves identically to having no trim configured.
  • Existing configs that do not use the feature are unaffected.
  • README documents the new action/parameters.

Out of scope

  • Any other use of the knobs (pan, routing selectors, filter parameters, non-audio targets).
  • Soft-takeover / pickup behaviour for absolute pots. It is a related and real problem, but it applies to faders today just as much and should be decided separately.
## Why Each channel strip has both a fader and a knob, but a node only needs one volume control. The fader is the natural volume control, which leaves the knob unused. Applications have wildly different output levels: one app is barely audible at fader top, another is painfully loud at a quarter travel. Today the only fix is a per-action `pow` curve in the config, which has to be guessed, edited, and reloaded. Giving the knob the role of *trim* turns that into a physical, per-strip calibration: set the knob once so the fader's full travel covers the useful range for that app, then forget about it. The strip still has exactly one volume control — the fader — and the knob becomes calibration, not a second thing to keep in sync. ## Scope A continuous action where the knob above a strip scales the range its fader maps onto, for the same target node. Two candidate semantics, to be decided: - **Headroom**: the knob sets the fader's top end; the fader maps `0 .. knob` instead of `0 .. 1`. - **Offset trim**: the knob applies a symmetric gain offset (e.g. ±6 dB) around the fader value, with the knob centered meaning "no trim". The offset variant has the advantage of a well-defined neutral resting position, which matters because the knobs are absolute pots with no LED: a centered pointer visibly means "untrimmed". The headroom variant is simpler to reason about and cannot boost beyond unity. ## Surface area - A new or extended continuous action type, plus its config parameters (trim range / curve). - **The main obstacle**: actions are currently independent. Each is constructed from its own config entry, keyed by a single `NanoKontrol2::Control`, and the only input it receives is `on_midi(int value)` (`includes/Actions/IAction.h`). There is no mechanism for the knob's action to influence the fader's action on the same strip. Possible directions: - One action instance owning both controls (registered for a fader *and* a knob CC), computing volume from the two latest values. - A shared per-strip state object both actions read and write. - A "strip" concept in the config: one block per channel declaring the target node plus the roles of its fader, knob, and buttons — instead of up to 24 independent entries that merely happen to name the same target. This is the most invasive option and changes the config format, but it also addresses the general awkwardness of expressing "this whole strip is about one node". - Volume is written via `AudioService::SetVolume(name, float)`, which should be sufficient; no new audio API is expected to be needed. Whichever control moves, the resulting volume must be recomputed from both values and pushed, so the trim takes effect immediately rather than on the next fader move. ## Open questions - Headroom or centered offset trim? (affects whether boost above unity is possible at all) - Does the trim compose with the existing `pow` curve, or replace it? - Should the knob's effect apply before or after the curve? - Is the trim purely in-memory, or should it be persisted so a strip keeps its calibration across restarts? Persisting it reintroduces the pot-desync problem: a saved value will not match the physical knob position on the next start. - Backwards compatibility: configs that map a knob directly to `volume` today must keep working. ## Acceptance - A strip can be configured so that its knob trims its fader for the same target node. - Moving either control updates the node volume immediately and consistently. - The neutral knob position is documented and behaves identically to having no trim configured. - Existing configs that do not use the feature are unaffected. - README documents the new action/parameters. ## Out of scope - Any other use of the knobs (pan, routing selectors, filter parameters, non-audio targets). - Soft-takeover / pickup behaviour for absolute pots. It is a related and real problem, but it applies to faders today just as much and should be decided separately.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/nanoKontroller2#14
No description provided.