BABE-2 / model /restoration.py
Vansh Chugh
fix device handling: build network on target device, keep recording on CPU until fed to sampler
dc8400f
Raw
History Blame Contribute Delete
5.9 kB
import numpy as np
import torch
from . import blind_bwe_utils
from .sampler import BlindSampler
def apply_LTAS_init(recording, LTAS_ref, sample_rate, nfft):
"""
Spectrally reshapes `recording` to match the checkpoint's reference
long-term-average spectrum (LTAS), used both as the diffusion warm-start
and as the reconstruction-guidance observation.
From Evaluator.apply_LTAS_init, testing/evaluator.py:48-80.
"""
freqs = np.fft.fftfreq(nfft, 1 / sample_rate)[0:nfft // 2 + 1]
freqs[-1] = sample_rate / 2
freqs = torch.tensor(freqs, dtype=torch.float32).to(recording.device)
LTAS_smooth = blind_bwe_utils.smooth_LTAS(LTAS_ref, freqs, Noct=3)
correction = 1 / LTAS_smooth.max()
LTAS_smooth = (LTAS_smooth * correction).to(recording.device)
win_length = nfft // 2
hop_length = nfft // 4
LTAS_y, std = blind_bwe_utils.compute_LTAS(recording.clone(), sample_rate, nfft=nfft, win_length=win_length, hop_length=hop_length, normalize=1)
LTAS_y_smooth = blind_bwe_utils.smooth_LTAS(LTAS_y, freqs, Noct=3) * correction.to(LTAS_y.device)
diff = torch.clamp(10 * torch.log10(LTAS_y_smooth) - 10 * torch.log10(LTAS_smooth), min=-20)
X = torch.stft(recording, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=torch.hann_window(win_length), return_complex=True)
diff_lin = torch.pow(10, diff / 20)
X = X / diff_lin.unsqueeze(-1)
x_init = torch.istft(X, n_fft=nfft, hop_length=hop_length, win_length=win_length, window=torch.hann_window(win_length), length=recording.shape[0])
x_init = std * x_init / x_init.std()
return x_init
def segment_signal(recording, seg_len, overlap, device):
"""From Evaluator.segment_signal, testing/evaluator.py:82-99."""
L = recording.shape[-1]
ix_start = []
ix_end = []
segs = []
ix = 0
while ix + seg_len < L:
segs.append(torch.Tensor(recording[..., ix:ix + seg_len]))
ix_start.append(ix)
ix_end.append(ix + seg_len)
ix += seg_len - overlap
segs.append(torch.cat((torch.Tensor(recording[..., ix::]).to(device), torch.zeros((seg_len - (L - ix),), device=device)), -1))
ix_start.append(ix)
ix_end.append(ix + seg_len)
expanded_size = recording.shape[-1] + seg_len - (L - ix)
return segs, ix_start, ix_end, expanded_size
def _crossfade_into_result(pred, y_masked, mask, seg_len, device):
"""
Factored from the inline Hann-crossfade block inside the original's
"Block-Autoregressive" branch, testing/evaluator.py:256-274.
"""
if mask[0] == 1:
first_0 = torch.where(mask == 0)[0][0]
hann_window = torch.hann_window(int(first_0) * 2, device=device)
hann_left = hann_window[0:first_0]
hann_right = hann_window[first_0:]
pred[..., 0:first_0] = pred[..., 0:first_0] * hann_left + y_masked[0:first_0] * hann_right
if mask[-1] == 1:
last_0 = torch.where(mask == 0)[0][-1]
size = seg_len - last_0
hann_window = torch.hann_window(int(size) * 2, device=device)
hann_left = hann_window[0:size]
hann_right = hann_window[size:]
pred[..., seg_len - size:] = pred[..., seg_len - size:] * hann_right + y_masked[..., seg_len - size:] * hann_left
return pred
def restore_audio(recording, cfg, network, diff_params, LTAS_ref, device, on_progress=None):
"""
Restores a mono waveform (1-D tensor, already resampled to cfg.exp.sample_rate)
by segmenting it into overlapping chunks and processing them autoregressively,
each conditioned on the previous chunk's already-reconstructed overlap
(Hann-crossfaded at the boundary). LTAS_init/LTAS_as_y are always True here,
so the LTAS-corrected signal doubles as both the diffusion warm-start and the
reconstruction-guidance observation for every segment.
From the "Block-Autoregressive" branch of Evaluator.evaluate_single_recording,
testing/evaluator.py:101-347 (lines 229-283).
"""
sample_rate = cfg.exp.sample_rate
seg_len = cfg.tester.evaluation.segment_length
overlap = int(cfg.tester.evaluation.overlap * seg_len)
sigma_norm = cfg.tester.blind_bwe.sigma_norm
recording = apply_LTAS_init(recording, LTAS_ref, sample_rate, cfg.tester.blind_bwe.LTAS_fft)
segs, ix_start, ix_end, expanded_size = segment_signal(recording, seg_len, overlap, device)
std_orig = torch.median(torch.Tensor([seg.std() for seg in segs]))
segs = [(sigma_norm * seg / std_orig).to(torch.float32) for seg in segs]
sampler = BlindSampler(model=network, diff_params=diff_params, args=cfg)
n_segments = len(segs)
result = torch.zeros((expanded_size,), device="cpu")
result_mask = torch.zeros((expanded_size,), device="cpu")
def step_cb(seg_idx):
def cb(step_i, total_steps):
if on_progress is not None:
frac = (seg_idx + (step_i + 1) / total_steps) / n_segments
on_progress(frac, f"segment {seg_idx + 1}/{n_segments}, step {step_i + 1}/{total_steps}")
return cb
y0 = segs[0].unsqueeze(0).to(device)
pred, _ = sampler.predict_blind_bwe(y0, x_init=y0, progress_cb=step_cb(0))
result[ix_start[0]:ix_end[0]] = pred[0].cpu()
result_mask[ix_start[0]:ix_end[0]] = 1
for i in range(1, n_segments):
y = segs[i].to(device)
x_init = segs[i].to(device)
y_masked = result[ix_start[i]:ix_end[i]].clone().to(device)
mask = result_mask[ix_start[i]:ix_end[i]].clone().to(device)
pred, _ = sampler.predict_blind_bwe_AR(y, y_masked, mask=mask, x_init=x_init, progress_cb=step_cb(i))
pred = _crossfade_into_result(pred[0], y_masked, mask, seg_len, device)
result_mask[ix_start[i]:ix_end[i]] = 1
result[ix_start[i]:ix_end[i]] = pred.cpu()
result = result[0:recording.shape[-1]]
result = std_orig * result / sigma_norm
return result