Spaces:
Build error
Build error
File size: 5,107 Bytes
51da11a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
import sys
import torch
import numpy as np
import scipy.signal
from numba import jit
from deepafx_st.processors.processor import Processor
# Adapted from: https://github.com/drscotthawley/signaltrain/blob/master/signaltrain/audio.py
@jit(nopython=True)
def my_clip_min(
x: np.ndarray,
clip_min: float,
): # does the work of np.clip(), which numba doesn't support yet
# TODO: keep an eye on Numba PR https://github.com/numba/numba/pull/3468 that fixes this
inds = np.where(x < clip_min)
x[inds] = clip_min
return x
@jit(nopython=True)
def compressor(
x: np.ndarray,
sample_rate: float,
threshold: float = -24.0,
ratio: float = 2.0,
attack_time: float = 0.01,
release_time: float = 0.01,
knee_dB: float = 0.0,
makeup_gain_dB: float = 0.0,
dtype=np.float32,
):
"""
Args:
x (np.ndarray): Input signal.
sample_rate (float): Sample rate in Hz.
threshold (float): Threhold in dB.
ratio (float): Ratio (should be >=1 , i.e. ratio:1).
attack_time (float): Attack time in seconds.
release_time (float): Release time in seconds.
knee_dB (float): Knee.
makeup_gain_dB (float): Makeup Gain.
dtype (type): Output type. Default: np.float32
Returns:
y (np.ndarray): Output signal.
"""
# print(f"dsp comp fs = {sample_rate}")
N = len(x)
dtype = x.dtype
y = np.zeros(N, dtype=dtype)
# Initialize separate attack and release times
# Where do these numbers come from
alpha_A = np.exp(-np.log(9) / (sample_rate * attack_time))
alpha_R = np.exp(-np.log(9) / (sample_rate * release_time))
# Turn the input signal into a uni-polar signal on the dB scale
x_G = 20 * np.log10(np.abs(x) + 1e-8) # x_uni casts type
# Ensure there are no values of negative infinity
x_G = my_clip_min(x_G, -96)
# Static characteristics with knee
y_G = np.zeros(N, dtype=dtype)
# Below knee
idx = np.where((2 * (x_G - threshold)) < -knee_dB)
y_G[idx] = x_G[idx]
# At knee
idx = np.where((2 * np.abs(x_G - threshold)) <= knee_dB)
y_G[idx] = x_G[idx] + (
(1 / ratio) * (((x_G[idx] - threshold + knee_dB) / 2) ** 2)
) / (2 * knee_dB)
# Above knee threshold
idx = np.where((2 * (x_G - threshold)) > knee_dB)
y_G[idx] = threshold + ((x_G[idx] - threshold) / ratio)
x_L = x_G - y_G
# this loop is slow but not vectorizable due to its cumulative, sequential nature. @autojit makes it fast(er).
y_L = np.zeros(N, dtype=dtype)
for n in range(1, N):
# smooth over the gainChange
if x_L[n] > y_L[n - 1]: # attack mode
y_L[n] = (alpha_A * y_L[n - 1]) + ((1 - alpha_A) * x_L[n])
else: # release
y_L[n] = (alpha_R * y_L[n - 1]) + ((1 - alpha_R) * x_L[n])
# Convert to linear amplitude scalar; i.e. map from dB to amplitude
lin_y_L = np.power(10.0, (-y_L / 20.0))
y = lin_y_L * x # Apply linear amplitude to input sample
y *= np.power(10.0, makeup_gain_dB / 20.0) # apply makeup gain
return y.astype(dtype)
class Compressor(Processor):
def __init__(
self,
sample_rate,
max_threshold=0.0,
min_threshold=-80,
max_ratio=20.0,
min_ratio=1.0,
max_attack=0.1,
min_attack=0.0001,
max_release=1.0,
min_release=0.005,
max_knee=12.0,
min_knee=0.0,
max_mkgain=48.0,
min_mkgain=-48.0,
eps=1e-8,
):
""" """
super().__init__()
self.sample_rate = sample_rate
self.eps = eps
self.ports = [
{
"name": "Threshold",
"min": min_threshold,
"max": max_threshold,
"default": -12.0,
"units": "",
},
{
"name": "Ratio",
"min": min_ratio,
"max": max_ratio,
"default": 2.0,
"units": "",
},
{
"name": "Attack Time",
"min": min_attack,
"max": max_attack,
"default": 0.001,
"units": "s",
},
{
"name": "Release Time",
"min": min_release,
"max": max_release,
"default": 0.045,
"units": "s",
},
{
"name": "Knee",
"min": min_knee,
"max": max_knee,
"default": 6.0,
"units": "dB",
},
{
"name": "Makeup Gain",
"min": min_mkgain,
"max": max_mkgain,
"default": 0.0,
"units": "dB",
},
]
self.num_control_params = len(self.ports)
self.process_fn = compressor
def forward(self, x, p, sample_rate=24000, **kwargs):
"All processing in the forward is in numpy."
return self.run_series(x, p, sample_rate)
|