/ proof-of-concept / plate-dynamics-lab-2
Sign in
proof-of-concept / plate-dynamics-lab-2
plate-dynamics-lab-2 / packages / components / src / audio / AudioPlayer.tsx
86 lines · 2.6 KBBlameHistoryRaw
1import { useEffect, useMemo, useRef, useState } from "react";
2import { encodeWav, prepareAudio, toAudioBuffer } from "@plate-dyn/core";
4export interface AudioPlayerProps {
5 /** Pressure signal [Pa] */
6 p: Float64Array;
7 fs: number;
8 /** Peak level of the export and the playback [dBFS]; default -1 */
9 normalizeDbfs?: number;
10 label?: string;
11 /** Base name for the exported .wav file (no extension) */
12 filename?: string;
15/**
16 * Plays the radiated-pressure estimate once, exactly as long as it is, and
17 * offers the same clip as a 24-bit mono WAV download, peak-normalized as the
18 * reference simulators write theirs. The displayed peak in pascals is not
19 * normalized.
20 */
21export function AudioPlayer({
22 p,
23 fs,
24 normalizeDbfs = -1,
25 label,
26 filename = "plate-sound",
27}: AudioPlayerProps) {
28 const prep = useMemo(
29 () => prepareAudio(p, fs, 10 ** (normalizeDbfs / 20)),
30 [p, fs, normalizeDbfs],
31 );
32 const ctxRef = useRef<AudioContext | null>(null);
33 const srcRef = useRef<AudioBufferSourceNode | null>(null);
34 const [playing, setPlaying] = useState(false);
35 useEffect(() => () => stop(), []);
37 const stop = () => {
38 srcRef.current?.stop();
39 srcRef.current = null;
40 setPlaying(false);
41 };
42 const play = () => {
43 if (!ctxRef.current) ctxRef.current = new AudioContext();
44 const ctx = ctxRef.current;
45 void ctx.resume();
46 stop();
47 const src = ctx.createBufferSource();
48 src.buffer = toAudioBuffer(ctx, prep);
49 src.connect(ctx.destination);
50 src.start();
51 src.onended = () => setPlaying(false);
52 srcRef.current = src;
53 setPlaying(true);
54 };
55 const download = () => {
56 const blob = new Blob([encodeWav(prep.samples, Math.round(prep.fs), 24) as BlobPart], {
57 type: "audio/wav",
58 });
59 const url = URL.createObjectURL(blob);
60 const a = document.createElement("a");
61 a.href = url;
62 a.download = `${filename}.wav`;
63 a.click();
64 setTimeout(() => URL.revokeObjectURL(url), 0);
65 };
67 return (
68 <div className="pl-row pl-small">
69 <button className="pl-btn" onClick={playing ? stop : play} disabled={p.length < 2}>
70 {playing ? "Stop" : `Play${label ? ` ${label}` : ""}`}
71 </button>
72 <button
73 className="pl-btn"
74 onClick={download}
75 disabled={p.length < 2}
76 title="Download as a 24-bit mono WAV file at the simulation's sample rate"
77 >
78 Save .wav
79 </button>
80 <span className="pl-muted">
81 {prep.durationS.toFixed(2)} s at {fs} Hz, peak {prep.peakPa.toExponential(2)} Pa; normalized
82 to {normalizeDbfs} dBFS for playback and export
83 </span>
84 </div>
85 );
↑↓ move↵openescclose