synops/docs/features/lydmixer.md
vegard 171c9b991a Fullfører oppgave 16.6: EQ-effektkjede med per-kanal toggles og presets
Implementerer tre EQ-effekter i Web Audio-grafen:
- Fat bottom: BiquadFilterNode lowshelf +8dB @ 200Hz
- Sparkle: BiquadFilterNode highshelf +4dB @ 10kHz
- Exciter: WaveShaperNode (soft-clip saturation) + highshelf +4dB @ 3.5kHz
- Highpass 80Hz alltid aktiv for rumble-fjerning

Signalkjede per kanal:
  Source → Analyser → HighPass → FatBottom → Exciter → Sparkle → Gain → Master

Per-kanal toggles synkroniseres via STDB toggle_effect reducer
(allerede implementert i fase B). UI viser fargede toggle-knapper
og preset-velger (Av, Podcast-stemme, Radio-stemme).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 05:24:24 +00:00

11 KiB
Raw Blame History

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 OscillatorNodeGainNode.gain

Signalflyt per kanal:

Kilde → HighPass(80Hz) → FatBottom → Exciter → Sparkle → 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 SpacetimeDB, slik at volumendringer, mutes, effekttogles og pad-avspilling reflekteres hos alle klienter umiddelbart.

Element Synkronisering
Volumslider STDB: MixerChannel-tabell med gain-verdi per kanal
Mute STDB: is_muted boolean per kanal
Effekt av/på STDB: 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 STDB-reducers. Ved samtidig endring av samme kanal vinner siste skriving — i praksis uproblematisk fordi endringer er visuelt synlige for alle og deltakerne koordinerer naturlig.

SpacetimeDB-tabeller:

#[spacetimedb::table(accessor = mixer_channel, public)]
pub struct MixerChannel {
    #[primary_key]
    pub id: String,              // "{room_id}:{target_user_id}"
    pub room_id: String,         // "communication_{node_uuid}"
    pub target_user_id: String,  // hvem kanalen tilhører
    pub gain: f64,               // 0.01.5
    pub is_muted: bool,
    pub active_effects: String,  // JSON: {"fat_bottom": true, "robot": false, ...}
    pub role: String,            // "editor" | "viewer" — tilgangskontroll per kanal
    pub updated_by: String,      // hvem som sist endret
    pub updated_at: Timestamp,
}

STDB Reducers:

  • create_mixer_channel(room_id, target_user_id, updated_by) — idempotent opprettelse
  • set_gain(room_id, target_user_id, gain, updated_by) — clamped 0.01.5, viewer-sjekk
  • set_mute(room_id, target_user_id, is_muted, updated_by) — viewer-sjekk
  • toggle_effect(room_id, target_user_id, effect_name, updated_by) — JSON-toggle
  • delete_mixer_channel(room_id, target_user_id) — opprydding ved disconnect
  • set_mixer_role(room_id, target_user_id, role, updated_by) — 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: ~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:

{
  "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)

  • livekit-client integrasjon i frontend (koble til rom, vise deltakere)
  • Web Audio-graf: MediaStreamSource per remote track → GainNode → destination
  • Kanalstripe-UI: volumslider + mute-knapp per deltaker
  • VU-meter (AnalyserNode → canvas/CSS)
  • Master fader og master mute

Fase B: Delt mixer-kontroll

  • SpacetimeDB: MixerChannel-tabell + reducers (set_gain, set_mute, toggle_effect, set_mixer_role)
  • Frontend abonnerer på mixer-state, oppdaterer Web Audio-graf ved endringer
  • Visuell feedback: alle ser sliders bevege seg i sanntid
  • Tilgangskontroll: eier/admin kan sette deltaker til "viewer" (kun observere)

Fase C: Sound Pads

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

Fase D: Lydbehandling (EQ)

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

Fase E: Stemmeeffekter

  • Robotstemme (ring-modulasjon med OscillatorNode)
  • Monsterstemme (pitch shift via egenutviklet AudioWorklet)
  • Effektvelger-UI per kanal
  • Parameterjustering (pitch-faktor, oscillator-frekvens)

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