Skip to main content

nanodsp

High-performance Python DSP toolkit built on C++ libraries via nanobind. All processing uses float32 in a planar [channels, frames] layout with block-based APIs that accept and return AudioBuffer objects.

Backends

Library License What it provides
signalsmith-dsp MIT Filters, FFT, delay, envelopes, spectral processing, rates, mix
signalsmith-stretch MIT High-quality time-stretching and pitch-shifting
DaisySP MIT + LGPL-2.1 Oscillators, effects, dynamics, drums, physical modeling, noise
STK MIT Physical modeling instruments, generators, filters, delays, effects
madronalib MIT FDN reverbs, resampling, generators, projections, windows
HISSTools Library BSD-3 Convolution, spectral processing, statistical analysis, windows
CHOC ISC FLAC codec (read/write)
GrainflowLib MIT Granular synthesis (grain collections, panning, recording, phasor)
fxdsp MIT Antialiased waveshaping, Schroeder/Moorer reverbs, formant filter, PSOLA pitch shift, MinBLEP oscillators, ping-pong delay, frequency shifter, ring modulator
DspFilters MIT Multi-order IIR filter design (Butterworth, Chebyshev I/II, Elliptic, Bessel)
vafilters MIT Virtual analog filters (Moog, Diode, Korg35, Oberheim) via Faust
PolyBLEP et al. MIT Band-limited oscillators (PolyBLEP, BLIT, DPW, MinBLEP, wavetable)

Requirements

  • Python >= 3.10

  • numpy

  • C++17 compiler

  • CMake >= 3.15

Install

pip install nanodsp

Or if you prefer to build from source (requires uv and cmake):

git clone https://github.com/shakfu/nanodsp.git
cd nanodsp
uv sync            # install dependencies + build extension
uv run pytest      # run tests
uv build           # build wheel

Use make help for additional targets (build, test, lint, format, typecheck, qa, coverage, etc.).

CLI

nanodsp ships with a command-line interface accessible via nanodsp or python -m nanodsp.

# File info
nanodsp info drums.wav
nanodsp info drums.wav --json

# Process with effect chain (single file)
nanodsp process input.wav -o output.wav \
  -f highpass:cutoff_hz=80 \
  -f compress:ratio=4,threshold=-18 \
  -f normalize_peak:target_db=-1

# Apply a preset
nanodsp process vocals.wav -o out.wav -p vocal_chain
nanodsp process input.wav -o out.wav -f lowpass:cutoff_hz=12000 -p master

# PaulStretch extreme time-stretch (8x longer, smeared pad texture)
nanodsp process input.wav -o out.wav -f paulstretch:stretch=8
nanodsp process input.wav -o out.wav -f paulstretch:stretch=20,pitch_semitones=12,onset=0.5

# Keyframe stretch -- cheap, transient-preserving, no FFT (DAFx26 algorithm)
nanodsp process drums.wav -o out.wav -f keyframe_stretch:stretch=2
nanodsp process drums.wav -o out.wav -f keyframe_stretch:stretch=1.5,semitones=-3

# Signalsmith high-quality stretch / pitch-shift (musical at modest ratios)
nanodsp process input.wav -o out.wav -f signalsmith_stretch:stretch=2
nanodsp process input.wav -o out.wav -f signalsmith_stretch:stretch=1,semitones=-5,tonality_hz=8000

# Batch mode -- process multiple files to a directory
nanodsp process *.wav -O out/ -f lowpass:cutoff_hz=2000

# Batch in parallel (-j 0 = one worker per CPU). All C++ processing releases
# the GIL, so this scales across cores without multiprocessing.
nanodsp process *.wav -O out/ -f reverb:preset=hall -j 0

# Report peak / true peak / loudness before and after the chain
nanodsp process input.wav -o out.wav --stats -f compress:ratio=4

# Stream a file larger than RAM: constant memory, identical output except
# for channel-linked dynamics, which run unlinked when streamed (the CLI
# says so; note that -q suppresses that message)
nanodsp process huge.wav -o out.wav --stream -f lowpass:cutoff_hz=2000

# Effects taking a second buffer: prefix the value with @ to load a file
nanodsp process bass.wav -o out.wav -f sidechain_compress:sidechain=@kick.wav,ratio=8
nanodsp process dry.wav -o wet.wav -f convolution_reverb:ir=@church.wav,mix=0.4

# Dry run -- show chain without reading/writing files
nanodsp process input.wav -n -f highpass:cutoff_hz=80 -f compress:ratio=4

# Verbose / quiet
nanodsp -v process input.wav -o out.wav -f lowpass:cutoff_hz=1000
nanodsp -q process input.wav -o out.wav -p master

# Analyze
nanodsp analyze input.wav loudness
nanodsp analyze input.wav pitch --fmin=80 --fmax=800
nanodsp analyze input.wav onsets --json
nanodsp analyze input.wav info

# Synthesize
nanodsp synth tone.wav sine --freq=440 --duration=2.0
nanodsp synth kick.wav drum --type=analog_bass_drum --freq=60
nanodsp synth melody.wav note --instrument=clarinet --freq=440 --duration=1.0
nanodsp synth seq.wav sequence --instrument=flute \
  --notes='[{"freq":440,"start":0,"dur":0.5},{"freq":554,"start":0.5,"dur":0.5}]'

# Convert
nanodsp convert input.wav output.flac
nanodsp convert input.wav output.wav --sample-rate=44100 --channels=1 -b 24

# Presets
nanodsp preset list
nanodsp preset list spatial
nanodsp preset info master
nanodsp preset apply master input.wav output.wav target_lufs=-16

# An override reaches every step of a chain preset whose signature accepts it.
# Prefix with a step name to target one function -- master_hiphop has a
# highpass and two shelves that all take cutoff_hz.
nanodsp preset apply master_hiphop in.wav out.wav highpass.cutoff_hz=40

# Pipe (stdin/stdout streaming)
cat input.wav | nanodsp pipe -f lowpass:cutoff_hz=1000 > output.wav
cat input.wav | nanodsp pipe -p telephone > output.wav
nanodsp pipe -f highpass:cutoff_hz=80 < in.wav | nanodsp pipe -f compress:ratio=4 > out.wav

# Benchmark
nanodsp benchmark lowpass:cutoff_hz=1000
nanodsp benchmark compress:ratio=4,threshold=-20 -n 100 --duration=2.0
nanodsp benchmark reverb:preset=hall --channels=2 --json

# List available functions
nanodsp list
nanodsp list filters
nanodsp list effects

Presets

30 built-in presets across 8 categories:

Category Presets
mastering master, master_pop, master_hiphop, master_classical, master_edm, master_podcast
voice vocal_chain
spatial room, hall, plate, cathedral, chamber
dynamics gentle_compress, heavy_compress, brick_wall
creative radio, underwater, megaphone, tape_warmth, shimmer, vaporwave, walkie_talkie
lofi telephone, lo_fi, vinyl, 8bit
cleanup dc_remove, de_noise, normalize, normalize_lufs

User-defined presets

Custom presets are loaded from ~/.nanodsp/presets.json (or the path in $NANODSP_PRESETS) and merged with the built-ins; a user preset with the same name as a built-in overrides it. They appear in preset list and work everywhere the built-ins do (preset apply, process -p). Each entry mirrors a built-in: either a single function or a chain.

{
  "my_boost":     { "category": "custom", "description": "low-shelf boost",
                    "fn": "effects.low_shelf_db", "defaults": {"cutoff_hz": 150.0, "db": 4.0} },
  "my_telephone": { "category": "custom", "description": "narrow band",
                    "chain": [["effects", "highpass", {"cutoff_hz": 400.0}],
                              ["effects", "lowpass",  {"cutoff_hz": 3000.0}]] }
}
nanodsp preset apply my_boost input.wav output.wav
nanodsp process input.wav -o output.wav -p my_telephone

Tab completion

The CLI supports shell tab completion (subcommands, options, preset names, categories, and function names) via argcomplete. Install it and enable completion for your shell:

