Files
diodiogod-TTS-Audio-Suite/utils/audio/librosa_fallback.py

271 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
LibROSA Fallback Functions for Python 3.13 Compatibility
This module provides fallback implementations for librosa functions that fail on Python 3.13
due to numba compatibility issues. It tries librosa first, then falls back to torchaudio/numpy
implementations if librosa fails.
"""
import sys
import os
import numpy as np
import torch
import torchaudio
_logged_fallbacks = set()
def _should_bypass_librosa():
"""Avoid importing librosa first in known-bad compatibility modes."""
return sys.version_info >= (3, 13) and os.environ.get("NUMBA_DISABLE_JIT") == "1"
def safe_load(file_path, sr=None, mono=True):
"""
Safe audio loading with librosa fallback to torchaudio
Args:
file_path: Path to audio file or BytesIO object
sr: Target sample rate (None for original)
mono: Convert to mono if True
Returns:
(audio_array, sample_rate): NumPy array and sample rate
"""
try:
if _should_bypass_librosa():
raise RuntimeError("Bypassing librosa due to Python 3.13 + NUMBA_DISABLE_JIT compatibility mode")
# Try librosa first (best quality)
import librosa
return librosa.load(file_path, sr=sr, mono=mono)
except Exception as e:
# Fallback to torchaudio for Python 3.13 compatibility
log_fallback_usage("librosa.load", str(e))
audio_tensor, sample_rate = torchaudio.load(file_path)
# Convert to mono if requested
if mono and audio_tensor.shape[0] > 1:
audio_tensor = torch.mean(audio_tensor, dim=0, keepdim=True)
# Resample if needed
if sr is not None and sample_rate != sr:
resampler = torchaudio.transforms.Resample(sample_rate, sr)
audio_tensor = resampler(audio_tensor)
sample_rate = sr
# Convert to numpy and squeeze
audio_array = audio_tensor.squeeze().numpy()
return audio_array, sample_rate
def safe_resample(audio, orig_sr, target_sr, res_type='kaiser_fast'):
"""
Safe audio resampling with librosa fallback to torchaudio
Args:
audio: Audio array
orig_sr: Original sample rate
target_sr: Target sample rate
res_type: Resampling type (ignored in fallback)
Returns:
Resampled audio array
"""
if orig_sr == target_sr:
return audio
try:
if _should_bypass_librosa():
raise RuntimeError("Bypassing librosa due to Python 3.13 + NUMBA_DISABLE_JIT compatibility mode")
# Try librosa first (best quality)
import librosa
return librosa.resample(audio, orig_sr=orig_sr, target_sr=target_sr, res_type=res_type)
except Exception as e:
# Fallback to torchaudio
log_fallback_usage("librosa.resample", str(e))
audio_tensor = torch.from_numpy(audio).unsqueeze(0) if audio.ndim == 1 else torch.from_numpy(audio)
resampler = torchaudio.transforms.Resample(orig_sr, target_sr)
resampled_tensor = resampler(audio_tensor)
return resampled_tensor.squeeze().numpy()
def safe_trim(audio, top_db=30, frame_length=2048, hop_length=512):
"""
Safe audio trimming with librosa fallback to simple energy-based trimming
Args:
audio: Audio array
top_db: Threshold in dB below peak
frame_length: Frame length (ignored in fallback)
hop_length: Hop length (ignored in fallback)
Returns:
Trimmed audio array
"""
try:
if _should_bypass_librosa():
raise RuntimeError("Bypassing librosa due to Python 3.13 + NUMBA_DISABLE_JIT compatibility mode")
# Try librosa first (best quality)
import librosa
return librosa.effects.trim(audio, top_db=top_db, frame_length=frame_length, hop_length=hop_length)[0]
except Exception as e:
# Fallback to simple energy-based trimming
log_fallback_usage("librosa.effects.trim", str(e))
return _simple_energy_trim(audio, top_db)
def _simple_energy_trim(audio, top_db=30):
"""Simple energy-based audio trimming fallback"""
if len(audio) < 1024:
return audio
# Convert dB to linear scale
threshold = 10 ** (-top_db / 20)
# Calculate RMS energy in sliding windows
window_size = 1024 # Fixed window size for simplicity
hop_size = 512
# Calculate RMS for each window
rms_values = []
for i in range(0, len(audio) - window_size, hop_size):
window = audio[i:i + window_size]
rms_values.append(np.sqrt(np.mean(window**2)))
if not rms_values:
return audio
max_rms = max(rms_values)
threshold_rms = max_rms * threshold
# Find start and end indices where audio is above threshold
start_idx = 0
end_idx = len(audio)
for i, rms in enumerate(rms_values):
if rms > threshold_rms:
start_idx = i * hop_size
break
for i, rms in enumerate(reversed(rms_values)):
if rms > threshold_rms:
end_idx = len(audio) - i * hop_size
break
# Ensure we don't trim too aggressively
start_idx = max(0, min(start_idx, len(audio) // 4))
end_idx = min(len(audio), max(end_idx, 3 * len(audio) // 4))
return audio[start_idx:end_idx]
def safe_mel_filters(sr, n_fft, n_mels=80, fmin=0.0, fmax=None, **kwargs):
"""
Safe mel filter bank creation with librosa fallback to torchaudio
Args:
sr: Sample rate
n_fft: FFT size
n_mels: Number of mel bands
fmin: Minimum frequency
fmax: Maximum frequency
**kwargs: Extra librosa.filters.mel options such as htk, norm, and dtype
Returns:
Mel filter bank matrix
"""
try:
# Try librosa first (best quality)
import librosa
return librosa.filters.mel(
sr=sr,
n_fft=n_fft,
n_mels=n_mels,
fmin=fmin,
fmax=fmax,
**kwargs,
)
except Exception as e:
# Fallback to torchaudio
if fmax is None:
fmax = sr // 2
mel_scale = "htk" if kwargs.get("htk", False) else "slaney"
norm = kwargs.get("norm", None)
torchaudio_norm = norm if norm in (None, "slaney") else None
dtype = kwargs.get("dtype", np.float32)
mel_transform = torchaudio.transforms.MelScale(
n_mels=n_mels,
sample_rate=sr,
f_min=fmin,
f_max=fmax,
n_stft=n_fft // 2 + 1,
norm=torchaudio_norm,
mel_scale=mel_scale,
)
# torchaudio stores filter banks as [n_stft, n_mels]; librosa returns [n_mels, n_stft].
return mel_transform.fb.T.numpy().astype(dtype, copy=False)
def safe_stft(audio, n_fft=2048, hop_length=512, win_length=None, window='hann', center=True, pad_mode='reflect'):
"""
Safe STFT computation with librosa fallback to torch
Args:
audio: Audio array
n_fft: FFT size
hop_length: Hop length
win_length: Window length
window: Window type
center: Center the audio
pad_mode: Padding mode (only used with librosa)
Returns:
STFT matrix
"""
try:
# Try librosa first (best quality)
import librosa
return librosa.stft(audio, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window=window, center=center, pad_mode=pad_mode)
except Exception as e:
# Fallback to torch STFT (pad_mode not supported)
if win_length is None:
win_length = n_fft
audio_tensor = torch.from_numpy(audio).float()
stft_result = torch.stft(
audio_tensor,
n_fft=n_fft,
hop_length=hop_length,
win_length=win_length,
window=torch.hann_window(win_length),
center=center,
return_complex=True
)
return stft_result.numpy()
def get_python313_status():
"""Check if running on Python 3.13+"""
return sys.version_info >= (3, 13)
def log_fallback_usage(function_name, reason="librosa compatibility"):
"""Report a successful compatibility fallback without dumping its traceback."""
if not get_python313_status():
return
key = function_name
if key in _logged_fallbacks:
return
_logged_fallbacks.add(key)
print(f"ℹ️ {function_name} is unavailable in this environment; using the compatible fallback.")
if os.environ.get("TTS_AUDIO_SUITE_DEBUG_FALLBACKS") == "1":
detail = next((line.strip() for line in str(reason).splitlines() if line.strip()), "unknown")
print(f" Fallback detail: {detail[:240]}")