# Feature: Lydmixer (Virtuelt studioverktøy)
**Filsti:** `docs/features/lydmixer.md`

## 1. Konsept
En nettleserbasert lydmixer inspirert av RødeCaster Pro II. Gir programledere
og møtedeltakere kontroll over lydnivåer, lydeffekter og stemmeeffekter
direkte i Synops — uten behov for ekstern maskinvare.

Bygget på Web Audio API med LiveKit-integrasjon. Alle lydstrømmer (mikrofon,
remote-deltakere, sound pads) rutes gjennom en Web Audio-graf som gir
per-kanal prosessering før avspilling.

## 2. Brukeropplevelse
1. Brukeren åpner studioet/møterommet og kobler seg til LiveKit-rommet.
2. I bunnen av skjermen vises en **mixerstripe** med én kanal per deltaker.
3. Hver kanal har: volumslider, mute-knapp, og valgfri effektkjede.
4. Over mixeren ligger et **pad-brett** med konfigurerbare lydeffekter.
5. En **effektvelger** per kanal lar brukeren slå av/på stemmeeffekter
   (monster, robot) og lydbehandling (fat bottom, exciter, sparkle).

## 3. Kjernekomponenter

### 3.1 Kanalstripe (per deltaker)
Hver LiveKit-deltaker får en dedikert kanalstripe i mixeren:

| Element | Funksjon | Web Audio |
|---|---|---|
| **Volumslider** | Visuell fader 0–150% | `GainNode` (0.0–1.5) |
| **Nød-mute** | Stor, rød knapp — umiddelbar demping | `gain.setValueAtTime(0, now)` |
| **Nivåmeter** | VU-meter som viser live lydnivå | `AnalyserNode` → canvas |
| **Navnelabel** | Deltakerens display_name | Fra LiveKit participant |

### 3.2 Effektkjede (per kanal, valgfri)
Hver kanal kan ha en kjede av prosesseringsmoduler som slås av/på individuelt:

| Effekt | Beskrivelse | Web Audio |
|---|---|---|
| **Fat bottom** | Lavfrekvent fylde (~200Hz, +6–12dB) | `BiquadFilterNode` lowshelf |
| **Exciter** | Harmonisk tilstedeværelse (3–5kHz) | `WaveShaperNode` + highshelf |
| **Sparkle** | Høyfrekvent luft (~10–16kHz, +3–6dB) | `BiquadFilterNode` highshelf |
| **Monsterstemme** | Pitch ned 4–8 halvtoner | `AudioWorkletNode` (phase vocoder) |
| **Robotstemme** | Metallisk ring-modulasjon | `OscillatorNode` → `GainNode.gain` |

Signalflyt per kanal:
```
Kilde → HighPass(80Hz) → FatBottom → Exciter → Sparkle → RobotMod → PitchShift → GainNode(fader) → Master
```

### 3.3 Sound Pads
Et grid med konfigurerbare lyd-pads (inspirert av RødeCaster Pro II sine 8×8 pads):

| Egenskap | Detalj |
|---|---|
| **Layout** | Grid, f.eks. 4×2 (utvidbart) |
| **Avspilling** | Trykk → spill fra start til slutt |
| **Lydkilde** | Forhåndslastede `AudioBuffer` fra CAS |
| **Volum** | Egen `GainNode` per pad |
| **Synkronisering** | LiveKit Data Message → alle klienter spiller samtidig |
| **Konfigurasjon** | Velg lydfil, farge, label per pad |

Eksempler på standard-pads: jingle/intro, applaus, latter, dramatisk pause,
"breaking news"-sting, rim shot, sad trombone, airhorn.

### 3.4 Delt mixer-kontroll (flerbruker)
Alle deltakere i rommet kan se og bruke mixeren samtidig. Mixer-state
synkroniseres i sanntid via PG LISTEN/NOTIFY + WebSocket, slik at
volumendringer, mutes, effekttogles og pad-avspilling reflekteres hos
alle klienter umiddelbart.

| Element | Synkronisering |
|---|---|
| **Volumslider** | PG: `mixer_channels`-tabell med `gain`-verdi per kanal |
| **Mute** | PG: `is_muted` boolean per kanal |
| **Effekt av/på** | PG: `active_effects` JSON per kanal |
| **Pad-trigger** | LiveKit Data Message (lav latens) |
| **Pad-konfig** | Node metadata (persistent, sjelden endring) |

