J.A.R.V.I.S-py/effects.py

72 lines
2.2 KiB
Python
Raw Permalink Normal View History

import numpy as np
from scipy.signal import butter, sosfiltfilt
def _ensure_float32_mono(audio):
arr = np.asarray(audio, dtype=np.float32)
if arr.ndim > 1:
arr = arr.mean(axis=1).astype(np.float32)
return arr
def apply_bandpass(audio, sr, low_hz=200, high_hz=7000, order=4):
arr = _ensure_float32_mono(audio)
nyq = 0.5 * sr
low = max(low_hz, 1) / nyq
high = min(high_hz, int(nyq) - 1) / nyq
if not (0 < low < high < 1):
return arr
sos = butter(order, [low, high], btype='band', output='sos')
return sosfiltfilt(sos, arr).astype(np.float32)
def apply_reverb(audio, sr, wet=0.20, decay_ms=100, taps=4):
arr = _ensure_float32_mono(audio)
if wet <= 0 or decay_ms <= 0:
return arr
dry = max(0.0, 1.0 - wet)
out = dry * arr
gap = max(1, int(sr * (decay_ms / 1000.0) / taps))
gain = wet
for i in range(1, taps + 1):
delay = gap * i
gain *= 0.6
if delay >= len(arr):
break
padded = np.zeros_like(arr)
padded[delay:] = arr[:len(arr) - delay]
out += gain * padded
peak = float(np.max(np.abs(out))) if out.size else 0.0
if peak > 0.99:
out *= 0.99 / peak
return out.astype(np.float32)
def apply_pitch(audio, sr, semitones=0):
arr = _ensure_float32_mono(audio)
if semitones == 0:
return arr
factor = 2.0 ** (semitones / 12.0)
n_out = int(round(len(arr) / factor))
if n_out <= 1:
return arr
src_idx = np.linspace(0, len(arr) - 1, num=n_out).astype(np.float32)
lo = np.floor(src_idx).astype(np.int64)
hi = np.clip(lo + 1, 0, len(arr) - 1)
frac = (src_idx - lo).astype(np.float32)
resampled = (1.0 - frac) * arr[lo] + frac * arr[hi]
return resampled.astype(np.float32)
def process(audio, sr, bandpass=None, reverb=None, pitch_semitones=0):
out = _ensure_float32_mono(audio)
if bandpass is not None:
low, high = bandpass
out = apply_bandpass(out, sr, low, high)
if reverb is not None:
wet, decay_ms = reverb
out = apply_reverb(out, sr, wet=wet, decay_ms=decay_ms)
if pitch_semitones:
out = apply_pitch(out, sr, semitones=pitch_semitones)
return out