pip install argcomplete
eval "$(register-python-argcomplete nanodsp)"   # add to ~/.bashrc or ~/.zshrc

argcomplete is an optional dependency: the CLI works normally without it.

Quick start

AudioBuffer is the one name exported at the top level (from nanodsp import AudioBuffer); it is the central data type that carries audio through every operation. All DSP functions are imported from their specific submodule -- e.g. filters from nanodsp.effects.filters, dynamics from nanodsp.effects.dynamics, metering from nanodsp.analysis. Chain them with AudioBuffer.pipe, which feeds the buffer in as the first argument:

from nanodsp import AudioBuffer
from nanodsp.effects.filters import highpass
from nanodsp.effects.dynamics import compress
from nanodsp.analysis import normalize_lufs

# Read, process, write
buf = (
    AudioBuffer.from_file("input.wav")
    .pipe(highpass, cutoff_hz=80.0)
    .pipe(compress, threshold=-18.0, ratio=4.0)
    .pipe(normalize_lufs, target_lufs=-14.0)
)
buf.write("output.wav")

Modules

nanodsp.buffer -- AudioBuffer

The central data type. A 2D float32 array with shape [channels, frames] plus metadata (sample_rate, channel_layout, label).

from nanodsp import AudioBuffer

# Construction. The buffer owns its samples: the constructor copies what it is
# given, so later writes through the buffer never reach the caller's array.
# Pass copy=False to hand over an array nothing else references.
buf = AudioBuffer(np.zeros((2, 44100), dtype=np.float32), sample_rate=44100)
buf = AudioBuffer(owned_array, sample_rate=44100, copy=False)   # no copy
buf = AudioBuffer.from_file("input.wav")       # read WAV/FLAC
buf = AudioBuffer.sine(440.0, channels=1, frames=44100, sample_rate=44100)
buf = AudioBuffer.noise(channels=2, frames=44100, sample_rate=44100)
buf = AudioBuffer.zeros(channels=1, frames=1024, sample_rate=44100)
buf = AudioBuffer.impulse(channels=1, frames=1024, sample_rate=44100)

# Properties
buf.channels        # number of channels
buf.frames          # number of frames
buf.sample_rate     # sample rate in Hz
buf.duration        # duration in seconds
buf.mono            # 1D view (mono buffers only)
buf.channel(0)      # 1D view of channel 0

# Channel operations
buf.to_mono("mean")                    # downmix
buf.to_channels(2)                     # upmix mono to stereo
AudioBuffer.concat_channels(a, b)      # stack channels

# Arithmetic
buf + other          # add
buf * 0.5            # scale
buf.gain_db(-6.0)    # apply dB gain

# I/O
buf.write("output.wav")                # write WAV/FLAC (detected by extension)
buf.write("output.flac", bit_depth=24)

# Slicing. slice() returns an independent buffer; index .data for a view.
buf.slice(1000, 2000)                  # frames 1000..2000, independent copy
buf.data[:, 1000:2000]                 # zero-copy numpy view

# Pipeline (DSP functions are imported from their submodule)
from nanodsp.effects.filters import lowpass
buf.pipe(lowpass, cutoff_hz=1000.0)

nanodsp.io -- Audio file I/O

Read and write WAV and FLAC with zero external dependencies (WAV is parsed and written directly; FLAC uses the CHOC codec).

Format Read Write
WAV PCM 8/16/24/32-bit 16/24-bit
WAV IEEE float 32/64-bit 32/64-bit
WAV extensible wrapping any of the above --
FLAC 16/24-bit 16/24-bit

bit_depth selects the encoding on write: 16 and 24 are signed PCM (clipped to [-1, 1] and rounded to nearest), while 32 and 64 are IEEE float, written verbatim so values above full scale survive a gain stage intact.

from nanodsp import io

buf = io.read("file.wav")          # auto-detect by extension
buf = io.read("file.flac")
io.write("out.wav", buf)           # 16-bit default
io.write("out.flac", buf, bit_depth=24)

# Format-specific
buf = io.read_wav("file.wav")
io.write_wav("out.wav", buf, bit_depth=24)
buf = io.read_flac("file.flac")
io.write_flac("out.flac", buf, bit_depth=16)

# Byte-level (for stdin/stdout/pipe workflows)
buf = io.read_wav_bytes(raw_bytes)           # parse WAV from bytes
raw = io.write_wav_bytes(buf, bit_depth=16)  # serialize to WAV bytes

nanodsp.ops -- Core DSP operations

Low-level building blocks: delay, envelopes, FFT, convolution, sample rates, mixing, panning, normalization, cross-correlation, Hilbert transform, median filter, LMS adaptive filter.

from nanodsp import ops

# Delay
ops.delay(buf, delay_samples=100)
ops.delay_varying(buf, delays=delay_curve)

# Envelopes
ops.box_filter(buf, length=64)
ops.box_stack_filter(buf, size=32, layers=4)
ops.peak_hold(buf, length=128)
ops.peak_decay(buf, length=256)

# FFT
spectra = ops.rfft(buf)                        # forward real FFT
buf = ops.irfft(spectra, size=1024, sample_rate=44100)  # inverse

# Convolution
ops.convolve(buf, ir, normalize=True)

# Sample rates
ops.upsample_2x(buf)
ops.oversample_roundtrip(buf)

# Mixing
ops.hadamard(buf)              # Hadamard matrix mixing
ops.householder(buf)           # Householder reflection
ops.crossfade(buf_a, buf_b, x=0.5)
ops.mix_buffers(a, b, c, gains=[1.0, 0.5, 0.8])

# LFO
lfo_signal = ops.lfo(frames=44100, low=0.0, high=1.0, rate=2.0)

# Normalization
ops.normalize_peak(buf, target_db=-1.0)
ops.trim_silence(buf, threshold_db=-60.0)

# Fades
ops.fade_in(buf, duration_ms=10.0)
ops.fade_out(buf, duration_ms=50.0, curve="ease_out")  # linear, ease_in, ease_out, smoothstep

# Panning and stereo
ops.pan(buf, position=0.3)     # equal-power pan
ops.mid_side_encode(buf)
ops.mid_side_decode(buf)
ops.stereo_widen(buf, width=1.5)

# Cross-correlation
corr = ops.xcorr(buf_a, buf_b)        # cross-correlation
auto = ops.xcorr(buf)                  # autocorrelation

# Hilbert / envelope
env = ops.hilbert(buf)                 # analytic signal envelope
env = ops.envelope(buf)                # alias for hilbert

# Median filter
ops.median_filter(buf, kernel_size=5)

# LMS adaptive filter
output, error = ops.lms_filter(buf, ref, filter_len=32, step_size=0.01)

nanodsp.effects -- Filters, effects, dynamics, mastering

Over 80 functions covering signalsmith biquad filters, state-variable/ladder/tone and virtual-analog filters, multi-order IIR design, DaisySP modulation and lo-fi effects, dynamics (compression, limiting, gating, sidechain, transient shaping, AGC), saturation and antialiased waveshaping, FDN/Schroeder/Moorer/STK reverbs, composed delays and mastering/vocal chains, formant filtering, PSOLA pitch shifting, shimmer/gated reverb, lo-fi, telephone, auto-pan, and a vocoder.

Effects live in submodules under nanodsp.effects; import the function you need from its submodule (e.g. from nanodsp.effects.filters import lowpass). The groupings below show which submodule each function belongs to.

Biquad filters -- nanodsp.effects.filters

Frequencies are in Hz, auto-converted to normalized frequency. The parameter name varies by filter shape: cutoff_hz for low/high pass and shelves, center_hz for band/notch/peak, freq_hz for allpass. Bandwidth is set via octaves.

from nanodsp.effects.filters import (
    lowpass, highpass, bandpass, notch, peak, peak_db,
    high_shelf, high_shelf_db, low_shelf, low_shelf_db, allpass,
)

