/ concept-collection / timeseries-compressibility
Sign in
concept-collection / timeseries-compressibility
timeseries-compressibility / src / components / Controls.tsx
128 lines · 3.5 KBBlameHistoryRaw
1import {
2 FAMILY_LABELS,
3 SIGMA_STOPS,
4 TAP_STOPS,
5 WIDTH_STOPS,
6 frequencyStops,
7 maxCutoffHz,
8 nearestStop,
9 type FilterFamily,
10 type FilterSpec,
11} from '../model/filters'
13export interface ControlsProps {
14 sigma: number
15 setSigma: (v: number) => void
16 sampleRateHz: number
17 setSampleRateHz: (v: number) => void
18 spec: FilterSpec
19 setSpec: (v: FilterSpec) => void
22function formatHz(hz: number): string {
23 return hz >= 1000 ? `${+(hz / 1000).toFixed(1)} kHz` : `${hz} Hz`
26/** A slider that moves between the given values rather than a continuum. */
27function StopSlider(props: {
28 label: string
29 stops: number[]
30 value: number
31 format: (v: number) => string
32 onChange: (v: number) => void
33}) {
34 const { stops } = props
35 const snapped = nearestStop(stops, props.value)
36 const index = stops.indexOf(snapped)
37 return (
38 <div className="control">
39 <label>
40 {props.label} <span className="value">{props.format(snapped)}</span>
41 </label>
42 <input
43 type="range"
44 min={0}
45 max={stops.length - 1}
46 step={1}
47 value={index}
48 onChange={e => props.onChange(stops[Number(e.target.value)])}
49 />
50 </div>
51 )
54export default function Controls(p: ControlsProps) {
55 const { spec } = p
56 const freqStops = frequencyStops(maxCutoffHz(p.sampleRateHz))
57 const sinc = spec.family === 'lowpass' || spec.family === 'bandpass'
58 return (
59 <div className="controls">
60 <StopSlider
61 label="σ (quantization steps)"
62 stops={SIGMA_STOPS}
63 value={p.sigma}
64 format={v => String(v)}
65 onChange={p.setSigma}
66 />
67 <div className="control">
68 <label>filter</label>
69 <select
70 value={spec.family}
71 onChange={e => p.setSpec({ ...spec, family: e.target.value as FilterFamily })}
72 >
73 {(Object.keys(FAMILY_LABELS) as FilterFamily[]).map(f => (
74 <option key={f} value={f}>
75 {FAMILY_LABELS[f]}
76 </option>
77 ))}
78 </select>
79 </div>
80 {spec.family === 'bandpass' && (
81 <StopSlider
82 label="low cutoff"
83 stops={freqStops.filter(f => f < spec.highHz)}
84 value={spec.lowHz}
85 format={formatHz}
86 onChange={v => p.setSpec({ ...spec, lowHz: v })}
87 />
88 )}
89 {sinc && (
90 <StopSlider
91 label={spec.family === 'bandpass' ? 'high cutoff' : 'cutoff'}
92 stops={spec.family === 'bandpass' ? freqStops.filter(f => f > spec.lowHz) : freqStops}
93 value={spec.highHz}
94 format={formatHz}
95 onChange={v => p.setSpec({ ...spec, highHz: v })}
96 />
97 )}
98 {sinc && (
99 <StopSlider
100 label="kernel length"
101 stops={TAP_STOPS}
102 value={spec.taps}
103 format={v => `${v} taps`}
104 onChange={v => p.setSpec({ ...spec, taps: v })}
105 />
106 )}
107 {spec.family === 'movingAverage' && (
108 <StopSlider
109 label="width"
110 stops={WIDTH_STOPS}
111 value={spec.width}
112 format={v => `${v} samples`}
113 onChange={v => p.setSpec({ ...spec, width: v })}
114 />
115 )}
116 <div className="control">
117 <label>sample rate</label>
118 <select value={p.sampleRateHz} onChange={e => p.setSampleRateHz(Number(e.target.value))}>
119 {[1000, 8000, 16000, 30000, 44100, 96000].map(r => (
120 <option key={r} value={r}>
121 {formatHz(r)}
122 </option>
123 ))}
124 </select>
125 </div>
126 </div>
127 )
moveopenescclose