**Tilgangsnivåer:**
- **Full kontroll** (default): alle deltakere kan justere alle kanaler,
  trigge pads og endre effekter. Tilsvarer at alle sitter rundt samme
  RødeCaster.
- **Begrenset**: eier/admin kan låse enkeltdeltakere til kun "viewer" —
  de ser mixeren live men kan ikke interagere. Bruker eksisterende
  rolle-system (owner/admin/member-edges).

**Konflikthåndtering:** Last-write-wins. Volumslidere er kontinuerlige
verdier som oppdateres via maskinrommet. Ved samtidig endring av samme
kanal vinner siste skriving — i praksis uproblematisk fordi endringer
er visuelt synlige for alle og deltakerne koordinerer naturlig.

**PG-tabell:** `mixer_channels` med NOTIFY-trigger for sanntidspropagering via WebSocket.

**API-endepunkter (maskinrommet):**
- `POST /intentions/create_mixer_channel` — idempotent opprettelse
- `POST /intentions/set_gain` — clamped 0.0–1.5, viewer-sjekk
- `POST /intentions/set_mute` — viewer-sjekk
- `POST /intentions/toggle_effect` — JSON-toggle
- `POST /intentions/set_mixer_role` — sett editor/viewer

Mixer-kanaler ryddes automatisk ved `close_live_room` og `clear_all`.

### 3.5 Master-seksjon
| Element | Funksjon |
|---|---|
| **Master fader** | Samlet utgangsnivå |
| **Master mute** | Demper all lyd |
| **Master VU** | Stereo nivåmeter for utgangen |

## 4. Teknisk arkitektur

### 4.1 Web Audio-graf
```
                    ┌─────────────────────────────────┐
                    │         AudioContext             │
                    │                                  │
  Remote Track 1 ──→ MediaStreamSource → EffektKjede → GainNode ─┐
  Remote Track 2 ──→ MediaStreamSource → EffektKjede → GainNode ─┤
  Lokal mikrofon ──→ MediaStreamSource → EffektKjede → GainNode ─┼→ MasterGain → destination
  Sound Pad      ──→ AudioBufferSource ─────────────→ GainNode ─┘
                    │                                  │
                    └─────────────────────────────────┘
```

### 4.2 LiveKit-integrasjon
- **Innkommende lyd:** Deaktiver LiveKit sin auto-attach av `<audio>`-elementer.
  Hent `MediaStreamTrack` fra remote participant, opprett
  `MediaStreamSourceNode`, rut gjennom Web Audio-kjeden.
- **Utgående lyd:** Mikrofon → Web Audio-kjede → `MediaStreamAudioDestinationNode`
  → publiser den prosesserte tracken via LiveKit.
- **Sound pads over nett:** Send LiveKit Data Message (reliable) med pad-ID
  og tidsstempel. Alle klienter trigge samme pad lokalt. Akseptabel synk: <50ms.

### 4.3 Stemmeeffekter (AudioWorklet)
Pitch-shifting (monsterstemme) krever en `AudioWorkletProcessor` som kjører
på lyd-tråden:
- **Algoritme:** Phase vocoder med identity phase locking
- **Bibliotek:** Vurder `phaze` eller `SoundTouchJS` AudioWorklet
- **Latens:** ~20–40ms avhengig av FFT-vindu (1024–2048 samples ved 48kHz)
- **Parametere:** Pitch-faktor (0.5 = oktav ned, 0.7 = monster, 1.0 = normal)

Robot-stemme bruker ring-modulasjon — ingen AudioWorklet nødvendig:
- `OscillatorNode` (sinus, 50–200Hz) kobles til `GainNode.gain` AudioParam
- Stemmekilden kobles til samme `GainNode`
- Resultatet er metallisk, Dalek-aktig stemme

### 4.4 Pad-konfigurasjon (lagring)
Sound pads lagres i samlingsnoden sin metadata:
```json
{
  "traits": ["recording", "mixer"],
  "mixer": {
    "pads": [
      { "label": "Jingle", "cas_hash": "abc123...", "color": "#FF6B6B" },
      { "label": "Applaus", "cas_hash": "def456...", "color": "#4ECDC4" }
    ]
  }
}
```

## 5. Avhengigheter

| Avhengighet | Type | Lisens | Formål |
|---|---|---|---|
| `livekit-client` | npm | Apache-2.0 | WebRTC-klient for LiveKit |
| Web Audio API | nettleser | — | All lydprosessering (innebygd) |
| CAS | eksisterende | — | Lagring av pad-lydfiler |