lowpass(buf, cutoff_hz=1000.0)
highpass(buf, cutoff_hz=80.0)
bandpass(buf, center_hz=1000.0, octaves=2.0)
notch(buf, center_hz=50.0)
peak(buf, center_hz=1000.0, gain=2.0, octaves=1.0)
peak_db(buf, center_hz=1000.0, db=6.0)
high_shelf(buf, cutoff_hz=8000.0, gain=1.5)
high_shelf_db(buf, cutoff_hz=8000.0, db=3.0)
low_shelf(buf, cutoff_hz=200.0, gain=0.8)
low_shelf_db(buf, cutoff_hz=200.0, db=-2.0)
allpass(buf, freq_hz=1000.0)

State-variable, ladder, and tone filters -- nanodsp.effects.filters

from nanodsp.effects.filters import (
    svf_lowpass, svf_highpass, svf_bandpass, svf_notch, svf_peak,
    ladder_filter, moog_ladder, tone_lowpass, tone_highpass,
    modal_bandpass, comb_filter,
)

svf_lowpass(buf, freq_hz=1000.0, resonance=0.5)
svf_highpass(buf, freq_hz=200.0, resonance=0.5)
svf_bandpass(buf, freq_hz=1000.0, resonance=0.7)
svf_notch(buf, freq_hz=1000.0, resonance=0.5)
svf_peak(buf, freq_hz=1000.0, resonance=0.8)
ladder_filter(buf, freq_hz=800.0, resonance=0.6, mode="lp24")
moog_ladder(buf, freq_hz=1000.0, resonance=0.7)
tone_lowpass(buf, freq_hz=2000.0)
tone_highpass(buf, freq_hz=100.0)
modal_bandpass(buf, freq_hz=440.0, q=50.0)
comb_filter(buf, freq_hz=500.0, rev_time=0.5)

Virtual-analog filters -- nanodsp.effects.filters

from nanodsp.effects.filters import (
    va_moog_ladder, va_moog_half_ladder, va_diode_ladder,
    va_korg35_lpf, va_korg35_hpf, va_oberheim,
)

va_moog_ladder(buf, cutoff_hz=1000.0, q=1.0)
va_diode_ladder(buf, cutoff_hz=1000.0, q=1.0)
va_korg35_lpf(buf, cutoff_hz=1000.0, q=1.0)
va_oberheim(buf, cutoff_hz=1000.0, q=1.0, mode="lpf")   # lpf, hpf, bpf, bsf

Multi-order IIR filters -- nanodsp.effects.filters

from nanodsp.effects.filters import iir_filter, iir_design

iir_filter(buf, family="butterworth", filter_type="lowpass", order=4, freq=1000.0)
iir_filter(buf, family="chebyshev1", filter_type="highpass", order=6, freq=200.0, ripple_db=1.0)
iir_filter(buf, family="elliptic", filter_type="bandpass", order=4, freq=1000.0, width=500.0)
iir_filter(buf, family="bessel", filter_type="lowpass", order=8, freq=5000.0)

# SOS coefficients without applying
sos = iir_design("butterworth", "lowpass", order=4, sample_rate=44100, freq=1000.0)

Modulation and lo-fi effects -- nanodsp.effects.daisysp

from nanodsp.effects.daisysp import (
    autowah, chorus, flanger, phaser, tremolo, overdrive,
    wavefold, fold, bitcrush, decimator, sample_rate_reduce,
    pitch_shift, reverb_sc, dc_block,
)

autowah(buf, wah=0.5)
chorus(buf, lfo_freq=1.0, lfo_depth=0.5)
flanger(buf, lfo_freq=0.2, lfo_depth=0.5, feedback=0.5)
phaser(buf, lfo_freq=0.3, lfo_depth=0.5, feedback=0.5)
tremolo(buf, freq=5.0, depth=0.8)
overdrive(buf, drive=0.7)
wavefold(buf, gain=2.0)
fold(buf, increment=1.0)
bitcrush(buf, bit_depth=8)
decimator(buf, downsample_factor=0.5, bitcrush_factor=0.5)
sample_rate_reduce(buf, freq=0.5)
pitch_shift(buf, semitones=12.0)
reverb_sc(buf, feedback=0.8, lp_freq=10000.0)
dc_block(buf)

Dynamics -- nanodsp.effects.dynamics

from nanodsp.effects.dynamics import (
    compress, limit, noise_gate, agc,
    sidechain_compress, transient_shape, lookahead_limit,
)

compress(buf, threshold=-20.0, ratio=4.0, attack=0.01, release=0.1)
limit(buf, pre_gain=2.0)
noise_gate(buf, threshold_db=-40.0)

# Stereo detectors are linked by default: one gain curve derived from the
# per-frame maximum across channels, applied to all of them, so the stereo
# image is preserved. link=False gives each channel its own detector, which
# is occasionally wanted for per-channel utility work. No-op for mono.
compress(buf, ratio=4.0, link=False)
limit(buf, pre_gain=2.0, link=False)
agc(buf, target_level=1.0, max_gain_db=60.0)
sidechain_compress(buf, sidechain, ratio=4.0, threshold=-20.0)   # sidechain is an AudioBuffer
transient_shape(buf, attack_gain=1.5, sustain_gain=0.8)
lookahead_limit(buf, threshold_db=-1.0, lookahead_ms=5.0)

Saturation and antialiased waveshaping -- nanodsp.effects.saturation

from nanodsp.effects.saturation import saturate, aa_hard_clip, aa_soft_clip, aa_wavefold

saturate(buf, drive=0.7, mode="soft")    # soft, hard, tape
aa_hard_clip(buf, drive=2.0)             # 1st-order antiderivative hard clip
aa_soft_clip(buf, drive=2.0)             # 1st-order antiderivative soft clip
aa_wavefold(buf, drive=2.0)              # 2nd-order Buchla-style wavefolder

Reverb -- nanodsp.effects.reverb

from nanodsp.effects.reverb import (
    reverb, schroeder_reverb, moorer_reverb, stk_reverb, stk_chorus, stk_echo,
)

reverb(buf, preset="hall", mix=0.3)                       # room, hall, plate, chamber, cathedral
schroeder_reverb(buf, feedback=0.7, diffusion=0.5)
moorer_reverb(buf, feedback=0.7, diffusion=0.7, mod_depth=0.1)
stk_reverb(buf, algorithm="freeverb", t60=1.5, mix=0.3)   # freeverb, jcrev, nrev, prcrev
stk_chorus(buf, mod_depth=0.02, mod_freq=1.0, mix=0.5)
stk_echo(buf, delay_ms=250.0, mix=0.5)

Composed effects, delays, and mastering chains -- nanodsp.effects.composed

from nanodsp.effects.composed import (
    exciter, de_esser, parallel_compress, multiband_compress,
    stereo_delay, ping_pong_delay, tape_echo, freq_shift, ring_mod,
    auto_pan, formant_filter, psola_pitch_shift,
    gated_reverb, shimmer_reverb, lo_fi, telephone,
    master, vocal_chain, vocoder,
)

exciter(buf, freq=3000.0, amount=0.4)
de_esser(buf, freq=6000.0, threshold_db=-20.0)
parallel_compress(buf, mix=0.5, threshold_db=-24.0, ratio=8.0)
multiband_compress(buf, crossover_freqs=[200.0, 2000.0, 8000.0])
stereo_delay(buf, left_ms=250.0, right_ms=375.0, feedback=0.4, ping_pong=True)
ping_pong_delay(buf, delay_ms=375.0, feedback=0.5, mix=0.5)
tape_echo(buf, delay_ms=300.0, feedback=0.5, mix=0.5)
freq_shift(buf, shift_hz=100.0)                  # Bode-style frequency shifting
ring_mod(buf, carrier_freq=300.0, mix=1.0)
auto_pan(buf, rate=2.0, depth=1.0)
formant_filter(buf, vowel="a")                   # a, e, i, o, u
psola_pitch_shift(buf, semitones=5.0)            # pitch-synchronous overlap-add
gated_reverb(buf, preset="plate", gate_threshold_db=-30.0)
shimmer_reverb(buf, mix=0.4, shift_semitones=12.0)
lo_fi(buf, bit_depth=8, reduce=0.5, drive=0.3)
telephone(buf, low_cut=300.0, high_cut=3400.0)

