# Web OSC control surface (GrosMac → macm1 concert)

Date: 2026-06-27
Status: design approved (web · patterns + concert control · target macm1)
Scope: `web_realart/` (Node bridge + web UI) + `sound_algo/data_only/` (SC launchpad + control OSC)

## Goal

From a browser on GrosMac (or a tablet/phone on the LAN), a control surface that
(a) **launches** a bank of musical patterns/sequences/melodies and (b) **controls
the running concert** (next morceau, harmony, master FX) — all over OSC into the
**macm1** SC engine, layered on the live body-driven concert (same audio out).

## Architecture

```
Browser (GrosMac / tablet / phone, on the LAN)
   │  http + WebSocket  ->  http://supra-m1.local:<WEB_PORT>
   ▼
Node server  (runs ON macm1, extends web_realart)
   │  OSC/UDP  ->  127.0.0.1:57121   (local SC, no network hop)
   ▼
macm1 SC concert engine
```

- The Node server **runs on macm1**, co-located with the SC concert engine, so it
  forwards OSC to **localhost** (`127.0.0.1:57121`) — no LAN hop, lowest latency.
  The browser (GrosMac or any LAN device) loads the page from
  `http://supra-m1.local:<WEB_PORT>`. Env `AVLIVE_SC_HOST` (default `127.0.0.1`),
  `AVLIVE_SC_PORT` (default `57121`), `AVLIVE_WEB_PORT` (page port); only set
  `AVLIVE_SC_HOST` if the bridge ever runs off-box.
- The concert + patterns play on macm1's audio. The browser is only the surface.

## A. SC launchpad (`sound_algo/data_only/launchpad.scd`, new, loaded last by boot)

A bank of **launchable** elements, each a `Pdef` armed/disarmed by OSC, all on a
shared `~lp[\clock]` (TempoClock, permanent, default 126 bpm) and — for pitched
ones — reading `~ccNote`/`~ccHarmony` (so they sit in the body-conducted key).

| name | kind | content |
|------|------|---------|
| `kick` | drums | 4-on-the-floor |
| `hats` | drums | offbeat / 16th hats |
| `clap` | drums | backbeat 2 & 4 |
| `perc` | drums | syncopated percussion |
| `sub` | bass | sustained root `~ccNote.(0,-1)` |
| `acid` | bass | 303-style 16th sequence over `~ccNote` degrees + accent/slide |
| `arp` | melody | 16th arpeggio over `~ccNote` chord tones |
| `lead` | melody | sparse melodic phrase from `~ccNote` |
| `stab` | chords | offbeat chord stab, `~ccNote` triad |
| `pad` | chords | sustained chord, `~ccNote` triad, timbre by `~ccHarmony[\pad]` |

Each pattern uses its own `\lp_<name>` SynthDef (one-shots `doneAction:2`; the pad
gated, released on disarm). `Out.ar(0,…)` → through the existing FX rack.

OSC (one OSCdef each, all guarded):
- `/launch <name> <0|1>` — arm (`Pdef(\lp_<name>).play(~lp[\clock])`) / disarm
  (`.stop`, pad lifts its gate). Unknown name → ignored.
- `/launch/tempo <bpm>` — `~lp[\clock].tempo = bpm/60` (clamp 60–200).
- `/launch/clear` — disarm all patterns.

`~lp[\armed]` (Set) tracks armed names so the UI can be re-synced and `clear` works.

## B. Concert / harmony / FX control OSC (in `launchpad.scd`, after the patterns)

Reuse the concert handles (`~ccHarmony`, `~ccNote`, `~ccMaster`, `~ccFire` from
`scene_concert.scd` + `concert_fx.scd`; load launchpad AFTER them). New OSCdefs:

- Concert: `/control/concertNext` (exists), `/control/doScene <name>` (exists).
- Harmony: `/control/harmony/root <delta>` (`~ccHarmony[\root]` += delta, clip
  33–57) · `/control/harmony/scale <name>` (set `~ccHarmony[\scale]`, validated
  against `~ccScales`) · `/control/harmony/octave <0|1>` · `/control/harmony/phrase <0–3>`.
