from __future__ import annotations import sys from pathlib import Path import numpy as np import pytest SRC = Path(__file__).resolve().parents[1] / "src" sys.path.insert(0, str(SRC)) sys.path.insert(0, str(Path(__file__).resolve().parent)) SR = 44100 def speechish( dur: float, sr: int = SR, level_dbfs: float = -20.0, seed: int = 0, ) -> np.ndarray: rng = np.random.default_rng(seed) n = int(sr * dur) t = np.arange(n) / sr f0 = 110.0 * (1.0 + 0.02 * np.sin(2 * np.pi * 0.9 * t)) phase = 2 * np.pi * np.cumsum(f0) / sr x = np.zeros(n) for k in range(1, 9): x += (1.0 / k**1.3) * np.sin(k * phase + 0.3 * k) syll = 0.5 + 0.5 * np.sin(2 * np.pi * 3.0 * t + float(rng.uniform(0, 6))) pauses = (np.sin(2 * np.pi * 0.5 * t) > -0.6).astype(float) env = np.clip(syll, 0.02, 1.0) ** 0.6 * np.maximum(pauses, 0.05) x = x * env x /= np.max(np.abs(x)) + 1e-12 return (x * (10 ** (level_dbfs / 20.0))).astype(np.float32) def sine(freq: float, dur: float, sr: int = SR, peak_dbfs: float = -20.0) -> np.ndarray: t = np.arange(int(sr * dur)) / sr return (10 ** (peak_dbfs / 20.0) * np.sin(2 * np.pi * freq * t)).astype(np.float32) def band_db(x: np.ndarray, sr: int, lo: float, hi: float) -> float: from scipy import signal sos = signal.butter(4, [lo, hi], btype="bandpass", fs=sr, output="sos") y = signal.sosfilt(sos, x.astype(np.float64)) r = np.sqrt(np.mean(np.square(y))) if r <= 0: return -120.0 return float(20 * np.log10(r)) @pytest.fixture def sr() -> int: return SR @pytest.fixture def speech() -> np.ndarray: return speechish(6.0, level_dbfs=-20.0) @pytest.fixture def noisy_speech(sr, speech) -> np.ndarray: rng = np.random.default_rng(7) noise = rng.standard_normal(speech.size) noise *= (10 ** (-48.0 / 20.0)) / np.sqrt(np.mean(np.square(noise))) hum = 0.003 * np.sin(2 * np.pi * 50.0 * np.arange(speech.size) / sr) return (speech + noise + hum).astype(np.float32)