# Mastering and vocal chains
master(buf, target_lufs=-14.0)
vocal_chain(buf, de_ess_freq=6000.0)

# Vocoder takes a modulator and a carrier (both AudioBuffers)
vocoder(modulator, carrier, n_bands=16, freq_range=(80.0, 8000.0))

nanodsp.spectral -- STFT and spectral processing

Short-time Fourier transform, spectral utilities, and spectral transforms.

Most utilities and transforms operate on a Spectrogram (the object returned by stft); a few that wrap the full analysis/synthesis round-trip take an AudioBuffer directly (pitch_shift_spectral, eq_match).

from nanodsp import spectral

# STFT / inverse
spec = spectral.stft(buf, window_size=2048, hop_size=512)
buf = spectral.istft(spec)

# Spectral utilities (operate on a Spectrogram)
mag = spectral.magnitude(spec)
ph = spectral.phase(spec)
spec = spectral.from_polar(mag, ph, spec)               # spec supplies the geometry/metadata
spec = spectral.apply_mask(spec, mask)
spec = spectral.spectral_gate(spec, threshold_db=-40.0)
spec = spectral.spectral_emphasis(spec, low_db=-3.0, high_db=3.0)
freq = spectral.bin_freq(spec, bin_index=10)
b = spectral.freq_to_bin(spec, freq_hz=1000.0)

# Spectral transforms (Spectrogram -> Spectrogram unless noted)
stretched = spectral.time_stretch(spec, rate=0.5)        # half speed; istft to render
locked = spectral.phase_lock(spec)                        # phase-locking
frozen = spectral.spectral_freeze(spec, frame_index=10)   # frozen texture
morphed = spectral.spectral_morph(spec_a, spec_b, mix=0.5)
shifted = spectral.pitch_shift_spectral(buf, semitones=5.0)   # takes/returns AudioBuffer
denoised = spectral.spectral_denoise(spec, noise_frames=10)

# EQ matching (AudioBuffer -> AudioBuffer)
matched = spectral.eq_match(source_buf, target_buf)

nanodsp.timestretch -- Time-stretching and pitch-shifting

Two complementary backends for changing duration and pitch.

PaulStretch (by Nasca Octavian Paul, public domain) for extreme time-stretching via phase-randomized spectral resynthesis. It produces the smeared, ambient, pad-like textures PaulStretch is known for and is intended for large stretch factors where a phase-vocoder (spectral.time_stretch) breaks down. This is an original implementation built on the signalsmith FFT; it does not use the GPLv3 paulxstretch application sources.

from nanodsp.timestretch import paulstretch

# Core extreme stretch -- 8x longer, pitch preserved
out = paulstretch(buf, stretch=8.0)

# Larger window -> smoother/more diffuse; transient preservation for attacks
out = paulstretch(buf, stretch=20.0, window_size=8192, onset=0.5)

# Spectral effects applied during resynthesis
out = paulstretch(buf, stretch=8.0, pitch_semitones=12.0)   # up one octave
out = paulstretch(buf, stretch=8.0, harmonics=3, spread=8.0) # thicker, more diffuse
out = paulstretch(buf, stretch=8.0, highpass_hz=500.0, lowpass_hz=6000.0)

# Constant-Q spread -- each partial smeared by a fixed fraction of its own
# frequency, unlike `spread`, which blurs a fixed number of bins
out = paulstretch(buf, stretch=8.0, spread_octaves=0.3)

# Tonal/noise separation: +1 keeps the pitched peaks, -1 keeps the noise floor
out = paulstretch(buf, stretch=8.0, tonal_vs_noise=1.0)    # cleaner drone
out = paulstretch(buf, stretch=8.0, tonal_vs_noise=-1.0)   # breathy wash

Output length is approximately frames * stretch; all channels share the same length, and stereo material is decorrelated (per-channel seeds) for a wider image. Output is reproducible for a given seed. Also available as the CLI filter paulstretch:stretch=....

Signalsmith stretch (signalsmith-stretch, MIT) is a transient-aware, phase-vocoder-derived stretcher that stays musical at modest ratios and decouples time-stretch from pitch-shift. Use it for clean slow-downs/speed-ups and independent pitch-shifting rather than the smeared PaulStretch character.

from nanodsp.timestretch import signalsmith_stretch

# Time-stretch, pitch preserved (2x longer)
out = signalsmith_stretch(buf, stretch=2.0)

# Pure pitch-shift, length unchanged (down a perfect fifth)
out = signalsmith_stretch(buf, stretch=1.0, semitones=-7.0)

# Stretch and pitch-shift together; tonality limit preserves high-frequency air
out = signalsmith_stretch(buf, stretch=1.5, semitones=12.0, tonality_hz=8000.0)

Output length is approximately frames * stretch; time-stretch and pitch-shift are independent, all channels are processed coherently in one pass, and output is reproducible for a given seed. Also available as the CLI filter signalsmith_stretch:stretch=....

Keyframe stretch -- content-adaptive overlap-add, no FFT:

from nanodsp.timestretch import keyframe_stretch, keyframe_sparsify

out = keyframe_stretch(buf, stretch=2.0)                      # twice as long
out = keyframe_stretch(buf, stretch=1.5, semitones=-3.0)      # both at once
out = keyframe_stretch(buf, stretch=2.0, splice_keyframes=64) # longer splices

out = keyframe_sparsify(buf)                                  # the representation alone
out = keyframe_sparsify(buf, threshold=0.02)                  # coarser: lo-fi

The signal is reduced to its local extrema; their spacing tracks local bandwidth, so it doubles as a free estimate of information density and sizes each crossfade -- short at transients, long across sustained notes, with no separate transient detector. Output length is exactly round(frames * stretch). It is the cheapest stretcher here and the only one with no block latency, at the cost of some spectral haze on tonally nuanced material. An original implementation of the algorithm in Nielsen, "Keyframe Time Stretching via Extrema Sampling" (DAFx26, CC BY 4.0). Also available as the CLI filters keyframe_stretch:stretch=... and keyframe_sparsify:threshold=....

nanodsp.synthesis -- Oscillators, noise, drums, physical modeling

Sound generators using DaisySP and STK backends.

from nanodsp import synthesis

# Oscillators
synthesis.oscillator(frames=44100, freq=440.0, waveform="saw")
synthesis.fm2(frames=44100, freq=440.0, ratio=2.0, index=1.0)
synthesis.formant_oscillator(frames=44100, carrier_freq=440.0, formant_freq=800.0)
synthesis.bl_oscillator(frames=44100, freq=440.0, waveform="saw")

# Noise
synthesis.white_noise(frames=44100, amp=0.5)
synthesis.clocked_noise(freq=1000.0, frames=44100)
synthesis.dust(density=100.0, frames=44100)

# The voices that draw on the shared C rand() stream -- the STK instruments,
# the drums, pluck, drip, string_voice, clocked_noise and dust -- take a seed
# and are pure functions of their arguments. Pass a different seed for a
# different variation.
synthesis.dust(density=100.0, frames=44100, seed=7)
synthesis.synth_note("clarinet", freq=440.0, duration=1.0, seed=7)

# Drums
synthesis.analog_bass_drum(freq=60.0, frames=44100)
synthesis.analog_snare_drum(freq=200.0, frames=44100)
synthesis.hihat(freq=3000.0, frames=44100)
synthesis.synthetic_bass_drum(freq=60.0, frames=44100)
synthesis.synthetic_snare_drum(freq=200.0, frames=44100)

# Physical modeling
synthesis.karplus_strong(buf, freq_hz=440.0, brightness=0.5, damping=0.5)   # excites an AudioBuffer
synthesis.modal_voice(frames=44100, freq=440.0)
synthesis.string_voice(frames=44100, freq=440.0)
synthesis.pluck(frames=44100, freq=440.0)
synthesis.drip(frames=44100, dettack=0.01)

