import { useEffect, useMemo, useRef, useState } from "react"; import { encodeWav, prepareAudio, toAudioBuffer } from "@plate-lab/core"; export interface AudioPlayerProps { /** Pressure signal [Pa] */ p: Float64Array; fs: number; label?: string; /** Base name for the exported .wav file (no extension) */ filename?: string; } /** * Plays the radiated-pressure estimate once, exactly as long as it is, and * offers the same clip as a 16-bit mono WAV download. Playback and export level * are normalized; the displayed peak is not. A clip of a few milliseconds * sounds like a click, and is labelled as such rather than repeated. */ export function AudioPlayer({ p, fs, label, filename = "plate-sound" }: AudioPlayerProps) { const prep = useMemo(() => prepareAudio(p, fs), [p, fs]); const ctxRef = useRef(null); const srcRef = useRef(null); const [playing, setPlaying] = useState(false); useEffect(() => () => stop(), []); const stop = () => { srcRef.current?.stop(); srcRef.current = null; setPlaying(false); }; const play = () => { if (!ctxRef.current) ctxRef.current = new AudioContext(); const ctx = ctxRef.current; void ctx.resume(); stop(); const src = ctx.createBufferSource(); src.buffer = toAudioBuffer(ctx, prep); src.connect(ctx.destination); src.start(); src.onended = () => setPlaying(false); srcRef.current = src; setPlaying(true); }; const download = () => { // The exported file carries the same normalized signal that is played, at the // simulation's own sample rate; the peak in Pa is reported alongside it. const blob = new Blob([encodeWav(prep.samples, Math.round(prep.fs)) as BlobPart], { type: "audio/wav", }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${filename}.wav`; a.click(); setTimeout(() => URL.revokeObjectURL(url), 0); }; const short = prep.durationS < 0.1; return (
{(prep.durationS * 1000).toFixed(1)} ms at {fs} Hz, peak {prep.peakPa.toExponential(2)} Pa; normalized for playback, played once {short ? ". This excerpt is too short to hear as a note; lengthen the simulated window for a longer clip" : ""}
); }