Skip to content

perf(spectrum): offload FFT to Rust and render bars via canvas - #62

Open
a-32x wants to merge 1 commit into
Horuse:mainfrom
a-32x:perf/spectrum-fft-canvas
Open

a-32x wants to merge 1 commit into
Horuse:mainfrom
a-32x:perf/spectrum-fft-canvas

Conversation

@a-32x

@a-32x a-32x commented Oct 10, 2026 •

Copy link
Copy Markdown

Summary

Fixes a severe memory leak and CPU bottleneck in the Spectrum node on WebView2 / Windows.

Prior to this change:

  • The UI thread received 8,192 raw PCM float samples over IPC (audio://scope) at ~25-30 Hz. In Tauri on Windows, each emit translates to an inter-process COM ExecuteScript call with JSON serialisation, which flooded V8 heap memory up to 5.15 GB and pinned CPU at ~13%.
  • Spectrum FFT analysis (4096-point Hann) ran synchronously on the JavaScript main thread.
  • Rendering was performed by re-creating 80 dynamic SVG <rect> nodes on every animation frame, triggering continuous DOM reconciliation overhead.

Changes

  1. Rust FFT Calculation: Added realfft dependency. Moved 4096-point Hann FFT and 80-bin logarithmic power aggregation into the background meter thread (meter-tick). It now emits a lightweight payload of 80 dB floats over audio://spectrum.
  2. Canvas Rendering: Switched the spectrum bar visualizer in spectrum.svelte to a flat HTML5 2D Canvas (fillRect), keeping axes and labels as crisp vector SVG overlays.
  3. Event Teardown: Updated tauriListen to return an explicit cleanup function and guard against listener leaks during component unmount races.

Results

  • WebView2 renderer process: dropped from 5,154 MB to 162 MB (~97% reduction on the leaking renderer process).
  • Total application memory: reduced by ~10x (from ~5.2 GB down to ~300–500 MB across all processes).
  • CPU usage: reduced from ~13% to baseline (<1%).
  • Framerate: smooth 60 FPS without memory accumulation or garbage collection pauses.

Dependency Note

Added realfft = "3.4" to src-tauri/Cargo.toml with opt-level = 3 in dev profile. RealFFT was chosen because it operates purely on real-valued inputs with half the memory and computation overhead compared to general complex FFTs.

Verification

  • Developed on: Windows 11 (x64)
  • Tested on: Windows 11 — continuously monitored RAM/CPU over extended playback, verified canvas bar responsiveness across sine, sweep, and noise signals.
  • Not tested: macOS, Linux (Rust pipeline and Canvas rendering are platform-agnostic, but requires maintainer verification).

Move 4096-point Hann FFT calculation from the JavaScript main thread to the background meter tick in Rust using realfft. Emit pre-binned 80 dB values over audio://spectrum instead of streaming 8192 raw PCM float samples over IPC.

Replace dynamic SVG rect re-creation with HTML5 2D canvas in the spectrum component to eliminate DOM reconciliation overhead. Add teardown to tauri event listeners to prevent memory accumulation in WebView2.
@a-32x
a-32x requested a review from Horuse as a code owner October 10, 2026 14:47

@Horuse Horuse left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing to the project! This is a pretty serious issue that I hadn't noticed, and I appreciate you fixing it. A few things before merging:

  1. The canvas is blurry on Retina because it's sized in CSS pixels. It needs devicePixelRatio scaling; waveform_scope.svelte (~line 790) already does this.
  2. The spectrum story in Storybook is empty now: src/stories/harness/signal.ts still sends raw audio://scope data for spectrum. It should send bars on audio://spectrum.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants