BlitzFFT
BlitzFFT is a Rust CLI for audio Fourier analysis and exact whole-signal FFT benchmarking, with a native Rust CPU engine, 64-bit CPU processing, and an opt-in experimental 128-bit binary128 CPU mode for framed analysis on nightly Rust.
It has three complementary jobs:
- framed analysis, similar to an STFT pipeline, with backend auto-selection
CUDA -> Metal -> CPU - with frequency estimation errors of ±0.0000028 Hz (vs ±0.023 Hz for 32-bit) on an hour-long window, provide >180 dB SNR for scientific instrumentation applications
- exact whole-file FFT benchmarking on one long real-valued waveform, with a pure-Rust default comparison set and optional foreign-library comparisons
This repository is partly a tool and partly a notebook on FFT implementation tradeoffs. The code is useful on its own, but it is also a compact place to compare:
- framed versus whole-signal analysis
- real-input FFTs versus complex-input FFTs
- stable
64-bitprocessing versus experimental128-bitprocessing - GPU throughput versus CPU planning and execution costs
- simple libraries versus highly tuned planners
- power-of-two framing constraints versus arbitrary-length exact transforms
Table of Contents
- Why this repo exists
- What changed recently
- How To Make It Better
- What an FFT is
- Math conventions used in this repo
- Why real-input FFTs matter
- How BlitzFFT uses FFTs
- Project architecture
- Install
- Build
- CLI
- Examples
- Whole-file benchmark methodology
- Simulated 60-minute whole-file FFT at 384 kHz
- Estimated scaling to multi-day FFTs at 384 kHz
- Roadmap
- Repository layout
- Reference map
- License
Why this repo exists
There are many excellent FFT libraries already. BlitzFFT is not trying to replace them all with a single universal implementation. Instead, the repo tries to answer a more practical question:
For audio work on a real machine, what actually changes when we switch the transform shape, the backend, the planning model, and the library?
That leads to two different workflows:
- Framed analysis for ordinary audio inspection.
- Exact whole-file transforms for benchmark and algorithm comparisons.
The framed path is the one most people expect from "audio FFT" software: split a waveform into overlapping windows, apply a tapering function, and transform each frame independently. That is what you want for time-local spectral analysis, spectrogram generation, transient inspection, and quick peak summaries.
The whole-file path is different. It treats the entire waveform as one signal and computes one exact forward FFT over the full sample count. That is useful when you care about:
- exact bin spacing for a long observation interval
- arbitrary, non-power-of-two signal lengths
- comparing library setup cost versus execution cost
- measuring how much work is saved by exploiting real-valued input structure
What changed recently
- The CPU framed backend now uses the native BlitzFFT engine instead of materializing a full complex
RustFFTinput buffer for every frame. - Framed results no longer allocate an unused full complex spectrum for each frame.
- The framed
f32CPU path can batch pairs of real frames by packing one frame into the complex real lane and the next frame into the imaginary lane, then recovering both real spectra from one complex FFT. - The native
f64power-of-two CPU FFT path now uses SIMD butterflies on supportedx86_64andaarch64machines. - Bluestein's arbitrary-length convolution now reuses the native power-of-two SIMD plan and its precomputed twiddles.
- The native power-of-two kernel fuses its first two butterfly stages, and Bluestein avoids separate full-buffer conjugation and scaling passes. On one Apple Silicon release build, the median
f64execution time at65,521samples fell from about3.19ms to3.00ms across repeated local trials; the65,536-sample case fell from about0.345ms to0.320ms. These are workload-specific measurements, not a general speed claim. - CPU
--top-binsoutput keeps only the requested bins per frame inf32andf64modes. --precision 32 --backend cpu --summary -f nonenow scans peaks directly, avoiding per-frame magnitude arrays.- The repo now has an exact whole-file benchmark path for long real-valued signals, including non-power-of-two lengths.
- The default whole-file benchmark compares three native-Rust paths side by side:
BlitzFFT native,RealFFT, andRustFFT complex. - Optional
FFTW3f,KissFFT, andPocketFFTcomparisons can be enabled explicitly with Cargo features. - The benchmark docs now center a simulated one-hour
384kHz scenario using a439.997Hz sine and a full-signal Hann window.
How To Make It Better
If we want BlitzFFT to become meaningfully better, the next work should stay focused on a few concrete themes.
1. Make the native engine faster where it actually matters
The biggest remaining speed opportunity is the native CPU FFT core, especially the f64 whole-file path and the arbitrary-length Bluestein path. That means:
- measure and tune the existing
f64SIMD kernels - keep reducing copies and scratch churn in the real-input kernels
- benchmark the new Bluestein inner plan across awkward lengths and tune it further
- extend the summary and top-bin memory savings to other precisions and GPU backends
2. Keep every speed claim tied to correctness
This repo gets better when optimizations come with tests, not just faster tables. The native engine needs broader randomized checks, more cross-validation against rustfft and realfft, and clearer documented tolerances for f32 and f64.
3. Keep the default build simple
The default story should remain: stable Rust, native CPU path, no required foreign FFT libraries. Optional GPU and comparison backends are valuable, but they should stay optional enough that the core tool remains easy to build and trust.
4. Make the CLI do less unnecessary work
The f32 CPU path computes peak summaries without storing every magnitude when using --summary -f none. CPU --top-bins output uses bounded per-frame storage in f32 and f64 modes. Other precision modes and GPU backends remain opportunities to avoid unnecessary storage and formatting.
5. Make benchmark docs even easier to trust
The benchmark presentation is much stronger now than it used to be, but the repo still improves when it makes measured versus simulated results unmistakable, records feature flags and machine details, and ships a reproducible benchmark workflow.
The longer-form plan lives in ROADMAP.md.
What an FFT is
An FFT is a fast algorithm for computing the discrete Fourier transform, or DFT.
The DFT takes a finite list of samples and rewrites it as a sum of discrete complex sinusoids. Instead of asking "what is the sample value at time index $n$?", it asks "how much of frequency bin $k$ is present in the signal?"
For a length-$N$ signal $x[n]$, the forward DFT is
X[k] = \sum_{n=0}^{N-1} x[n] e^{-j 2 \pi k n / N}, \qquad 0 \le k < N
and the inverse DFT is
x[n] = \frac{1}{N} \sum_{k=0}^{N-1} X[k] e^{j 2 \pi k n / N}
If you implement that definition directly, it costs $O(N^2)$ operations. The FFT family of algorithms computes the same result in roughly $O(N \log N)$ time by factoring the problem into smaller transforms.
That is the whole reason FFTs matter. They do not change the transform being computed. They change the cost of computing it.
Intuition
Some quick intuition helps:
- the time-domain signal is a sequence of samples
- the frequency-domain signal is a sequence of complex coefficients
- each coefficient describes the amplitude and phase of one discrete frequency bin
- a longer observation window gives finer frequency spacing
- a shorter observation window gives better time localization
The bin frequencies are
f_k = \frac{k f_s}{N}
where $f_s$ is the sample rate. The frequency resolution is therefore
\Delta f = \frac{f_s}{N}
This is why a one-hour signal at $48{,}000$ Hz has extremely fine bin spacing: the observation interval is very long, so the frequency grid is correspondingly dense.
Why the FFT is "fast"
The most famous FFT family is the Cooley-Tukey decomposition. When $N$ factors as $N = N_1 N_2$, the transform can be reorganized into smaller pieces instead of recomputing every sinusoid against every sample from scratch. There are many variants:
- radix-2 and radix-4 decompositions
- mixed-radix decompositions
- split-radix variants
- prime-factor algorithms
- Bluestein and Rader methods for awkward lengths, especially primes
Different libraries package those ideas differently. Some are tiny and simple. Others, like FFTW, search over many possible plans and choose the fastest measured one for the host machine.
Math conventions used in this repo
BlitzFFT follows the standard forward-transform sign convention with a negative exponential:
e^{-j 2 \pi k n / N}
Some practical conventions matter when you compare FFT libraries:
- Most FFT libraries return an unnormalized forward transform.
- Normalization, if desired, is usually applied separately.
- For real-valued input, the negative-frequency half of the spectrum is redundant.
For a real signal,
X[N-k] = \overline{X[k]}
so a real-input forward FFT only needs to return the nonnegative-frequency half-spectrum:
k = 0, 1, \dots, \left\lfloor \frac{N}{2} \right\rfloor
That is why the positive-frequency output length is
\frac{N}{2} + 1
for even $N$.
Why real-input FFTs matter
Audio samples are real-valued. That sounds obvious, but it changes the implementation story a lot.
If you feed a real signal into a generic complex FFT, you usually have to:
- allocate a complex buffer of length $N$
- copy each real sample into the real part
- set all imaginary parts to zero
- compute a full complex FFT
- ignore half the result because it is conjugate-redundant
A real-input FFT avoids most of that waste. In practical terms, that can mean:
- less input marshaling
- less memory traffic
- less output storage
- faster execution
- a cleaner API for audio workloads
RealFFT in Rust builds on RustFFT but exposes the real-to-complex path directly. FFTW3f, KissFFT, and PocketFFT also provide real-input transforms. This repo makes that comparison explicit.
Paired real-frame batching
For framed f32 CPU analysis, BlitzFFT also uses a related two-real-FFTs-in-one-complex-FFT trick. Given two real frames a[n] and b[n], the CPU backend packs them as:
z[n] = a[n] + i b[n]
It then computes one complex FFT and recovers the two real spectra with conjugate symmetry:
A[k] = \frac{Z[k] + \overline{Z[N-k]}}{2}
B[k] = \frac{Z[k] - \overline{Z[N-k]}}{2i}
This does not make the whole pipeline twice as fast, because packing, unpacking, magnitudes, memory traffic, and output still cost time. It does reduce transform scheduling overhead for frame-heavy workloads and keeps the all-real signal structure explicit in the native CPU backend.
How BlitzFFT uses FFTs
There are two very different transform modes in this repository.
1. Framed analysis
The framed path is an STFT-style workflow:
- load or synthesize audio
- downmix to mono if needed
- split the signal into overlapping frames
- apply a Hann window per frame
- compute one FFT per frame
- emit magnitudes, summaries, CSV, or binary output
For frame index $m$, hop size $H$, and frame length $N$, the transform is
X_m[k] = \sum_{n=0}^{N-1} x[n + mH] \, w[n] \, e^{-j 2 \pi k n / N}
where $w[n]$ is the analysis window.
The Hann window used in this repo is
w[n] = \frac{1}{2} \left(1 - \cos\left(\frac{2 \pi n}{N - 1}\right)\right), \qquad 0 \le n < N
In the CPU framed backend, the transform work is done with cached native BlitzFFT plans plus thread-local work buffers. On Apple hardware, the Metal backend applies the frame window on-device. On NVIDIA systems, the CUDA backend is preferred when enabled and available.
2. Whole-file exact benchmark
The whole-file mode computes one forward FFT over the entire signal:
X[k] = \sum_{n=0}^{N-1} x[n] e^{-j 2 \pi k n / N}
This is not an STFT. There is no hop size and no time-local frame index. You get one spectrum covering the full observation interval.
That is why the reported frequency resolution for the one-hour example is
\Delta f = \frac{384000}{1382400000} = \frac{1}{3600} \approx 0.0002777778 \text{ Hz}
This mode exists to compare transform engines and data-motion costs, not to provide time-local spectral evolution.
Tuning-theory interpretation of a tiny interval
The value above is a linear frequency-bin spacing in hertz. If, separately, you want to think about a similarly sized interval in logarithmic pitch space, take
\Delta = 0.0002777778
interpreted as a base-2 log-frequency interval. The corresponding frequency ratio is
\text{ratio} = 2^{0.0002777778} \approx 1.0001925
A cent, written ¢, is one hundredth of a semitone, or 1/1200 of an octave. The equivalent size here is
1200 \cdot 0.0002777778 = 0.33333336 \, \text{¢}
So that target interval is approximately:
0.3333 ¢1.0001925as a frequency ratio
It is also exactly one step of 3600-EDO, since
\frac{1200}{3600} = \frac{1}{3} \, \text{¢}
per equal division of the octave.
This is smaller than the usual named commas in tuning theory. A useful nearby reference is one sixth of a schisma. Using
\text{schisma} \approx 1.95 \, \text{¢}
gives
\frac{1.95}{6} \approx 0.325 \, \text{¢}
so
\frac{1}{6}\text{ schisma} \approx 0.325 \, \text{¢}
which is very close to 0.3333 ¢, with an error of about 0.008 ¢.
Project architecture
At a high level, the project is organized like this:
src/main.rsCLI parsing, input selection, framing, backend selection, and dispatchsrc/audio.rsWAV loading, mono downmixing, Hann windows, framing, sine synthesis, and bin-to-Hz conversionsrc/backends/framed FFT backends for CPU, Metal, and CUDAsrc/benchmark.rsframed benchmark harness comparing the selected backend against the CPU baselinesrc/whole_fft.rsexact whole-signal benchmark harness across multiple FFT librariessrc/native/pocketfft_bridge.ccthin C++ bridge exposing vendored PocketFFT to Rustvendor/kissfft/vendored KISS FFT sourcesvendor/pocketfft/vendored PocketFFT header-only implementation
Backend selection
Automatic backend selection is:
CUDA -> Metal -> CPU
If you force a backend from the CLI, that explicit choice wins.
What each whole-file benchmark entry means
The benchmark table compares closely related but not identical implementation styles:
BlitzFFT nativeThe repo's own native Rust real-input path with reusable plans, scratch buffers, and output buffers.RealFFTA directrealfftcrate benchmark path that still uses a real-input transform, but with less aggressive buffer reuse in the harness.RustFFT complexA baseline that converts the real signal into a full complex buffer and runs a standard complex FFT.FFTW3fAn optional single-precision real-to-complex FFTW path discovered from the local machine when thefftwfeature is enabled.KissFFTAn optional real FFT path from the vendored KISS FFT C implementation.PocketFFTAn optional vendored C++ header-only path accessed throughsrc/native/pocketfft_bridge.cc.
That means the benchmark is not just "algorithm A versus algorithm B". It is also measuring planning policy, data layout policy, and buffer-management style.
When --precision 64 is selected, the whole-file benchmark currently uses the CPU-side RealFFT and RustFFT double-precision paths plus the repo's reusable native real-input path. The optional PocketFFT, KissFFT, and FFTW3f comparisons remain single-precision-only in this codebase today.
Install
pip (after the first PyPI release)
python3 -m pip install blitzfft
blitzfft --version
blitzfft --list-backends
This installs the blitzfft command-line executable, not a Python importable API. Published wheels contain the default native Rust CPU build; CUDA, Metal, experimental binary128, and foreign-library comparisons are still opt-in Cargo builds. PyPI installation is not available until the first release is published.
To install this checkout before then, install a Rust toolchain and run:
python3 -m pip install .
Source installs need Rust because the Python build backend compiles the CLI. Use a virtual environment if you do not want to modify your system Python environment.
PyPI release registration
The project owner must create a pending trusted publisher for the blitzfft project on PyPI before the first upload. Set GitHub owner to TheColby, repository to BlitzFFT, workflow filename to release.yml, and environment to pypi. The pending publisher does not reserve the project name; confirm name availability and ownership before publishing. Configure a GitHub pypi environment, ideally with required reviewers. No PyPI API token is needed in repository secrets.
For a release, verify CI, create a vX.Y.Z tag matching Cargo.toml, and publish a GitHub Release for that tag. The release workflow builds platform wheels and a source distribution, then uploads them through PyPI trusted publishing. Do not publish an unreviewed release: PyPI version files cannot be replaced after upload.
See PyPI's pending publisher instructions and trusted publishing guide.
Homebrew
BlitzFFT ships a head-only Homebrew formula in this repository. Until the project has tagged release tarballs, install the current main branch with:
brew install --HEAD ./Formula/blitzfft.rb
From outside a clone, you can install directly from the public formula URL:
brew install --HEAD https://raw.githubusercontent.com/TheColby/BlitzFFT/main/Formula/blitzfft.rb
After installation:
blitzfft --list-backends
blitzfft --generate-sine 440,48000,0.1 --summary -f none
The Homebrew formula builds the default native Rust CPU configuration. Optional comparison backends such as FFTW, KISS FFT, PocketFFT, CUDA, and Metal remain Cargo feature builds for now.
Build
# Native Rust CPU build
cargo build --release
# Enable the optional foreign-library whole-file comparisons
cargo build --release --features foreign-fft
# Apple GPU backend
cargo build --release --features metal
# NVIDIA GPU backend
cargo build --release --features cuda
# Everything enabled, including optional comparison backends
cargo build --release --features "cuda metal foreign-fft"
Build notes
- The default build is native Rust and does not require FFTW, KISS FFT, or PocketFFT.
--features foreign-fftenables the optional comparison backends used in the larger whole-file benchmark tables.FFTW3fis only needed when thefftwfeature is enabled.build.rstriespkg-config --libs --cflags fftw3ffirst, then checks/opt/homebrew/liband/usr/local/libforlibfftw3f.dylib.KissFFTis only compiled when thekissfftfeature is enabled.PocketFFTis only compiled when thepocketfftfeature is enabled through a small C++ bridge against the vendored header invendor/pocketfft/.- The Metal shader is built by
build.rswhen themetalfeature is enabled. - The CPU path always remains available.
- GitHub Actions now checks the default stable build on Linux and macOS, plus a Linux
foreign-fftjob with FFTW installed.
CLI
The project name and binary name are both blitzfft.
Usage: blitzfft [OPTIONS] [INPUT]
Arguments:
[INPUT] Input WAV file (16/24/32-bit PCM or f32)
Options:
--list-backends List backend choices and whether they are compiled into this build
--list-precisions List precision modes and their build/runtime requirements
--list-formats List output formats supported by this build
-n, --fft-size <FFT_SIZE> FFT frame size (must be a power of two) [default: 2048]
--hop <HOP> Hop size in samples (default = fft_size/2)
--batch-size <BATCH_SIZE> Number of frames to process per GPU batch (default = all frames) [default: 0]
-b, --backend <BACKEND> Force a specific backend [default: auto]
--channel <avg|left|right|N> Input channel selection [default: avg]
--window <WINDOW> Window applied to each analysis frame [default: hann] [possible values: rect, hann, hamming, blackman]
--precision <PRECISION> Internal processing precision in bits [default: 64] [possible values: 32, 64, 128]
-o, --output <PATH> Output file (optional; stdout if omitted for text/csv)
-f, --format <FORMAT> Output format [default: text]
--min-hz <MIN_HZ> Only emit bins at or above this frequency
--max-hz <MAX_HZ> Only emit bins at or below this frequency
--top-bins <TOP_BINS> Only emit the N loudest bins per frame (0 = all bins) [default: 0]
--benchmark Run framed benchmark comparing selected backend against CPU baseline
--bench-repeats <BENCH_REPEATS> Number of benchmark repeats [default: 5]
--whole-file-benchmark Run one exact FFT over the entire signal
--generate-sine <Hz,SR,Secs> Synthesize a sine wave in memory
--write-generated-wav <PATH> Persist the generated/loaded mono signal as 32-bit float WAV
--full-window <FULL_WINDOW> Apply a window across the entire loaded/generated signal before whole-file FFT
--apply-full-hann Apply a Hann window across the entire loaded/generated signal
--summary Print per-frame peak-frequency summary to stdout
Input and output behavior
- WAV input supports 16-bit PCM, 24-bit PCM, 32-bit PCM, and 32-bit float.
- Multi-channel input can be averaged to mono or a single channel can be selected with
--channel. - Framed output can be emitted as
text,csv,json,bin, ornone. --min-hzand--max-hzlimit emittedtext,csv, andjsonbins plus summary peaks to a frequency band.--windowcontrols framed analysis windows, while--full-windowapplies a whole-signal window before an exact whole-file FFT.--precision 64enables double-precision CPU processing for framed analysis and a reduced whole-file benchmark set.--precision 128enables an experimental truebinary128CPU path for framed analysis when the crate is built with--features binary128on nightly Rust.--precision 128currently does not support--whole-file-benchmark, and it does not use GPU backends.- Whole-file benchmark mode prints a comparison table and exits.
--list-backends,--list-precisions, and--list-formatswork without an input file and report what this build supports.
Examples
Show build capabilities
blitzfft --list-backends
blitzfft --list-precisions
blitzfft --list-formats
Run the parallel Python reference CLI
python3 scripts/blitzfft_parallel.py --generate-sine 440,48000,0.05 --summary --format none
The Python script in scripts/blitzfft_parallel.py is a dependency-light framed-analysis reference path. It uses a pure-Python radix-2 FFT and prefers process parallelism, but it automatically falls back to a thread executor on restricted systems where process pools are unavailable.
Standard framed analysis
cargo run --release -- input.wav --backend cpu --summary -f none
Analyze only the left channel in a frequency band
cargo run --release -- input.wav --channel left --min-hz 80 --max-hz 5000 --summary -f none
Run framed analysis in double precision
cargo run --release -- input.wav --precision 64 --backend cpu --summary -f none
Run framed analysis in experimental true 128-bit precision
cargo +nightly run --release --features binary128 -- input.wav --precision 128 --backend cpu --summary -f none
The current 128-bit path is intentionally opt-in. It uses Rust's unstable f128 type behind the binary128 Cargo feature, with crate-local math helpers for trig and square root. The default build stays on stable Rust; if you pass --precision 128 without that feature, the CLI exits with a clear error.
Framed benchmark against the CPU baseline
cargo run --release -- input.wav --benchmark --bench-repeats 5 -f none
Whole-file exact FFT benchmark on an existing WAV
cargo run --release -- input.wav --full-window hann --whole-file-benchmark --bench-repeats 1 -f none
Whole-file benchmark in double precision
cargo run --release -- input.wav --precision 64 --backend cpu --full-window hann --whole-file-benchmark --bench-repeats 1 -f none
Generate a sine wave and benchmark the entire signal
target/release/blitzfft \
--generate-sine 439.997,384000,3600 \
--apply-full-hann \
--write-generated-wav data/sine_439p997hz_60min_384khz_f32_hann.wav \
--whole-file-benchmark \
--bench-repeats 1 \
-f none
Export framed magnitudes to CSV
cargo run --release -- input.wav --fft-size 4096 --hop 1024 --format csv --output spectrum.csv
Export filtered spectra as JSON
cargo run --release -- input.wav --window blackman --min-hz 20 --max-hz 2000 --format json --output spectrum.json
Whole-file benchmark methodology
This section matters because FFT benchmarks are easy to misread.
Reproduce measured comparisons
The standard-library-only runner builds the release binary and records the Git commit, dirty-tree status, OS, architecture, CPU count, Rust toolchain, Cargo features, signal settings, exact CLI commands, and per-trial results in JSON:
python3 scripts/measure_whole_fft.py --precision 64 --sizes 1009 1024 4093 4096 65521 65536 --repeats 5 --trials 3 --output /tmp/blitzfft-measured.json
The default size pairs contrast awkward Bluestein lengths with nearby power-of-two lengths. Use --precision 32 and optionally --features foreign-fft for other available comparisons. Every JSON row is a measurement on the local machine; it is not the simulated 384 kHz one-hour projection below. The runner uses an unwindowed synthetic sine and records separate setup and execution times. The CLI benchmark runs algorithms in a fixed order, so thermal state and cache effects can influence close results. For a published claim, retain the JSON artifact, use a clean commit, and report the median over trials with the hardware and feature flags.
What is being timed
The whole-file table reports two times:
Setupplanner creation, config creation, allocation, or equivalent transform preparationExecone measured transform call, excluding WAV load time and excluding repeat-to-repeat input restoration performed by the benchmark harness
That distinction is important. A planner-heavy library can look slower in one-shot runs even if it is excellent when a plan is reused many times. Conversely, a lightweight library can look great in a single transform but lose ground when the workload changes shape or dimensionality.
What the benchmark is not claiming
The table is useful, but it is not a universal ranking of FFT libraries.
It does not mean:
- the fastest library here is always fastest for every size and workload
- the slowest library here is poorly engineered
- framed STFT workloads will behave the same way as one giant transform
- setup and execution costs will scale identically on every machine
It does show:
- how these exact implementations behave in this repository
- how much real-input optimization changes the picture
- how much data conversion overhead hurts the full complex baseline
- how sharp the frequency resolution becomes when $N$ is very large
Why the full-signal Hann window is included
The benchmark asset is windowed across the entire one-hour signal before the exact whole-file FFT. That is unusual compared with frame-by-frame STFT work, but it makes the benchmarked signal handling explicit and reproducible.
The full-signal Hann is
w[n] = \frac{1}{2} \left(1 - \cos\left(\frac{2 \pi n}{N - 1}\right)\right)
applied for $0 \le n < N$ across the entire loaded or synthesized waveform.
Simulated 60-minute whole-file FFT at 384 kHz
Updated on 2026-04-01 for a mono $384$ kHz, 32-bit float, one-hour sine-wave scenario with:
- frequency:
439.997 Hz - duration:
3600 s - samples:
1,382,400,000 - frequency resolution:
\Delta f = \frac{384000}{1382400000} = \frac{1}{3600} \approx 0.0002777778 \text{ Hz}
- nearest-bin frequency to the target sine:
f_{\text{peak}} = \frac{1{,}583{,}989}{3600} \approx 439.996944444444 \text{ Hz}
- window: full-signal Hann window applied before the FFT
- command: the reproduction command shown above
The corresponding WAV would be about 5.15 GiB on disk. In this workspace, the one-hour 384 kHz benchmark is treated as a simulation anchored to the measured 48 kHz run, because a direct six-library exact rerun at 1.3824 billion samples would exceed practical local memory and disk limits.
The table below therefore reports a simulated one-hour anchor. Exec is projected with the same $N \log_2 N$ model used in the scaling graph. In the live CLI benchmark table, Peak est. (Hz) is a quadratic sub-bin estimate around the loudest FFT bin and is printed to a conservative 12 decimal places to avoid implying more certainty than the f64 estimate can support. The simulated table below still shows the common nearest-bin center because this 384 kHz one-hour case is modeled rather than freshly rerun.
| Algorithm | Setup (s) | Exec (s) | Peak bin | Peak est. (Hz) |
|---|---|---|---|---|
| PocketFFT | simulated | 10.776736 | 1,583,989 | 439.996944444444 |
| RealFFT | simulated | 20.514858 | 1,583,989 | 439.996944444444 |
| BlitzFFT native | simulated | 22.263636 | 1,583,989 | 439.996944444444 |
| FFTW3f | simulated | 36.360388 | 1,583,989 | 439.996944444444 |
| RustFFT complex | simulated | 38.872592 | 1,583,989 | 439.996944444444 |
| KissFFT | simulated | 46.515731 | 1,583,989 | 439.996944444444 |
The same numbers are also recorded in benchmarks/60min_whole_file_fft.md.
On a measured shorter run, the extra resolution does expose tiny implementation differences. For example, with
cargo run --release -- --generate-sine 439.997,48000,10 --precision 32 --apply-full-hann --whole-file-benchmark --bench-repeats 1 -f none
the interpolated peak estimates come out as:
| Algorithm | Peak bin | Peak est. (Hz) |
|---|---|---|
| BlitzFFT native | 4,400 | 439.997757311980877 |
| RealFFT | 4,400 | 439.997757311980877 |
| RustFFT complex | 4,400 | 439.997757318645654 |
| FFTW3f | 4,400 | 439.997757317381456 |
| KissFFT | 4,400 | 439.997757313537420 |
| PocketFFT | 4,400 | 439.997757311980877 |
Estimated scaling to multi-day FFTs at 384 kHz
The simulated table above gives one exact-size anchor point at
N_0 = 1{,}382{,}400{,}000
samples, or one hour of mono audio at $384$ kHz.
To visualize how the whole-file execution time should grow as the FFT gets longer, the graph below extrapolates each algorithm's simulated Exec time with an $N \log_2 N$ model:
\hat{T}(N) = T(N_0) \frac{N \log_2 N}{N_0 \log_2 N_0}
This is an execution-time scaling estimate, not a fresh measured benchmark at every duration. The one-hour 384 kHz anchor is itself simulated from the measured 48 kHz benchmark by the same $N \log_2 N$ model. It does not try to model planner heuristics, cache cliffs, allocator behavior, out-of-core I/O, or OS-level memory pressure for truly enormous transforms.
To regenerate the chart after updating the anchor timings, run:
python3 scripts/generate_whole_fft_scaling_svg.py
At the multi-day end of that estimate, the projected execution times at $384$ kHz are:
| Duration | Samples | PocketFFT | RealFFT | BlitzFFT native | FFTW3f | RustFFT complex | KissFFT |
|---|---|---|---|---|---|---|---|
| 24 hr | 33,177,600,000 | 297.696 s | 566.701 s | 615.009 s | 1004.417 s | 1073.814 s | 1284.948 s |
| 48 hr | 66,355,200,000 | 612.428 s | 1165.832 s | 1265.213 s | 2066.312 s | 2209.077 s | 2643.427 s |
| 72 hr | 99,532,800,000 | 933.589 s | 1777.203 s | 1928.700 s | 3149.902 s | 3367.535 s | 4029.660 s |
Repository layout
BlitzFFT/
|- Formula/
| `- blitzfft.rb
|- data/
| `- generated benchmark WAVs (for example, a 384 kHz 60-minute sine asset; not checked in)
|- src/
| |- main.rs
| |- audio.rs
| |- benchmark.rs
| |- output.rs
| |- whole_fft.rs
| |- native/
| | `- pocketfft_bridge.cc
| `- backends/
| |- mod.rs
| |- cpu.rs
| |- cuda.rs
| `- metal.rs
|- vendor/
| |- kissfft/
| `- pocketfft/
`- shaders/
`- fft.metal
Reference map
This section is intentionally broad and repo-focused. It is not literally the entire FFT literature, but it does collect the main sources relevant to the theory, libraries, and design choices that appear in BlitzFFT.
Core FFT and DFT references
- J. W. Cooley and J. W. Tukey, "An algorithm for the machine calculation of complex Fourier series," Mathematics of Computation, 1965. DOI: 10.1090/S0025-5718-1965-0178586-1
- Matteo Frigo and Steven G. Johnson, "The Design and Implementation of FFTW3," Proceedings of the IEEE, 2005. PDF: fftw-paper-ieee.pdf
- FFTW manual, especially "What FFTW Really Computes": https://www.fftw.org/doc/
- FFTPACK at Netlib, important because PocketFFT is a heavily modified FFTPACK descendant: https://www.netlib.org/fftpack/
- Bluestein-style handling of difficult lengths is summarized in the PocketFFT README and commonly associated with the chirp z-transform family: https://en.wikipedia.org/wiki/Chirp_Z-transform
Windowing and spectral analysis references
- F. J. Harris, "On the Use of Windows for Harmonic Analysis with the Discrete Fourier Transform," Proceedings of the IEEE, 1978. DOI: 10.1109/PROC.1978.10837
- Julius O. Smith III, online DSP references on spectral analysis and windows: https://ccrma.stanford.edu/~jos/
FFTW references
- FFTW home page: https://www.fftw.org/
- FFTW manual top page: https://www.fftw.org/doc/
- FFTW paper: https://www.fftw.org/fftw-paper-ieee.pdf
- Relevant manual topics for this repo:
- planner and wisdom
- real-data DFTs
- "What FFTW Really Computes"
KISS FFT references
- Upstream repository: https://github.com/mborgerding/kissfft
- Upstream README: https://github.com/mborgerding/kissfft/blob/master/README.md
- Vendored copy in this repo:
vendor/kissfft/README.md - SIMD notes:
vendor/kissfft/README.simd
Why it matters here: KISS FFT is a good contrast case against FFTW. It aims to stay simple and reasonably efficient rather than planner-heavy and maximally optimized for every machine.
PocketFFT references
- Original upstream noted by the GitHub mirror: https://gitlab.mpcdf.mpg.de/mtr/pocketfft
- GitHub mirror used as a convenient public reference: https://github.com/mreineck/pocketfft
- Vendored README in this repo:
vendor/pocketfft/README.md - PocketFFT README highlights relevant to this repo:
- real-to-complex and complex-to-real transforms
- mixed support for awkward sizes
- Bluestein handling for large prime factors
- plan caching for repeated sizes
Why it matters here: PocketFFT is a strong exact-whole-signal comparison point because it is compact, modern C++, and explicitly optimized for more than just textbook radix-2 cases.
RustFFT and RealFFT references
rustfftcrate docs: https://docs.rs/crate/rustfft/latestrealfftcrate docs: https://docs.rs/crate/realfft/latestrealfftcrate summary: it wrapsrustfftto expose real-to-complex and complex-to-real transforms directly, avoiding manual real-to-complex expansion in user code
Why they matter here:
- the framed CPU backend uses the native BlitzFFT engine
- the whole-file benchmark compares
RealFFTandRustFFT complexdirectly - the repo's native whole-file path is mostly about planner and buffer reuse around a real-input transform, not about inventing a brand-new Fourier algorithm
Repo-local implementation references
src/audio.rsWAV loading, mono downmixing, Hann window generation, and framingsrc/backends/cpu.rsnative BlitzFFT CPU execution paths plus reusable work bufferssrc/whole_fft.rsexact whole-signal benchmark harness and per-library bridge codesrc/native/pocketfft_bridge.ccRust-to-PocketFFT bridgebuild.rsoptional foreign-backend compilation/linking plus Metal shader compilation
License
MIT
Metadata
Release files for blitzfft 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| blitzfft-0.1.0.tar.gz | 162.1 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| blitzfft-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| blitzfft-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| blitzfft-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| blitzfft-0.1.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| blitzfft-0.1.0-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 5.7 MB
Release files / blitzfft-0.1.0.tar.gz
| Download URL | blitzfft-0.1.0.tar.gz |
|---|---|
| Size | 162.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
83ee10e8951d6d518f38ca56af5e58806dc732d4172673e37cd3299f3fce8c53
|
|
BLAKE2b-256 checksum How to use checksums |
54e30399e2161784f5888488daf8353274c0147223b66c6b9d11d8ee6db27a1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / blitzfft-0.1.0-py3-none-win_amd64.whl
| Download URL | blitzfft-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
e9f5bdf7f86ede2c5a2e9a67dfbd914a59e0018879518e98c105d4bb4b2ef4e9
|
|
BLAKE2b-256 checksum How to use checksums |
3c29945b81471fda9a3d0158097ffe50d744c01ba19352849c65df5a158015cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / blitzfft-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | blitzfft-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
38043a6d0f80a0585a7b83e665caf6eb80ad2f75e08d52058f05eae18c4ce318
|
|
BLAKE2b-256 checksum How to use checksums |
7326661effad1902ce58045be99b2655fcc5b85f152f5f7e5ddf3b927a31f513
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / blitzfft-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | blitzfft-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 958.6 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
103498e7a82b667581796832e7be22796702d734419e8715215a2cfc65003c8c
|
|
BLAKE2b-256 checksum How to use checksums |
c1be601174b6bdd6500835cc6c04d16c8a851b905c3661c907f200d5b4f96a28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / blitzfft-0.1.0-py3-none-macosx_11_0_arm64.whl
| Download URL | blitzfft-0.1.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 868.1 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9e549dff31346d7782dc2a5e11ce6e3238cee953fb5c1e8406ce1e516da3f590
|
|
BLAKE2b-256 checksum How to use checksums |
0a0e81d3ea266c36de8f18e8a00a4159a105f79bce28e49282200470915f0707
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / blitzfft-0.1.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | blitzfft-0.1.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
1f761313826d4836373757bdabe2afa905e5a0ec69f8d6e6bf291095c44a1907
|
|
BLAKE2b-256 checksum How to use checksums |
e10d59fe814b8b8ff8dbf39b62e7b82dca0581f4776e24e74b24dadad262cf53
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log