I built dspx after hitting a wall with real-time EMG processing in Node.js.
Problem: 2 kHz × 8 channels → filters → decimation → FFT.
JS is too slow; the Python sidecar is complex; WASM lacks native threading.
Solution: Native C++ DSP with Redis-persisted state. Workers process a chunk, save state in ~1–2ms, die, and resume elsewhere. Serverless DSP without losing filter history.
await pipeline.process(audioData, {sampleRate:16000});
const state = await pipeline.saveState();
// worker dies → new worker
await pipeline.loadState(state);
Platform
• x64 + ARM
• Tested on Pixel 9 Pro XL (stable; NEON tuning pending)
• Node 18/20/22
• N-API v8
• Prebuilds for x64 (Linux/macOS/Win)
• ARM compiles once → works like normal
I built dspx after hitting a wall with real-time EMG processing in Node.js.
Problem: 2 kHz × 8 channels → filters → decimation → FFT. JS is too slow; the Python sidecar is complex; WASM lacks native threading.
Solution: Native C++ DSP with Redis-persisted state. Workers process a chunk, save state in ~1–2ms, die, and resume elsewhere. Serverless DSP without losing filter history.
Repo: https://github.com/a-kgeorge/dspx Benchmarks: https://github.com/a-kgeorge/dspx-benchmark npm: https://www.npmjs.com/package/dspx
DSP Primitives: • STFT, Mel-spectrogram, MFCC • FIR/IIR (Butterworth, Chebyshev, Parks-McClellan) • Time + FFT convolution • Decimation, resampling, rectification • Hilbert transform, wavelet decomposition
Infrastructure • Multi-channel streaming (audio, biosignals, IoT) • Redis-backed persistence • TypeScript-first API w/ full type safety • SIMD: AVX2 / SSE2 / NEON
Performance (i5-12600T • Node 22.21.1 • 16GB RAM): • 1 pipeline → ~34M samples/s • 32 pipelines → ~101M samples/s • Small inputs → sub-ms p50 • With Redis → ~31ms p50 / ~39ms p99 • Zero leaks over 10k+ save/restore cycles
Benchmark repo: https://github.com/a-kgeorge/dspx-benchmark
Code Example:
const pipeline = await createDspPipeline({ redisHost: "localhost", redisPort: 6379, stateKey: "dsp:user:ch1", });
pipeline .filter({type:"butterworth",mode:"bandpass",lowCutoffFrequency:20,highCutoffFrequency:450}) .stft({windowSize:512,hopSize:160}) .melSpectrogram({numMelBands:26}) .mfcc({numCoefficients:13});
await pipeline.process(audioData, {sampleRate:16000}); const state = await pipeline.saveState(); // worker dies → new worker await pipeline.loadState(state);
Platform • x64 + ARM • Tested on Pixel 9 Pro XL (stable; NEON tuning pending) • Node 18/20/22 • N-API v8 • Prebuilds for x64 (Linux/macOS/Win) • ARM compiles once → works like normal
Why not WASM? • Native threading • AVX2/SSE2/NEON • Zero-copy buffers to/from Redis • Clean integration w/ Node streams
Who It’s For • Node backends running streaming DSP • Systems needing stateful pipelines + fault tolerance • Teams avoiding separate Python/C++ services • IoT gateways w/ intermittent workloads
Not ideal: • Browser-only DSP (use WebAudio) • Hard realtime (<1ms) embedded audio
Help Wanted • Portable NEON optimization (FFT + convolution) • Kafka integration (batching / backpressure / checkpoints) • Production war stories
Status STFT / Mel / MFCC Convolution + FFT (native C/C++) Redis persistence stable ARM functional (not optimized) 891 tests passing Kafka integration (seeking input)
Repos: https://github.com/a-kgeorge/dspx https://github.com/a-kgeorge/dspx-benchmark