- FX: `/control/fx/filter <0–1>` (`~ccMaster[\filterTo].(val.linexp(0,1,120,20000),0.2)`)
  · `/control/fx/stutter` (`~ccMaster[\stutterOn].(0.9)`) · `/control/fx/crash|kick|swell`
  (`~ccFire`) · `/control/fx/breakdown <0|1>` (1 → `filterTo(450,1.0)`, 0 → `filterTo(20000,1.5)`).

All nil-guard their handles (`~ccMaster !? {…}`) so launchpad still loads in tests
without the full concert.

## C. Node bridge (extend `web_realart/server.js`)

- Serve a new static page `public/control.html` (+ `control.js`, `control.css`).
- A WebSocket endpoint: on `{addr, args}` JSON, send an OSC message to
  `AVLIVE_SC_HOST:57121` (reuse web_realart's existing OSC client/lib; add a sender
  to the SC host if it only targets oscope today).
- Runs on macm1 alongside SC → OSC to localhost. Env: `AVLIVE_SC_HOST` (default
  `127.0.0.1`), `AVLIVE_SC_PORT` (default 57121), `AVLIVE_WEB_PORT` (page port).
- Keep the existing feeds functionality intact (additive only).

## D. Web UI (`web_realart/public/control.html` + `control.js` + `control.css`)

Single dark page, large touch targets, sections:
- **Patterns** — a toggle pad per element (lit = armed), grouped drums / bass /
  mélodies / accords. Click → `/launch <name> <0|1>`. A "Tout couper" → `/launch/clear`.
- **Concert** — "Morceau suivant" (`/control/concertNext`), scene buttons
  (`/control/doScene`), tempo slider (`/launch/tempo`).
- **Harmonie** — root −/+ (`/control/harmony/root ∓2`), scale buttons
  (minor/dorian/phrygian/penta), octave toggle, phrase 0–3.
- **FX** — filter fader (`/control/fx/filter`), one-shot pads stutter / crash /
  kick / swell, breakdown toggle.

Pad armed-state is tracked client-side (the SC engine is fire-and-forget). A small
"connecté / déconnecté" indicator on the WebSocket.

## Testing

- **SC** (headless on GrosMac, server output-only, no `timeout` → background+kill):
  balance `P:0 B:0 TLB:1`; load `launchpad.scd` after engine+fx+concert; assert the
  10 `\lp_*` SynthDefs compile and `~lp` exists; fire `/launch acid 1` (+ a synthetic
  `~ccHarmony`) and assert `Pdef(\lp_acid).isPlaying`; `/launch acid 0` stops it;
  `/launch/clear` empties `~lp[\armed]`; `/control/harmony/root 2` bumps the root;
  `/control/fx/crash` runs without error. No live-audio assertion (tuned at smoke).
- **Node** (vitest/node): a WS `{addr:"/launch",args:["kick",1]}` produces one OSC
  packet addressed to the configured host:port (mock the UDP socket).
- **Web UI**: manual — from GrosMac open `http://supra-m1.local:<port>/control/`
  (server on macm1), click pads, confirm OSC reaches the local SC (engine posts on `/launch`).
- **Live smoke**: GrosMac browser → arm drums + acid + arp, change harmony + fire
  FX, with the body-concert running on macm1; tune levels/tempo by ear.

## Out of scope (later, additive)

- Per-pattern level faders, pattern variations/fills, a step-sequencer grid editor,
  MIDI-clock sync, saving/recalling pad scenes. All add OSC routes + UI without
  touching the bridge contract.

## Implementation note (for the plan)

Decompose: (1) SC launchpad SynthDefs + `~lp` clock/registry + `/launch` arm/disarm
+ tempo/clear (drums first, pitched second). (2) Concert/harmony/FX control OSCdefs.
(3) Node WS⇄OSC bridge to macm1 (extend server.js, env host/port). (4) Web UI page
(patterns + concert + harmony + FX). (5) Live smoke. Each SC/Node step headless-
testable; UI + audio tuned at the smoke.
