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 type QuickStart = { name: string; expect: string; sigmaMs: number; map: PhaseMapSpec } const randomMap = (timeScaleMs: number | null): PhaseMapSpec => ({ kind: 'random', smoothBands: 8, timeScaleMs, seed: 1, strength: 1 }) const QUICK: QuickStart[] = [ { name: 'Fixed scramble, short window', expect: 'Expected inaudible: transients can only smear by a fraction of a millisecond.', sigmaMs: 0.5, map: randomMap(null) }, { name: 'Fixed scramble, medium window', expect: 'Borderline: attacks smear by a few milliseconds, near the reported threshold. Steady parts should sound the same.', sigmaMs: 5, map: randomMap(null) }, { name: 'Fixed scramble, long window', expect: 'Expected audible on attacks: they smear over tens of milliseconds. Steady parts should sound the same.', sigmaMs: 20, map: randomMap(null) }, { name: 'Slowly changing scramble', expect: 'Expected inaudible or nearly so: each frequency drifts by about 1 Hz.', sigmaMs: 5, map: randomMap(500) }, { name: 'Quickly changing scramble', expect: 'Expected audible: each frequency wobbles by tens of Hz, heard as warble or roughness.', sigmaMs: 5, map: randomMap(20) }, { name: 'Pure delay (control)', expect: 'B is A delayed by a little over a millisecond. Must be inaudible; a check on the pipeline and the listener.', sigmaMs: 5, map: { kind: 'delay', delayMs: 1.25 } }, ] const sameSpec = (a: PhaseMapSpec, b: PhaseMapSpec) => JSON.stringify(a) === JSON.stringify(b) /** 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(randomMap(null)) 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 const active = QUICK.find((q) => q.sigmaMs === sigmaMs && sameSpec(q.map, map)) return (

2. Choose how the phases are scrambled

The sound is split into overlapping frequency bands. Each band keeps its loudness over time but its phase is shifted by an amount that depends on the band and, optionally, on time. The bands are then added back together to make B.

Quick start: {QUICK.map((q) => ( ))}
{active ? active.expect : 'Custom settings.'}
{map.kind === 'random' && ( <> )} {map.kind === 'delay' && ( )} {map.kind === 'chirp' && ( )}
{map.kind === 'random' && (
{map.timeScaleMs !== null && ( )}
)} {result && result.maxMagDeviationDb > 1 && (
The loudness of some frequencies changes by up to {result.maxMagDeviationDb.toFixed(1)} dB, so B is not just a phase change of A. The pattern changes too quickly from one band to the next for this window: lengthen the time window, increase the smoothness, or reduce the delay.
)} {result && (
Show the phase pattern and the equivalent filter

A pattern that is fixed in time is an all-pass filter: it leaves every frequency's loudness alone and only delays each frequency by its group delay, the slope of the phase across frequency. Group delay peaks beyond about {THRESHOLD_MS} ms have been reported to be audible on impulsive sounds, and the achievable group delay grows with the time window. A pattern that changes in time shifts each band's frequency by the rate of change of its phase, which becomes audible on steady sounds once the shift approaches the pitch threshold, roughly 1 Hz for low tones.

Phase shift by frequency and time{result.timeVarying ? '' : ' (fixed in time)'} -180° to 180°
Loudness change by frequency{timeVarying ? ', at mid-clip' : ''} (should stay near 0 dB)
Group delay by frequency{timeVarying ? ', at mid-clip' : ''} (largest {result.maxGroupDelayMs.toFixed(2)} ms)
)}
) }