Created
September 4, 2022 11:30
-
-
Save ryukau/33cd8b89659399e141f40a5443f559b5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
# 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