SCOREQ
audio_samples_qoe.Scoreq computes SCOREQ (Speech Contrastive
Regression for Quality Assessment), a neural speech-quality metric built on a
wav2vec2 encoder. Unlike ViSQOL, it runs a trained model via ONNX Runtime
rather than a pure DSP pipeline.
Construction
A Scoreq is constructed for one (domain, mode)
pair:
from audio_samples_qoe import Scoreq
model = Scoreq(domain="natural", mode="nr")
domain"natural"for recorded human speech (codecs, VoIP, telephony, enhancement, restoration), or"synthetic"for machine-generated speech (TTS, voice conversion, generative models). Choosing the wrong domain yields meaningless scores.mode"nr"(no-reference) or"ref"(reference-based) — see below.
Loading is expensive: the first use of a given (domain, mode) pair
downloads ~378 MB of weights from Zenodo into ~/.cache/scoreq/, and every
construction loads an ONNX Runtime session. Construct one Scoreq per
(domain, mode) pair and reuse it across calls rather than re-instantiating
per signal.
No-Reference Mode
model = Scoreq(domain="natural", mode="nr")
mos = model.predict("degraded.wav")
predict() returns a predicted MOS from the
test signal alone — no clean reference needed. Use this when you don’t have
access to an undegraded original.
Reference Mode
model = Scoreq(domain="natural", mode="ref")
distance = model.predict_ref("degraded.wav", "reference.wav")
predict_ref() returns the Euclidean distance
between the test and reference embeddings. Smaller means closer to the
reference’s quality; identical signals score ~0.
Calling predict() on a "ref"-mode model,
or predict_ref() on an "nr"-mode model,
raises ScoreqError.
Sample Rate
SCOREQ operates at 16 kHz. Inputs at any other rate are resampled internally, which differs from upstream’s torchaudio kernel by ~0.05–0.09 MOS. Feed 16 kHz audio for exact agreement with the reference implementation.
Threading
The GIL is released while the model runs, so AudioSamples inputs can be
shared across threads freely, same as with visqol()
— see the Parallel Scoring section of Quickstart.
A single Scoreq instance does not parallelise, though: predict/predict_ref
take an exclusive lock on the model for the call’s duration, so concurrent
calls from a thread pool contend rather than run side by side — an
overlapping call raises RuntimeError: Already borrowed instead of
queuing. For real throughput scaling, construct one Scoreq per thread
(for example with threading.local) rather than sharing one
instance:
import threading
from concurrent.futures import ThreadPoolExecutor
from audio_samples_qoe import Scoreq
_local = threading.local()
def get_model() -> Scoreq:
if not hasattr(_local, "model"):
_local.model = Scoreq(domain="natural", mode="nr")
return _local.model
def score(path: str) -> float:
return get_model().predict(path)
with ThreadPoolExecutor() as pool:
scores = list(pool.map(score, ["a.wav", "b.wav", "c.wav"]))
Each thread pays the model-load cost once (not the ~378 MB download, which is cached after the first model construction — just the ONNX session load), in exchange for genuine concurrent inference.
ViSQOL or SCOREQ?
Property |
ViSQOL / SCOREQ |
|---|---|
Approach |
Pure DSP / neural (wav2vec2 + ONNX) |
Reference needed |
Always / optional ( |
Content |
Full-band audio or speech / speech only |
Model weights |
None / ~378 MB, downloaded on first use |
Use ViSQOL for full-bandwidth or general audio content, or when you need a
dependency-free, deterministic DSP metric. Use SCOREQ for speech when you
either lack a reference signal (nr mode) or want a learned, perceptually
tuned distance (ref mode).