use crate::matrix::Matrix;
use super::transform::{CqtParams, complex_magnitude, complex_matmul, cqt};
use super::wavelet::{WaveletLengthsParams, WaveletParams, wavelet, wavelet_lengths};
pub fn pseudo_cqt(y: &[f64], params: &CqtParams) -> Matrix {
let freqs = crate::convert::cqt_frequencies(
params.n_bins,
params.fmin,
params.bins_per_octave,
params.tuning,
);
let wl_params = WaveletLengthsParams {
sr: params.sr,
window: params.window,
filter_scale: params.filter_scale,
gamma: Some(0.0),
};
let (lengths, _f_cutoff) = wavelet_lengths(&freqs, &wl_params);
let wp = WaveletParams {
sr: params.sr,
window: params.window,
filter_scale: params.filter_scale,
pad_fft: true,
norm: params.norm,
gamma: Some(0.0),
};
let (filters_re, filters_im, _) = wavelet(&freqs, &wp);
let min_fft = (2 * params.hop_length).next_power_of_two();
let n_fft = filters_re.cols().max(min_fft).next_power_of_two();
let n_bins_fft = n_fft / 2 + 1;
let n_freqs = freqs.len();
let mut planner = realfft::RealFftPlanner::<f64>::new();
let fft = planner.plan_fft_forward(n_fft);
let mut input_buf = fft.make_input_vec();
let mut output_buf = fft.make_output_vec();
let mut scratch = fft.make_scratch_vec();
let mut basis_re_data = vec![0.0; n_freqs * n_bins_fft];
let mut basis_im_data = vec![0.0; n_freqs * n_bins_fft];
for k in 0..n_freqs {
let scale = lengths[k] / n_fft as f64;
let filter_cols = filters_re.cols();
let pad_start = if n_fft > filter_cols {
(n_fft - filter_cols) / 2
} else {
0
};
let copy_len = filter_cols.min(n_fft);
input_buf.fill(0.0);
for j in 0..copy_len {
input_buf[pad_start + j] = filters_re.get(k, j) * scale;
}
fft.process_with_scratch(&mut input_buf, &mut output_buf, &mut scratch)
.expect("FFT failed");
let mut fft_re_re = vec![0.0; n_bins_fft];
let mut fft_re_im = vec![0.0; n_bins_fft];
for j in 0..n_bins_fft {
fft_re_re[j] = output_buf[j].re;
fft_re_im[j] = output_buf[j].im;
}
input_buf.fill(0.0);
for j in 0..copy_len {
input_buf[pad_start + j] = filters_im.get(k, j) * scale;
}
fft.process_with_scratch(&mut input_buf, &mut output_buf, &mut scratch)
.expect("FFT failed");
for j in 0..n_bins_fft {
let fft_im_re = output_buf[j].re;
let fft_im_im = output_buf[j].im;
basis_re_data[k * n_bins_fft + j] = fft_re_re[j] - fft_im_im;
basis_im_data[k * n_bins_fft + j] = fft_re_im[j] + fft_im_re;
}
}
let basis_re = Matrix::from_vec(basis_re_data, n_freqs, n_bins_fft);
let basis_im = Matrix::from_vec(basis_im_data, n_freqs, n_bins_fft);
let stft_params = crate::stft::StftParams {
n_fft,
hop_length: params.hop_length,
win_length: n_fft,
center: true,
window: Some(vec![1.0; n_fft]),
};
let (stft_re, stft_im) = crate::stft::stft_as_real_imag(y, &stft_params);
let (resp_re, resp_im) = complex_matmul(&basis_re, &basis_im, &stft_re, &stft_im);
let mut result = complex_magnitude(&resp_re, &resp_im);
if params.scale {
for k in 0..params.n_bins {
let s = 1.0 / lengths[k].sqrt();
for f in 0..result.cols() {
*result.get_mut(k, f) *= s;
}
}
}
result
}
pub fn hybrid_cqt(y: &[f64], params: &CqtParams) -> (Matrix, Matrix) {
let freqs = crate::convert::cqt_frequencies(
params.n_bins,
params.fmin,
params.bins_per_octave,
params.tuning,
);
let wl_params = WaveletLengthsParams {
sr: params.sr,
window: params.window,
filter_scale: params.filter_scale,
gamma: Some(0.0),
};
let (lengths, _) = wavelet_lengths(&freqs, &wl_params);
let pseudo_threshold = 2 * params.hop_length;
let mut n_full = params.n_bins;
for k in (0..params.n_bins).rev() {
let flen_pow2 = (lengths[k].ceil() as usize).next_power_of_two();
if flen_pow2 < pseudo_threshold {
n_full = k;
break;
}
}
if n_full == params.n_bins {
return cqt(y, params);
}
if n_full == 0 {
let mag = pseudo_cqt(y, params);
let im = Matrix::zeros(mag.rows(), mag.cols());
return (mag, im);
}
let full_params = CqtParams {
sr: params.sr,
hop_length: params.hop_length,
fmin: params.fmin,
n_bins: n_full,
bins_per_octave: params.bins_per_octave,
tuning: params.tuning,
filter_scale: params.filter_scale,
norm: params.norm,
window: params.window,
scale: params.scale,
};
let (full_re, full_im) = cqt(y, &full_params);
let n_pseudo = params.n_bins - n_full;
let pseudo_fmin = freqs[n_full];
let pseudo_params = CqtParams {
sr: params.sr,
hop_length: params.hop_length,
fmin: pseudo_fmin,
n_bins: n_pseudo,
bins_per_octave: params.bins_per_octave,
tuning: 0.0, filter_scale: params.filter_scale,
norm: params.norm,
window: params.window,
scale: params.scale,
};
let pseudo_mag = pseudo_cqt(y, &pseudo_params);
let n_frames = full_re.cols().min(pseudo_mag.cols());
let mut out_re = vec![0.0; params.n_bins * n_frames];
let mut out_im = vec![0.0; params.n_bins * n_frames];
for k in 0..n_full {
for f in 0..n_frames {
out_re[k * n_frames + f] = full_re.get(k, f);
out_im[k * n_frames + f] = full_im.get(k, f);
}
}
for k in 0..n_pseudo {
for f in 0..n_frames {
out_re[(n_full + k) * n_frames + f] = pseudo_mag.get(k, f);
}
}
(
Matrix::from_vec(out_re, params.n_bins, n_frames),
Matrix::from_vec(out_im, params.n_bins, n_frames),
)
}