7d910acPhase perception 2: subband phase rotation of arbitrary sounds with filter bank, phase maps, plots, and playbackJeremy Magland 1/** Small helpers shared by the canvas plots. */
3/** Round tick positions covering [min, max] with about `count` steps. */
4export function niceTicks(min: number, max: number, count = 5): number[] {
5 if (!(max > min)) return [min]
6 const raw = (max - min) / count
7 const mag = 10 ** Math.floor(Math.log10(raw))
8 const norm = raw / mag
9 const step = (norm < 1.5 ? 1 : norm < 3.5 ? 2 : norm < 7.5 ? 5 : 10) * mag
10 const out: number[] = []
11 for (let v = Math.ceil(min / step) * step; v <= max + step * 1e-9; v += step) out.push(Number(v.toFixed(10)))
12 return out
13}
15export const formatTick = (v: number): string => {
16 if (v === 0) return '0'
17 const a = Math.abs(v)
18 if (a >= 1000) return `${Number((v / 1000).toPrecision(3))}k`
19 if (a >= 10) return String(Number(v.toPrecision(3)))
20 return String(Number(v.toPrecision(2)))
21}
23export const formatSeconds = (s: number): string => (s >= 1 ? `${Number(s.toPrecision(3))} s` : `${Number((s * 1000).toPrecision(3))} ms`)
25/** A magma-like colormap: t in [0, 1] to rgb. */
26const STOPS: [number, number, number][] = [
27 [0, 0, 4],
28 [28, 16, 68],
29 [79, 18, 123],
30 [129, 37, 129],
31 [181, 54, 122],
32 [229, 80, 100],
33 [251, 135, 97],
34 [254, 194, 135],
35 [252, 253, 191],
36]
38export function magma(t: number): [number, number, number] {
39 const u = Math.min(1, Math.max(0, t)) * (STOPS.length - 1)
40 const i = Math.min(STOPS.length - 2, Math.floor(u))
41 const a = u - i
42 const c0 = STOPS[i]
43 const c1 = STOPS[i + 1]
44 return [c0[0] + a * (c1[0] - c0[0]), c0[1] + a * (c1[1] - c0[1]), c0[2] + a * (c1[2] - c0[2])]
45}
47export const FONT = '11px system-ui, sans-serif'
48export const AXIS = '#666'
49export const GRID = '#e6e8eb'
50export const FRAME = '#cfd3d8'