# backend/voice/post_process.py # §0.4 — JARVIS DSP Post-Processing Chain # Shared by Kokoro and XTTS engines. Profile-driven via JARVIS_VOICE_PROFILE. # Uses Spotify's pedalboard library (fast, lightweight, no FFmpeg dependency). import numpy as np import logging logger = logging.getLogger(__name__) def apply_jarvis_dsp_chain(audio: np.ndarray, sr: int) -> np.ndarray: """ Full JARVIS DSP chain per JARVIS_VOICE_PROFILE["dsp_chain"]: noise_reduction → eq_low_mid_boost → compression → de_esser → limiter → subtle_room_reverb Input: float32 numpy array, any sample rate Output: float32 numpy array, same sample rate, peak-limited to -1 dBFS """ try: from pedalboard import Pedalboard, NoiseGate, LowShelfFilter, HighShelfFilter, \ PeakFilter, Compressor, Limiter, Reverb # Ensure float32 audio = audio.astype(np.float32) # Reshape to (channels, samples) for pedalboard if 1D if audio.ndim == 1: audio = audio[np.newaxis, :] # (1, N) board = Pedalboard([ # Step 1: Noise reduction — light noise gate to suppress breath/hiss NoiseGate(threshold_db=-40, ratio=2.0, attack_ms=5.0, release_ms=100.0), # Step 2: EQ — boost 80–400 Hz body, cut harsh highs LowShelfFilter(cutoff_frequency_hz=200, gain_db=3.5, q=0.7), # low + low-mid body PeakFilter(cutoff_frequency_hz=150, gain_db=2.0, q=1.0), # chest resonance HighShelfFilter(cutoff_frequency_hz=5000, gain_db=-2.0, q=0.7), # tame brightness # Step 3: De-esser — tame sibilance around 6 kHz PeakFilter(cutoff_frequency_hz=6000, gain_db=-4.0, q=3.0), # Step 4: Compression — 3:1, fast attack, medium release Compressor(threshold_db=-18.0, ratio=3.0, attack_ms=5.0, release_ms=80.0), # Step 5: Limiter — hard ceiling at -1 dBFS Limiter(threshold_db=-1.0, release_ms=100.0), # Step 6: Subtle room reverb — adds slight intimacy without muddying Reverb(room_size=0.08, wet_level=0.04, dry_level=0.96, damping=0.6), ]) processed = board(audio, sr) # Return as 1D float32 return processed[0].astype(np.float32) except ImportError: logger.warning("[DSP] pedalboard not installed — skipping DSP chain. Run: pip install pedalboard") # Graceful degradation: return audio as-is (flat float32) if audio.ndim > 1: audio = audio[0] return audio.astype(np.float32) except Exception as e: logger.error(f"[DSP] Chain failed: {e} — returning dry audio") if audio.ndim > 1: audio = audio[0] return audio.astype(np.float32)