# STK synthesis
synthesis.synth_note("clarinet", freq=440.0, velocity=0.8, duration=1.0)
# notes are (freq_hz, start_s, duration_s) tuples
synthesis.synth_sequence("flute", notes=[
    (440.0, 0.0, 0.5),
    (554.37, 0.5, 0.5),
])

# Band-limited oscillators (PolyBLEP et al.)
synthesis.polyblep(frames=44100, freq=440.0, waveform="sawtooth")  # sawtooth, square, triangle
synthesis.blit_saw(frames=44100, freq=220.0)
synthesis.blit_square(frames=44100, freq=220.0)
synthesis.dpw_saw(frames=44100, freq=440.0)
synthesis.dpw_pulse(frames=44100, freq=440.0, duty=0.5)
synthesis.minblep(frames=44100, freq=440.0, waveform="saw")        # saw, rsaw, square, triangle
synthesis.minblep(frames=44100, freq=440.0, waveform="square", pulse_width=0.3)

Available STK instruments: clarinet, flute, brass, bowed, plucked, sitar, stifkarp, saxofony, recorder, blowbotl, blowhole, whistle.

nanodsp.analysis -- Loudness, spectral features, pitch, onsets, resampling

from nanodsp import analysis

# Loudness (ITU-R BS.1770-4)
lufs = analysis.loudness_lufs(buf)
dbtp = analysis.true_peak_dbtp(buf)                       # true-peak via 4x oversampling
buf = analysis.normalize_lufs(buf, target_lufs=-14.0)

# Spectral features
centroid = analysis.spectral_centroid(buf)
bandwidth = analysis.spectral_bandwidth(buf)
rolloff = analysis.spectral_rolloff(buf, percentile=0.85)
flux = analysis.spectral_flux(buf, rectify=True)
flatness = analysis.spectral_flatness_curve(buf)
chroma = analysis.chromagram(buf, n_chroma=12, tuning_hz=440.0)

# Pitch detection (YIN algorithm)
f0, confidence = analysis.pitch_detect(buf, method="yin", fmin=50.0, fmax=2000.0)

# Onset detection
onsets = analysis.onset_detect(buf, method="spectral_flux", threshold=0.5)

# Resampling
buf_48k = analysis.resample(buf, target_sr=48000.0)       # madronalib backend
buf_22k = analysis.resample_fft(buf, target_sr=22050.0)   # FFT-based

# GCC-PHAT delay estimation
delay_sec, corr = analysis.gcc_phat(buf, ref)

nanodsp.stream -- Real-time streaming infrastructure

Stateless effects rebuild their DSP object every call, so feeding them successive blocks restarts the filter at each boundary. The stateful_* constructors keep one object per channel alive instead, so block-by-block processing matches whole-buffer processing exactly.

import numpy as np
from nanodsp import AudioBuffer
from nanodsp.io import read_blocks, BlockWriter
from nanodsp.stream import stateful_lowpass, stateful_compress, STREAMABLE

# 18 effects have a streaming form
sorted(STREAMABLE)          # lowpass, compress, flanger, schroeder_reverb, ...

# Process a file of any size in constant memory
filt = stateful_lowpass(2000.0, channels=2, sample_rate=48000.0)
with BlockWriter("out.wav", 48000.0, channels=2) as writer:
    for block in read_blocks("huge.wav", block_size=65536):
        writer.write(filt.process(block))

Channel-linked dynamics and the mono-to-stereo effects (the FDN reverb, chorus, the STK reverbs) have no per-channel form and so cannot stream; nanodsp process --stream rejects a chain containing them rather than silently producing different audio.

Block-based processing, ring buffers, and processor chains for streaming audio.

from nanodsp.stream import (
    RingBuffer, BlockProcessor, CallbackProcessor, ProcessorChain, process_blocks
)

# Ring buffer
rb = RingBuffer(channels=2, capacity=8192)
rb.write(frame_data)
chunk = rb.read(512)

# Block processor
class MyProcessor(BlockProcessor):
    def process_block(self, block):
        return block * 0.5

proc = MyProcessor(block_size=512)
out = proc.process(buf)

# Callback processor (callback is the first argument)
proc = CallbackProcessor(lambda b: b * 0.5, block_size=512)

# Chain processors (pass processors as positional args)
chain = ProcessorChain(proc1, proc2, proc3)
out = chain.process(buf)

# Process with overlap-add (fn is the second argument)
out = process_blocks(buf, my_spectral_fn, block_size=2048, hop_size=512)

Stateful streaming filters

Unlike nanodsp.effects.filters, which rebuild their filter on every call and so cannot be streamed without discontinuities at block boundaries, StatefulFilter keeps one persistent filter per channel. Feeding a signal through it in arbitrary chunks gives exactly the same result as processing the whole signal at once -- suitable for real-time and long-file streaming, and composable in a ProcessorChain.

from nanodsp.stream import (
    StatefulFilter, ProcessorChain,
    stateful_lowpass, stateful_highpass, stateful_bandpass, stateful_notch,
    stateful_moog_ladder,
)

# Construct once, then feed successive blocks -- state carries across calls.
lp = stateful_lowpass(1000.0, channels=2, sample_rate=48000.0)
for block in blocks:                 # any block sizes, even 1 sample
    out_block = lp.process(block)     # continuous; no boundary clicks

lp.reset()                            # clear filter state

# Cascade stateful filters; the chain streams continuously too.
chain = ProcessorChain(
    stateful_highpass(200.0, sample_rate=48000.0),
    stateful_lowpass(4000.0, sample_rate=48000.0),
)
out = chain.process(block)

# Resonant DaisySP Moog ladder (demonstrates cross-backend support)
ml = stateful_moog_ladder(1000.0, resonance=0.3, sample_rate=48000.0)

# Wrap any stateful per-channel DSP object via a factory (process(1d) -> 1d)
from nanodsp._core import filters
sf = StatefulFilter(lambda: _configured_biquad(), channels=1, sample_rate=48000.0)

nanodsp._core.grainflow -- Granular synthesis (low-level)

Direct access to GrainflowLib's granular synthesis engine.

from nanodsp._core import grainflow as gf

# Create a buffer and fill with audio data
buf = gf.GfBuffer(4096, 1, 48000)
buf.set_data(audio_array)  # [channels, frames] float32

# Create a grain collection
gc = gf.GrainCollection(num_grains=8, samplerate=48000)
gc.set_buffer(buf, gf.BUF_BUFFER, 0)  # 0 = set for all grains

# Set parameters (enum-based or string reflection)
gc.param_set(0, gf.PARAM_RATE, gf.PTYPE_BASE, 1.0)
gc.param_set_str(0, "delayRandom", 10.0)

# Generate a clock and process
phasor = gf.Phasor(rate=10.0, samplerate=48000)
clock = phasor.perform(256).reshape(1, 256)
traversal = np.linspace(0, 0.5, 256, dtype=np.float32).reshape(1, 256)
fm = np.zeros((1, 256), dtype=np.float32)
am = np.zeros((1, 256), dtype=np.float32)

# Returns 8-element tuple: (output, state, progress, playhead, amp, envelope, buf_ch, stream_ch)
result = gc.process(clock, traversal, fm, am, 48000)
grain_output = result[0]  # [num_grains, block_size]

# Pan grains to stereo
panner = gf.Panner(in_channels=8, out_channels=2, pan_mode=gf.PAN_STEREO)
panner.set_pan_spread(0.5)
stereo = panner.process(grain_output, result[1], out_channels=2)  # [2, block_size]

nanodsp._core -- C++ bindings (low-level)

Direct access to the C++ extension module with 17 submodules. All high-level Python modules build on these.

