Skip to content

Instantly share code, notes, and snippets.

@ryukau
Created September 4, 2022 11:30
Show Gist options
  • Save ryukau/33cd8b89659399e141f40a5443f559b5 to your computer and use it in GitHub Desktop.
Save ryukau/33cd8b89659399e141f40a5443f559b5 to your computer and use it in GitHub Desktop.
"""
# Usage
Prepare some wav file to fade-out. The point of cut is better not to be already faded-out.
Change variables:
- `filename` to set input file path.
- `duration` to set fade-out length in seconds.
Result will be written into `snd` directory.
Note that this script will fail to run if input audio length is shorter than `duration`.
"""
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
import soundfile
from pathlib import Path
def convertoToInverseStepResponse(win):
win /= np.sum(win)
step = np.ones_like(win)
return 1 - signal.convolve(win, step)[:len(win)]
def linear(frames):
return np.linspace(1, 0, frames)
def decibel(frames):
eps = np.finfo(np.float32).eps
target = 20 * np.log10(eps)
return 10**(np.linspace(0, target, frames) / 20)
def expDecay(frames):
# Same as `decibel()`.
eps = np.finfo(np.float32).eps
return np.geomspace(eps, 1, frames)[::-1]
def expDecayInverse(frames):
eps = np.finfo(np.float32).eps
return 1 - np.geomspace(eps, 1, frames)
def quarterCosine(frames):
# Equal power curve for cross-fade.
return np.cos(np.linspace(0, np.pi / 2, frames))
def quarterCircle(frames):
x = np.linspace(0, 1, frames)
return np.sqrt(1 - x * x)
def triangleFIR(frames):
win = signal.get_window("bartlett", frames)
return convertoToInverseStepResponse(win)
def halfCosineFIR(frames):
win = signal.get_window("cosine", frames)
return convertoToInverseStepResponse(win)
def fullCosineFIR(frames):
win = 1 + np.cos(np.linspace(-np.pi, np.pi, frames))
return convertoToInverseStepResponse(win)
def exponentialFIR(frames):
# Better to add some normalization to tau.
# Current one is `fs * 100 / 48000`.
win = signal.get_window(("exponential", None, 100), frames)
win -= np.min(win)
return convertoToInverseStepResponse(win)
def checkDecibelAndExpDecayEquality():
# Unused test.
db = decibel(frames)
exp = expDecay(frames)
plt.plot(db)
plt.plot(exp)
plt.grid()
plt.show()
filename = "fadeout_short.wav"
duration = 2
out_dir = Path("snd")
if not out_dir.exists():
out_dir.mkdir(parents=True, exist_ok=True)
source, samplerate = soundfile.read(filename)
sourceT = source.T
frames = int(samplerate * duration)
frames += frames % 2 # Align to even number.
gainFuncs = [
linear,
expDecay,
expDecayInverse,
quarterCosine,
quarterCircle,
triangleFIR,
halfCosineFIR,
fullCosineFIR,
exponentialFIR,
]
start = len(sourceT[0]) - frames
for gainFunc in gainFuncs:
gain = gainFunc(frames)
data = sourceT.copy()
for i in range(len(data)):
data[i][start:] *= gain
soundfile.write(out_dir / Path(f"{gainFunc.__name__}.wav"), data.T, samplerate)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment