import sys, numpy as np from scipy.io import wavfile from scipy.signal import butter, sosfiltfilt src, out = sys.argv[1], sys.argv[2] sr, x = wavfile.read(src) x = x.astype(np.float64) / 32768.0 # trim leading / trailing near-silence so the repeats sit evenly amp = np.abs(x).max(axis=1) thr = 10 ** (-60 / 20) idx = np.where(amp > thr)[0] x = x[max(idx[0] - int(0.02 * sr), 0): min(idx[-1] + int(0.3 * sr), len(x))] print("trimmed seg: %.2fs, peak %.3f" % (len(x) / sr, np.abs(x).max())) N = 21 # ~12 minutes gap = np.zeros((int(2.0 * sr), 2)) parts = [] for i in range(N): seg = x.copy() # volume: full for the first 4 rounds, then slowly drifting down g = 1.0 if i < 4 else 1.0 - 0.65 * (i - 3) / (N - 4) # tone: gradually softer / "farther away" after round 4 if i >= 4: fc = 12000 * (3000 / 12000) ** ((i - 4) / (N - 5)) sos = butter(2, fc, btype="low", fs=sr, output="sos") seg = sosfiltfilt(sos, seg, axis=0) parts.append(seg * g) parts.append(gap) y = np.concatenate(parts + [np.zeros((int(3.0 * sr), 2))]) # final 60 s: gentle cosine fade to silence L = int(60 * sr); end = len(y) - int(3.0 * sr) fade = 0.5 * (1 + np.cos(np.linspace(0, np.pi, L))) y[end - L:end] *= fade[:, None] peak = np.abs(y).max() if peak > 0.98: y *= 0.98 / peak print("total: %.1fs (%.1f min), peak %.3f, last-3s max %.5f" % (len(y) / sr, len(y) / sr / 60, np.abs(y).max(), np.abs(y[-3*sr:]).max())) wavfile.write(out, sr, (y * 32767).astype(np.int16))