Submodule Backend Contents
filters signalsmith Biquad with 16 filter designs, BiquadDesign enum
fft signalsmith FFT (complex), RealFFT (real)
delay signalsmith Delay (linear), DelayCubic (cubic interpolation)
envelopes signalsmith CubicLfo, BoxFilter, BoxStackFilter, PeakHold, PeakDecayLinear
spectral signalsmith STFT (multi-channel analysis/synthesis)
rates signalsmith Oversampler2x
mix signalsmith Hadamard, Householder, cheap_energy_crossfade
daisysp DaisySP 9 submodules, ~60 classes (oscillators, filters, effects, dynamics, drums, noise, physical modeling, control, utility)
stk STK 5 submodules, 39 classes (instruments, generators, filters, delays, effects)
madronalib madronalib FDN reverbs, resampling, generators, 18 projection functions, 6 window functions
hisstools HISSTools Convolution (mono/multi), spectral processing, 24 statistics functions, 28 window functions, partial tracking
choc CHOC FLAC read/write
grainflow GrainflowLib GfBuffer, GrainCollection, Panner, Recorder, Phasor, 37 enum constants
vafilters vafilters (Faust) 6 virtual analog filters (Moog ladder, Diode ladder, Korg35 LP/HP, Oberheim multi-mode)
bloscillators PolyBLEP et al. 5 band-limited oscillator algorithms (PolyBLEP, BLIT, DPW, MinBLEP, wavetable)
fxdsp fxdsp Antialiased clippers/wavefolder, Schroeder/Moorer reverbs, formant filter, PSOLA, MinBLEP oscillator, ping-pong delay, frequency shifter, ring modulator
iirdesign DspFilters Multi-order IIR filter design (Butterworth, Chebyshev I/II, Elliptic, Bessel, orders 1-16)
from nanodsp._core import filters, fft, delay, daisysp, stk, madronalib, hisstools, grainflow, vafilters, bloscillators, fxdsp, iirdesign

# Example: direct biquad usage
bq = filters.Biquad()
bq.lowpass(0.1, 0.707)
out = bq.process(input_array)

# Example: direct FFT
f = fft.RealFFT(1024)
spectrum = f.fft(signal)

# Example: DaisySP oscillator
osc = daisysp.oscillators.Oscillator()
osc.init(44100.0)
osc.set_freq(440.0)
samples = osc.process(1024)

Full type stubs are provided in _core.pyi for IDE autocompletion and type checking.

Architecture

nanodsp/
  __init__.py          # package root (exports AudioBuffer, __version__)
  __main__.py          # CLI entry point (argparse, subcommand handlers)
  _cli.py              # function/preset registries, fx parser, type coercion
  _core.cpython-*.so   # compiled C++ extension (nanobind)
  _core.pyi            # type stubs for C++ extension
  _helpers.py          # shared private utilities
  buffer.py            # AudioBuffer class
  io.py                # audio file I/O (WAV + FLAC)
  ops.py               # delay, envelopes, FFT, convolution, rates, mix, pan, xcorr, hilbert, median, LMS
  effects/             # filters, daisysp, dynamics, saturation, reverb, composed
  spectral.py          # STFT, spectral transforms, eq_match
  synthesis.py         # oscillators, noise, drums, physical modeling
  analysis.py          # loudness, spectral features, pitch, onsets, resample, gcc_phat
  stream.py            # ring buffer, block processors, overlap-add

Performance Guidance

Computational cost tiers

Cost estimates assume a typical stereo buffer at 44.1 kHz. Actual times vary with buffer length, sample rate, and hardware.

Tier Typical latency Functions
Cheap (< 1 ms) Near-instant lowpass, highpass, bandpass, notch, peak, allpass, shelving filters, gain_db, normalize_peak, box_filter, peak_hold, peak_decay, delay, pan, mix_buffers, crossfade, hadamard, householder, fade_in, fade_out, trim_silence, dc_block
Moderate (1--10 ms) Noticeable in tight loops chorus, flanger, phaser, tremolo, autowah, compress, limit, noise_gate, saturate, overdrive, exciter, de_esser, parallel_compress, svf_*, ladder_filter, moog_ladder, iir_filter, agc, aa_hard_clip, aa_soft_clip, aa_wavefold, formant_filter
Expensive (> 10 ms) Dominates processing time stft/istft, convolve, reverb (FDN, reverb_sc), schroeder_reverb, moorer_reverb, time_stretch, pitch_shift_spectral, eq_match, spectral_denoise, spectral_freeze, psola_pitch_shift, resample, resample_fft, multiband_compress, lms_filter, master, vocal_chain

Block size recommendations

  • Offline processing: Pass the full file as a single AudioBuffer. This minimizes per-block overhead and is the simplest approach.

  • Streaming / real-time: Use BlockProcessor or process_blocks with 256--1024 samples per block. This range balances throughput against latency.

  • Throughput vs. latency: Larger blocks amortize fixed overhead (function calls, GIL acquire/release) but increase latency proportionally. At 44.1 kHz, a 512-sample block is ~11.6 ms of latency.

  • Stateful effects: Effects with internal state (IIR filters, compressors, FDN reverb, delays) must be initialized once and reused across blocks. BlockProcessor and ProcessorChain handle this automatically.

GIL release

All C++ block-processing functions release the Python GIL during computation. This means you can process multiple AudioBuffer objects in parallel using threading or concurrent.futures.ThreadPoolExecutor and achieve true multi-core parallelism -- no need for multiprocessing, and no pickling of buffers across a process boundary.

The CLI uses this for batch work: nanodsp process *.wav -O out/ -j 0 runs one worker per CPU. Measured 2.67x end-to-end on 8 cores for 8 x 20 s stereo files through a reverb/compress/saturate chain, and closer to 3.9x on the DSP alone once fixed interpreter startup is subtracted. Each file is independent -- effects build their own DSP objects per call and hold no shared state -- so the same pattern is safe in your own code.

Limits on that:

  • A low-level nanodsp._core object is not locked. Do not call one object from two threads at once; reconfiguring it (for example IIRFilter.setup()) while another thread runs process() corrupts memory.
  • STK's sample rate is process-global, and changing it retunes every live STK object. The STK-based functions (synth_note, synth_sequence, stk_reverb, stk_chorus, stk_echo) therefore take a shared lock and run one at a time.
  • The C rand() stream and DaisySP's PitchShifter seed are process-global, so seeded noise voices and pitch_shift are not bit-reproducible under threads.

Benchmarking

The CLI provides a built-in benchmark command for measuring function throughput:

nanodsp benchmark lowpass:cutoff_hz=1000
nanodsp benchmark compress:ratio=4,threshold=-20 -n 100 --duration=2.0
nanodsp benchmark reverb:preset=hall --channels=2 --json

This reports iterations per second, mean time per call, and buffer throughput in seconds-of-audio per wall-second.

Demos

21 demo scripts in demos/ showcase the full API surface. Run them all at once:

make demos                              # uses demos/s01.wav
make demos DEMO_INPUT=my_audio.wav      # use a custom input file

Or run individual demos:

uv run python demos/demo_filters.py demos/s01.wav
uv run python demos/demo_reverb.py demos/s01.wav -o /tmp/reverb-output
uv run python demos/demo_distortion.py demos/s01.wav --no-normalize
uv run python demos/demo_synthesis.py                # no input file needed
uv run python demos/demo_analysis.py demos/s01.wav   # prints to stdout

make demos writes a few hundred files, which is more than anyone wants to open one at a time. demos/play.sh filters them by name and plays what matches, using play from sox. Extra keywords narrow rather than widen, so a broad match is refined by adding words:

./demos/play.sh keyframe                # every keyframe render
./demos/play.sh keyframe splice         # just the splice-threshold sweep
./demos/play.sh -l reverb               # list matches, do not play
./demos/play.sh -r 'stretch-(2|4)x'     # regex instead of substring
./demos/play.sh -n 5 -g 0.5 compare     # first five, at half volume

Matches sort naturally, so a parameter sweep plays as k4, k16, k64, k256 rather than in lexicographic order. -h lists the options.

