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'
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 9import type { BoundResult, CodecResult } from './compress/codecs'
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 10import type { CompressRequest, CompressResponse } from './worker/compressWorker'
12const BLOCK_SIZE = 120000
13const BLOCK_SEED = 20260729
15interface CompressionState {
16 results: CodecResult[]
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 17 bounds: BoundResult[]
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 18 empiricalStd: number
19 computing: boolean
20 error: string | null
21}
23/** The nine codec sizes, measured in a worker on a debounced parameter set. */
24function useCompression(kernel: Float64Array, sigma: number, dither: boolean): CompressionState {
25 const [state, setState] = useState<CompressionState>({
26 results: [],
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 28 empiricalStd: 0,
29 computing: true,
30 error: null,
31 })
32 const workerRef = useRef<Worker | null>(null)
33 const idRef = useRef(0)
35 useEffect(() => {
36 const worker = new Worker(new URL('./worker/compressWorker.ts', import.meta.url), {
37 type: 'module',
38 })
39 worker.onmessage = (e: MessageEvent<CompressResponse>) => {
40 if (e.data.id !== idRef.current) return
41 setState({
42 results: e.data.error ? [] : e.data.results,
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 43 bounds: e.data.error ? [] : e.data.bounds,
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 44 empiricalStd: e.data.empiricalStd,
45 computing: false,
46 error: e.data.error ?? null,
47 })
48 }
49 workerRef.current = worker
50 return () => {
51 worker.terminate()
52 workerRef.current = null
53 }
54 }, [])
56 useEffect(() => {
57 setState(s => ({ ...s, computing: true }))
58 const id = ++idRef.current
59 const timer = setTimeout(() => {
60 const request: CompressRequest = {
61 id,
62 kernel,
63 sigma,
64 dither,
65 blockSize: BLOCK_SIZE,
66 seed: BLOCK_SEED,
67 }
68 workerRef.current?.postMessage(request)
69 }, 250)
70 return () => clearTimeout(timer)
71 }, [kernel, sigma, dither])
73 return state
74}
76export default function App() {
77 const [sigma, setSigma] = useState(5)
78 const [sampleRateHz, setSampleRateHz] = useState(30000)
79 const [spec, setSpec] = useState(DEFAULT_SPEC)
80 const [dither, setDither] = useState(false)
82 const kernel = useMemo(() => designKernel(spec, sampleRateHz), [spec, sampleRateHz])
83 const sigmaY = useMemo(() => {
84 const filtered = sigma * kernelNorm(kernel)
85 return dither ? Math.sqrt(filtered * filtered + 1 / 12) : filtered
86 }, [kernel, sigma, dither])
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 87 const theoryBits = useMemo(() => theoreticalRateBits(kernel, sigma, dither), [kernel, sigma, dither])
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 88 const compression = useCompression(kernel, sigma, dither)
90 return (
91 <div className="app">
92 <header className="app-header">
93 <h1>Time-series compressibility</h1>
94 <p>
95 Gaussian noise → FIR filter → optional dither → round to integers. How well can the
96 integer stream be losslessly compressed, and does the spectral entropy-rate formula
97 predict the limit?
98 </p>
99 </header>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 101 {/* The controls stay pinned so the parameters and the ratios they move
102 are always on screen together, whatever is scrolled to. */}
103 <section className="card control-bar">
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 104 <Controls
105 sigma={sigma}
106 setSigma={setSigma}
107 sampleRateHz={sampleRateHz}
fb1a12fSnap every parameter slider to a ladder of round valuesJeremy Magland 108 setSampleRateHz={rate => {
109 // Band edges are absolute, so a new rate can push them past
110 // Nyquist; re-snap the spec so sliders and kernel stay in step.
111 setSampleRateHz(rate)
112 setSpec(s => clampSpec(s, rate))
113 }}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 114 spec={spec}
115 setSpec={setSpec}
116 dither={dither}
117 setDither={setDither}
118 />
119 </section>
121 <section className="card">
122 <h2>Compression</h2>
123 <div className="stat-row">
124 <div className="stat">
125 <span className="label">predicted std of z</span>
126 <span className="value">
127 {sigmaY.toFixed(2)} <small>steps</small>
128 </span>
129 </div>
130 <div className="stat">
131 <span className="label">measured std of z</span>
132 <span className="value">
133 {compression.results.length > 0 ? compression.empiricalStd.toFixed(2) : '…'}{' '}
134 <small>steps</small>
135 </span>
136 </div>
137 <div className="stat">
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 138 <span className="label">theoretical rate R</span>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 139 <span className="value">
140 {theoryBits.toFixed(2)} <small>bits/sample</small>
141 </span>
142 </div>
143 <div className="stat">
144 <span className="label">implied best ratio</span>
145 <span className="value">{theoryBits > 0 ? `${(16 / theoryBits).toFixed(2)}×` : '—'}</span>
146 </div>
147 </div>
148 {compression.error ? (
149 <p className="card-note">Compression failed: {compression.error}</p>
150 ) : (
151 <CompressionChart
152 results={compression.results}
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 153 bounds={compression.bounds}
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 154 theoryBits={theoryBits}
155 computing={compression.computing}
156 />
157 )}
158 <p className="card-note">
5bab85aRatio-first chart, quantization-floor theory formula, line-segment view, fixed latent dataJeremy Magland 159 Measured on a {BLOCK_SIZE.toLocaleString()}-sample block of the same latent data the
160 signal view shows; sizes include everything a decoder needs (ANS symbol table, LPC
355ddbbAdd per-group entropy limit bars to the compression chartJeremy Magland 161 coefficients). Baseline is raw int16 (16 bits/sample). The hollow bar in each group is
162 that group's entropy limit — the order-0 entropy of the stream being coded, which no
163 per-sample entropy coder can beat and ANS falls short of by its symbol table plus its
164 own arithmetic loss. The dashed line is the theoretical rate R from the spectral
165 formula in the math section — approximate where quantization dominates the spectrum
166 (see the S(f) = 1 threshold on the response plot).
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 167 </p>
168 </section>
8a09dfcPin the parameter bar, put compression first, shrink signal and filter panelsJeremy Magland 170 <section className="card">
171 <h2>Quantized signal z</h2>
172 <ScrollingView kernel={kernel} sigma={sigma} dither={dither} sigmaY={sigmaY} />
173 <p className="card-note">
174 A window of samples from the model, drawn from a fixed latent noise sequence — changing
175 σ, the filter, or dither transforms the same underlying data, so the trace morphs
176 rather than resampling. Press play to advance through the sequence.
177 </p>
178 </section>
180 <section className="card">
181 <h2>Filter</h2>
182 <FilterViz kernel={kernel} sampleRateHz={sampleRateHz} sigma={sigma} />
183 </section>
36e8ceaInteractive explorer for compressibility of quantized filtered Gaussian time seriesJeremy Magland 185 <section className="card">
186 <h2>The math</h2>
187 <MathSection />
188 </section>
189 </div>
190 )
191}