Skip to main content

audio-clean-ai

Remove steady background noise - hum, hiss, fans, air conditioning - from speech recordings, without a model. It is spectral gating, a signal-processing heuristic, not a neural denoiser.

It learns what the noise sounds like from the quietest stretch of the recording, then turns down every time-frequency bin that sits close to that noise. That works well on noise that stays the same from second to second, and does little for noise that moves the way speech does: music, babble, a second speaker or traffic going by are not removed. Nothing is downloaded, nothing is sent anywhere, and the only dependency is numpy.

Install

pip install audio-clean-ai

Needs numpy and nothing else. No model download, no compiler, no system audio library.

Quickstart

import numpy as np
from audio_clean_ai import clean

t = np.arange(32000) / 16000                                         # 2 s at 16 kHz
noisy = 0.3 * np.sin(2 * np.pi * 220 * t) * (t > 0.5)                # a tone from 0.5 s on
noisy += 0.03 * np.random.default_rng(0).standard_normal(t.size)     # plus steady hiss
print(clean(noisy, sample_rate=16000).summary())
audio clean: noise down 15.5 dB (estimated), SNR 15.7 dB before, 30.0 dB after  [<array>, 2.000 s at 16000 Hz, mono]
  noise floor -30.5 dBFS, noise learned from the quietest stretch 0.01-0.43 s (reliable); noise is strongest in the 4000 Hz octave
  strength 0.80: noise-only bins cut by up to 24 dB, the 300-3400 Hz speech band by at most 15 dB

With a file it is one line each way:

result = clean("interview.wav")        # learn the noise, turn it down
result.save("interview-clean.wav")     # same channels, rate and bit depth

What it does

  • Learns the noise from the recording itself. It finds the quietest 0.4 s of sound, takes the per-frequency median of its power (so a breath or a click in that stretch does not inflate it), and treats that as the noise. Leading digital silence, such as a zero-filled lead-in, is skipped rather than learned as "no noise at all".
  • Turns down only what sits near the noise. Every 32 ms frame is split into frequency bins with numpy.fft. A bin within about 2 dB of the noise profile is noise and is cut; a bin 10 dB or more above it is signal and passes untouched; in between the gain rises smoothly.
  • Does not warble. The gate is smoothed across time (about 40 ms) and frequency (about 100 Hz) before it is applied. Unsmoothed gating flickers bins on and off, which is the watery "musical noise" that makes cheap noise reduction sound worse than the noise.
  • Never gates speech away entirely. With preserve_speech=True (the default) the 300-3400 Hz band is never cut by more than 15 dB, so a quiet consonant that happens to sit near the noise level is turned down, not deleted.
  • Says when it is guessing. If the quietest stretch is barely quieter than the loudest parts (less than 6 dB), it is probably not a pause, so the profile is flagged as unreliable, a warning says so, and the cut is held to 6 dB instead of guessing hard. Give it a noise_profile learned from a noise-only clip to clean such recordings fully.
  • Leaves silence silent and never clips. Digital silence comes back as exact zeros. No bin is ever boosted. If the rebuilt waveform would pass full scale, the whole output is turned down instead of clipped (a fraction of a dB in practice, more if a float input was itself louder than -1 to 1), measured against the input format's own rails: 8-bit WAV stops at +127/128, not +1, and each rail is counted separately.
  • Keeps the shape of your audio. The output has exactly as many samples as the input, in the same layout (1-D, samples x channels, or channels x samples). Stereo and multi-channel audio are cleaned one channel at a time, each with its own profile.
  • Takes the audio however you have it. A .wav path (8/16/24/32-bit PCM and 32/64-bit float, including WAVE_FORMAT_EXTENSIBLE), a numpy array of floats or integers, or a (samples, sample_rate) pair, at 8 kHz to 48 kHz. Your array is never modified, and a read-only array is fine.
  • Deterministic. The same input gives the same output every time.

What the numbers mean: noise_reduction_db, snr_before and snr_after are estimates made from the noise profile, since there is no clean reference to measure against. On a synthetic test - a voice-like harmonic signal in white noise at 0 dB SNR - the true SNR measured against the known clean signal rose by about 8 dB at 8 kHz, 11 dB at 16 kHz, 12 dB at 22.05 kHz and 14 dB at 44.1 and 48 kHz, with the voice harmonics kept within 1 dB. Higher rates gain more because more of their spectrum is noise alone. Mains hum in the same test dropped by 20 dB or more in the pauses. Real recordings vary.

