BENCHMARK & METHOD
How we measured our noise-removal numbers.
The quality table on our homepage comes from a small, scripted test. This page shows how it works, every result it produced, and the code that scores it, so you can judge the numbers for yourself.
Read the limits first
- The test set is small and synthetic. Two speech clips of about 10 and 11 seconds, each mixed with 4 kinds of noise at 2 levels: 16 test files in total. Real recordings vary more than this.
- It measures the noise-removal step only. Soundproof's full chain also applies a gate, a compressor and loudness normalization. Those steps are not part of these scores.
- SI-SDR is not a listening test. It measures how close the output is to the clean original. Two files with the same score can sound different. We also listened to the outputs; our ears agreed with the scores that background voices are the weak case.
- It is a single run on one Mac. The model is deterministic, so re-running gives the same scores, but a different test set would give different numbers.
Results by noise type
Each cell is the range across the two clips. Higher is better. "5 dB" is a hard case where the noise is nearly as loud as the voice; "15 dB" is a milder one.
| Noise type | Gain at 5 dB SNR (hard) | Gain at 15 dB SNR (mild) |
|---|---|---|
| Mains hum | +10.0 to +13.3 dB | +6.2 to +10.1 dB |
| Hiss (white noise) | +10.9 to +12.3 dB | +7.2 to +8.3 dB |
| Fan / room (pink noise) | +8.6 to +10.2 dB | +4.8 to +6.3 dB |
| Babble (background voices) | −0.2 to +3.4 dB | +2.4 to +3.1 dB |
Every result
All 16 files, before and after. "Before" is the noisy input scored against the clean original; it lands at +5.0 and +15.0 dB, which confirms each mixture was made at the intended level. Download these results as CSV.
| Clip | Noise | Input SNR | SI-SDR before | SI-SDR after | Gain |
|---|---|---|---|---|---|
| Clip 1 | Mains hum | 5 dB | +5.0 dB | +18.3 dB | +13.3 dB |
| Clip 2 | Mains hum | 5 dB | +5.0 dB | +15.0 dB | +10.0 dB |
| Clip 1 | Mains hum | 15 dB | +15.0 dB | +25.1 dB | +10.1 dB |
| Clip 2 | Mains hum | 15 dB | +15.0 dB | +21.2 dB | +6.2 dB |
| Clip 1 | Hiss (white) | 5 dB | +5.0 dB | +17.3 dB | +12.3 dB |
| Clip 2 | Hiss (white) | 5 dB | +5.0 dB | +15.9 dB | +10.9 dB |
| Clip 1 | Hiss (white) | 15 dB | +15.0 dB | +23.3 dB | +8.3 dB |
| Clip 2 | Hiss (white) | 15 dB | +15.0 dB | +22.2 dB | +7.2 dB |
| Clip 1 | Fan / room (pink) | 5 dB | +5.0 dB | +15.2 dB | +10.2 dB |
| Clip 2 | Fan / room (pink) | 5 dB | +5.0 dB | +13.6 dB | +8.6 dB |
| Clip 1 | Fan / room (pink) | 15 dB | +15.0 dB | +21.3 dB | +6.3 dB |
| Clip 2 | Fan / room (pink) | 15 dB | +15.0 dB | +19.8 dB | +4.8 dB |
| Clip 1 | Babble (background voices) | 5 dB | +5.1 dB | +4.9 dB | −0.2 dB |
| Clip 2 | Babble (background voices) | 5 dB | +5.0 dB | +8.3 dB | +3.4 dB |
| Clip 1 | Babble (background voices) | 15 dB | +15.0 dB | +17.4 dB | +2.4 dB |
| Clip 2 | Babble (background voices) | 15 dB | +15.0 dB | +18.1 dB | +3.1 dB |
One babble file came out slightly worse than it went in (−0.2 dB). That is the honest weak case: when the "noise" is other people talking, a speech model cannot always tell which voice you want to keep.
How the test works
- Clean speech. Two synthetic speech clips, 48 kHz, mono, 16-bit.
- Noisy mixtures. Each clip is mixed with mains hum, white noise (hiss), pink noise (fan and room tone) and babble (overlapping background voices), at a signal-to-noise ratio of 5 dB and of 15 dB.
- Noise removal. Each mixture runs through the same model Soundproof uses, DeepFilterNet3 (open source, MIT or Apache-2.0), with the same settings the app uses: 10 ms frames (480 samples at 48 kHz), state carried from frame to frame, no attenuation limit, one CPU thread.
- Scoring. The output is aligned with the clean original (the model's lookahead delays it by 1,440 samples, 30 ms) and scored with SI-SDR, the scale-invariant signal-to-distortion ratio. The gain is the score after minus the score before.
Speed
On an Apple M1, the test harness below runs the model at about 5× realtime on a single CPU thread: a 10-second clip takes about 2 seconds (measured October 29, 2026). The app runs the model on one thread as well, from native code rather than Python. Speed varies with the Mac and with what else it is doing.
The test code
Both scripts need Python 3 with numpy; the first also needs onnxruntime and a streaming ONNX export of DeepFilterNet3. The noisy test audio and the script that mixed it are not published. If you have questions about the method, email [email protected].
df3_denoise.py: runs the model the way the app does
#!/usr/bin/env python3
"""Standalone DeepFilterNet3 streaming inference, mirroring
AristoTrack's deepfilter3_filter.cpp (480-sample hops @48kHz, carried state)."""
import sys, time, wave
import numpy as np
import onnxruntime as ort
HOP = 480
STATE_SIZE = 45304
def read_wav_48k_mono(path):
with wave.open(path, 'rb') as w:
assert w.getframerate() == 48000, f"{path}: expected 48kHz, got {w.getframerate()}"
assert w.getnchannels() == 1, f"{path}: expected mono"
assert w.getsampwidth() == 2
data = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
return data.astype(np.float32) / 32768.0
def write_wav_48k_mono(path, x):
x = np.clip(x, -1.0, 1.0)
with wave.open(path, 'wb') as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(48000)
w.writeframes((x * 32767.0).astype(np.int16).tobytes())
def denoise(x, model_path, atten_lim_db=100.0):
# One thread, so the reported speed matches the published
# "single CPU thread" figure rather than onnxruntime's default pool.
opts = ort.SessionOptions()
opts.intra_op_num_threads = 1
opts.inter_op_num_threads = 1
sess = ort.InferenceSession(model_path, sess_options=opts, providers=['CPUExecutionProvider'])
states = np.zeros(STATE_SIZE, dtype=np.float32)
atten = np.array(atten_lim_db, dtype=np.float32)
n = len(x)
pad = (-n) % HOP
xp = np.concatenate([x, np.zeros(pad, dtype=np.float32)])
out = np.zeros_like(xp)
t0 = time.time()
for i in range(0, len(xp), HOP):
frame = xp[i:i+HOP]
enhanced, states, _lsnr = sess.run(
["enhanced_audio_frame", "new_states", "lsnr"],
{"input_frame": frame, "states": states, "atten_lim_db": atten})
out[i:i+len(enhanced)] = enhanced[:HOP]
rtf = (time.time() - t0) / (len(xp) / 48000.0)
return out[:n], rtf
if __name__ == "__main__":
model, inp, outp = sys.argv[1], sys.argv[2], sys.argv[3]
x = read_wav_48k_mono(inp)
y, rtf = denoise(x, model)
write_wav_48k_mono(outp, y)
print(f"{inp} -> {outp} RTF={rtf:.3f} ({1/rtf:.1f}x realtime)")
score.py: aligns the output and computes SI-SDR
#!/usr/bin/env python3
"""SI-SDR scoring vs clean reference, with cross-correlation alignment
(DF3 streaming output is delayed by its internal lookahead)."""
import sys, wave
import numpy as np
def read_wav(path):
with wave.open(path, 'rb') as w:
data = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
return data.astype(np.float64) / 32768.0
def align(ref, est, max_shift=4800):
n = min(len(ref), len(est))
r, e = ref[:n], est[:n]
corr = np.correlate(e, r[:n - max_shift], mode='valid')[:max_shift]
shift = int(np.argmax(np.abs(corr)))
e2 = est[shift:shift + len(ref)]
n2 = min(len(ref), len(e2))
return ref[:n2], e2[:n2], shift
def si_sdr(ref, est):
ref = ref - ref.mean(); est = est - est.mean()
alpha = np.dot(est, ref) / (np.dot(ref, ref) + 1e-12)
target = alpha * ref
noise = est - target
return 10 * np.log10((np.dot(target, target) + 1e-12) / (np.dot(noise, noise) + 1e-12))
if __name__ == "__main__":
clean, noisy, enhanced = sys.argv[1], sys.argv[2], sys.argv[3]
c, n_, e = read_wav(clean), read_wav(noisy), read_wav(enhanced)
cn, nn, _ = align(c, n_)
before = si_sdr(cn, nn)
ce, ee, shift = align(c, e)
after = si_sdr(ce, ee)
print(f"{enhanced}: SI-SDR {before:+.1f} dB -> {after:+.1f} dB (gain {after-before:+.1f} dB, align {shift})")
Try it on your own recording
A benchmark is no substitute for your own files. Soundproof is a free download with unlimited preview and 3 free exports, so you can hear what it does to your recording before paying anything. See what noise can and can't be removed for what to expect.
Measured July 1, 2026. Re-scored October 29, 2026 with the code above; every score matched.