Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,607 @@
//! Griffin-Lim Algorithm for phase reconstruction
//!
//! This module implements the Griffin-Lim algorithm, an iterative method
//! for reconstructing audio waveforms from magnitude spectrograms.
//!
//! The algorithm is particularly useful as a fast, non-neural baseline vocoder.
use rtx_tensor::{ComplexTensor, Device, Tensor};
use rtx_tensor::signal::{stft, istft, WindowType};
use serde::{Deserialize, Serialize};
use crate::error::{Result, TtsError};
use super::{Vocoder, MelSpectrogram, MelConfig};
/// Configuration for Griffin-Lim algorithm
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GriffinLimConfig {
/// FFT size
pub n_fft: usize,
/// Hop length between frames
pub hop_length: usize,
/// Window length
pub win_length: usize,
/// Number of iterations
pub n_iter: usize,
/// Sample rate in Hz
pub sample_rate: usize,
/// Mel configuration for converting mel to linear spectrogram
pub mel_config: MelConfig,
}
impl Default for GriffinLimConfig {
fn default() -> Self {
Self {
n_fft: 1024,
hop_length: 256,
win_length: 1024,
n_iter: 60,
sample_rate: 22050,
mel_config: MelConfig::default(),
}
}
}
impl GriffinLimConfig {
/// Validate configuration
pub fn validate(&self) -> Result<()> {
if self.n_fft == 0 {
return Err(TtsError::InvalidConfig("FFT size must be > 0".to_string()));
}
if self.hop_length == 0 {
return Err(TtsError::InvalidConfig("Hop length must be > 0".to_string()));
}
if self.win_length == 0 || self.win_length > self.n_fft {
return Err(TtsError::InvalidConfig("Window length must be > 0 and <= n_fft".to_string()));
}
if self.n_iter == 0 {
return Err(TtsError::InvalidConfig("Number of iterations must be > 0".to_string()));
}
if self.sample_rate == 0 {
return Err(TtsError::InvalidConfig("Sample rate must be > 0".to_string()));
}
self.mel_config.validate()?;
Ok(())
}
}
/// Griffin-Lim vocoder implementation
pub struct GriffinLim {
config: GriffinLimConfig,
mel_processor: MelSpectrogram,
device: Device,
}
impl GriffinLim {
/// Create a new Griffin-Lim vocoder
pub fn new(config: GriffinLimConfig, device: &Device) -> Result<Self> {
config.validate()?;
let mel_processor = MelSpectrogram::new(config.mel_config.clone(), device)?;
Ok(Self {
config,
mel_processor,
device: device.clone(),
})
}
/// Convert mel spectrogram to linear spectrogram
fn mel_to_linear(&self, mel_spec: &Tensor) -> Result<Tensor> {
self.mel_processor.mel_to_spectrogram(mel_spec)
}
/// Reconstruct phase using Griffin-Lim algorithm
///
/// # Arguments
/// * `magnitude` - Magnitude spectrogram [n_freqs, time_frames]
///
/// # Returns
/// Reconstructed audio waveform
fn reconstruct_phase(&self, magnitude: &Tensor) -> Result<Tensor> {
let dims = magnitude.dims();
if dims.len() != 2 {
return Err(TtsError::InvalidInput("Magnitude must be 2D [n_freqs, time_frames]".to_string()));
}
let n_freqs = dims[0];
let n_frames = dims[1];
let expected_n_freqs = self.config.n_fft / 2 + 1;
if n_freqs != expected_n_freqs {
return Err(TtsError::InvalidInput(format!(
"Expected {} frequency bins, got {}",
expected_n_freqs, n_freqs
)));
}
// Initialize with random phase
let phase = Tensor::rand([n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// Convert to radians (0 to 2π)
let phase_data = phase.to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let phase_rad: Vec<f32> = phase_data.iter().map(|&p| p * 2.0 * std::f32::consts::PI).collect();
let mut phase_tensor = Tensor::from_data(phase_rad, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// Get magnitude data
let mag_data = magnitude.to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// Griffin-Lim iterations
for _iter in 0..self.config.n_iter {
// Construct complex spectrum from magnitude and phase
let phase_data = phase_tensor.to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let mut real_data = Vec::with_capacity(n_freqs * n_frames);
let mut imag_data = Vec::with_capacity(n_freqs * n_frames);
for i in 0..(n_freqs * n_frames) {
let mag = mag_data[i];
let phase = phase_data[i];
real_data.push(mag * phase.cos());
imag_data.push(mag * phase.sin());
}
let real = Tensor::from_data(real_data, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let imag = Tensor::from_data(imag_data, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let complex_spec = ComplexTensor::from_real_imag(real, imag)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
// ISTFT to get waveform
let waveform = istft(
&complex_spec,
self.config.win_length,
self.config.hop_length,
Some(WindowType::Hann),
None,
)
.map_err(|e| TtsError::VocodingError(format!("ISTFT failed: {}", e)))?;
// STFT to get new spectrum
let stft_result = stft(
&waveform,
self.config.win_length,
self.config.hop_length,
Some(WindowType::Hann),
true,
)
.map_err(|e| TtsError::VocodingError(format!("STFT failed: {}", e)))?;
// Extract phase from new spectrum
let new_real = stft_result.stft.real().to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let new_imag = stft_result.stft.imag().to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let new_phase_data: Vec<f32> = new_real
.iter()
.zip(new_imag.iter())
.map(|(&r, &i)| i.atan2(r))
.collect();
phase_tensor = Tensor::from_data(new_phase_data, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
}
// Final reconstruction
let phase_data = phase_tensor.to_cpu()
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let mut real_data = Vec::with_capacity(n_freqs * n_frames);
let mut imag_data = Vec::with_capacity(n_freqs * n_frames);
for i in 0..(n_freqs * n_frames) {
let mag = mag_data[i];
let phase = phase_data[i];
real_data.push(mag * phase.cos());
imag_data.push(mag * phase.sin());
}
let real = Tensor::from_data(real_data, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let imag = Tensor::from_data(imag_data, vec![n_freqs, n_frames], &self.device)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
let final_spec = ComplexTensor::from_real_imag(real, imag)
.map_err(|e| TtsError::TensorError(e.to_string()))?;
istft(
&final_spec,
self.config.win_length,
self.config.hop_length,
Some(WindowType::Hann),
None,
)
.map_err(|e| TtsError::VocodingError(format!("Final ISTFT failed: {}", e)))
}
/// Get configuration
pub fn config(&self) -> &GriffinLimConfig {
&self.config
}
}
impl Vocoder for GriffinLim {
fn synthesize(&self, mel: &Tensor) -> Result<Tensor> {
// Convert mel to linear spectrogram
let linear_spec = self.mel_to_linear(mel)?;
// Reconstruct phase and synthesize audio
self.reconstruct_phase(&linear_spec)
}
fn get_sample_rate(&self) -> usize {
self.config.sample_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_griffin_lim_config_default() {
let config = GriffinLimConfig::default();
assert_eq!(config.n_fft, 1024);
assert_eq!(config.hop_length, 256);
assert_eq!(config.win_length, 1024);
assert_eq!(config.n_iter, 60);
assert_eq!(config.sample_rate, 22050);
}
#[test]
fn test_griffin_lim_config_validate_valid() {
let config = GriffinLimConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_griffin_lim_config_validate_invalid_n_fft() {
let mut config = GriffinLimConfig::default();
config.n_fft = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_validate_invalid_hop_length() {
let mut config = GriffinLimConfig::default();
config.hop_length = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_validate_invalid_win_length() {
let mut config = GriffinLimConfig::default();
config.win_length = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_validate_win_length_too_large() {
let mut config = GriffinLimConfig::default();
config.win_length = config.n_fft + 1;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_validate_invalid_n_iter() {
let mut config = GriffinLimConfig::default();
config.n_iter = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_validate_invalid_sample_rate() {
let mut config = GriffinLimConfig::default();
config.sample_rate = 0;
assert!(config.validate().is_err());
}
#[test]
fn test_griffin_lim_config_serialize() {
let config = GriffinLimConfig::default();
let json = serde_json::to_string(&config).unwrap();
let deserialized: GriffinLimConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.n_fft, deserialized.n_fft);
assert_eq!(config.n_iter, deserialized.n_iter);
}
#[test]
fn test_griffin_lim_creation() {
let device = Device::cpu();
let config = GriffinLimConfig::default();
let gl = GriffinLim::new(config.clone(), &device);
assert!(gl.is_ok());
let gl = gl.unwrap();
assert_eq!(gl.get_sample_rate(), config.sample_rate);
assert_eq!(gl.config().n_fft, config.n_fft);
}
#[test]
fn test_griffin_lim_creation_invalid_config() {
let device = Device::cpu();
let mut config = GriffinLimConfig::default();
config.n_fft = 0;
let result = GriffinLim::new(config, &device);
assert!(result.is_err());
}
#[test]
fn test_mel_to_linear() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 10,
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config.clone(), &device).unwrap();
// Create a dummy mel spectrogram
let n_frames = 50;
let mel_spec = Tensor::randn([config.mel_config.n_mels, n_frames], &device).unwrap();
// Convert to linear
let linear_spec = gl.mel_to_linear(&mel_spec).unwrap();
let linear_dims = linear_spec.dims();
assert_eq!(linear_dims[0], config.n_fft / 2 + 1);
assert_eq!(linear_dims[1], n_frames);
}
#[test]
fn test_reconstruct_phase_shape() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5, // Use fewer iterations for faster test
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config.clone(), &device).unwrap();
// Create a magnitude spectrogram
let n_freqs = config.n_fft / 2 + 1;
let n_frames = 50;
let magnitude = Tensor::rand([n_freqs, n_frames], &device).unwrap();
// Reconstruct phase
let audio = gl.reconstruct_phase(&magnitude).unwrap();
let audio_dims = audio.dims();
assert_eq!(audio_dims.len(), 1);
assert!(audio_dims[0] > 0);
}
#[test]
fn test_reconstruct_phase_invalid_shape() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5,
sample_rate: 16000,
mel_config: MelConfig::default(),
};
let gl = GriffinLim::new(config, &device).unwrap();
// Create 1D tensor (invalid)
let magnitude = Tensor::rand([100], &device).unwrap();
let result = gl.reconstruct_phase(&magnitude);
assert!(result.is_err());
}
#[test]
fn test_reconstruct_phase_wrong_n_freqs() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5,
sample_rate: 16000,
mel_config: MelConfig::default(),
};
let gl = GriffinLim::new(config, &device).unwrap();
// Create magnitude with wrong number of frequency bins
let magnitude = Tensor::rand([100, 50], &device).unwrap();
let result = gl.reconstruct_phase(&magnitude);
assert!(result.is_err());
}
#[test]
fn test_synthesize() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5, // Use fewer iterations for faster test
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config.clone(), &device).unwrap();
// Create a dummy mel spectrogram
let n_frames = 50;
let mel_spec = Tensor::rand([config.mel_config.n_mels, n_frames], &device).unwrap();
// Synthesize audio
let audio = gl.synthesize(&mel_spec).unwrap();
let audio_dims = audio.dims();
assert_eq!(audio_dims.len(), 1);
assert!(audio_dims[0] > 0);
}
#[test]
fn test_synthesize_invalid_mel_shape() {
let device = Device::cpu();
let config = GriffinLimConfig::default();
let gl = GriffinLim::new(config, &device).unwrap();
// Create invalid mel spectrogram (wrong number of mels)
let mel_spec = Tensor::rand([40, 50], &device).unwrap();
let result = gl.synthesize(&mel_spec);
assert!(result.is_err());
}
#[test]
fn test_vocoder_trait_implementation() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5,
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config.clone(), &device).unwrap();
// Test Vocoder trait methods
assert_eq!(gl.get_sample_rate(), 16000);
let mel_spec = Tensor::rand([40, 50], &device).unwrap();
let audio = gl.synthesize(&mel_spec).unwrap();
assert!(audio.dims()[0] > 0);
}
#[test]
fn test_different_iterations() {
let device = Device::cpu();
let iterations = vec![1, 5, 10, 20];
let base_config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 1, // Will be overridden
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let mel_spec = Tensor::rand([40, 30], &device).unwrap();
for n_iter in iterations {
let mut config = base_config.clone();
config.n_iter = n_iter;
let gl = GriffinLim::new(config, &device).unwrap();
let audio = gl.synthesize(&mel_spec).unwrap();
assert!(audio.dims()[0] > 0);
}
}
#[test]
fn test_different_sample_rates() {
let device = Device::cpu();
let sample_rates = vec![8000, 16000, 22050, 44100];
for sample_rate in sample_rates {
let config = GriffinLimConfig {
n_fft: 512,
hop_length: 128,
win_length: 512,
n_iter: 5,
sample_rate,
mel_config: MelConfig {
sample_rate,
n_fft: 512,
hop_length: 128,
win_length: 512,
n_mels: 40,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config.clone(), &device).unwrap();
assert_eq!(gl.get_sample_rate(), sample_rate);
}
}
#[test]
fn test_deterministic_reconstruction() {
let device = Device::cpu();
let config = GriffinLimConfig {
n_fft: 256,
hop_length: 64,
win_length: 256,
n_iter: 3,
sample_rate: 16000,
mel_config: MelConfig {
sample_rate: 16000,
n_fft: 256,
hop_length: 64,
win_length: 256,
n_mels: 20,
f_min: 0.0,
f_max: None,
},
};
let gl = GriffinLim::new(config, &device).unwrap();
// Create a fixed mel spectrogram
let mel_data = vec![0.5f32; 20 * 30];
let mel_spec = Tensor::from_data(mel_data, vec![20, 30], &device).unwrap();
// Synthesize twice - note: results may differ due to random phase initialization
let audio1 = gl.synthesize(&mel_spec).unwrap();
let audio2 = gl.synthesize(&mel_spec).unwrap();
// Both should produce valid audio
assert_eq!(audio1.dims().len(), 1);
assert_eq!(audio2.dims().len(), 1);
}
}