MEGAMI / utils /fx_normalization /fxnorm_v2_public.py
Vansh Chugh
initial deploy
1a3ce68
Raw
History Blame Contribute Delete
5.7 kB
import torch
import numpy as np
from utils.training_utils import Gauss_smooth_vectorized, prepare_smooth_filter
def T602logmag(t60, sample_rate=44100, hop_length=512):
return 6.908 / (t60 * (sample_rate / hop_length)) # Convert T60 to delta log magnitude
from utils.data_utils import apply_RMS_normalization
class FxNormAug:
def __init__(self,
sample_rate=44100, # Sample rate of the audio
device="cuda" if torch.cuda.is_available() else "cpu",
mode="train", # Mode can be "train" or "eval"
seed=42,
features_path="features_tency1_4instr_v4.npy", # Path to the features file
):
torch.random.manual_seed(seed)
#the path is in the same directory as this file
self.features_path = features_path
self.sample_rate = sample_rate
self.device = device
self.EQ_normalize_setup() # Initialize EQ normalization function
def EQ_normalize_setup(self ):
features_mean = np.load(self.features_path, allow_pickle='TRUE')[()]
target_cuves_original= {
"vocals": torch.tensor(features_mean["eq"]["vocals"]).to(torch.float32).to(self.device),
"drums": torch.tensor(features_mean["eq"]["drums"]).to(torch.float32).to(self.device),
"bass": torch.tensor(features_mean["eq"]["bass"]).to(torch.float32).to(self.device),
"other": torch.tensor(features_mean["eq"]["other"]).to(torch.float32).to(self.device),
}
nfft=4096 # FFT size hardcoded
nfft_orig = 65536 # FFT size for the smoothing filter
win_length=2048 # Window length hardcoded
hop_length=1024 # Hop length hardcoded
window = torch.sqrt(torch.hann_window(win_length, device=self.device))
window_energy = window.pow(2).sum().sqrt() # Energy of the window
freqs = torch.fft.rfftfreq(nfft, d=1.0).to(self.device)
freqs_Hz=torch.fft.rfftfreq(nfft, d=1.0).to(self.device) * self.sample_rate
smooth_filter = prepare_smooth_filter(freqs_Hz, Noct=3).to(self.device) # Prepare the smoothing filter
freqs_Hz_orig=torch.fft.rfftfreq(nfft_orig, d=1.0).to(self.device) * self.sample_rate
smooth_filter_orig = prepare_smooth_filter(freqs_Hz_orig, Noct=3).to(self.device) # Prepare the smoothing filter
def downsample_curve(x):
return torch.nn.functional.interpolate(
x.unsqueeze(0).unsqueeze(0),
size=(nfft // 2 + 1,),
mode='linear',
align_corners=False
).squeeze(0).squeeze(0)
target_curves = {
"vocals": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["vocals"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
"drums": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["drums"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
"bass": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["bass"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
"other": downsample_curve(Gauss_smooth_vectorized(target_cuves_original["other"], freqs_Hz_orig, Noct=3, smooth_filter=smooth_filter_orig)),
}
def EQ_normalize_fn(x):
shape= x.shape
target_curves_tensor=torch.zeros((shape[0], nfft // 2 + 1), device=self.device, dtype=torch.float32)
for i in range(shape[0]):
track_class = "other"
assert track_class in target_curves, f"track_class {track_class} not found in target_curves"
target_curves_tensor[i] = target_curves[track_class]
x=x.view(-1, shape[-1])
#ensure x.shape[-1] is divisible by hop_length
if x.shape[-1] % hop_length != 0:
# Pad the input to make it divisible by hop_length
pad_length = hop_length - (x.shape[-1] % hop_length)
x = torch.nn.functional.pad(x, (0, pad_length), mode='constant', value=0)
X=torch.stft(x, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=window, return_complex=True)/ window_energy
X_pow=X.abs().pow(2)
X_mean= torch.sqrt(X_pow.mean(dim=-1, keepdim=False)) # Mean power spectrum
ratio= target_curves_tensor / (X_mean + 1e-6)
ratio = torch.clamp(ratio, max=10.0**(40.0/20.0))
ratio_smooth = Gauss_smooth_vectorized(ratio, freqs_Hz, Noct=3, smooth_filter=smooth_filter)
X= X * ratio_smooth.unsqueeze(-1)
X_unnormalized=X* window_energy
x_reconstructed = torch.istft(X_unnormalized,
n_fft=nfft,
hop_length=hop_length,
win_length=win_length,
window=window,
return_complex=False) # Set to True if you want complex outpu
#remove the padding if it was added
if x_reconstructed.shape[-1] > shape[-1]:
x_reconstructed = x_reconstructed[..., :shape[-1]]
x_reconstructed = x_reconstructed.view(shape)
return x_reconstructed
self.EQ_normalize = EQ_normalize_fn
def __call__(self, x, use_gate=False, RMS=-25):
B, C, T = x.shape
if C > 1:
x = x.mean(dim=1, keepdim=True)
x=x/x.max()
x=self.EQ_normalize(x)
x= apply_RMS_normalization(x, RMS, use_gate=use_gate)
assert not torch.isnan(x).any(), "NaN detected in x after EQ normalization"
return x