Any container in
Decoded, downmixed, resampled and leveled in one pass, following the first stage of Emilia-Pipe.
AeroSieve cuts raw recordings into 3–30 second clips, measures each one for noise, clipping, tones and silence, and writes the keepers as WebDataset shards. Every clip it throws away comes with a reason.
Caught 24,385 of 24,409 damaged clips across six languages · MIT · CLI, Python, Rust
Passed through
Caught, with a reason
This is the same Rust code as the command-line tool, compiled to WebAssembly. Nothing is uploaded.
Loading the WebAssembly build…
| Start | Length | SNR dB | dBFS | Clip | Flatness | Verdict | Why |
|---|
Moving a slider re-applies FilterPolicy to the stored measurements. Nothing is decoded again.
Drag any wav, mp3, flac, ogg or mp4 onto this panel. Recordings up to 15 minutes.
Samples are LibriSpeech utterances (CC BY 4.0) with noise, clipping and a dial tone added.
Decoded, downmixed, resampled and leveled in one pass, following the first stage of Emilia-Pipe.
WADA-SNR estimates noise from the clip alone. No clean copy needed.
A hysteresis VAD finds speech and cuts it where people pause. Silero VAD is optional.
shard-000000.tar
manifest.jsonl
stats.json
{"key":"call-0412-000","shard":0,"wada_snr_db":20.4,…}
Every kept clip is a wav and a json in a tar shard. aerosieve nemo-manifest adds NVIDIA NeMo's layout.
Measured against the source's own full scale, so turning a file down can't hide it.
Spectral flatness in the 300–3400 Hz band catches dial tones without flagging phone audio.
A small compressed file can't expand without bound, and a finished run can't be overwritten.
SieveBench
Noise at six levels, clipping, tones, hum, near-silence and a phone line, injected into real recordings in six languages. Every damaged clip goes through the same code as the CLI.
24,385 of 24,409 damaged clips caught, out of 51,352 damaged copies of 1,834 real recordings in six languages. On a laptop CPU.
| Language | Audio | Untouched audio kept | Over a phone line | Defects caught | Named the right defect |
|---|
FLEURS is read speech recorded on volunteers' own devices, so some untouched clips really are noisy: most of what Hindi, Tamil and Bengali lose is source audio already under the 10 dB floor. LibriSpeech, recorded for audiobooks, keeps every clean clip.
Median WADA-SNR reading against the noise level actually injected. Hover a point for the spread.
White and pink noise read within about 1.3 dB of the truth from 0 to 20 dB. Babble, other people talking, reads up to 2.7 dB cleaner than it is, because it looks like speech. Above 20 dB the curves flatten at the recordings' own noise floor.
A loud, clipped file was turned down 3 dB before scoring, so its flat tops sat at 0.7 and passed as clean. Clipping is now measured against the source's own full scale.
A 100 ms pause ended a clip, so natural speech broke into fragments under 3 s and was dropped: 23 of 73 clean English recordings produced nothing.
Same 43 min of Hindi16.4 → 25.6 minof usable speech, +56%
940×
faster than real time: 43 minutes of Hindi as 48 kHz FLAC, decoded, resampled, segmented, scored and sharded in 2.75 s.
8 threads on one laptop. Files are inspected in parallel and written in order, so shards match a single-threaded run byte for byte.
Every number in this section is reproduced by crates/aerosieve-sievebench/scripts/run_all.sh.
Cheap checks run before expensive ones, and each stage is its own crate you can use alone.
Symphonia reads the container, rubato resamples to 16 kHz, and loudness is leveled toward −20 dBFS.
aerosieve-decodeA hysteresis state machine with Silero's defaults turns speech probability into 3–30 s clips.
aerosieve-segmentFour measurements per clip, then a FilterPolicy that names the first check a clip fails.
aerosieve-acousticKept clips go into WebDataset tar shards with a JSONL manifest and run totals.
aerosieve-shard$ cargo install aerosieve-cli
$ aerosieve inspect recordings/
recordings/call-0412.wav (33.6 s, 8000 Hz source)
START LENGTH SNR dB dBFS CLIP FLATNESS VERDICT
0.0s 23.4s 20.4 -23.7 0.000 0.149 accept
23.4s 10.2s 18.1 -26.0 0.000 0.182 accept
$ aerosieve curate recordings/ --out data/run-1
# pip install ./bindings/python
import aerosieve
for clip in aerosieve.inspect("recording.wav")["clips"]:
print(clip["start_s"], clip["duration_s"], clip["verdict"])
stats = aerosieve.curate(["a.wav", "b.mp3"], "data/run-1")
use aerosieve::{Pipeline, PipelineConfig};
let config = PipelineConfig::new("data/staging", "data/run-1");
let mut pipeline = Pipeline::new(config)?;
pipeline.process_file("recording.wav".as_ref(), Some("transcript"))?;
let stats = pipeline.finish()?;
println!("kept {:.1}%", stats.retention_rate() * 100.0);
$ cargo run --release -p aerosieve-serve
# WebSocket /v1/stream: send 16 kHz PCM, get a verdict per utterance
← {"type":"ready","sample_rate":16000,"encoding":"pcm_s16le"}
→ <20 ms PCM frame> <20 ms PCM frame> ...
← {"type":"clip","start_s":0.51,"duration_s":4.92,"verdict":"accept","decided_at_s":5.92}
← {"type":"clip","start_s":7.30,"duration_s":9.12,"verdict":"low_snr","decided_at_s":16.90}
← {"type":"clip","start_s":17.83,"duration_s":9.31,"verdict":"clipping","decided_at_s":27.62}
→ {"type":"end"}
← {"type":"summary","heard_s":70.6,"clips":6,"accepted":3}
The thresholds have been checked on a few hours of audio, not against human ratings. FLEURS has no noise labels, so the tables above show what the filter does, not whether each rejection is right.