"""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}"