72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
|
|
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
|