# -*- coding: utf-8 -*- """ Erweiterte Audio-Effekte fuer Voice-Morphing. Reverb, Lowpass, Highpass, Flanger, Distortion — rein numpy/scipy. """ from __future__ import annotations import numpy as np def apply_reverb( audio: np.ndarray, sample_rate: int, room_size: float = 0.5, damping: float = 0.5, wet: float = 0.3, ) -> np.ndarray: """ Einfacher algorithmischer Reverb (Schroeder-Moorer). Nutzt parallele Comb-Filter + serielle Allpass-Filter. room_size: 0.0-1.0 (Nachhalllaenge). damping: 0.0-1.0 (Hoehen-Daempfung). wet: 0.0-1.0 (Dry/Wet-Mix). """ if wet < 0.005: return audio audio = audio.astype(np.float64) # Comb-Filter Verzoegerungen (in Samples, skaliert mit room_size) base_delays = [1557, 1617, 1491, 1422, 1277, 1356, 1188, 1116] scale = 0.5 + room_size * 0.5 # 0.5-1.0 Skalierung delays = [int(d * scale * sample_rate / 44100) for d in base_delays] # Feedback-Koeffizienten feedback = 0.7 + room_size * 0.15 # 0.7-0.85 damp = damping * 0.4 # 0.0-0.4 # Parallele Comb-Filter comb_sum = np.zeros(len(audio) + max(delays), dtype=np.float64) for delay in delays: buf = np.zeros(len(comb_sum), dtype=np.float64) lp_state = 0.0 # Einfacher 1-Pol Lowpass fuer Damping for i in range(len(audio)): delayed = buf[i - delay] if i >= delay else 0.0 # Damping (1-Pol Lowpass auf Feedback) lp_state = delayed * (1.0 - damp) + lp_state * damp buf[i] = audio[i] + lp_state * feedback comb_sum += buf comb_sum /= len(delays) # Serielle Allpass-Filter allpass_delays = [225, 556, 441, 341] allpass_delays = [int(d * sample_rate / 44100) for d in allpass_delays] allpass_gain = 0.5 result = comb_sum[:len(audio)] for delay in allpass_delays: buf_in = result.copy() buf_out = np.zeros_like(result) for i in range(len(result)): delayed = buf_out[i - delay] if i >= delay else 0.0 delayed_in = buf_in[i - delay] if i >= delay else 0.0 buf_out[i] = -allpass_gain * buf_in[i] + delayed_in + allpass_gain * delayed result = buf_out # Dry/Wet Mix output = audio * (1.0 - wet) + result[:len(audio)] * wet # Clipping verhindern max_val = np.max(np.abs(output)) if max_val > 1.0: output /= max_val return output def apply_lowpass( audio: np.ndarray, sample_rate: int, cutoff: float = 4000.0, order: int = 2, ) -> np.ndarray: """ Butterworth Lowpass-Filter. cutoff: Grenzfrequenz in Hz. order: Filterordnung (1-6). """ from scipy.signal import butter, sosfilt nyq = sample_rate / 2.0 if cutoff >= nyq * 0.99: return audio audio = audio.astype(np.float64) norm_cutoff = min(cutoff / nyq, 0.99) order = max(1, min(order, 6)) sos = butter(order, norm_cutoff, btype="low", output="sos") return sosfilt(sos, audio) def apply_highpass( audio: np.ndarray, sample_rate: int, cutoff: float = 200.0, order: int = 2, ) -> np.ndarray: """ Butterworth Highpass-Filter. cutoff: Grenzfrequenz in Hz. order: Filterordnung (1-6). """ from scipy.signal import butter, sosfilt if cutoff < 10.0: return audio audio = audio.astype(np.float64) nyq = sample_rate / 2.0 norm_cutoff = min(cutoff / nyq, 0.99) order = max(1, min(order, 6)) sos = butter(order, norm_cutoff, btype="high", output="sos") return sosfilt(sos, audio) def apply_flanger( audio: np.ndarray, sample_rate: int, rate: float = 0.5, depth: float = 0.002, wet: float = 0.3, ) -> np.ndarray: """ Flanger-Effekt via moduliertem Delay. rate: LFO-Frequenz in Hz (0.1-5.0). depth: Maximale Verzoegerung in Sekunden (0.001-0.01). wet: Dry/Wet-Mix (0.0-1.0). """ if wet < 0.005 or depth < 0.0001: return audio audio = audio.astype(np.float64) max_delay_samples = int(depth * sample_rate) if max_delay_samples < 1: return audio # LFO (Sinus) t = np.arange(len(audio)) / sample_rate lfo = (1.0 + np.sin(2.0 * np.pi * rate * t)) * 0.5 # 0.0 - 1.0 # Moduliertes Delay delayed = np.zeros_like(audio) for i in range(len(audio)): delay = int(lfo[i] * max_delay_samples) src_idx = i - delay if src_idx >= 0: delayed[i] = audio[src_idx] output = audio * (1.0 - wet) + delayed * wet return output def apply_distortion( audio: np.ndarray, sample_rate: int, gain: float = 1.5, threshold: float = 0.8, ) -> np.ndarray: """ Weiche Verzerrung (Soft-Clipping). gain: Verstaerkungsfaktor vor dem Clipping (1.0-10.0). threshold: Soft-Clip Schwelle (0.1-1.0). """ if gain <= 1.001 and threshold >= 0.999: return audio audio = audio.astype(np.float64) # Verstaerken audio = audio * gain # Soft-Clipping via tanh audio = np.tanh(audio / threshold) * threshold return audio