mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 04:40:40 -07:00
120 lines
2.7 KiB
Python
120 lines
2.7 KiB
Python
"""
|
|
Audio processing utilities.
|
|
"""
|
|
|
|
import numpy as np
|
|
import soundfile as sf
|
|
import librosa
|
|
from typing import Tuple, Optional
|
|
|
|
|
|
def normalize_audio(
|
|
audio: np.ndarray,
|
|
target_db: float = -20.0,
|
|
peak_limit: float = 0.85,
|
|
) -> np.ndarray:
|
|
"""
|
|
Normalize audio to target loudness with peak limiting.
|
|
|
|
Args:
|
|
audio: Input audio array
|
|
target_db: Target RMS level in dB
|
|
peak_limit: Peak limit (0.0-1.0)
|
|
|
|
Returns:
|
|
Normalized audio array
|
|
"""
|
|
# Convert to float32
|
|
audio = audio.astype(np.float32)
|
|
|
|
# Calculate current RMS
|
|
rms = np.sqrt(np.mean(audio**2))
|
|
|
|
# Calculate target RMS
|
|
target_rms = 10**(target_db / 20)
|
|
|
|
# Apply gain
|
|
if rms > 0:
|
|
gain = target_rms / rms
|
|
audio = audio * gain
|
|
|
|
# Peak limiting
|
|
audio = np.clip(audio, -peak_limit, peak_limit)
|
|
|
|
return audio
|
|
|
|
|
|
def load_audio(
|
|
path: str,
|
|
sample_rate: int = 24000,
|
|
mono: bool = True,
|
|
) -> Tuple[np.ndarray, int]:
|
|
"""
|
|
Load audio file with normalization.
|
|
|
|
Args:
|
|
path: Path to audio file
|
|
sample_rate: Target sample rate
|
|
mono: Convert to mono
|
|
|
|
Returns:
|
|
Tuple of (audio_array, sample_rate)
|
|
"""
|
|
audio, sr = librosa.load(path, sr=sample_rate, mono=mono)
|
|
return audio, sr
|
|
|
|
|
|
def save_audio(
|
|
audio: np.ndarray,
|
|
path: str,
|
|
sample_rate: int = 24000,
|
|
) -> None:
|
|
"""
|
|
Save audio file.
|
|
|
|
Args:
|
|
audio: Audio array
|
|
path: Output path
|
|
sample_rate: Sample rate
|
|
"""
|
|
sf.write(path, audio, sample_rate)
|
|
|
|
|
|
def validate_reference_audio(
|
|
audio_path: str,
|
|
min_duration: float = 2.0,
|
|
max_duration: float = 30.0,
|
|
min_rms: float = 0.01,
|
|
) -> Tuple[bool, Optional[str]]:
|
|
"""
|
|
Validate reference audio for voice cloning.
|
|
|
|
Args:
|
|
audio_path: Path to audio file
|
|
min_duration: Minimum duration in seconds
|
|
max_duration: Maximum duration in seconds
|
|
min_rms: Minimum RMS level
|
|
|
|
Returns:
|
|
Tuple of (is_valid, error_message)
|
|
"""
|
|
try:
|
|
audio, sr = load_audio(audio_path)
|
|
duration = len(audio) / sr
|
|
|
|
if duration < min_duration:
|
|
return False, f"Audio too short (minimum {min_duration} seconds)"
|
|
if duration > max_duration:
|
|
return False, f"Audio too long (maximum {max_duration} seconds)"
|
|
|
|
rms = np.sqrt(np.mean(audio**2))
|
|
if rms < min_rms:
|
|
return False, "Audio is too quiet or silent"
|
|
|
|
if np.abs(audio).max() > 0.99:
|
|
return False, "Audio is clipping (reduce input gain)"
|
|
|
|
return True, None
|
|
except Exception as e:
|
|
return False, f"Error validating audio: {str(e)}"
|