/ concept-collection / timeseries-compressibility
Sign in
concept-collection / timeseries-compressibility
timeseries-compressibility / src / App.tsx
228 lines · 8.2 KBCodeBlameHistory
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 1import { useEffect, useMemo, useRef, useState } from 'react'
2import Controls from './components/Controls'
3import FilterViz from './components/FilterViz'
4import ScrollingView from './components/ScrollingView'
5import CompressionChart from './components/CompressionChart'
6import MathSection from './components/MathSection'
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 7import { DEFAULT_SPEC, clampSpec, designKernel, kernelNorm } from './model/filters'
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 8import { theoreticalRateBits } from './model/theory'
e411dffMake LPC order and compression block size controlsJeremy Magland 9import { LATENT_SEED } from './model/latent'
10import { DEFAULT_LPC_ORDER, LPC_ORDERS } from './compress/codecs'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 11import type { BoundResult, CodecResult } from './compress/codecs'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 12import type { CompressRequest, CompressResponse } from './worker/compressWorker'
e411dffMake LPC order and compression block size controlsJeremy Magland 14const BLOCK_SIZES = [10000, 20000, 50000, 100000, 200000, 500000, 1000000]
15const DEFAULT_BLOCK_SIZE = 100000
17interface CompressionState {
18 results: CodecResult[]
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 19 bounds: BoundResult[]
21 computing: boolean
22 error: string | null
25/** The nine codec sizes, measured in a worker on a debounced parameter set. */
e411dffMake LPC order and compression block size controlsJeremy Magland 26function useCompression(
27 kernel: Float64Array,
28 sigma: number,
29 dither: boolean,
30 lpcOrder: number,
31 blockSize: number,
32): CompressionState {
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 33 const [state, setState] = useState<CompressionState>({
34 results: [],
37 computing: true,
38 error: null,
39 })
40 const workerRef = useRef<Worker | null>(null)
41 const idRef = useRef(0)
43 useEffect(() => {
44 const worker = new Worker(new URL('./worker/compressWorker.ts', import.meta.url), {
45 type: 'module',
46 })
47 worker.onmessage = (e: MessageEvent<CompressResponse>) => {
48 if (e.data.id !== idRef.current) return
49 setState({
50 results: e.data.error ? [] : e.data.results,
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 51 bounds: e.data.error ? [] : e.data.bounds,
53 computing: false,
54 error: e.data.error ?? null,
55 })
56 }
57 workerRef.current = worker
58 return () => {
59 worker.terminate()
60 workerRef.current = null
61 }
62 }, [])
64 useEffect(() => {
65 setState(s => ({ ...s, computing: true }))
66 const id = ++idRef.current
67 const timer = setTimeout(() => {
68 const request: CompressRequest = {
69 id,
70 kernel,
71 sigma,
72 dither,
74 lpcOrder,
75 // The same seed the signal view draws from, so the block really is
76 // the data on screen.
77 seed: LATENT_SEED,
79 workerRef.current?.postMessage(request)
80 }, 250)
81 return () => clearTimeout(timer)
e411dffMake LPC order and compression block size controlsJeremy Magland 82 }, [kernel, sigma, dither, lpcOrder, blockSize])
84 return state
87export default function App() {
88 const [sigma, setSigma] = useState(5)
89 const [sampleRateHz, setSampleRateHz] = useState(30000)
90 const [spec, setSpec] = useState(DEFAULT_SPEC)
91 const [dither, setDither] = useState(false)
e411dffMake LPC order and compression block size controlsJeremy Magland 92 const [lpcOrder, setLpcOrder] = useState(DEFAULT_LPC_ORDER)
93 const [blockSize, setBlockSize] = useState(DEFAULT_BLOCK_SIZE)
95 const kernel = useMemo(() => designKernel(spec, sampleRateHz), [spec, sampleRateHz])
96 const sigmaY = useMemo(() => {
97 const filtered = sigma * kernelNorm(kernel)
98 return dither ? Math.sqrt(filtered * filtered + 1 / 12) : filtered
99 }, [kernel, sigma, dither])
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 100 const theoryBits = useMemo(() => theoreticalRateBits(kernel, sigma, dither), [kernel, sigma, dither])
e411dffMake LPC order and compression block size controlsJeremy Magland 101 const compression = useCompression(kernel, sigma, dither, lpcOrder, blockSize)
103 return (
104 <div className="app">
105 <header className="app-header">
106 <h1>Time-series compressibility</h1>
107 <p>
108 Gaussian noise → FIR filter → optional dither → round to integers. How well can the
109 integer stream be losslessly compressed, and does the spectral entropy-rate formula
110 predict the limit?
111 </p>
112 </header>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 114 {/* The controls stay pinned so the parameters and the ratios they move
115 are always on screen together, whatever is scrolled to. */}
116 <section className="card control-bar">
118 sigma={sigma}
119 setSigma={setSigma}
120 sampleRateHz={sampleRateHz}
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 121 setSampleRateHz={rate => {
122 // Band edges are absolute, so a new rate can push them past
123 // Nyquist; re-snap the spec so sliders and kernel stay in step.
124 setSampleRateHz(rate)
125 setSpec(s => clampSpec(s, rate))
126 }}
128 setSpec={setSpec}
129 dither={dither}
130 setDither={setDither}
131 />
132 </section>
134 <section className="card">
135 <h2>Compression</h2>
136 <div className="stat-row">
137 <div className="stat">
138 <span className="label">predicted std of z</span>
139 <span className="value">
140 {sigmaY.toFixed(2)} <small>steps</small>
141 </span>
142 </div>
143 <div className="stat">
144 <span className="label">measured std of z</span>
145 <span className="value">
146 {compression.results.length > 0 ? compression.empiricalStd.toFixed(2) : '…'}{' '}
147 <small>steps</small>
148 </span>
149 </div>
150 <div className="stat">
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 151 <span className="label">theoretical rate R</span>
153 {theoryBits.toFixed(2)} <small>bits/sample</small>
154 </span>
155 </div>
156 <div className="stat">
157 <span className="label">implied best ratio</span>
158 <span className="value">{theoryBits > 0 ? `${(16 / theoryBits).toFixed(2)}×` : '—'}</span>
159 </div>
160 </div>
e411dffMake LPC order and compression block size controlsJeremy Magland 161 {/* Settings of the measurement, not of the model — so they live with
162 the chart they change rather than in the model bar. */}
163 <div className="measure-row">
164 <label>
165 LPC order
166 <select value={lpcOrder} onChange={e => setLpcOrder(Number(e.target.value))}>
167 {LPC_ORDERS.map(o => (
168 <option key={o} value={o}>
169 {o}
170 </option>
171 ))}
172 </select>
173 </label>
174 <label>
175 block size
176 <select value={blockSize} onChange={e => setBlockSize(Number(e.target.value))}>
177 {BLOCK_SIZES.map(n => (
178 <option key={n} value={n}>
179 {n.toLocaleString()} samples
180 </option>
181 ))}
182 </select>
183 </label>
184 </div>
186 <p className="card-note">Compression failed: {compression.error}</p>
187 ) : (
188 <CompressionChart
189 results={compression.results}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 190 bounds={compression.bounds}
192 computing={compression.computing}
193 />
194 )}
195 <p className="card-note">
e411dffMake LPC order and compression block size controlsJeremy Magland 196 Measured on a {blockSize.toLocaleString()}-sample block of the same latent data the
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 197 signal view shows; sizes include everything a decoder needs (ANS symbol table, LPC
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 198 coefficients). Baseline is raw int16 (16 bits/sample). The hollow bar in each group is
199 that group's entropy limit — the order-0 entropy of the stream being coded, which no
200 per-sample entropy coder can beat and ANS falls short of by its symbol table plus its
201 own arithmetic loss. The dashed line is the theoretical rate R from the spectral
202 formula in the math section — approximate where quantization dominates the spectrum
203 (see the S(f) = 1 threshold on the response plot).
205 </section>
208 <h2>Quantized signal z</h2>
209 <ScrollingView kernel={kernel} sigma={sigma} dither={dither} sigmaY={sigmaY} />
210 <p className="card-note">
211 A window of samples from the model, drawn from a fixed latent noise sequence — changing
212 σ, the filter, or dither transforms the same underlying data, so the trace morphs
213 rather than resampling. Press play to advance through the sequence.
214 </p>
215 </section>
217 <section className="card">
218 <h2>Filter</h2>
219 <FilterViz kernel={kernel} sampleRateHz={sampleRateHz} sigma={sigma} />
220 </section>
223 <h2>The math</h2>
224 <MathSection />
225 </section>
226 </div>
227 )
moveopenescclose