diff --git a/frontend/src/lib/components/traits/MixerTrait.svelte b/frontend/src/lib/components/traits/MixerTrait.svelte new file mode 100644 index 0000000..9ecc55f --- /dev/null +++ b/frontend/src/lib/components/traits/MixerTrait.svelte @@ -0,0 +1,291 @@ + + + + {#snippet children()} + {#if getStatus() !== 'connected'} + Koble til opptak-rommet for å bruke mixeren. + {:else if displayChannels.length === 0} + Ingen lydkanaler aktive. + {:else} + + + {#each displayChannels as identity (identity)} + {@const state = channelStates.get(identity)} + {@const levels = channelLevels.get(identity)} + {@const isMuted = state?.muted ?? false} + {@const gain = state?.gain ?? 1.0} + + + + + + + {displayName(identity)} + + + + + + + + + + handleGainChange(identity, parseFloat((e.target as HTMLInputElement).value))} + disabled={isMuted} + class="flex-1 h-2 accent-indigo-600 disabled:opacity-40" + /> + + {gainToPercent(gain)}% + + + + + handleEmergencyMute(identity)} + class="flex-shrink-0 rounded-lg px-3 py-2 text-xs font-bold uppercase tracking-wide transition-colors + {isMuted + ? 'bg-red-600 text-white shadow-md hover:bg-red-700' + : 'bg-red-100 text-red-700 hover:bg-red-200'}" + title={isMuted ? 'Slå på lyd' : 'Nøddemp'} + > + {isMuted ? 'DEMPET' : 'DEMP'} + + + + {/each} + + + + + + + Master + + + + + + + + + + handleMasterGainChange(parseFloat((e.target as HTMLInputElement).value))} + disabled={masterState.muted} + class="flex-1 h-2 accent-indigo-600 disabled:opacity-40" + /> + + {gainToPercent(masterState.gain)}% + + + + + + {masterState.muted ? 'DEMPET' : 'DEMP'} + + + + {/if} + {/snippet} + diff --git a/frontend/src/routes/collection/[id]/+page.svelte b/frontend/src/routes/collection/[id]/+page.svelte index 69fcb75..645baac 100644 --- a/frontend/src/routes/collection/[id]/+page.svelte +++ b/frontend/src/routes/collection/[id]/+page.svelte @@ -14,6 +14,7 @@ import RecordingTrait from '$lib/components/traits/RecordingTrait.svelte'; import TranscriptionTrait from '$lib/components/traits/TranscriptionTrait.svelte'; import StudioTrait from '$lib/components/traits/StudioTrait.svelte'; + import MixerTrait from '$lib/components/traits/MixerTrait.svelte'; import GenericTrait from '$lib/components/traits/GenericTrait.svelte'; import TraitAdmin from '$lib/components/traits/TraitAdmin.svelte'; import NodeUsage from '$lib/components/NodeUsage.svelte'; @@ -49,7 +50,7 @@ /** Traits with dedicated components */ const knownTraits = new Set([ 'editor', 'chat', 'kanban', 'podcast', 'publishing', - 'rss', 'calendar', 'recording', 'transcription', 'studio' + 'rss', 'calendar', 'recording', 'transcription', 'studio', 'mixer' ]); /** Traits that have a dedicated component */ @@ -169,6 +170,8 @@ {:else if trait === 'studio'} + {:else if trait === 'mixer'} + {/if} {/each} diff --git a/tasks.md b/tasks.md index e691ce2..d2b77ae 100644 --- a/tasks.md +++ b/tasks.md @@ -179,8 +179,7 @@ Ref: `docs/features/lydmixer.md` - [x] 16.1 LiveKit-klient i frontend: installer `livekit-client`, koble til rom, vis deltakerliste. Deaktiver LiveKit sin auto-attach av ``-elementer — lyd rutes gjennom Web Audio API i stedet. - [x] 16.2 Web Audio mixer-graf: opprett `AudioContext`, `MediaStreamSourceNode` per remote track → per-kanal `GainNode` → master `GainNode` → `destination`. `AnalyserNode` per kanal for VU-meter. -- [~] 16.3 Mixer-UI (MixerTrait-komponent): kanalstripe per deltaker med volumslider (0–150%), nød-mute-knapp (stor, rød), VU-meter (canvas/CSS), navnelabel. Master-fader og master-mute. Responsivt design (mobil: kompakt fader-modus). - > Påbegynt: 2026-03-18T04:54 +- [x] 16.3 Mixer-UI (MixerTrait-komponent): kanalstripe per deltaker med volumslider (0–150%), nød-mute-knapp (stor, rød), VU-meter (canvas/CSS), navnelabel. Master-fader og master-mute. Responsivt design (mobil: kompakt fader-modus). - [ ] 16.4 Delt mixer-kontroll via SpacetimeDB: `MixerChannel`-tabell + reducers (`set_gain`, `set_mute`, `toggle_effect`). Frontend abonnerer og oppdaterer Web Audio-graf ved endring fra andre deltakere. Visuell feedback (sliders beveger seg i sanntid). Tilgangskontroll: eier/admin kan sette deltaker til viewer-modus. - [ ] 16.5 Sound pads: pad-grid UI (4×2), forhåndslast lydfiler fra CAS til `AudioBuffer`. Avspilling ved trykk (`AudioBufferSourceNode`). Pad-konfig i `metadata.mixer.pads` (label, farge, cas_hash). Synkronisert avspilling via LiveKit Data Message. - [ ] 16.6 EQ-effektkjede: fat bottom (`BiquadFilterNode` lowshelf ~200Hz), sparkle (`BiquadFilterNode` highshelf ~10kHz), exciter (`WaveShaperNode` + highshelf). Per-kanal toggles, synkronisert via STDB. Presets (podcast-stemme, radio-stemme).
Koble til opptak-rommet for å bruke mixeren.
Ingen lydkanaler aktive.