Skip to content

15 — Views & the control model

Status · reconciled 2026-08-30. The touch/null-profile views and parameter assignments are implemented. The abstract physical-control, profile, takeover and feedback path is the active M6 work.

The Yamaha S90 is a proof-of-concept chassis, not the product. The system must adopt to other hardware — a future in-house control surface, a third-party MIDI controller, or no hardware at all (a plain touch display). This doc fixes the software architecture that makes the chassis disposable: an abstract control surface that both the touch display and the physical hardware (06) bind into. The app stays hardware-free; a thin per-chassis profile does the binding.

This is the canonical model for the interaction layer. It refines the screen list in 13 §3 (which named PERFORM/MIX/EDIT/BROWSE) into the three views below, and builds on the three-layer parameter model in 13 §4.

There are exactly three top-level views, chosen by hardware buttons and by an elegant switcher in the display header:

View Purpose Driven by
PERFORM The play surface — the control surface, rendered. Big, glanceable, distraction-free. What’s on screen most of a gig. Touch and hardware (§2).
MIXER Build & set up everything: per-slot plugin, level/pan/mute/solo, zone/split, inserts & sends, master/limiter, and arranging the PERFORM controls. Touch.
SETTINGS System: audio device, MIDI, the hardware profile, display, store/updates. Touch.

Header (every view): the view switcher (mirrors the hardware buttons + shows the current view), the current patch name + setlist ◀ ▶, and a global readout (voices / master meter). BROWSE — the full patch library + setlist editing — is a header-invoked overlay, not a fourth view; patch navigation is always in the header. A native plugin editor (16) is a modal setup workbench, also not a core view.

One focused slot ties the views together: in MIXER you focus a strip to configure it; in PERFORM the surface’s controls follow that focused slot. Changing focus is the single gesture that changes “what am I playing/editing.”

2. The control-surface model — the core abstraction

Section titled “2. The control-surface model — the core abstraction”

A control surface is an ordered, laid-out set of controls. Each control is:

Control = { kind: Knob | Fader | Button, target, label, behavior }
  • kind — how it renders and reads: a rotary Knob, a Fader, or a Button.
  • target — the bindable destination (a deliberately small set):
    • PluginParam { slot, id } → Command::SetSlotParam (12);
    • Strip { slot, Level | Pan | Mute | Solo } / Master { Level | Dim };
    • Action { PatchNext | PatchPrev | Panic | FocusSlot(n) | View(v) }.
  • behavior — range / taper / detents (continuous) or toggle / momentary (button).

PERFORM is a renderer of this model, and both touch and hardware are bindings into it. The model lives in auvra-core (platform-free — it names no sdl3 / i2c / plugin type). Turn a physical knob → its bound control moves → the target updates → the on-screen control reflects it, and vice-versa.

Crucially, the physical hardware is wired to the PERFORM surface permanently. Selecting MIXER or SETTINGS changes what the display shows, not what the knobs do — so a performer can always reach the live controls even while glancing at the mixer. (Whether the knobs go inert when PERFORM is off-screen is a small policy choice — default: stay live.)

The surface is defined by two mappings that are kept separate, which is what lets the same patch play on any chassis:

  1. Patch → controls — what is on the surface. Patch-owned (13 §4, layer 1). Default: a consistent layout whose content follows the focused slot — knob 1 is always “focused slot, macro 1”, so muscle memory holds and only the values change per patch. A fully per-patch layout is a power-user override, not the default.
  2. Hardware profile → physical bindings — which physical control drives which surface position. Per-chassis, lives in SETTINGS. Touch-only = the null profile (nothing bound; the screen is the whole surface) — which is also the Mac development path.
flowchart LR
    subgraph SURFACE["Control surface (auvra-core)"]
      direction TB
      M["controls[]<br/>Knob / Fader / Button<br/>+ target + behavior"]
    end
    HW["Physical controls<br/>(06: pots, motor-faders, buttons)"]
    CT["auvra-ctld<br/>(physical I/O only)"]
    PROFILE["Hardware profile<br/>(app/core mapping)"]
    TOUCH["Touch display<br/>(14: PERFORM renderer)"]
    ENG["Engine<br/>(12: SetSlotParam, strip, master)"]
    HW -->|"node state"| CT -->|"auvra-proto event"| PROFILE -->|"physical id -> position"| M
    TOUCH <-->|"draw + touch"| M
    M -->|"target -> command"| ENG
    ENG -.->|"value / meter feedback"| M
    M -.->|"target feedback"| PROFILE -.->|"auvra-proto output"| CT -.->|"LED ring, motor-fader"| HW
  • Pages / banks. A patch may expose more controls than the hardware has physical knobs. A button pages the physical controls across the surface; the touch display shows the whole surface at once. Build paging into the model, not as an afterthought.
  • Feedback & takeover. Motor-faders (06 §5) track target values directly — no jump. Non-motor pots need soft takeover1 (the value doesn’t move until the physical position crosses it) plus LED-ring feedback to show the real value. Each control therefore tracks a physical-vs-actual delta.
  • Bidirectional sync. Touch → target → hardware (LED / motor-fader); hardware → target → touch. One model, pushed both ways, is the single source of truth.
Piece Home
Control model + targets auvra-core (platform-free).
Patch-owned control layout extends the persist model (12 §3) — the patch stores its control assignments.
Hardware profile SETTINGS / app config backed by the platform-free model. auvra-ctld reports physical ids and applies output feedback but knows no patch, surface position or engine target.
Physical I/O + IPC auvra-ctld and auvra-proto (11).
PERFORM rendering auvra-ui (14) draws the surface from the model.
Phase Scope Status
Touch MVP (M3–M5) Three views, focused-slot PERFORM controls, live audio/MIDI settings, parameter reassignment and MIDI learn Done
Control transport (M6.1) Versioned ctld/app IPC, physical ids/raw values, node status, LED/fader feedback and reconnect Done
Target mapping (M6.2) Abstract control state, hardware profiles, paging, takeover and bidirectional target mapping Active
Power features Fully custom per-patch layouts and an arrangement UI beyond the focused-slot default Later

M6 must reuse the existing semantic targets and engine commands. Hardware input is another binding onto the same model, not a parallel UI or parameter path. The end-to-end acceptance test is defined in 10.

  1. Physical control inventory (knob / fader / button count) — unknown until the in-house hardware is designed. The whole point of this abstraction is to defer that decision without blocking the software.
  2. Scope of per-patch layout overrides, and the MIXER UI for arranging controls.
  3. Do the physical knobs go inert when PERFORM is off-screen, or stay live (default)? Decide alongside the physical inventory.
  4. Persistence format for the control layout — embedded in the patch, or a sidecar keyed by patch id?
  1. Soft takeover (a.k.a. pickup / catch): a non-motorized physical control does not change the target value until its position matches (crosses) the current value, preventing an audible jump when the hardware and the stored value disagree — after a patch switch, a page change, or a touch edit. ↩