Spaces:
Running
Running
| """Regression tests for encode_waveform int16 fix. | |
| The bug that shipped: passing an un-normalized int16 waveform to soundfile | |
| caused values at Β±32768 β full-scale clipping noise. The fix in encode.py | |
| detects integer dtype and divides by ``np.iinfo(dtype).max`` before encoding. | |
| These tests verify: | |
| 1. ``int16_sine`` β a known 1-second sine at 1 kHz normalizes correctly: the | |
| decoded ogg waveform stays within [-1, 1] and has < 5 % clipped samples. | |
| 2. ``float_passthrough`` β a float32 waveform already in [-1, 1] encodes and | |
| decodes without corruption (max amplitude β€ 1.01, clipped% < 5 %). | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import math | |
| import numpy as np | |
| import pytest | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _sine_int16(freq_hz: int, duration_s: float, sample_rate: int) -> np.ndarray: | |
| """Pure sine at *freq_hz*, scaled to Β±30 000 in int16 (typical TTS output).""" | |
| t = np.linspace(0, duration_s, int(sample_rate * duration_s), endpoint=False) | |
| wave = np.sin(2 * math.pi * freq_hz * t) * 30_000 | |
| return wave.astype(np.int16) | |
| def _sine_float(freq_hz: int, duration_s: float, sample_rate: int) -> np.ndarray: | |
| """Pure sine at *freq_hz* as float32 in [-0.9, 0.9].""" | |
| t = np.linspace(0, duration_s, int(sample_rate * duration_s), endpoint=False) | |
| return (np.sin(2 * math.pi * freq_hz * t) * 0.9).astype(np.float32) | |
| def _decode_ogg(audio_bytes: bytes) -> tuple[np.ndarray, int]: | |
| """Decode an Ogg file and return (waveform_float32, sample_rate).""" | |
| import soundfile as sf | |
| buf = io.BytesIO(audio_bytes) | |
| data, sr = sf.read(buf, dtype="float32") | |
| return data, sr | |
| def _clipped_pct(data: np.ndarray, threshold: float = 0.99) -> float: | |
| """Fraction of samples whose absolute value exceeds *threshold*.""" | |
| if data.size == 0: | |
| return 0.0 | |
| return float(np.sum(np.abs(data) > threshold)) / data.size | |
| # --------------------------------------------------------------------------- | |
| # Tests | |
| # --------------------------------------------------------------------------- | |
| class TestEncodeWaveformInt16Regression: | |
| """int16 PCM input must be normalized before encoding (the shipped bug).""" | |
| def test_int16_sine_max_amplitude_within_range(self): | |
| """Decoded ogg amplitude β€ 1.01 β proves normalization happened.""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| pcm = _sine_int16(freq_hz=1_000, duration_s=1.0, sample_rate=16_000) | |
| assert pcm.dtype == np.int16 | |
| audio_bytes, mime, duration_ms = encode_waveform(pcm, 16_000) | |
| # Must produce an ogg (Opus or Vorbis); a WAV fallback still passes | |
| # but is unexpected on a modern libsndfile. | |
| assert mime in ("audio/ogg", "audio/wav"), f"unexpected mime: {mime}" | |
| assert len(audio_bytes) > 0 | |
| assert duration_ms > 0 | |
| decoded, _ = _decode_ogg(audio_bytes) | |
| max_amp = float(np.max(np.abs(decoded))) | |
| assert max_amp <= 1.01, ( | |
| f"max amplitude {max_amp:.4f} > 1.01 β int16 normalization missing " | |
| f"(pre-fix: raw int16 cast to float gives Β±32768 β full-scale clip)" | |
| ) | |
| def test_int16_sine_clipped_samples_below_5_pct(self): | |
| """< 5 % of samples may be clipped β rules out the full-scale-noise failure.""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| pcm = _sine_int16(freq_hz=1_000, duration_s=1.0, sample_rate=16_000) | |
| audio_bytes, _mime, _dur = encode_waveform(pcm, 16_000) | |
| decoded, _ = _decode_ogg(audio_bytes) | |
| pct = _clipped_pct(decoded) | |
| assert pct < 0.05, ( | |
| f"{pct*100:.1f}% of samples clipped (β₯ 5 %) β " | |
| f"this is the shipped 'random noise' bug: un-normalized int16 " | |
| f"fills every sample at full scale." | |
| ) | |
| def test_int16_duration_is_reasonable(self): | |
| """duration_ms returned by encode_waveform is within 10 % of the true | |
| value β ensures the frame count / sample_rate math is correct.""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| sample_rate = 22_050 | |
| duration_s = 2.0 | |
| pcm = _sine_int16(1_000, duration_s, sample_rate) | |
| _audio, _mime, duration_ms = encode_waveform(pcm, sample_rate) | |
| expected_ms = int(round(duration_s * 1000)) | |
| assert abs(duration_ms - expected_ms) <= expected_ms * 0.10, ( | |
| f"duration_ms={duration_ms} is more than 10% off from expected {expected_ms}" | |
| ) | |
| class TestEncodeWaveformFloatPassthrough: | |
| """Float32 waveform already in [-1, 1] must encode cleanly (no corruption).""" | |
| def test_float_sine_max_amplitude_within_range(self): | |
| """Float path: decoded amplitude β€ 1.01.""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| wave = _sine_float(freq_hz=440, duration_s=0.5, sample_rate=24_000) | |
| assert wave.dtype == np.float32 | |
| audio_bytes, mime, duration_ms = encode_waveform(wave, 24_000) | |
| assert mime in ("audio/ogg", "audio/wav") | |
| assert len(audio_bytes) > 0 | |
| decoded, _ = _decode_ogg(audio_bytes) | |
| max_amp = float(np.max(np.abs(decoded))) | |
| assert max_amp <= 1.01, f"float passthrough corrupted amplitude: {max_amp:.4f}" | |
| def test_float_sine_clipped_samples_below_5_pct(self): | |
| """Float path: < 5 % clipped.""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| wave = _sine_float(freq_hz=440, duration_s=0.5, sample_rate=24_000) | |
| audio_bytes, _mime, _dur = encode_waveform(wave, 24_000) | |
| decoded, _ = _decode_ogg(audio_bytes) | |
| pct = _clipped_pct(decoded) | |
| assert pct < 0.05, f"{pct*100:.1f}% of float-path samples clipped" | |
| def test_float_zero_signal_stays_silent(self): | |
| """All-zeros float input β all-zeros output (silence, not noise).""" | |
| from src.lib.audiobook.encode import encode_waveform | |
| silence = np.zeros(16_000, dtype=np.float32) | |
| audio_bytes, _mime, _dur = encode_waveform(silence, 16_000) | |
| decoded, _ = _decode_ogg(audio_bytes) | |
| max_amp = float(np.max(np.abs(decoded))) | |
| # Codec quantization noise is tiny; well below 1 % of full scale. | |
| assert max_amp < 0.01, f"silence encoded to noise: max_amp={max_amp:.6f}" | |