import { useRef, useState, useEffect } from "react"; import { Play, Pause, Check, Volume2, Music2, X, Waves, Settings2 } from "lucide-react"; import { MG_MUSIC, SFX_STYLES } from "../remotion/mg/catalog.mjs"; import { useStore } from "../store"; type Track = { id: string; label: string; file: string; genre: string; mood: string; bpm: number; durationSec: number; blurb: string; gradient: string }; const TRACKS = MG_MUSIC as Track[]; type SfxStyle = { id: string; label: string; icon: string; blurb: string }; const STYLES = SFX_STYLES as SfxStyle[]; // a representative cue to preview each style (so the chip "sounds like" what it does) const STYLE_DEMO: Record = { designed: "swoosh", cinematic: "whoosh", clean: "pop", punchy: "impact", soft: "chime", retro: "glitch", off: "" }; // a deterministic static "waveform" silhouette for a card at rest (no audio analysis needed) const restBar = (i: number) => 22 + (Math.sin(i * 1.7) * 0.5 + 0.5) * 64 + ((i * 41) % 23); // The Background-music panel — browse the library, preview any track, pick one (or "No music"). // The chosen track loops softly under the whole video (wired in EditorComposition). export function MusicLibrary() { const music = useStore((s) => s.music); const musicVolume = useStore((s) => s.musicVolume); const setMusic = useStore((s) => s.setMusic); const setMusicVolume = useStore((s) => s.setMusicVolume); const musicFadeInMs = useStore((s) => s.musicFadeInMs); const musicFadeOutMs = useStore((s) => s.musicFadeOutMs); const setMusicFade = useStore((s) => s.setMusicFade); const sfxStyle = useStore((s) => s.sfxStyle); const setSfxStyle = useStore((s) => s.setSfxStyle); const [preview, setPreview] = useState(null); const audioRef = useRef(null); const sfxRef = useRef(null); const playSfx = (file: string) => { if (!file) return; try { sfxRef.current?.pause(); sfxRef.current = new Audio("/sfx/" + file + ".mp3"); sfxRef.current.volume = 0.85; void sfxRef.current.play(); } catch { /* ignore */ } }; const stop = () => { audioRef.current?.pause(); audioRef.current = null; setPreview(null); }; const togglePreview = (m: Track) => { if (preview === m.id) { stop(); return; } audioRef.current?.pause(); const a = new Audio("/music/" + m.file); a.loop = true; a.volume = 0.9; a.play().catch(() => { /* autoplay blocked until a gesture — this IS a gesture, so fine */ }); a.onended = () => setPreview(null); audioRef.current = a; setPreview(m.id); }; useEffect(() => () => { audioRef.current?.pause(); }, []); const selected = TRACKS.find((m) => m.id === music) || null; return (
Sound & Music
Control every scene's sound effects and the background track in one place.
{/* ── Motion-graphics SOUND STYLE — re-skins every scene's entrance sound at once ── */}
Motion sound
Every scene plays a sound on its entrance. Pick a style to re-skin them all — tap to hear it.
{STYLES.map((s) => { const on = (sfxStyle || "designed") === s.id; return ( ); })}
Fine-tune one scene: select it on the timeline → the inspector's Sound section (or click its 🔊 badge).
{/* ── BACKGROUND MUSIC ── */}
Background music — loops under the video
{/* No music */} {TRACKS.map((m) => { const isSel = music === m.id, isPlaying = preview === m.id; return (
setMusic(m.id)} title={`Use “${m.label}” — or drag onto the timeline`} draggable onDragStart={(e) => { e.dataTransfer.setData("application/x-elg-music", m.id); e.dataTransfer.effectAllowed = "copy"; }} className={"group relative cursor-grab overflow-hidden rounded-xl border transition active:cursor-grabbing " + (isSel ? "border-primary ring-1 ring-primary/50" : "border-border hover:border-primary/60")}>
{m.label}{isSel && }
{m.genre} · {m.mood}
{/* live equalizer / waveform */}
{Array.from({ length: 32 }).map((_, i) => ( ))}
{m.blurb} {m.bpm} BPM · {m.durationSec}s
); })}
{/* volume + status footer (only when a track is chosen) */} {selected && (
setMusicVolume(Number(e.target.value))} className="h-1 flex-1 accent-primary" /> {Math.round(musicVolume * 100)}
Fade in setMusicFade(Number(e.target.value), musicFadeOutMs)} className="h-1 flex-1 accent-primary" />{(musicFadeInMs / 1000).toFixed(1)}s
Fade out setMusicFade(musicFadeInMs, Number(e.target.value))} className="h-1 flex-1 accent-primary" />{(musicFadeOutMs / 1000).toFixed(1)}s
{selected.label} loops under your video, fading in & out. Pick “No music” to remove.
)}
); }