effects.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. """
  3. Erweiterte Audio-Effekte fuer Voice-Morphing.
  4. Reverb, Lowpass, Highpass, Flanger, Distortion — rein numpy/scipy.
  5. """
  6. from __future__ import annotations
  7. import numpy as np
  8. def apply_reverb(
  9. audio: np.ndarray,
  10. sample_rate: int,
  11. room_size: float = 0.5,
  12. damping: float = 0.5,
  13. wet: float = 0.3,
  14. ) -> np.ndarray:
  15. """
  16. Einfacher algorithmischer Reverb (Schroeder-Moorer).
  17. Nutzt parallele Comb-Filter + serielle Allpass-Filter.
  18. room_size: 0.0-1.0 (Nachhalllaenge).
  19. damping: 0.0-1.0 (Hoehen-Daempfung).
  20. wet: 0.0-1.0 (Dry/Wet-Mix).
  21. """
  22. if wet < 0.005:
  23. return audio
  24. audio = audio.astype(np.float64)
  25. # Comb-Filter Verzoegerungen (in Samples, skaliert mit room_size)
  26. base_delays = [1557, 1617, 1491, 1422, 1277, 1356, 1188, 1116]
  27. scale = 0.5 + room_size * 0.5 # 0.5-1.0 Skalierung
  28. delays = [int(d * scale * sample_rate / 44100) for d in base_delays]
  29. # Feedback-Koeffizienten
  30. feedback = 0.7 + room_size * 0.15 # 0.7-0.85
  31. damp = damping * 0.4 # 0.0-0.4
  32. # Parallele Comb-Filter
  33. comb_sum = np.zeros(len(audio) + max(delays), dtype=np.float64)
  34. for delay in delays:
  35. buf = np.zeros(len(comb_sum), dtype=np.float64)
  36. lp_state = 0.0 # Einfacher 1-Pol Lowpass fuer Damping
  37. for i in range(len(audio)):
  38. delayed = buf[i - delay] if i >= delay else 0.0
  39. # Damping (1-Pol Lowpass auf Feedback)
  40. lp_state = delayed * (1.0 - damp) + lp_state * damp
  41. buf[i] = audio[i] + lp_state * feedback
  42. comb_sum += buf
  43. comb_sum /= len(delays)
  44. # Serielle Allpass-Filter
  45. allpass_delays = [225, 556, 441, 341]
  46. allpass_delays = [int(d * sample_rate / 44100) for d in allpass_delays]
  47. allpass_gain = 0.5
  48. result = comb_sum[:len(audio)]
  49. for delay in allpass_delays:
  50. buf_in = result.copy()
  51. buf_out = np.zeros_like(result)
  52. for i in range(len(result)):
  53. delayed = buf_out[i - delay] if i >= delay else 0.0
  54. delayed_in = buf_in[i - delay] if i >= delay else 0.0
  55. buf_out[i] = -allpass_gain * buf_in[i] + delayed_in + allpass_gain * delayed
  56. result = buf_out
  57. # Dry/Wet Mix
  58. output = audio * (1.0 - wet) + result[:len(audio)] * wet
  59. # Clipping verhindern
  60. max_val = np.max(np.abs(output))
  61. if max_val > 1.0:
  62. output /= max_val
  63. return output
  64. def apply_lowpass(
  65. audio: np.ndarray,
  66. sample_rate: int,
  67. cutoff: float = 4000.0,
  68. order: int = 2,
  69. ) -> np.ndarray:
  70. """
  71. Butterworth Lowpass-Filter.
  72. cutoff: Grenzfrequenz in Hz.
  73. order: Filterordnung (1-6).
  74. """
  75. from scipy.signal import butter, sosfilt
  76. nyq = sample_rate / 2.0
  77. if cutoff >= nyq * 0.99:
  78. return audio
  79. audio = audio.astype(np.float64)
  80. norm_cutoff = min(cutoff / nyq, 0.99)
  81. order = max(1, min(order, 6))
  82. sos = butter(order, norm_cutoff, btype="low", output="sos")
  83. return sosfilt(sos, audio)
  84. def apply_highpass(
  85. audio: np.ndarray,
  86. sample_rate: int,
  87. cutoff: float = 200.0,
  88. order: int = 2,
  89. ) -> np.ndarray:
  90. """
  91. Butterworth Highpass-Filter.
  92. cutoff: Grenzfrequenz in Hz.
  93. order: Filterordnung (1-6).
  94. """
  95. from scipy.signal import butter, sosfilt
  96. if cutoff < 10.0:
  97. return audio
  98. audio = audio.astype(np.float64)
  99. nyq = sample_rate / 2.0
  100. norm_cutoff = min(cutoff / nyq, 0.99)
  101. order = max(1, min(order, 6))
  102. sos = butter(order, norm_cutoff, btype="high", output="sos")
  103. return sosfilt(sos, audio)
  104. def apply_flanger(
  105. audio: np.ndarray,
  106. sample_rate: int,
  107. rate: float = 0.5,
  108. depth: float = 0.002,
  109. wet: float = 0.3,
  110. ) -> np.ndarray:
  111. """
  112. Flanger-Effekt via moduliertem Delay.
  113. rate: LFO-Frequenz in Hz (0.1-5.0).
  114. depth: Maximale Verzoegerung in Sekunden (0.001-0.01).
  115. wet: Dry/Wet-Mix (0.0-1.0).
  116. """
  117. if wet < 0.005 or depth < 0.0001:
  118. return audio
  119. audio = audio.astype(np.float64)
  120. max_delay_samples = int(depth * sample_rate)
  121. if max_delay_samples < 1:
  122. return audio
  123. # LFO (Sinus)
  124. t = np.arange(len(audio)) / sample_rate
  125. lfo = (1.0 + np.sin(2.0 * np.pi * rate * t)) * 0.5 # 0.0 - 1.0
  126. # Moduliertes Delay
  127. delayed = np.zeros_like(audio)
  128. for i in range(len(audio)):
  129. delay = int(lfo[i] * max_delay_samples)
  130. src_idx = i - delay
  131. if src_idx >= 0:
  132. delayed[i] = audio[src_idx]
  133. output = audio * (1.0 - wet) + delayed * wet
  134. return output
  135. def apply_distortion(
  136. audio: np.ndarray,
  137. sample_rate: int,
  138. gain: float = 1.5,
  139. threshold: float = 0.8,
  140. ) -> np.ndarray:
  141. """
  142. Weiche Verzerrung (Soft-Clipping).
  143. gain: Verstaerkungsfaktor vor dem Clipping (1.0-10.0).
  144. threshold: Soft-Clip Schwelle (0.1-1.0).
  145. """
  146. if gain <= 1.001 and threshold >= 0.999:
  147. return audio
  148. audio = audio.astype(np.float64)
  149. # Verstaerken
  150. audio = audio * gain
  151. # Soft-Clipping via tanh
  152. audio = np.tanh(audio / threshold) * threshold
  153. return audio