Script Variants What it demonstrates
demo_filters.py 13 Lowpass, highpass, bandpass, notch, peak EQ, high/low shelf
demo_modulation.py 10 Chorus, flanger, phaser, tremolo
demo_distortion.py 14 Overdrive, wavefold, bitcrush, decimator, saturation, fold
demo_reverb.py 12 FDN presets, ReverbSc, STK freeverb/jcrev/nrev/prcrev
demo_dynamics.py 9 Compression, limiting, noise gate, parallel/multiband compression
demo_delay.py 8 Stereo delay, ping-pong, slapback, STK echo
demo_pitch.py 10 Time-domain and spectral pitch shifting
demo_spectral.py 12 Time stretch, phase lock, spectral gate, tilt EQ, freeze
demo_daisysp_filters.py 21 SVF, ladder, moog, tone, modal, comb filters
demo_composed.py 28 Autowah, SR reduce, DC block, exciter, de-esser, vocal chain, mastering, STK chorus, shimmer reverb, tape echo, lo-fi, telephone, gated reverb, auto-pan
demo_spectral_extra.py 8 Spectral denoise, EQ match, spectral morph
demo_ops.py 29 Delay, vibrato, convolution, envelopes, fades, panning, stereo widening, crossfade
demo_resample.py 6 Madronalib and FFT resampling at 22k/48k/96k
demo_synthesis.py 44 Oscillators, FM, noise, drums, physical modeling, STK instruments (no input file)
demo_analysis.py -- Loudness, spectral features, pitch, onsets, chromagram (stdout only)
demo_grainflow.py 7 Granular clouds (basic, dense), pitch shift, sparse stochastic, stereo panning, recorder
demo_fxdsp.py 38 Antialiased waveshaping, Schroeder/Moorer reverbs, formant filter, PSOLA pitch shift, MinBLEP oscillators, ping-pong delay, frequency shifter, ring modulator
demo_iir_filters.py 23 Butterworth, Chebyshev I/II, Elliptic, Bessel filters at various orders
demo_paulstretch.py 15 PaulStretch extreme time-stretch: stretch factors, window size, transient preservation, octave shift, harmonics/spread, constant-Q spread, tonal/noise separation, spectral band-pass, long drone
demo_signalsmith_stretch.py 15 Signalsmith time-stretch / pitch-shift: stretch factors, pure pitch-shifts (octave, fifth, detune), tonality limit, combined stretch+pitch (monster/chipmunk), cheaper preset, and an extreme-factor signalsmith-vs-PaulStretch comparison
demo_keyframe.py 22 Keyframe stretch via extrema sampling: stretch factors, pitch-shifts, the splice-threshold macro (K = 4/16/64/256), the splice duration cap, a sparsification threshold sweep with retained-sample counts, and a timed three-way comparison against the other two stretchers

File-processing scripts share the same interface:

usage: demo_*.py [-h] [-o OUT_DIR] [-n] infile

positional arguments:
  infile                Input .wav file

options:
  -o, --out-dir DIR     Output directory (default: build/demo-output)
  -n, --no-normalize    Skip peak normalization (may clip on PCM output)

demo_synthesis.py generates sounds from scratch (no input file; takes -o and -n only). demo_analysis.py prints measurements to stdout (no audio output). demo_grainflow.py processes an input file through granular synthesis.

Development

make build    # rebuild extension after C++ changes
make test     # run the test suite
make demos    # run all 21 demo scripts
make qa       # test + lint + typecheck + format
make coverage # tests with coverage report
make asan     # rebuild with AddressSanitizer and run the suite under it

make asan is worth running after any vendored-library upgrade. Memory errors in the C++ layer are invisible from Python and invisible to the test suite: an out-of-bounds write corrupts the allocator's metadata and the process traps later, inside an unrelated allocation, so the reported crash location is meaningless. Two such bugs in vendored STK were found this way. It leaves an instrumented build installed -- run make build afterwards to restore a normal one.

Regression fixtures

tests/GOLDEN.json pins the numeric output of 65 cases across every backend, so a vendored library that starts producing different numbers is caught even when it still satisfies every property the suite asserts. It is a committed fixture, not a build artefact. Comparison is numeric with a tolerance rather than by hash, since an exact hash of float output is not portable across compilers or CPUs. When a change to the output is intended, regenerate and review the diff as part of the same commit:

uv run python tests/test_golden.py --update

License

nanodsp's own code is MIT. A built wheel statically links the vendored C++ backends, so it is a combined work: MIT AND LGPL-2.1-only AND BSD-3-Clause AND ISC. Every notice is reproduced in THIRD_PARTY_LICENSES.md, which ships inside the wheel, and the corresponding source is vendored under thirdparty/ and included in the sdist.

The LGPL-2.1 component is DaisySP-LGPL, which supplies Compressor, ReverbSc, MoogLadder, BlOsc, Bitcrush, Fold, Pluck, Tone and Comb among others -- all reachable from the public API, so it is not an optional part. Embedding nanodsp in a proprietary product means taking on LGPL-2.1 section 6 for that component.

Metadata

Release files for nanodsp 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nanodsp 0.4.0
File Size Uploaded
nanodsp-0.4.0.tar.gz 35.1 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for nanodsp 0.4.0
File
nanodsp-0.4.0-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
nanodsp-0.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
nanodsp-0.4.0-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details
nanodsp-0.4.0-cp314-cp314-macosx_10_15_x86_64.whl CPython 3.14 CPython 3.14 macOS 10.15+ x86-64 Details
nanodsp-0.4.0-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
nanodsp-0.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
nanodsp-0.4.0-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
nanodsp-0.4.0-cp313-cp313-macosx_10_15_x86_64.whl CPython 3.13 CPython 3.13 macOS 10.15+ x86-64 Details
nanodsp-0.4.0-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
nanodsp-0.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 Details
nanodsp-0.4.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
nanodsp-0.4.0-cp312-cp312-macosx_10_15_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.15+ x86-64 Details
nanodsp-0.4.0-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
nanodsp-0.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
nanodsp-0.4.0-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
nanodsp-0.4.0-cp311-cp311-macosx_10_15_x86_64.whl CPython 3.11 CPython 3.11 macOS 10.15+ x86-64 Details
nanodsp-0.4.0-cp310-cp310-win_amd64.whl CPython 3.10 CPython 3.10 Windows x86-64 Details
nanodsp-0.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
nanodsp-0.4.0-cp310-cp310-macosx_11_0_arm64.whl CPython 3.10 CPython 3.10 macOS 11.0+ ARM64 Details
nanodsp-0.4.0-cp310-cp310-macosx_10_15_x86_64.whl CPython 3.10 CPython 3.10 macOS 10.15+ x86-64 Details

Total release size: 52.6 MB

Release files / nanodsp-0.4.0.tar.gz

Download URL nanodsp-0.4.0.tar.gz
Size 35.1 MB
Tags Source
SHA-256 checksum
How to use checksums
ddde6445a26304ca11257f46480592c5170dcf7ec49332f37831e737ac1adfe9
BLAKE2b-256 checksum
How to use checksums
e9f2fdaf1ebc721d5ce4139d89f3cf91b5897d41f406f5691498cc2dfa2b690d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp314-cp314-win_amd64.whl

Download URL nanodsp-0.4.0-cp314-cp314-win_amd64.whl
Size 877.0 kB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
47edf7465b9bc757e805ec07ccfa08d279d1c083daba9d192954a337ca03df65
BLAKE2b-256 checksum
How to use checksums
5ea41ee619049113876514b86af189aaff1a8d0bcd9aa4e13a7111a026258cae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL nanodsp-0.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 972.3 kB
Tags CPython 3.14 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
86da44fbc2f024fa813e2ba64ec7670c4b66df855256ebf329348265a4a9036a
BLAKE2b-256 checksum
How to use checksums
c179d61d621ec60ff11d14ee871360cf2bba04782b04a272a4d4970e0153a8d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp314-cp314-macosx_11_0_arm64.whl

