import { UNIFORM_STD, type PhaseMapSpec } from '../phasemap' import { phaseColor } from '../plot' import type { RenderResult } from '../types' import { ImagePlot } from './ImagePlot' import { LinePlot } from './LinePlot' type Props = { sigmaMs: number setSigmaMs: (v: number) => void map: PhaseMapSpec setMap: (m: PhaseMapSpec) => void fmax: number result: RenderResult | null } const SIGMA_LOG_MIN = Math.log10(0.3) const SIGMA_LOG_MAX = Math.log10(50) const TIME_LOG_MIN = 0 // 1 ms const TIME_LOG_MAX = Math.log10(3000) const sig3 = (x: number) => Number(x.toPrecision(3)) const THRESHOLD_MS = 1 /** Window width, phase map choice and parameters, and the plots that describe the resulting filter. */ export function AnalysisPanel({ sigmaMs, setSigmaMs, map, setMap, fmax, result }: Props) { const sigmaF = 1000 / (2 * Math.PI * sigmaMs) const setKind = (kind: PhaseMapSpec['kind']) => { if (kind === map.kind) return if (kind === 'random') setMap({ kind, smoothBands: 8, timeScaleMs: null, seed: 1, strength: 1 }) else if (kind === 'delay') setMap({ kind, delayMs: sig3(sigmaMs / 4) }) else setMap({ kind, delayAtNyquistMs: sig3(sigmaMs / 4) }) } const timeVarying = map.kind === 'random' && map.timeScaleMs !== null // a Gaussian-smoothed field with std s and time scale T has a derivative of std s / T const fmHz = map.kind === 'random' && map.timeScaleMs !== null ? (map.strength * UNIFORM_STD) / (2 * Math.PI * (map.timeScaleMs / 1000)) : 0 return (

Filter bank and phase map

The sound is split into overlapping Gaussian bands of width sigma in time (and 1 / (2 pi sigma) in frequency). Sound B is made by rotating the phase of each band by phi(f, t) and adding the bands back together, which leaves every band's envelope unchanged. A map that is fixed in time is an all-pass filter; its group delay, minus the slope of the phase in frequency, is what smears transients, and peaks beyond about {THRESHOLD_MS} ms are reported to be audible on impulsive sounds. A map that changes in time shifts the frequency of each band by the rate of change of its phase, which is audible on steady sounds once the shift approaches the pitch threshold. Because the map is sampled at the band centers, it must change by less than about a radian from one band to the next; the magnitude plot shows when it does not.

{map.kind === 'random' && ( <> seed {map.seed} )} {map.kind === 'delay' && ( )} {map.kind === 'chirp' && ( )}
{map.kind === 'random' && (
{map.timeScaleMs !== null && ( )}
)} {result && result.maxMagDeviationDb > 1 && (
The magnitude response deviates by up to {result.maxMagDeviationDb.toFixed(1)} dB, so B is not just a phase change of A. The map changes too quickly across neighbouring bands for this window: increase sigma, increase the smoothing, or reduce the delay.
)} {result && (
Phase map phi(f, t){result.timeVarying ? '' : ', fixed in time'} -180° to 180°
Magnitude response of the equivalent filter{timeVarying ? ' at mid-clip' : ''} (should stay near 0 dB)
Group delay{timeVarying ? ' at mid-clip' : ''} (largest {result.maxGroupDelayMs.toFixed(2)} ms)
)}
) }