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 (nr mode doesn’t)

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).