Skip to content

Repository files navigation

noisereduce-cpp

A dependency-free C++17 port of noisereduce, the spectral gating noise reduction library for audio. Optional CMake flags enable KissFFT, xsimd/Eigen and multithreading for extra speed (see Optional acceleration).

Waveform and spectrogram of birdsong-like chirps buried in broadband noise, above the same signal after noisereduce-cpp: the noise floor is gone from both views while the chirps remain

Birdsong-like chirps in heavy broadband noise (top), denoised with the default non-stationary settings — no noise clip needed (bottom). The spectrograms share the same dB color scale. Regenerate with tools/generate_readme_figure.py.

Noise reduction works by computing a spectrogram of the signal, estimating a noise threshold for each frequency band, and gating out everything below it:

  • Non-stationary (default): the threshold tracks the signal over time, so the algorithm adapts to a changing noise floor and needs no noise recording.
  • Stationary: the threshold is estimated once, either from a separate noise recording or from the signal itself.

The port is numerically faithful: the test suite compares its output against reference data generated by the Python library and requires agreement within 1e-9 (see Fidelity).

Usage

#include <noisereduce/noisereduce.hpp>

std::vector<double> samples = /* mono audio, any sample rate */;

// Non-stationary (default), like nr.reduce_noise(y=samples, sr=48000)
std::vector<double> denoised = noisereduce::reduce_noise(samples, 48000.0);

// Stationary, with a noise-only recording and custom parameters
noisereduce::Parameters params;
params.stationary = true;
params.prop_decrease = 0.9;
std::vector<double> denoised2 =
    noisereduce::reduce_noise(samples, noise_clip, 48000.0, params);

// Multi-channel (planar): channels are denoised independently
noisereduce::MultiChannel stereo = {left, right};
noisereduce::MultiChannel out = noisereduce::reduce_noise(stereo, 48000.0);

Invalid parameters throw std::invalid_argument.

Building

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build          # run the tests

Optional acceleration

The default build is dependency-free. For more speed, each of these CMake flags (all OFF by default) swaps in an optimized implementation; any combination still passes the full test suite, including the Python reference comparisons:

Flag Effect Dependency
NOISEREDUCE_WITH_KISSFFT FFTs via KissFFT (double precision) find_package, else fetched automatically
NOISEREDUCE_WITH_XSIMD SIMD-vectorized spectral kernels (magnitude, dB, sigmoid mask) find_package, else fetched automatically
NOISEREDUCE_WITH_EIGEN Same kernels vectorized with Eigen instead requires installed Eigen ≥ 3.3
NOISEREDUCE_WITH_STL_PARALLEL Channels and chunks processed concurrently with std::async none (C++ standard library threads)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
    -DNOISEREDUCE_WITH_KISSFFT=ON \
    -DNOISEREDUCE_WITH_XSIMD=ON \
    -DNOISEREDUCE_WITH_STL_PARALLEL=ON

Notes:

  • If both XSIMD and EIGEN are enabled, xsimd takes precedence for the kernels.
  • n_fft must be a power of two in every configuration (kept for consistent behavior across builds, even though KissFFT would allow other sizes).
  • Ballpark on an Apple M-series laptop, 2 minutes of stereo 44.1 kHz audio: ~1.8 s with the default build, ~0.4 s with KissFFT + xsimd + STL parallel.
  • Installed accelerated builds record their dependencies in the exported CMake config (find_dependency), so consumers need those packages discoverable; the dependency-free default has no such requirement.

Using it in your project

As a subdirectory (vendored or via FetchContent):

add_subdirectory(noisereduce-cpp)
target_link_libraries(your_target PRIVATE noisereduce::noisereduce)

Or install it (cmake --install build) and use:

find_package(noisereduce REQUIRED)
target_link_libraries(your_target PRIVATE noisereduce::noisereduce)

Example program

examples/denoise_wav.cpp denoises a WAV file (16-bit PCM or 32-bit float):

./build/examples/denoise_wav noisy.wav clean.wav
./build/examples/denoise_wav noisy.wav clean.wav --noise noise_only.wav
./build/examples/denoise_wav noisy.wav clean.wav --stationary --prop 0.8

Parameters

noisereduce::Parameters mirrors the keyword arguments of the Python reduce_noise function, with the same defaults:

Field Default Description
stationary false Stationary instead of non-stationary gating
prop_decrease 1.0 Proportion of noise to remove (1.0 = 100%)
time_constant_s 2.0 Noise-floor smoothing time constant (non-stationary)
freq_mask_smooth_hz 500.0 Mask smoothing range across frequency; std::nullopt disables
time_mask_smooth_ms 50.0 Mask smoothing range across time; std::nullopt disables
thresh_n_mult_nonstationary 2.0 Threshold multiplier over the noise floor (non-stationary)
sigmoid_slope_nonstationary 10.0 Slope of the sigmoid mask (non-stationary)
n_std_thresh_stationary 1.5 Std deviations above the mean noise level (stationary)
chunk_size 600000 Samples per processing chunk; 0 disables chunking
padding 30000 Zero-padding around each chunk
n_fft 1024 FFT size (must be a power of two)
win_length n_fft STFT window length (0 selects the default)
hop_length win_length / 4 STFT hop (0 selects the default)
clip_noise_stationary true Limit the noise recording to chunk_size samples

A tip carried over from the Python library: the default mask smoothing (freq_mask_smooth_hz = 500) deliberately smears the mask across neighboring frequency bands, which strongly attenuates very narrow-band content such as pure test tones. For such signals, reduce or disable freq_mask_smooth_hz.

Fidelity to the Python library

The STFT/ISTFT follows the scipy.signal.stft/istft conventions used by noisereduce (periodic Hann window, zero boundary extension, 1/sum(window) spectrum scaling), and the gating math replicates SpectralGateStationary/SpectralGateNonStationary step for step, including chunked processing. tests/data/ contains reference outputs generated by the Python library (tools/generate_reference.py); tests/test_reference.cpp checks both algorithms, custom parameters, chunked processing and stereo input against them with a 1e-9 absolute tolerance.

Intentional differences:

  • n_fft must be a power of two (the built-in FFT is radix-2; the Python docs recommend powers of two anyway).
  • Where the Python library produces NaN on 0/0 (a frequency band that is zero for an entire chunk in the non-stationary algorithm), this port gates the band instead.
  • Processing is single-threaded (n_jobs/use_torch have no equivalent); the use_tqdm/tmp_folder options do not apply.

To regenerate the reference data:

pip install noisereduce
python tools/generate_reference.py

Real-time use

This is an offline/batch library, like the Python original — it is not suitable for real-time processing. The algorithm itself is non-causal: the non-stationary noise floor is smoothed forward and backward in time (filtfilt), the dB conversion clips against a per-frequency maximum taken over the whole spectrogram, and the mask is smoothed across future frames. The implementation reflects that: it takes whole buffers, pads chunks with 30,000 samples, and allocates freely — none of which belongs on an audio callback.

A causal, streaming variant of the stationary mode (precomputed noise threshold, one-sided mask smoothing, fixed dB reference) is feasible but would produce different output from the Python library and still carry latency of roughly n_fft plus the smoothing lookahead. For genuinely real-time denoising, consider purpose-built causal solutions such as RNNoise or the WebRTC audio processing module.

License

MIT, like the original library. See LICENSE; algorithm by Tim Sainburg.

About

C++17 port of the noisereduce Python library. Spectral gating noise reduction for audio. Dependency-free by default, with optional KissFFT/xsimd/Eigen acceleration, validated against the original to 1e-9.

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages