# Mixer tracks

The Mixer is the strip section of the right-side **Master Control** panel. One **MixerTrack** per channel — gain / mute / solo / sends + its own FX chain — just like a DAW mixer. The key idea: a MixerTrack is an **audio-side entity decoupled from the source that feeds it** (the Ableton / Logic / Bitwig pattern). Sources (a Builder, a Faust `.dsp`) route INTO a track; the track owns the fader, the inserts, the sends, and routes on to `master`.

> 🟦 **WHY tracks, not "one strip per module"?** A MixerTrack survives the lifecycle of whatever feeds it. Delete the Builder and the track keeps its fader position, FX chain, and sends — re-attach a new source and your mix investment is intact. (Earlier versions tied a strip to a `moduleId`; that's gone.) There is also no bundled "Default Instrument" anymore — the master bus starts empty until you add a source.

## Adding audio — the "+ ADD AUDIO" picker

Master Control starts with **no tracks**. You populate it explicitly:

- **"+ ADD AUDIO"** — lists the audio-shaped `.builder` DSPs in your **project** (Documents → PROJECT / `assets/`). Pick one; the IDE opens/mounts it as a Source and attaches its output to a MixerTrack. (FX-shaped DSPs — those with an `Input` node — appear under **"+ ADD FX"** instead and go to the Master-FX rack; see § 2.)

Attaching runs a **3-stage Auto-Create-mit-Reconnect resolver** to decide which track the source lands on:

1. **Persisted edge** — if this source was attached to a track before, that routing is restored.
2. **Name match** — an existing track whose name equals the source's title.
3. **Auto-create** — otherwise a fresh track is created (named after the source) and the source routes into it.

> 🟦 Merely running a Builder no longer auto-creates a track — `registerModuleOutput` just registers the source as routable. Only the **"+ ADD AUDIO"** flow (or its MCP mirror `mixer.tracks.input.set`) creates/attaches a track. This keeps the mixer from filling with strips you didn't ask for.

## Anatomy of a track strip

Top-to-bottom:

- **Label** — the track name (defaults to the attached source's title; rename via `mixer.tracks.rename`).
- **FX chain** — per-track insert slots (see *Per-track FX* below). Distinct from the global Master-FX rack in § 2.
- **Send sliders** — one per Master-FX *send* slot, a level into that send bus (default 0).
- **Mute / Solo** — standard mixer semantics. **Solo wins**: when ANY track is soloed, only soloed tracks pass; un-soloed tracks are silenced regardless of their own mute. Multiple solos allowed.
- **Gain fader** — linear 0..1 (unity = 1.0), 5 ms ramp so live moves don't click. Display dB via `20 · log10(gain)`. Per-track L/R can be split (`ganged: false`) for balance.
- **Per-track meter** — post-gain peak of this track's contribution to master.
- **× delete** — removes the track (its inbound sources become unrouted, audio keeps running but silent until re-attached).

Track state (`gain` / `muted` / `soloed` / `fxSlots` / `sendLevels` / `routesTo`) persists in the project's `mixerTracks` slice of project.json, so your mix comes back on reload.

## Per-track FX

Each track has its own insert chain, independent of the global Master-FX rack. Append a Faust-DSP slot to a track via `mixer.tracks.fx.add` (compiled asynchronously — the slot appears immediately, wires in once compile succeeds), toggle bypass via `enabled`, set params via `mixer.tracks.fx.set`. Use per-track FX for channel-strip processing (EQ/comp on one source); use the Master-FX rack (§ 2) for bus-wide processing.

## Driving the mixer from MCP

Precondition for the chain: at least one MixerTrack exists (add one via **"+ ADD AUDIO"** or `mixer.tracks.input.set` — a fresh project has none, and the template below targets track 0).

```yaml
actions:
  - action_id: surface.open
    input: { surface: faustwave-bundled/mixer }
    label: "▶ 0. Show Master Control (watch the faders move)"
  - action_id: system.state
    input: { sections: ["mixer_tracks"] }
    save_as: m
    label: "▶ 1. Inspect current tracks"
  - action_id: mixer.tracks.set
    input:
      track_id: "{{m.mixer_tracks.tracks.0.id}}"
      gain: 0.5
    label: "▶ 2. Drop the first track to −6 dB"
  - action_id: mixer.tracks.set
    input:
      track_id: "{{m.mixer_tracks.tracks.0.id}}"
      muted: true
    label: "▶ 3. Mute it"
  - action_id: mixer.tracks.clear
    input: { field: mutes }
    label: "■ Clear all mutes"
```

> 🟦 **HANDS-FREE PATH**: step 1 fetches the track list; step 2 sets a track's gain (5 ms ramp); step 3 mutes it; the last button un-mutes EVERYTHING via `mixer.tracks.clear { field: mutes }`. The chord-of-MUTES idiom beats iterating track-by-track when the user says "go back to normal".

### The MCP actions

| Action | Purpose |
|---|---|
| `mixer.tracks.create { name?, routesTo? }` | New track (name → `Track N`, routesTo → `master:in`). Returns its id. |
| `mixer.tracks.set { track_id, gain? / gain_l? / gain_r? / ganged? / muted? / soloed? / sends? }` | Update fields. `gain` ramps 5 ms; `sends` REPLACES the whole send-set. Solo wins over mute. |
| `mixer.tracks.rename { track_id, name }` | Rename (also used to keep track-name = source-title in sync). |
| `mixer.tracks.delete { track_id }` | Remove a track; inbound sources become unrouted. |
| `mixer.tracks.reorder { ids }` | Reorder the strip list to the given permutation. |
| `mixer.tracks.input.set { ... }` | MCP mirror of "+ ADD AUDIO" — attach a Source output to a track via the 3-stage resolver. |
| `mixer.tracks.fx.add / remove / set` | Per-track FX-chain slot CRUD. |
| `mixer.tracks.clear { field: mutes \| solos }` | Reset every track's mute or solo flag in one shot. |
| `system.state { sections: ["mixer_tracks"] }` | Snapshot: array of `{ id, name, gainL, gainR, ganged, muted, soloed, fxSlots, sendLevels, routesTo }`. |

> 🔘 A legacy `mixer.set { module_id, … }` + `system.state { sections: ["mixer"] }` slice still exist for per-Source-module Faust-param persistence (`slotParams`), but the **visible faders are MixerTracks** — reach for `mixer.tracks.*`.

## Sends — feeding a track into a Master-FX send slot

The Master-FX rack supports **inserts** (serial chain) and **sends** (parallel bus). Each track carries one **send level** per send slot — how much of THIS track to feed into that send bus:

```
1. master.fx.slots.list {}                          → slot ids (send slots included)
2. mixer.tracks.set {
     track_id: "<track id from system.state>",
     sends: { "<send slot id>": 0.4 }
   }
```

(Not a click-chain — the track id and send-slot id are yours to pick from step 1 and `system.state { sections: ["mixer_tracks"] }`.)

The `sends` map is **set as a whole** — omitting a slot removes its send. To keep a send wired but silent (for a smooth ramp-back-up), pass `0` for that slot id rather than omitting it.

## Common moves

- **"Hear only one track, mute the rest"** — `mixer.tracks.set { track_id, soloed: true }`. Solo wins.
- **"Add my Hub snare to the mix"** — install it (it lands in the project), then "+ ADD AUDIO" → pick it; a track auto-creates and the source routes in.
- **"Disarm all solos and mutes"** — `mixer.tracks.clear { field: mutes }` then `{ field: solos }`.
- **"Audition just one source"** — set it `soloed: true`, listen, then `mixer.tracks.clear { field: solos }`.

Next section: the Master-FX rack — inserts vs sends, slot management, per-param control.