Download URL nanodsp-0.4.0-cp314-cp314-macosx_11_0_arm64.whl
Size 790.0 kB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
ecd019148cb8c147b84fb64ab10ba3ea0da87f8910951c792d382e97eb98414b
BLAKE2b-256 checksum
How to use checksums
1262b9536b5f0bdd7704f2dbd6bdcdf3009e3733a7cff6563dc9261ce6c648df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp314-cp314-macosx_10_15_x86_64.whl

Download URL nanodsp-0.4.0-cp314-cp314-macosx_10_15_x86_64.whl
Size 873.5 kB
Tags CPython 3.14 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
c8dcb1ebfe992d3a8878fadcde3e063ec633c5e70ce74f17999a2329d33aa5a0
BLAKE2b-256 checksum
How to use checksums
ad23ccc21971b5677dc19a1b097524a9c2f730d46017e2658951de783008716d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp313-cp313-win_amd64.whl

Download URL nanodsp-0.4.0-cp313-cp313-win_amd64.whl
Size 851.7 kB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
4d2d8c4b9a5001ee1252008906100ee1de8f840d1ed10747f4ad80c901a8d3f8
BLAKE2b-256 checksum
How to use checksums
38cf7511be0b27e36f47354eee0251c01f9e20042d6ea5e715c865d4abc83fbc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL nanodsp-0.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 972.0 kB
Tags CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
63e50a872fbfe71c7d85f992d9f97980e4ef93e7c72ec55431da456f8f40b6e4
BLAKE2b-256 checksum
How to use checksums
39acb49627f532c8ded0cc6677bcd3195a516f935ac6970886f2ce4f7f4733db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp313-cp313-macosx_11_0_arm64.whl

Download URL nanodsp-0.4.0-cp313-cp313-macosx_11_0_arm64.whl
Size 789.9 kB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a97b6743b8791655ee6a1537d6d97d42b573dcbd4b3a924fb43028a6d34b4301
BLAKE2b-256 checksum
How to use checksums
c393c135aa006e8149b964571ba98431d75376a54a50ebdea26dac5e0e9d41e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp313-cp313-macosx_10_15_x86_64.whl

Download URL nanodsp-0.4.0-cp313-cp313-macosx_10_15_x86_64.whl
Size 873.7 kB
Tags CPython 3.13 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
13d81542772d7e53a54e283e1f84d35a46882ac63fbecf76464ae9248900cb38
BLAKE2b-256 checksum
How to use checksums
d807b6ad212dad38f1381d7517b618a6e41dce6f52ea4fdd5039b8aab71eb834
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp312-cp312-win_amd64.whl

Download URL nanodsp-0.4.0-cp312-cp312-win_amd64.whl
Size 851.6 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
32805ece6250667efa86abb2a4f6ae78fdbe3b56cfa815c1447f87f7b332f099
BLAKE2b-256 checksum
How to use checksums
e530915cbd9e25b01fe6083a4d9a1c815a4f22df6833ea72f0ada3eb95a1cf94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL nanodsp-0.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 972.1 kB
Tags CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
d82f90ad76bc7dc8028a2d04dc7ad7e395100f5a2b2c41b41e1c9c5f1e9c2f2d
BLAKE2b-256 checksum
How to use checksums
b19b8ce0d9c5a3c8ab534b799081f454c23bb9ac5528c7c3e5f750dfa5a15c67
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL nanodsp-0.4.0-cp312-cp312-macosx_11_0_arm64.whl
Size 789.9 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
884fdce46ced217737e752a8ad9c11c8d946640e78fc92eda5559d5b11baab6d
BLAKE2b-256 checksum
How to use checksums
ffef35cf8dd527ce67e2b2b02efdc8b8b08e694fd6c34bb8457d9a8df5644593
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp312-cp312-macosx_10_15_x86_64.whl

Download URL nanodsp-0.4.0-cp312-cp312-macosx_10_15_x86_64.whl
Size 873.8 kB
Tags CPython 3.12 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
14f23e6d3bc9272831e10ec8ecb004218a654f956119918a1089592477ce76df
BLAKE2b-256 checksum
How to use checksums
9d6559f58423b5230dab244693fbff7079fc4f21d16920d0f6ba75b88e20378a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp311-cp311-win_amd64.whl

Download URL nanodsp-0.4.0-cp311-cp311-win_amd64.whl
Size 851.1 kB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
7375b8cec8a616cec3f42a249d04e9eb2c00b219c992c4f03ba07227992f0c50
BLAKE2b-256 checksum
How to use checksums
9282f3182dc3cf43d5a706c75e0576f2c8633e5c42d08894a4f92681b1875dd7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL nanodsp-0.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 973.4 kB
Tags CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
cfe38fbe49be8bf4ebc4f1df743a389aad3d07f5a4ab62ec8dfacb9046c641b1
BLAKE2b-256 checksum
How to use checksums
5a6551e4346964efb372dc7b614207dab43b0ee8117dfc5dab54b7b255889bb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp311-cp311-macosx_11_0_arm64.whl

Download URL nanodsp-0.4.0-cp311-cp311-macosx_11_0_arm64.whl
Size 790.7 kB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
c89a6ae7e014f01a3c0b275f5f74c6b42baf9d78fab5964fedd979b5139e970c
BLAKE2b-256 checksum
How to use checksums
08fa67c2076d337e13a625b1aa9f03dadc963d5064679c3c20f15dfef356b8a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp311-cp311-macosx_10_15_x86_64.whl

Download URL nanodsp-0.4.0-cp311-cp311-macosx_10_15_x86_64.whl
Size 874.4 kB
Tags CPython 3.11 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
10372150d228fac6896a50f0ec4193ccea4560497d10236de3b4144eba1e73c9
BLAKE2b-256 checksum
How to use checksums
f4223179cd894f0e417a5ba97ebb9b606d122cf864771e5df98627ff43acd7a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp310-cp310-win_amd64.whl

Download URL nanodsp-0.4.0-cp310-cp310-win_amd64.whl
Size 850.7 kB
Tags CPython 3.10 Windows x86-64
SHA-256 checksum
How to use checksums
ba34925d7f22a62ee0cd6b4884e634cd0516f9eb8923fb3eb56e964ffbc82730
BLAKE2b-256 checksum
How to use checksums
08c07b4ea33067bcd5bac0d7c77a0aff6695f55ac58bbbf93b4ec9f35c37d0b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL nanodsp-0.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 973.3 kB
Tags CPython 3.10 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
3507ad8160654cf030ea7f333f00e8b9853a572e3e9841778a00c2c16a1dc1c6
BLAKE2b-256 checksum
How to use checksums
59ed6cd69677c7f0fd81ddf453a2cca108965fff64f99db12a616b4e3cf5e45f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp310-cp310-macosx_11_0_arm64.whl

Download URL nanodsp-0.4.0-cp310-cp310-macosx_11_0_arm64.whl
Size 790.3 kB
Tags CPython 3.10 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
7fa765af58f582ac9bfb3304efed710a1fd37e068812a6c70a3c77346d987a8f
BLAKE2b-256 checksum
How to use checksums
0b2baac54d263b3545b8d1f13391931175cd78cb5120a5ef97bab012eb7908cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release files / nanodsp-0.4.0-cp310-cp310-macosx_10_15_x86_64.whl

Download URL nanodsp-0.4.0-cp310-cp310-macosx_10_15_x86_64.whl
Size 873.6 kB
Tags CPython 3.10 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
ec2c2b900325815dd7cdb08f36699200e58253e7c16e334360e6ecf2de2502cf
BLAKE2b-256 checksum
How to use checksums
36a3993f8e17358cf88ebd2af5a0c308b29dc12ef3d4db4ecba0d1448a66c609
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.2

Release history Release notifications | RSS feed

This release

0.4.0 This release

21 release files

0.3.0

21 release files

0.1.9

21 release files

0.1.8

21 release files

0.1.7

21 release files

0.1.5

21 release files

0.1.4

21 release files

0.1.3

21 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page