Changelog¶
All notable changes to voicequal are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
0.4.0 - 2026-09-30¶
The AI layer, built on the fixed feature pipeline from 0.3.0.
Added¶
- Distilled P.835 quality model (
voicequal.models.predict_mos,QualityModel): predicts DNSMOS-style SIG / BAK / OVRL from 169 clip features (9 DSP aggregates + log-mel statistics) with a ~13k-parameter MLP that ships in the wheel as numpy weights (quality_mlp.npz) and runs in pure numpy. Trained on VoiceBank-DEMAND train shard 0 with DNSMOS as teacher, evaluated on the unseen-speaker test split: BAK Pearson 0.93 / OVRL 0.87 / SIG 0.65 against DNSMOS on unseen speakers, MAE ~0.19 MOS, 51 KB, ~70 us per clip after features. Same network exported to ONNX for browsers. voicequal.features.clip_features()and a numpy log-mel spectrogram.- Per-segment explainability:
FileAssessment.segmentsgives a 1 s breakdown (snr_estimate, hnr, energy_snr, background_db, tier) andworst_segments()returns the seconds that hurt most. The CLI prints a timeline with a bar per second and a "Weakest:" line (--no-timelineto hide). - Advice layer (
voicequal.advise): deterministic, dependency-free rules that turn the metrics into one headline and up to three actions ("Recording is clipping", "Loud room is drowning the voice", ...). Optionalvoicequal[llm]addsadvise_with_llm(), which asks Claude to rephrase and prioritise but never to invent causes. - MCP server (
voicequal[mcp],voicequal-mcp): exposesassess_fileandassess_base64_wavtools returning the full result plus advice, for Claude Desktop, Claude Code, or any MCP client. - Training and labelling scripts under
benchmarks/distill/.
Changed¶
- Whole-file tier and all benchmark numbers are unchanged from 0.3.0.
0.3.0 - 2026-09-30¶
Speech support and the first comparison against a neural reference.
Added¶
energy_snr()andblock_rms()metrics: mixing SNR from the loudest half of 25 ms blocks against the quietest 30%. Tracks true SNR on read speech (VoiceBank-DEMAND: Spearman +0.91) where HNR does not, and collapses safely on sustained singing where HNR takes over.snr_estimatefield onFileAssessmentandLiveAssessment:max(hnr, energy_snr). This is now the value the tier is read from.assess_samples(samples, sample_rate): the file pipeline for an in-memory array.voicequal[neural]extra withvoicequal.neural.DNSMOS: Microsoft's P.835 predictor (SIG / BAK / OVRL) via ONNX Runtime. Model is downloaded once, SHA-256 pinned, cached under~/.voicequal/models/.- Speech benchmark
benchmarks/speech/run_voicebank_demand.py: 824 public VoiceBank-DEMAND test clips with the true mixing SNR computed from each clean/noisy pair, scored by voicequal and DNSMOS, with latency. Results in the README.
Changed¶
- Tier ladder now reads
snr_estimateat 18.5 / 13.5 / 10 dB, fitted jointly on the speech and singing benchmarks.assess_quality()gains asnr_estimateargument;hnr-only calls keep the v0.2.0 ladder and calls with neither keep the v0.1.x logic. - VocalSet benchmark (200 clips): exact 55.5% -> 58.0%, off-by-one
89.5% -> 86.0%, Spearman +0.662 -> +0.645.
very_loud_snr32.5% -> 60.0%,loud_snr57.5% -> 55.0%,moderate_snr32.5% -> 45.0%,clean_vocal55.0% -> 30.0% (breathy / lip-trill clips now readgood). - VoiceBank-DEMAND speech (824 clips, nominal-SNR tiers): exact 80.0%, off-by-one 99.2%. The v0.2.0 HNR-only ladder scored 27.9% / 63.3% on the same set.
- CLI shows
SNR estimatewith its two inputs.
Found¶
- DNSMOS does not transfer to singing: clean VocalSet vowels score ~1.1 on every axis, the same as white noise. The DSP-vs-DNSMOS comparison is therefore done on speech only.
0.2.0 - 2026-09-25¶
The mixing-SNR fix. See the README benchmark section for the full numbers.
Added¶
hnr()andharmonic_ratio()metrics: harmonic-to-noise ratio from the normalised autocorrelation peak in the pitch-lag range (Boersma 1993). For voice mixed with broadband noise this tracks the mixing SNR that the spectralsnr()cannot see. Exposed ashnronFileAssessmentandLiveAssessment, and shown by the CLI.clipping_ratio()metric and matchingclipping_ratiofield: fraction of samples at or above the saturation threshold.- Benchmark runner now records
hnr,spectral_concentration, andclipping_ratioper clip, and reports MAE / bias / Spearman of each dB-valued estimator against the manifest's mixing SNR.
Changed¶
- Tier decision is now HNR-gated.
assess_quality()takes an optionalhnrargument; when present it uses a quiet-room gate (background below 60 dBA is excellent) followed by an HNR ladder at 14.5 / 11 / 7 dB. The v0.1.x spectral-SNR-gated path is kept whenhnris omitted. - Benchmark (200 clips): exact 46.0% -> 55.5%, off-by-one 82.0% -> 89.5%,
Spearman +0.496 -> +0.662.
very_loud_snr5.0% -> 32.5%,loud_snr27.5% -> 57.5%,quiet_noise97.5% -> 100%.moderate_snrregressed 50.0% -> 32.5% (mostly toexcellent, one tier off).
Tried and rejected¶
- WADA-SNR (Kim & Stern 2008): assumes Gamma-distributed speech amplitudes; sung vowels do not fit. MAE 13.5 dB against mixing SNR. Not shipped.
- Percentile level SNR (loud frames vs quiet frames): needs gaps between phrases; sustained scales have none. MAE ~6 dB, no tier separation.
- Pre-emphasis before autocorrelation: whitens the harmonics too and lowers HNR separation. Not used.
Phase 0 hygiene¶
- GitHub Actions CI running tests, ruff, and mypy on Python 3.10, 3.11, and 3.12.
ruff(lint + format) andmypyconfiguration.- MkDocs Material documentation site, deployed to GitHub Pages.
CONTRIBUTING.md,CHANGELOG.md,ROADMAP.md, issue templates, and a pull request template.__version__is now derived from installed package metadata instead of a hand-edited string, so it cannot drift frompyproject.toml.
0.1.1 - 2026-07-16¶
Added¶
spectral_concentrationmetric: ratio of energy in the top-3 loudest FFT bins to total energy. Exposed onFileAssessmentandLiveAssessment.- Reproducible 200-clip benchmark under
benchmarks/(generator, runner, manifest), built from VocalSet vocals mixed with MUSAN noise at controlled signal-to-noise ratios. - Benchmark section in the README with per-category results.
Changed¶
- SNR fast-paths in the tier decision are now gated by
spectral_concentration, so noise-like audio cannot ride a high spectral-SNR reading straight toexcellent.
Known issues¶
- The concentration gate slightly reduced exact tier accuracy on the benchmark, from 47.0% to 46.0%. It was kept for its diagnostic value.
- The
very_loud_snrcategory (voice mixed with noise at ~5 dB) scores 5.0% exact accuracy. Root cause is documented in the README.
0.1.0 - 2026-07¶
Added¶
- Initial release.
assess(path)for one-shot file analysis returning aFileAssessment.LiveDetectorfor streaming analysis with a hysteresis buffer andon_changecallbacks.- CLI:
voicequal assess <path>andvoicequal listen. - Mic calibration (
voicequal listen --calibrate), saved to~/.voicequal/. - Four metrics: SNR, spectral flatness, temporal variance, and background dBA.