use ndarray::Array2;
use num_complex::Complex;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PhaseRecoveryError {
#[error("Computation failed: {0}")]
ComputationFailed(String),
}
pub fn griffinlim(s: &Array2<f32>) -> GriffinLimBuilder<'_> {
GriffinLimBuilder {
s,
n_iter: 32,
hop_length: None, }
}
#[derive(Debug, Clone)]
pub struct GriffinLimBuilder<'a> {
s: &'a Array2<f32>,
n_iter: usize,
hop_length: Option<usize>,
}
impl GriffinLimBuilder<'_> {
#[must_use]
pub fn n_iter(mut self, n_iter: usize) -> Self {
self.n_iter = n_iter;
self
}
#[must_use]
pub fn hop_length(mut self, hop_length: usize) -> Self {
self.hop_length = Some(hop_length);
self
}
pub fn compute(self) -> Result<Vec<f32>, PhaseRecoveryError> {
griffinlim_impl(self.s, self.n_iter, self.hop_length)
}
}
fn griffinlim_impl(s: &Array2<f32>, n_iter: usize, hop_length: Option<usize>) -> Result<Vec<f32>, PhaseRecoveryError> {
let n_fft = (s.shape()[0] - 1) * 2;
let hop = hop_length.unwrap_or(n_fft / 4).max(1);
let signal_len = hop * (s.shape()[1] - 1) + n_fft;
let mut y = crate::signal_generation::generators::tone(440.0, 44100)
.duration(signal_len as f32 / 44100.0)
.compute();
for _ in 0..n_iter {
let stft_y = crate::signal_processing::time_frequency::stft(&y)
.n_fft(n_fft)
.hop_length(hop)
.compute()
.map_err(|e| PhaseRecoveryError::ComputationFailed(format!("STFT computation failed: {e}")))?;
let (mut mag, mut phase) = crate::signal_processing::time_frequency::magphase(&stft_y, None);
for ((i, j), m) in mag.indexed_iter_mut() {
*m = s[[i, j]]; let p = &mut phase[[i, j]];
if m.abs() > 1e-10 {
*p /= p.norm(); }
}
let new_stft = mag.mapv(|x| Complex::new(x, 0.0)) * phase;
y = crate::signal_processing::time_frequency::istft(&new_stft)
.hop_length(hop)
.length(signal_len)
.compute();
}
Ok(y)
}
#[derive(Debug, Clone)]
pub struct GriffinLimCqtBuilder<'a> {
c_mag: &'a Array2<f32>,
sr: u32,
n_iter: usize,
hop_length: usize,
fmin: f32,
}
impl GriffinLimCqtBuilder<'_> {
#[must_use]
pub fn n_iter(mut self, v: usize) -> Self {
self.n_iter = v;
self
}
#[must_use]
pub fn hop_length(mut self, v: usize) -> Self {
self.hop_length = v;
self
}
#[must_use]
pub fn fmin(mut self, v: f32) -> Self {
self.fmin = v;
self
}
pub fn compute(self) -> Result<Vec<f32>, PhaseRecoveryError> {
griffinlim_cqt_impl(self.c_mag, self.sr, self.n_iter, self.hop_length, self.fmin)
}
}
pub fn griffinlim_cqt(c_mag: &Array2<f32>, sr: u32) -> GriffinLimCqtBuilder<'_> {
GriffinLimCqtBuilder { c_mag, sr, n_iter: 32, hop_length: 512, fmin: 32.70 }
}
fn griffinlim_cqt_impl(
c_mag: &Array2<f32>,
sr: u32,
n_iter: usize,
hop_length: usize,
fmin: f32,
) -> Result<Vec<f32>, PhaseRecoveryError> {
use crate::signal_processing::time_frequency::{cqt, icqt, magphase};
let n_bins = c_mag.shape()[0];
let n_frames = c_mag.shape()[1];
if n_bins == 0 || n_frames == 0 {
return Err(PhaseRecoveryError::ComputationFailed(
"Empty CQT magnitude spectrogram".into(),
));
}
let n_samples = n_frames * hop_length;
let mut y: Vec<f32> = (0..n_samples)
.map(|i| (i as f32 * 0.031_415_93).sin() * 0.01)
.collect();
for _ in 0..n_iter {
let c = cqt(&y, sr)
.hop_length(hop_length)
.fmin(fmin)
.n_bins(n_bins)
.compute()
.map_err(|e| PhaseRecoveryError::ComputationFailed(e.to_string()))?;
let (_mag, phase) = magphase(&c, None);
let (nk, nf) = (
n_bins.min(phase.shape()[0]),
n_frames.min(phase.shape()[1]),
);
let mut c_new = Array2::from_elem(phase.raw_dim(), Complex::new(0.0_f32, 0.0));
for k in 0..nk {
for t in 0..nf {
c_new[[k, t]] = phase[[k, t]] * c_mag[[k, t]];
}
}
y = icqt(&c_new)
.sample_rate(sr)
.hop_length(hop_length)
.fmin(fmin)
.compute()
.map_err(|e| PhaseRecoveryError::ComputationFailed(e.to_string()))?;
}
Ok(y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_griffinlim_cqt_empty() {
let empty = Array2::zeros((0, 0));
assert!(griffinlim_cqt_impl(&empty, 44100, 4, 512, 32.7).is_err());
}
#[test]
fn test_griffinlim_cqt_smoke() {
let mag = Array2::from_elem((12, 20), 0.1_f32);
let result = griffinlim_cqt_impl(&mag, 44100, 2, 512, 32.7);
assert!(result.is_ok(), "griffinlim_cqt should succeed: {:?}", result.err());
let y = result.unwrap();
assert!(!y.is_empty());
}
}