| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- # -*- 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
|