fix(gallery): calibrate preview guard against shipped speech fixtures
This commit is contained in:
@@ -57,21 +57,12 @@ _PREVIEW_SEED = 42
|
||||
# 32 reliably converges to speech across the gallery's instruct/script space
|
||||
# at a one-time (cached) render cost.
|
||||
_PREVIEW_NUM_STEP = 32
|
||||
# Spectral-flatness floor below which a render is a degenerate tonal artifact
|
||||
# rather than speech. Real, mastered speech sits ~0.04–0.07; a tonal buzz
|
||||
# collapses to <0.005. 0.015 separates the two with wide margin and sits well
|
||||
# below even breathy/whisper voices (which are broadband → high flatness).
|
||||
# Reject below this MEAN FRAMED spectral flatness (see _spectral_flatness).
|
||||
# Calibrated against measurements, not intuition — the previous 0.015 was set
|
||||
# from a synthetic speech stand-in and sat in the middle of the real-speech
|
||||
# range, so it rejected most legitimate renders (Japanese/Korean/English
|
||||
# previews alike). Measured on this engine's own output:
|
||||
# pure tone 80 Hz 2.6e-10 | two-tone buzz 3.3e-09
|
||||
# quietest REAL speech 2.0e-04 (VoxCPM2 ko, verified by ASR)
|
||||
# 1e-5 sits ~3000x above the tonal cases and ~20x below the quietest real
|
||||
# render — wide margin on both sides. Keep it there unless new measurements
|
||||
# (not synthetic signals) say otherwise.
|
||||
_DEGENERATE_FLATNESS = 1e-5
|
||||
# Reject near-pure tonal artifacts using mean framed spectral flatness.
|
||||
# Calibrated against the tracked speech demos exercised by
|
||||
# test_archetype_preview_quality.py: the quietest (Mandarin dubbing, 44.1 kHz)
|
||||
# measures ~7.7e-6, while the worst tested tonal buzz measures ~3.3e-9.
|
||||
# 1e-7 leaves >10x margin on both sides without rejecting low-flatness speech.
|
||||
_DEGENERATE_FLATNESS = 1e-7
|
||||
|
||||
|
||||
def _preview_key(a: dict) -> str:
|
||||
|
||||
@@ -7,13 +7,15 @@ old silence-only guard missed it (the buzz is loud, not silent) so the garbage
|
||||
was cached and served.
|
||||
|
||||
These tests cover the fix *without the 5 GB model / a GPU*: they drive the pure
|
||||
``_spectral_flatness`` / ``_is_unusable_audio`` helpers with synthetic signals,
|
||||
and assert the render constants didn't regress. The real end-to-end render is
|
||||
verified manually (spectral flatness back in the speech range + Whisper ASR).
|
||||
``_spectral_flatness`` / ``_is_unusable_audio`` helpers with synthetic tones and
|
||||
tracked speech demo renders, and assert the render constants did not regress.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import soundfile as sf
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -93,14 +95,9 @@ def test_speech_like_is_usable():
|
||||
|
||||
|
||||
# ── Threshold stays between the two things it has to separate ───────────────
|
||||
# The original 0.015 was calibrated against `_speech_like()` below, which is two
|
||||
# orders of magnitude flatter than real speech, so the threshold landed inside
|
||||
# the real-speech range and rejected legitimate previews (Japanese, Korean and
|
||||
# English alike — ASR transcribed every one of them correctly).
|
||||
#: Flattest REAL render observed, measured on engine output and confirmed to be
|
||||
#: speech by transcribing it (VoxCPM2, ko). Re-measure with real renders — never
|
||||
#: with a synthetic signal — before changing it.
|
||||
MEASURED_SPEECH_FLOOR = 2.0e-4
|
||||
# Synthetic broadband speech has much higher flatness than real voiced audio.
|
||||
# Measure both sides of the threshold against actual inputs, including the
|
||||
# existing demo renders that the old thresholds rejected.
|
||||
|
||||
|
||||
def test_tonal_ceiling_is_measured_not_assumed():
|
||||
@@ -119,17 +116,33 @@ def test_tonal_ceiling_is_measured_not_assumed():
|
||||
assert max(tones) * 10 < arch._DEGENERATE_FLATNESS
|
||||
|
||||
|
||||
def test_threshold_clears_the_measured_real_speech_floor():
|
||||
"""The speech side of the margin cannot be synthesized — that IS the bug.
|
||||
_SAMPLES = Path(__file__).resolve().parents[1] / "assets" / "samples"
|
||||
_SPEECH_FIXTURES = [
|
||||
"demo_voice.wav",
|
||||
"demo_clone_output.wav",
|
||||
*[f"voice_design/demo_voice_design_{name}.wav" for name in (
|
||||
"audiobook_uk_narrator", "aussie_podcaster", "bedtime_storyteller",
|
||||
"gravelly_villain", "indian_support_agent", "mandarin_sichuan", "us_news_anchor",
|
||||
)],
|
||||
*[f"dictation/{name}.wav" for name in (
|
||||
"en_conversational", "en_technical", "fr_reservation",
|
||||
)],
|
||||
*[f"demo/dubbing/{name}.src.wav" for name in (
|
||||
"source", "dubbed_es", "dubbed_fr", "dubbed_ja", "dubbed_zh",
|
||||
)],
|
||||
]
|
||||
|
||||
Shipping a real render as a fixture would add a binary to a suite whose
|
||||
whole point is running without one, so the measurement is recorded in
|
||||
``MEASURED_SPEECH_FLOOR`` and the assumption behind it is asserted here:
|
||||
the synthetic stand-in sits nowhere near the real floor, which is exactly
|
||||
why it cannot stand in for it.
|
||||
"""
|
||||
assert arch._DEGENERATE_FLATNESS < MEASURED_SPEECH_FLOOR / 10
|
||||
assert arch._spectral_flatness(_speech_like()) > MEASURED_SPEECH_FLOOR * 10
|
||||
|
||||
@pytest.mark.parametrize("fixture", _SPEECH_FIXTURES)
|
||||
def test_real_shipped_speech_clears_quality_floor(fixture):
|
||||
# These are existing tracked demo renders, not synthesized stand-ins or
|
||||
# asserted measurements. Loading PCM needs neither a model nor a network.
|
||||
audio, _sample_rate = sf.read(_SAMPLES / fixture, dtype="float32", always_2d=True)
|
||||
speech = torch.from_numpy(audio.T)
|
||||
flatness = arch._spectral_flatness(speech)
|
||||
assert flatness is not None
|
||||
assert flatness > arch._DEGENERATE_FLATNESS * 10
|
||||
assert arch._is_unusable_audio(speech) is False
|
||||
|
||||
|
||||
def test_flatness_is_not_clip_length_dependent():
|
||||
|
||||
@@ -128,3 +128,7 @@ for Chinese — the model auto-fixes mismatches).
|
||||
- **Case-insensitive**: `"Male"`, `"MALE"`, and `"male"` are all accepted, the code will normalize them to lower case.
|
||||
|
||||
- **Accent vs Dialect**: English accents are only applied to English speech, Chinese dialects are only applied to Chinese speech.
|
||||
|
||||
Gallery previews reject silent output and near-pure tonal buzz. The quality
|
||||
check measures short audio frames rather than the whole clip, so longer or
|
||||
softly voiced speech is not rejected merely for having low spectral flatness.
|
||||
|
||||
Reference in New Issue
Block a user