Files
abogen/tests/test_tts_supertonic_unsupported_chars.py
Artem Akymenko 9c6b3774b4 feat: add SupertonicBackend implementing TTSBackend protocol
Encapsulate SupertonicPipeline as an internal detail of
SupertonicBackend. The factory create_supertonic_backend() now
returns a SupertonicBackend instance instead of a raw
SupertonicPipeline, satisfying the TTSBackend protocol with
metadata, synthesize, get_available_voices, get_supported_formats,
and get_info methods. Backward-compatible __call__ delegates to
the internal pipeline.
2026-07-06 14:09:30 +00:00

109 lines
2.9 KiB
Python

import numpy as np
from abogen.tts_backends.supertonic import SupertonicBackend, SupertonicPipeline
class _DummyTTS:
def get_voice_style(self, voice_name: str):
return {"voice": voice_name}
def synthesize(
self,
*,
text: str,
voice_style,
total_steps: int,
speed: float,
max_chunk_length: int,
silence_duration: float,
verbose: bool,
):
if "•" in text:
raise ValueError("Found 1 unsupported character(s): ['•']")
# Return 50ms of audio at 24kHz.
sr = 24000
audio = np.zeros(int(0.05 * sr), dtype="float32")
return audio, 0.05
def _make_pipeline() -> SupertonicPipeline:
pipeline = SupertonicPipeline.__new__(SupertonicPipeline)
pipeline.sample_rate = 24000
pipeline.total_steps = 5
pipeline.max_chunk_length = 1000
pipeline._tts = _DummyTTS()
return pipeline
def _make_backend() -> SupertonicBackend:
backend = SupertonicBackend.__new__(SupertonicBackend)
backend._pipeline = _make_pipeline()
return backend
def test_supertonic_pipeline_strips_unsupported_characters_and_retries():
pipeline = _make_pipeline()
segs = list(pipeline("Hello • world", voice="M1", speed=1.0))
assert len(segs) == 1
assert segs[0].graphemes == "Hello world" or segs[0].graphemes == "Hello world"
assert isinstance(segs[0].audio, np.ndarray)
assert segs[0].audio.dtype == np.float32
assert segs[0].audio.size > 0
def test_supertonic_pipeline_drops_chunk_if_only_unsupported_characters():
pipeline = _make_pipeline()
segs = list(pipeline("•", voice="M1", speed=1.0))
assert segs == []
# --- SupertonicBackend tests ---
def test_backend_metadata():
backend = _make_backend()
meta = backend.metadata
assert meta.id == "supertonic"
assert meta.name == "SuperTonic"
assert "SuperTonic" in meta.description
def test_backend_get_available_voices():
backend = _make_backend()
voices = backend.get_available_voices()
assert isinstance(voices, list)
assert "M1" in voices
assert "F1" in voices
def test_backend_get_supported_formats():
backend = _make_backend()
formats = backend.get_supported_formats()
assert "wav" in formats
def test_backend_get_info():
backend = _make_backend()
info = backend.get_info()
assert info["sample_rate"] == 24000
assert info["total_steps"] == 5
assert isinstance(info["voices"], list)
def test_backend_call_delegates_to_pipeline():
backend = _make_backend()
segs = list(backend("Hello • world", voice="M1", speed=1.0))
assert len(segs) == 1
assert segs[0].audio.size > 0
def test_backend_synthesize_returns_wav_bytes():
backend = _make_backend()
wav_bytes = backend.synthesize("Hello world", voice="M1", speed=1.0)
assert isinstance(wav_bytes, bytes)
assert len(wav_bytes) > 0
# WAV magic number
assert wav_bytes[:4] == b"RIFF"