Spaces:
Build error
Build error
| 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 | |
| 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 | |
| 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) | |