Where it does not help: music, a television, a crowd, a second voice, wind gusts, a door slam, reverberation, and clipping are not steady noise, and one profile cannot describe them. Steady noise that lands right on the voice is only partly removed while the voice is speaking: a hum harmonic within a few tens of Hz of a voice harmonic cannot be told apart from it in a 32 ms frame, so it drops in the pauses but survives under the words. A recording that is steady noise from end to end (no quiet stretch that is quieter than the rest) is flagged as unreliable and cleaned only gently, because a steady tone you want to keep looks exactly like a hum you want gone. A recording that opens with a muted or near-silent lead-in (quiet, but not digital zero) learns that lead-in as its noise and removes little; trim it, or pass noise_profile.

API

clean(audio, *, sample_rate=None, strength=0.8, noise_profile=None,
      preserve_speech=True) -> CleanResult
Argument Meaning
audio a .wav path, a numpy array (1-D, or 2-D either way round), or (samples, sample_rate)
sample_rate required for a bare array; must agree if given with a file or pair
strength 0 to 1, how hard to cut; 0 returns the audio unchanged, 0.8 cuts noise by up to 24 dB, 1 by up to 30 dB
noise_profile a profile from noise_profile() or a previous result, used instead of learning one
preserve_speech keep the 300-3400 Hz band within 15 dB of where it was
noise_profile(audio, *, sample_rate=None, frame_ms=32) -> ndarray

Learns a profile from a clip that holds only the noise - a few seconds of the room with nobody talking. Returns per-bin noise power, 1-D for one channel or (channels, bins) for more; 10 * log10(profile.sum()) is the noise level in dBFS. The clip must be at the same sample rate as the recordings you clean with it.

profile = noise_profile("room-tone.wav")
result = clean("interview.wav", noise_profile=profile)

CleanResult carries:

Member Meaning
.audio cleaned samples, float64 in -1 to 1, same shape and layout as the input
.sample_rate frames per second
.noise_reduction_db estimated drop in the noise level, averaged over the recording
.noise_profile the per-bin noise power the gate used; .frequencies gives each bin's Hz
.snr_before, .snr_after estimated signal-to-noise ratio in dB, or None when no signal stands above the noise
.profile_reliable False when the noise could not be learned with confidence and the cut was held back
.channels one ChannelReport per channel: where its profile came from, its noise floor, its octave-band noise levels
.clipping_before, .clipping_after samples at the positive and negative rails, counted separately
.output_gain_db 0, or the small negative gain used to stay inside full scale
.warnings, .notes what deserves a look, and what was decided for you
.summary() the human-readable report above
.to_dict() everything except the samples, as a JSON-safe dict
.save(path, bits=None) write a PCM .wav with the wave module; keeps the input file's bit depth, else 16-bit

For more control, SpectralGate is the class underneath, with every threshold exposed:

from audio_clean_ai import SpectralGate

gate = SpectralGate(strength=0.9, max_cut_db=36, time_smoothing_ms=60)
result = gate.clean("interview.wav")

CLI

audio-clean-ai noisy.wav                              # dry run: print what would be removed
audio-clean-ai noisy.wav --output clean.wav           # clean and write the result
audio-clean-ai noisy.wav -o clean.wav --strength 0.6  # cut less
audio-clean-ai noisy.wav -o clean.wav --noise-clip room.wav   # learn the noise from a clip
audio-clean-ai noisy.wav --json --report report.json  # full report as JSON, also to a file
audio-clean-ai noisy.wav -o clean.wav --no-preserve-speech --bits 24

Exit code is 0 on success and 2 when the input could not be read or the output could not be written.

License

MIT

Metadata

Release files for audio-clean-ai 0.1.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 audio-clean-ai 0.1.0
File Size Uploaded
audio_clean_ai-0.1.0.tar.gz 39.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for audio-clean-ai 0.1.0
File Interpreter ABI Platform
audio_clean_ai-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 72.5 kB

Release files / audio_clean_ai-0.1.0.tar.gz

Download URL audio_clean_ai-0.1.0.tar.gz
Size 39.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ddf60e5ed841e9d751b5da6cbae363c4217b50857a01c06d746260324ab810f9
BLAKE2b-256 checksum
How to use checksums
affb7a1480d54a90530d466b8b76721a6f3c30cda831b58207756621100e8690
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / audio_clean_ai-0.1.0-py3-none-any.whl

Download URL audio_clean_ai-0.1.0-py3-none-any.whl
Size 32.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e8c9bb8c937747ed21a812fecb6ade0a4db441a77ef8ed7bb97082c2caa7181e
BLAKE2b-256 checksum
How to use checksums
d06d1ba85b7f3b5c95cc50ed2d457cf895a0904d28ad2330af75df644befd47f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 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