/ proof-of-concept / phase-perception-2
Sign in
proof-of-concept / phase-perception-2
phase-perception-2 / src / components / AnalysisPanel.tsx
194 lines · 9.0 KBCodeBlameHistory
2import { phaseColor } from '../plot'
7type Props = {
8 sigmaMs: number
9 setSigmaMs: (v: number) => void
10 map: PhaseMapSpec
11 setMap: (m: PhaseMapSpec) => void
12 fmax: number
13 result: RenderResult | null
16const SIGMA_LOG_MIN = Math.log10(0.3)
17const SIGMA_LOG_MAX = Math.log10(50)
19const TIME_LOG_MAX = Math.log10(3000)
21const THRESHOLD_MS = 1
23/** Window width, phase map choice and parameters, and the plots that describe the resulting filter. */
24export function AnalysisPanel({ sigmaMs, setSigmaMs, map, setMap, fmax, result }: Props) {
25 const sigmaF = 1000 / (2 * Math.PI * sigmaMs)
26 const setKind = (kind: PhaseMapSpec['kind']) => {
27 if (kind === map.kind) return
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 28 if (kind === 'random') setMap({ kind, smoothBands: 8, timeScaleMs: null, seed: 1, strength: 1 })
7d910acPhase perception 2: subband phase rotation of arbitrary sounds with filter bank, phase maps, plots, and playbackJeremy Magland 29 else if (kind === 'delay') setMap({ kind, delayMs: sig3(sigmaMs / 4) })
30 else setMap({ kind, delayAtNyquistMs: sig3(sigmaMs / 4) })
31 }
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 32 const timeVarying = map.kind === 'random' && map.timeScaleMs !== null
33 // a Gaussian-smoothed field with std s and time scale T has a derivative of std s / T
34 const fmHz = map.kind === 'random' && map.timeScaleMs !== null ? (map.strength * UNIFORM_STD) / (2 * Math.PI * (map.timeScaleMs / 1000)) : 0
36 return (
37 <section className="card">
38 <h2>Filter bank and phase map</h2>
39 <p className="hint">
40 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
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 41 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
42 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,
43 and peaks beyond about {THRESHOLD_MS} ms are reported to be audible on impulsive sounds. A map that changes in time shifts the
44 frequency of each band by the rate of change of its phase, which is audible on steady sounds once the shift approaches the pitch
45 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
46 magnitude plot shows when it does not.
48 <div className="global-controls">
49 <label>
50 <span>Window sigma</span>
51 <input
52 type="range"
53 className="wide-range"
54 min={SIGMA_LOG_MIN}
55 max={SIGMA_LOG_MAX}
56 step={0.01}
57 value={Math.min(SIGMA_LOG_MAX, Math.max(SIGMA_LOG_MIN, Math.log10(sigmaMs)))}
58 onChange={(e) => setSigmaMs(sig3(10 ** Number(e.target.value)))}
59 />
60 <input
61 type="number"
62 className="slider-value"
63 min={0.1}
64 max={200}
65 step="any"
66 value={sigmaMs}
67 onChange={(e) => {
68 const v = Number(e.target.value)
69 if (Number.isFinite(v) && v > 0) setSigmaMs(Math.min(200, Math.max(0.1, v)))
70 }}
71 />
72 <span className="slider-unit">ms</span>
73 <span className="info">
74 band width {sig3(sigmaF)} Hz{result ? `, ${result.bands} bands` : ''}
75 </span>
76 </label>
77 </div>
78 <div className="global-controls">
79 <label>
80 <span>Phase map</span>
81 <select value={map.kind} onChange={(e) => setKind(e.target.value as PhaseMapSpec['kind'])}>
82 <option value="random">Smoothed random</option>
83 <option value="delay">Pure delay (control)</option>
84 <option value="chirp">Chirp (group delay linear in frequency)</option>
85 </select>
86 </label>
87 {map.kind === 'random' && (
88 <>
89 <label>
7d910acPhase perception 2: subband phase rotation of arbitrary sounds with filter bank, phase maps, plots, and playbackJeremy Magland 91 <input type="range" min={1} max={40} step={1} value={map.smoothBands} onChange={(e) => setMap({ ...map, smoothBands: Number(e.target.value) })} />
92 <span className="slider-unit">
93 {map.smoothBands} bands ({sig3(map.smoothBands * sigmaF)} Hz)
94 </span>
95 </label>
96 <label>
97 <span>Strength</span>
98 <input type="range" min={0} max={2} step={0.05} value={map.strength} onChange={(e) => setMap({ ...map, strength: Number(e.target.value) })} />
99 <span className="slider-unit">{map.strength.toFixed(2)}</span>
100 </label>
101 <button onClick={() => setMap({ ...map, seed: (map.seed % 100000) + 1 })}>New random map</button>
102 <span className="info">seed {map.seed}</span>
103 </>
104 )}
105 {map.kind === 'delay' && (
106 <label>
107 <span>Delay</span>
108 <input type="range" min={-20} max={20} step={0.05} value={map.delayMs} onChange={(e) => setMap({ ...map, delayMs: Number(e.target.value) })} />
109 <input type="number" className="slider-value" step="any" value={map.delayMs} onChange={(e) => setMap({ ...map, delayMs: Number(e.target.value) || 0 })} />
110 <span className="slider-unit">ms</span>
111 </label>
112 )}
113 {map.kind === 'chirp' && (
114 <label>
115 <span>Group delay at Nyquist</span>
116 <input type="range" min={-20} max={20} step={0.05} value={map.delayAtNyquistMs} onChange={(e) => setMap({ ...map, delayAtNyquistMs: Number(e.target.value) })} />
117 <input type="number" className="slider-value" step="any" value={map.delayAtNyquistMs} onChange={(e) => setMap({ ...map, delayAtNyquistMs: Number(e.target.value) || 0 })} />
118 <span className="slider-unit">ms</span>
119 </label>
120 )}
121 </div>
123 <div className="global-controls">
124 <label>
125 <input type="checkbox" checked={map.timeScaleMs === null} onChange={(e) => setMap({ ...map, timeScaleMs: e.target.checked ? null : 200 })} />
126 <span>Fixed in time</span>
127 </label>
128 {map.timeScaleMs !== null && (
129 <label>
130 <span>Smoothing in time</span>
131 <input
132 type="range"
133 className="wide-range"
134 min={TIME_LOG_MIN}
135 max={TIME_LOG_MAX}
136 step={0.01}
137 value={Math.min(TIME_LOG_MAX, Math.max(TIME_LOG_MIN, Math.log10(map.timeScaleMs)))}
138 onChange={(e) => setMap({ ...map, timeScaleMs: sig3(10 ** Number(e.target.value)) })}
139 />
140 <input
141 type="number"
142 className="slider-value"
143 min={0.1}
144 step="any"
145 value={map.timeScaleMs}
146 onChange={(e) => {
147 const v = Number(e.target.value)
148 if (Number.isFinite(v) && v > 0) setMap({ ...map, timeScaleMs: v })
149 }}
150 />
151 <span className="slider-unit">ms</span>
152 <span className="info">typical frequency shift {sig3(fmHz)} Hz; the window is {sigmaMs} ms</span>
153 </label>
154 )}
155 </div>
156 )}
158 {result && result.maxMagDeviationDb > 1 && (
159 <div className="warning">
160 The magnitude response deviates by up to {result.maxMagDeviationDb.toFixed(1)} dB, so B is not just a phase change of A. The map
161 changes too quickly across neighbouring bands for this window: increase sigma, increase the smoothing, or reduce the delay.
162 </div>
163 )}
164 {result && (
165 <div className="response-plots">
166 <div>
168 Phase map phi(f, t){result.timeVarying ? '' : ', fixed in time'} <span className="phase-legend" /> -180° to 180°
169 </div>
170 <ImagePlot image={result.phaseField} color={phaseColor} label="" labelColor="#000" />
172 <div>
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 173 <div className="plot-label">Magnitude response of the equivalent filter{timeVarying ? ' at mid-clip' : ''} (should stay near 0 dB)</div>
7d910acPhase perception 2: subband phase rotation of arbitrary sounds with filter bank, phase maps, plots, and playbackJeremy Magland 174 <LinePlot series={[{ x: result.magDb.f, y: result.magDb.y, color: '#1f5fa8' }]} xRange={[0, fmax]} yRange={[-30, 3]} xLabel="frequency (Hz)" yLabel="dB" zeroLine />
175 </div>
176 <div>
177 <div className="plot-label">
e9f92d1Time-varying random phase maps by decimated subband synthesis; drag-to-zoom, scrollbar pan and zoom buttons for the waveformsJeremy Magland 178 Group delay{timeVarying ? ' at mid-clip' : ''} (largest {result.maxGroupDelayMs.toFixed(2)} ms)
180 <LinePlot
181 series={[{ x: result.groupDelayMs.f, y: result.groupDelayMs.y, color: '#1f5fa8' }]}
182 xRange={[0, fmax]}
183 symmetric
184 xLabel="frequency (Hz)"
185 yLabel="ms"
186 band={{ y0: -THRESHOLD_MS, y1: THRESHOLD_MS, color: 'rgba(46, 160, 67, 0.15)', label: 'approximate audibility threshold' }}
187 zeroLine
188 />
189 </div>
190 </div>
191 )}
192 </section>
193 )
↑↓ move↵openescclose