Robotstemme: Ring-modulasjon via OscillatorNode som modulerer GainNode.gain — gir metallisk, Dalek-aktig effekt. Justerbar frekvens (30–300 Hz) og modulasjonsdybde (0–100%). Monsterstemme: Egenutviklet AudioWorkletProcessor med phase vocoder for sanntids pitch-shifting. Bruker overlap-add med 2048-sample FFT og 4x overlap for ~42ms latens ved 48kHz. Pitch-faktor 0.5x–2.0x. UI: Effektvelger-knapper (Robot/Monster) i FX-seksjon per kanal, med fargekodede parametersliders som vises når effekten er aktiv. On/off-state synkroniseres via STDB toggle_effect, parametere er per-klient (ulike brukere kan ha forskjellige monitorinnstillinger). STDB: Lagt til set_effect_param reducer for fremtidig param-synk (krever spacetime CLI for publish — ikke deployet ennå). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
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
- Brukeren åpner studioet/møterommet og kobler seg til LiveKit-rommet.
- I bunnen av skjermen vises en mixerstripe med én kanal per deltaker.
- Hver kanal har: volumslider, mute-knapp, og valgfri effektkjede.
- Over mixeren ligger et pad-brett med konfigurerbare lydeffekter.
- 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 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.0–1.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 opprettelseset_gain(room_id, target_user_id, gain, updated_by)— clamped 0.0–1.5, viewer-sjekkset_mute(room_id, target_user_id, is_muted, updated_by)— viewer-sjekktoggle_effect(room_id, target_user_id, effect_name, updated_by)— JSON-toggledelete_mixer_channel(room_id, target_user_id)— opprydding ved disconnectset_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. HentMediaStreamTrackfra remote participant, opprettMediaStreamSourceNode, 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
phazeellerSoundTouchJSAudioWorklet - 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 tilGainNode.gainAudioParam- 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-clientintegrasjon 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 → GainNode.gain, frekvens 30–300Hz, dybde 0–100%)
- Monsterstemme (pitch shift via egenutviklet AudioWorkletProcessor med phase vocoder, pitch 0.5–2.0x)
- Effektvelger-UI per kanal (Robot/Monster-knapper med FX-seksjon, fargekodede parametersliders)
- 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)
AudioContextmå opprettes fra brukergest (klikk/tap) — nettleserkrav- Pad-lydfiler lagres i CAS via eksisterende
upload_mediaintention - 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