synops/docs/features/lydmixer.md
vegard b5aa5bb243 Fjern SpacetimeDB komplett (oppgave 22.4)
SpacetimeDB er nå helt fjernet fra Synops. Sanntid håndteres av
PG LISTEN/NOTIFY + WebSocket i portvokteren (maskinrommet).

Kode fjernet:
- spacetimedb/ Rust-modul og spacetime.json
- maskinrommet/src/stdb.rs (HTTP-klient for STDB-reducers)
- frontend module_bindings/ (23 auto-genererte filer)
- spacetimedb npm-avhengighet fra package.json
- scripts/test-sanntid.sh (testet STDB-flyt)

Infrastruktur:
- Docker-container stoppet og fjernet fra docker-compose.yml
- Caddy: fjernet /spacetime/* reverse proxy
- maskinrommet-env.sh: fjernet STDB_IP og SPACETIMEDB_*-variabler
- .env.example: fjernet SpacetimeDB-seksjoner

Dokumentasjon oppdatert:
- CLAUDE.md: stack, lagmodell, kjerneprinsipper, driftsmodell
- docs/arkitektur.md: skrivestien, lesestien, datalag, teknologivalg
- docs/retninger/datalaget.md: migrasjonshistorikk, status "fjernet"
- 37 andre docs oppdatert (features, concepts, infra, ops, retninger)
- Alle kode-kommentarer med STDB-referanser oppdatert

Verifisert: maskinrommet bygger og starter OK, frontend bygger OK,
helsesjekk returnerer 200. Caddy reloadet.
2026-03-18 13:39:09 +00:00

234 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 0150% | `GainNode` (0.01.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, +612dB) | `BiquadFilterNode` lowshelf |
| **Exciter** | Harmonisk tilstedeværelse (35kHz) | `WaveShaperNode` + highshelf |
| **Sparkle** | Høyfrekvent luft (~1016kHz, +36dB) | `BiquadFilterNode` highshelf |
| **Monsterstemme** | Pitch ned 48 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.01.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
lyd-tråden:
- **Algoritme:** Phase vocoder med identity phase locking
- **Bibliotek:** Vurder `phaze` eller `SoundTouchJS` AudioWorklet
- **Latens:** ~2040ms avhengig av FFT-vindu (10242048 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, 50200Hz) 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 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 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 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 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 30300Hz, dybde 0100%)
- [x] Monsterstemme (pitch shift via egenutviklet AudioWorkletProcessor med phase vocoder, pitch 0.52.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` 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