**Ingen betalte tjenester.** All lydprosessering skjer lokalt i nettleseren
via Web Audio API. Stemmeeffekter (robot, monster) og EQ (fat bottom, exciter,
sparkle) bygges med innebygde Web Audio-noder og en egenutviklet
`AudioWorkletProcessor` for pitch shifting. Phase vocoder-algoritmen er
veldokumentert og implementeres direkte — ingen tredjepartslisens nødvendig.

## 6. Node/trait-integrasjon
Lydmixeren aktiveres via `mixer`-traitet på en samlings-node. Krever at
`recording`-traitet også er aktivt (LiveKit-avhengighet).

- **Trait:** `mixer` (kategori: Lyd & video)
- **Frontend:** MixerTrait-komponent i samlingssiden
- **Backend:** Ingen nye endepunkter — bruker eksisterende LiveKit token-generering
  og CAS for pad-lydfiler

## 7. Avgrensning
- Mixeren er for **live-bruk**, ikke postproduksjon/redigering
- Effektene prosesseres **lokalt** i nettleseren — ikke på serveren
- Hver bruker kontrollerer sin egen mixer (personlig mix)
- Pad-synkronisering er best-effort (~50ms) — akseptabelt for lydeffekter
- Støtter ikke multitrack-opptak (det er en separat feature)

## 8. Utviklingsfaser

### Fase A: Grunnleggende mixer (MVP)
- [x] `livekit-client` integrasjon i frontend (koble til rom, vise deltakere)
- [x] Web Audio-graf: MediaStreamSource per remote track → GainNode → destination
- [x] Kanalstripe-UI: volumslider + mute-knapp per deltaker
- [x] VU-meter (AnalyserNode → canvas/CSS)
- [x] Master fader og master mute

### Fase B: Delt mixer-kontroll
- [x] PG: `mixer_channels`-tabell + NOTIFY-trigger + maskinrommet-endepunkter
- [x] Frontend abonnerer på mixer-state, oppdaterer Web Audio-graf ved endringer
- [x] Visuell feedback: alle ser sliders bevege seg i sanntid
- [x] Tilgangskontroll: eier/admin kan sette deltaker til "viewer" (kun observere)

### Fase C: Sound Pads
- [x] Pad-grid UI (4×2 grid med fargede knapper)
- [x] Last lydfiler fra CAS → AudioBuffer
- [x] Avspilling ved trykk (AudioBufferSourceNode)
- [x] Pad-konfigurasjon: velg lydfil, farge, label (lagres i node metadata)
- [x] LiveKit Data Message for synkronisert avspilling på tvers av deltakere

### Fase D: Lydbehandling (EQ)
- [x] Fat bottom (lowshelf filter, +8dB @ 200Hz)
- [x] Sparkle (highshelf filter, +4dB @ 10kHz)
- [x] Exciter (WaveShaperNode soft-clip + highshelf @ 3.5kHz)
- [x] Per-kanal av/på-toggles for hver effekt (synkronisert via PG `active_effects`)
- [x] Preset-konfigurasjon: "Av", "Podcast-stemme" (bass+luft), "Radio-stemme" (bass+luft+exciter)
- [x] Highpass-filter (80Hz) alltid aktiv for rumble-fjerning

### Fase E: Stemmeeffekter
- [x] Robotstemme (ring-modulasjon med OscillatorNode → GainNode.gain, frekvens 30–300Hz, dybde 0–100%)
- [x] Monsterstemme (pitch shift via egenutviklet AudioWorkletProcessor med phase vocoder, pitch 0.5–2.0x)
- [x] Effektvelger-UI per kanal (Robot/Monster-knapper med FX-seksjon, fargekodede parametersliders)
- [x] Parameterjustering (pitch-faktor, oscillator-frekvens, modulasjonsdybde)

## 9. Instruks for Claude Code
- Lydmixeren er **ren frontend** — ingen nye Rust-endepunkter nødvendig
- Bruk Web Audio API, IKKE `<audio>`-elementer for prosessering
- AudioWorklet for pitch shifting — aldri ScriptProcessorNode (deprecated)
- `AudioContext` må opprettes fra brukergest (klikk/tap) — nettleserkrav
- Pad-lydfiler lagres i CAS via eksisterende `upload_media` intention
- Pad-konfigurasjon i samlingsnoden sin `metadata.mixer`-nøkkel
- Stemmeeffekter prosesseres lokalt — ikke send prosessert lyd over LiveKit
  med mindre brukeren eksplisitt vil at andre skal høre effekten
