import { useCallback, useEffect, useState } from 'react' import type { View } from '../hooks/useZoomPan' import { formatSeconds } from '../plot' import type { RenderResult, Source } from '../types' import { SpectrogramPlot } from './SpectrogramPlot' import { WaveformPlot } from './WaveformPlot' type Props = { source: Source result: RenderResult stale: boolean playing: string | null play: (label: string, which: ('A' | 'B')[]) => void stop: () => void } const COLOR_A = '#1f5fa8' const COLOR_B = '#d9641e' const DB_RANGE = 70 /** Play A and B, and compare their waveforms and spectrograms. */ export function PlaybackPanel({ source, result, stale, playing, play, stop }: Props) { const fs = source.fs const duration = source.samples.length / fs const [view, setView] = useState({ start: 0, span: duration }) // A new clip resets the view; the same clip re-rendered with a new map keeps it. useEffect(() => { setView({ start: 0, span: source.samples.length / source.fs }) }, [source]) const onView = useCallback((v: View) => setView(v), []) let yMax = 0 for (let i = 0; i < source.samples.length; i++) yMax = Math.max(yMax, Math.abs(source.samples[i])) for (let i = 0; i < result.y.length; i++) yMax = Math.max(yMax, Math.abs(result.y[i])) yMax = Number(yMax.toPrecision(2)) || 1 const busy = playing !== null const zoomed = view.span < duration const highlight: [number, number] | undefined = zoomed ? [view.start, view.start + view.span] : undefined return (

Listen and compare

A is the source; B has the phase map applied. Both are played with the same gain{stale ? ' (B is being re-rendered)' : ''}. The waveforms differ by {Math.round(100 * result.relDiff)}% of A's RMS, while the spectrograms below, computed at the analysis resolution, should look the same.

{playing ? `Playing ${playing}…` : ''}
Scroll to zoom, drag to pan, double-click to reset. Both waveforms share the view. {zoomed ? `showing ${formatSeconds(view.span)} from ${formatSeconds(view.start)}` : `full clip, ${formatSeconds(duration)}`}
Spectrograms at the analysis resolution, {DB_RANGE} dB range, shared scale{zoomed ? '; the box marks the waveform view' : ''}
) }