from __future__ import annotations import struct import numpy as np from app.livekit.adapters.audio_gain import ( GainEmitter, SoftClipGain, tts_output_gain_from_env, ) def _pcm(*samples: int) -> bytes: return struct.pack("<" + "h" * len(samples), *samples) def _samples(pcm: bytes) -> list[int]: return list(struct.unpack("<" + "h" * (len(pcm) // 2), pcm)) def test_gain_1_0_is_disabled_and_passthrough() -> None: g = SoftClipGain(gain=1.0) assert g.enabled is False pcm = _pcm(1000, -2000, 3000) assert g.process(pcm) == pcm def test_normal_level_is_boosted_near_linear() -> None: g = SoftClipGain(gain=2.0) # ceiling default -1 dBFS # sinal baixo (~ -30 dBFS): boost deve ser praticamente 2x out = _samples(g.process(_pcm(1000, -1000))) assert abs(out[0] - 2000) <= 40 assert abs(out[1] + 2000) <= 40 def test_hot_peaks_never_clip_past_ceiling() -> None: ceiling = 0.891 # ~ -1 dBFS g = SoftClipGain(gain=2.0, ceiling=ceiling) limit = int(ceiling * 32768) + 1 # picos quentes que, com 2x linear, estourariam o fundo de escala out = _samples(g.process(_pcm(30000, -30000, 25000, -25000))) assert all(abs(s) <= limit for s in out), out # e continua monotonicamente crescente (sem wraparound/inversao de fase) assert out[0] > 0 and out[1] < 0 def test_monotonic_transfer_curve() -> None: g = SoftClipGain(gain=2.0) xs = list(range(0, 32000, 1000)) ys = [_samples(g.process(_pcm(x)))[0] for x in xs] assert all(b >= a for a, b in zip(ys, ys[1:])), ys def test_odd_length_bytes_do_not_crash() -> None: g = SoftClipGain(gain=2.0) pcm = _pcm(1000, -1000) + b"\x7f" # 1 byte solto out = g.process(pcm) assert len(out) == len(pcm) assert out[-1:] == b"\x7f" def test_empty_input() -> None: assert SoftClipGain(gain=2.0).process(b"") == b"" def test_env_loader_defaults_to_disabled(monkeypatch) -> None: monkeypatch.delenv("TTS_OUTPUT_GAIN", raising=False) monkeypatch.delenv("TTS_OUTPUT_CEILING_DBFS", raising=False) g = tts_output_gain_from_env() assert g.gain == 1.0 assert g.enabled is False def test_env_loader_reads_gain_and_ceiling(monkeypatch) -> None: monkeypatch.setenv("TTS_OUTPUT_GAIN", "2.0") monkeypatch.setenv("TTS_OUTPUT_CEILING_DBFS", "-6") g = tts_output_gain_from_env() assert g.gain == 2.0 assert abs(g.ceiling - 10 ** (-6 / 20.0)) < 1e-6 class _FakeEmitter: def __init__(self) -> None: self.pushed: list[bytes] = [] self.initialized = False self.flushed = False def initialize(self, **kwargs) -> None: self.initialized = True def push(self, data: bytes) -> None: self.pushed.append(data) def flush(self) -> None: self.flushed = True def test_gain_emitter_transforms_push_and_forwards_rest() -> None: inner = _FakeEmitter() em = GainEmitter(inner, SoftClipGain(gain=2.0)) em.initialize(sample_rate=24000) em.push(_pcm(1000, -1000)) em.flush() assert inner.initialized is True assert inner.flushed is True assert len(inner.pushed) == 1 # o que chegou ao emitter real foi amplificado out = _samples(inner.pushed[0]) assert abs(out[0] - 2000) <= 40 def test_gain_emitter_matches_numpy_reference() -> None: gain = SoftClipGain(gain=2.0, ceiling=0.891) pcm = _pcm(500, -12000, 28000, -31000, 0) ref_x = np.frombuffer(pcm, dtype=" None: monkeypatch.setenv("TTS_OUTPUT_GAIN", "nan") monkeypatch.setenv("TTS_OUTPUT_CEILING_DBFS", "inf") gain = tts_output_gain_from_env() assert gain.gain == 1.0 monkeypatch.setenv("TTS_OUTPUT_GAIN", "-2") monkeypatch.setenv("TTS_OUTPUT_CEILING_DBFS", "-200") gain = tts_output_gain_from_env() assert gain.ceiling == 0.05 assert gain.gain == 0.0