import { useRef, useState } from "react"; import { Recorder } from "@/lib/audio"; import { addVoice } from "@/lib/idb"; import { encodeWav } from "@/lib/wav"; type Props = { onSaved: () => void; }; export default function VoiceComposer({ onSaved }: Props) { const fileRef = useRef(null); const recorderRef = useRef(null); const [recState, setRecState] = useState<"idle" | "recording" | "stopping" | "error">("idle"); const [name, setName] = useState(""); async function importBlob(blob: Blob, defaultName: string) { const arr = new Uint8Array(await blob.arrayBuffer()); const ctx = new AudioContext(); const buf = await ctx.decodeAudioData(arr.buffer.slice(0)); // Re-encode as 16-bit PCM mono WAV so the server (libsndfile) can decode it. const wav = encodeWav(buf); await addVoice({ name: name || defaultName || `voice-${Date.now()}`, blob: wav, sampleRate: buf.sampleRate, durationMs: Math.round(buf.duration * 1000), }); setName(""); onSaved(); } async function onFile(e: React.ChangeEvent) { const f = e.target.files?.[0]; if (!f) return; await importBlob(f, f.name.replace(/\.[^.]+$/, "")); e.target.value = ""; } async function startRec() { const r = new Recorder(); recorderRef.current = r; try { await r.start(); setRecState("recording"); } catch { setRecState("error"); } } async function stopRec() { setRecState("stopping"); const blob = await recorderRef.current?.stop(); setRecState("idle"); if (blob) await importBlob(blob, "recorded"); } return (
setName(e.target.value)} className="field-input" />
{recState === "recording" ? ( ) : ( )}
{recState === "error" && (

microphone permission denied

)}
); }