Add smoothness training pipeline with STFT/adversarial/vocoder-consistency losses
- inflect_nano/train_smooth.py: enhanced training with multi-res STFT loss, adversarial mel discriminator, vocoder consistency loss, deeper residual postnet, and cosine LR schedule - preprocess_dataset.py: convert HF datasets, local dirs, or LJSpeech CSVs to the durations.jsonl format needed by training - inference.py: add --smooth-prosody, --mel-smooth-sigma, --lowpass-hz flags for zero-cost inference-time quality improvements - test_inference.py: smoke tests for model loading and synthesis - colab_smooth_finetune.ipynb: Colab notebook for T4 GPU fine-tuning - requirements.txt: add numba, scipy, datasets
This commit is contained in:
+44
-1
@@ -78,6 +78,31 @@ def normalize_audio(audio: np.ndarray, target_rms_db: float = -20.0, peak_db: fl
|
||||
return np.clip(audio, -1.0, 1.0)
|
||||
|
||||
|
||||
def smooth_mel(mel: torch.Tensor, sigma: float = 1.0) -> torch.Tensor:
|
||||
"""Gaussian temporal smoothing on mel frames to reduce frame-to-frame jitter."""
|
||||
if sigma <= 0:
|
||||
return mel
|
||||
kernel_size = int(2 * math.ceil(2 * sigma) + 1)
|
||||
if kernel_size < 3:
|
||||
return mel
|
||||
kernel = torch.exp(-0.5 * (torch.arange(kernel_size, device=mel.device, dtype=mel.dtype) - kernel_size // 2) ** 2 / sigma**2)
|
||||
kernel = kernel / kernel.sum()
|
||||
# [B, n_mels, T] -> pad last dim (time), then conv1d over time
|
||||
pad = kernel_size // 2
|
||||
mel_padded = torch.nn.functional.pad(mel, (pad, pad), mode="replicate")
|
||||
kernel_expanded = kernel.view(1, 1, -1).expand(mel.shape[1], 1, -1)
|
||||
return torch.nn.functional.conv1d(mel_padded, kernel_expanded, groups=mel.shape[1])
|
||||
|
||||
|
||||
def apply_lowpass(wav: np.ndarray, cutoff_hz: float, sample_rate: int = 24000) -> np.ndarray:
|
||||
"""Simple low-pass filter to reduce vocoder buzz above cutoff."""
|
||||
if cutoff_hz <= 0 or cutoff_hz >= sample_rate / 2:
|
||||
return wav
|
||||
from scipy import signal
|
||||
sos = signal.butter(4, cutoff_hz, btype="low", fs=sample_rate, output="sos")
|
||||
return signal.sosfiltfilt(sos, wav).astype(np.float32)
|
||||
|
||||
|
||||
@torch.inference_mode()
|
||||
def synthesize(
|
||||
text: str,
|
||||
@@ -88,6 +113,9 @@ def synthesize(
|
||||
length_scale: float = 1.0,
|
||||
pitch_scale: float = 1.0,
|
||||
energy_scale: float = 1.0,
|
||||
smooth_prosody: bool = False,
|
||||
mel_smooth_sigma: float = 0.0,
|
||||
lowpass_hz: float = 0.0,
|
||||
) -> np.ndarray:
|
||||
phone, tone, lang = text_to_tokens(text)
|
||||
phone = phone.unsqueeze(0).to(device)
|
||||
@@ -102,9 +130,15 @@ def synthesize(
|
||||
length_scale=float(length_scale),
|
||||
pitch_scale=float(pitch_scale),
|
||||
energy_scale=float(energy_scale),
|
||||
smooth_predictors=smooth_prosody,
|
||||
)
|
||||
if mel_smooth_sigma > 0:
|
||||
mel = smooth_mel(mel, mel_smooth_sigma)
|
||||
wav = vocoder(mel).squeeze().detach().cpu().numpy()
|
||||
return normalize_audio(wav)
|
||||
wav = normalize_audio(wav)
|
||||
if lowpass_hz > 0:
|
||||
wav = apply_lowpass(wav, lowpass_hz)
|
||||
return wav
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -117,6 +151,12 @@ def main() -> None:
|
||||
ap.add_argument("--length-scale", type=float, default=1.0)
|
||||
ap.add_argument("--pitch-scale", type=float, default=1.0)
|
||||
ap.add_argument("--energy-scale", type=float, default=1.0)
|
||||
ap.add_argument("--smooth-prosody", action="store_true",
|
||||
help="Apply 3-frame averaging to pitch/energy/brightness (reduces jitter)")
|
||||
ap.add_argument("--mel-smooth-sigma", type=float, default=0.0,
|
||||
help="Gaussian temporal smooth on mel frames (0.5-1.5 reduces buzzing)")
|
||||
ap.add_argument("--lowpass-hz", type=float, default=0.0,
|
||||
help="Low-pass cutoff in Hz (e.g. 8000-11000 reduces vocoder buzz)")
|
||||
args = ap.parse_args()
|
||||
|
||||
device = torch.device(args.device)
|
||||
@@ -131,6 +171,9 @@ def main() -> None:
|
||||
length_scale=args.length_scale,
|
||||
pitch_scale=args.pitch_scale,
|
||||
energy_scale=args.energy_scale,
|
||||
smooth_prosody=args.smooth_prosody,
|
||||
mel_smooth_sigma=args.mel_smooth_sigma,
|
||||
lowpass_hz=args.lowpass_hz,
|
||||
)
|
||||
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||
sf.write(str(args.out), audio, 24000, subtype="PCM_16")
|
||||
|
||||
Reference in New Issue
Block a user