4#include <unsupported/Eigen/FFT>
10 constexpr double k_pi = std::numbers::pi;
11 constexpr double k_tau = 2.0 * std::numbers::pi;
18 [[nodiscard]] std::vector<double> make_hann(uint32_t n)
20 std::vector<double> w(n);
21 const double inv = 1.0 /
static_cast<double>(n - 1);
22 for (uint32_t i = 0; i < n; ++i)
23 w[i] = 0.5 * (1.0 - std::cos(k_tau * i * inv));
30 [[nodiscard]]
inline double wrap_phase(
double p)
noexcept
32 p -= k_tau * std::floor((p + k_pi) / k_tau);
40 [[nodiscard]] Eigen::VectorXcd to_full_spectrum(
41 const std::vector<std::complex<double>>& onesided,
44 const uint32_t bins =
N / 2 + 1;
45 Eigen::VectorXcd full(
static_cast<Eigen::Index
>(
N));
46 for (uint32_t
b = 0;
b < bins; ++
b)
47 full(
static_cast<Eigen::Index
>(
b)) = onesided[
b];
48 for (uint32_t
b = bins;
b <
N; ++
b)
49 full(
static_cast<Eigen::Index
>(
b)) = std::conj(onesided[
N -
b]);
60 std::span<const double> src,
68 const uint32_t
N = window_size;
69 const uint32_t bins =
N / 2 + 1;
70 const std::vector<double> win = make_hann(
N);
72 const size_t n_frames = (src.size() >=
N)
73 ? (src.size() -
N) / hop_size + 1
76 std::vector<double> output(src.size(), 0.0);
77 std::vector<double> norm(src.size(), 0.0);
79 Eigen::FFT<double> fft;
80 Eigen::VectorXd frame(
N);
81 Eigen::VectorXcd spectrum;
82 Eigen::VectorXd ifft_out;
84 for (
size_t f = 0; f < n_frames; ++f) {
85 const size_t pos = f * hop_size;
87 for (uint32_t k = 0; k <
N; ++k) {
88 const size_t idx = pos + k;
89 frame(
static_cast<Eigen::Index
>(k)) = (idx < src.size()) ? src[idx] * win[k] : 0.0;
92 fft.fwd(spectrum, frame);
94 std::vector<std::complex<double>> onesided(bins);
95 for (uint32_t
b = 0;
b < bins; ++
b)
96 onesided[
b] = spectrum(
static_cast<Eigen::Index
>(
b));
98 processor(onesided, f);
100 Eigen::VectorXcd full = to_full_spectrum(onesided,
N);
101 fft.inv(ifft_out, full);
103 const double inv_N = 1.0 /
static_cast<double>(
N);
104 for (uint32_t k = 0; k <
N && pos + k < src.size(); ++k) {
105 const double w = win[k];
106 output[pos + k] += ifft_out(
static_cast<Eigen::Index
>(k)) * inv_N * w;
107 norm[pos + k] += w * w;
111 for (
size_t i = 0; i < src.size(); ++i) {
113 output[i] /= norm[i];
124 std::span<const double> src,
128 uint32_t window_size,
131 const double bin_hz = sample_rate /
static_cast<double>(window_size);
134 [lo_hz, hi_hz, bin_hz](std::vector<std::complex<double>>& spec,
size_t) {
135 for (
size_t b = 0;
b < spec.size(); ++
b) {
136 const double f =
static_cast<double>(
b) * bin_hz;
137 if (f < lo_hz || f > hi_hz)
144 std::span<const double> src,
145 uint32_t window_size,
149 [](std::vector<std::complex<double>>& spec,
size_t) {
150 for (
auto& bin : spec)
151 bin = std::conj(bin);
156 std::span<const double> src,
157 double enhancement_factor,
158 uint32_t window_size,
161 const auto nyquist_bin =
static_cast<double>((double)window_size / 2);
164 [enhancement_factor, nyquist_bin](std::vector<std::complex<double>>& spec,
size_t) {
165 for (
size_t b = 1;
b < spec.size(); ++
b) {
166 const double t =
static_cast<double>(
b) / nyquist_bin;
167 spec[
b] *= 1.0 + (enhancement_factor - 1.0) * t;
173 std::span<const double> src,
175 uint32_t window_size,
178 const double lin = std::pow(10.0, threshold_db / 20.0);
181 [lin](std::vector<std::complex<double>>& spec,
size_t) {
182 for (
auto& bin : spec) {
183 if (std::abs(bin) < lin)
194 std::span<const double> src,
195 double stretch_factor,
196 uint32_t window_size,
197 uint32_t analysis_hop)
199 if (src.empty() || stretch_factor <= 0.0)
202 if (stretch_factor == 1.0)
203 return { src.begin(), src.end() };
205 const uint32_t
N = window_size;
206 const uint32_t Ha = analysis_hop;
207 const auto Hs =
static_cast<uint32_t
>(std::round(Ha * stretch_factor));
208 const uint32_t bins =
N / 2 + 1;
210 const std::vector<double> win = make_hann(
N);
212 const double omega_factor = k_tau *
static_cast<double>(Ha) /
static_cast<double>(
N);
214 const size_t n_frames = (src.size() >=
N)
215 ? (src.size() -
N) / Ha + 1
218 const size_t out_len =
static_cast<size_t>(
static_cast<double>(src.size()) * stretch_factor) +
N;
220 std::vector<double> output(out_len, 0.0);
221 std::vector<double> norm(out_len, 0.0);
223 std::vector<double> phase_accum(bins, 0.0);
224 std::vector<double> prev_phase(bins, 0.0);
226 Eigen::FFT<double> fft;
227 Eigen::VectorXd frame(
N);
228 Eigen::VectorXcd spectrum;
229 Eigen::VectorXd ifft_out;
231 for (
size_t f = 0; f < n_frames; ++f) {
232 const size_t src_pos = f * Ha;
234 for (uint32_t k = 0; k <
N; ++k) {
235 const size_t idx = src_pos + k;
236 frame(
static_cast<Eigen::Index
>(k)) = (idx < src.size()) ? src[idx] * win[k] : 0.0;
239 fft.fwd(spectrum, frame);
241 std::vector<std::complex<double>> synth(bins);
242 for (uint32_t
b = 0;
b < bins; ++
b) {
243 const std::complex<double> bin = spectrum(
static_cast<Eigen::Index
>(
b));
244 const double mag = std::abs(bin);
245 const double phase = std::arg(bin);
247 const double delta = phase - prev_phase[
b]
248 - omega_factor *
static_cast<double>(
b);
249 const double true_f = omega_factor *
static_cast<double>(
b)
252 phase_accum[
b] +=
static_cast<double>(Hs) * true_f
253 /
static_cast<double>(Ha);
254 prev_phase[
b] = phase;
256 synth[
b] = std::polar(mag, phase_accum[
b]);
259 Eigen::VectorXcd full = to_full_spectrum(synth,
N);
260 fft.inv(ifft_out, full);
262 const size_t out_pos = f * Hs;
263 const double inv_N = 1.0 /
static_cast<double>(
N);
264 for (uint32_t k = 0; k <
N && out_pos + k < out_len; ++k) {
265 const double w = win[k];
266 output[out_pos + k] += ifft_out(
static_cast<Eigen::Index
>(k)) * inv_N * w;
267 norm[out_pos + k] += w * w;
271 for (
size_t i = 0; i < out_len; ++i) {
273 output[i] /= norm[i];
277 static_cast<size_t>(
static_cast<double>(src.size()) * stretch_factor));
283 std::span<const double> src,
285 uint32_t window_size,
286 uint32_t analysis_hop)
288 if (src.empty() || semitones == 0.0)
289 return { src.begin(), src.end() };
291 const double pitch_ratio = std::pow(2.0, semitones / 12.0);
292 const double stretch_ratio = 1.0 / pitch_ratio;
294 std::vector<double> stretched =
phase_vocoder_stretch(src, stretch_ratio, window_size, analysis_hop);
296 if (stretched.empty())
299 const size_t out_n = src.size();
300 std::vector<double> out(out_n);
301 const size_t in_n = stretched.size();
302 const double step =
static_cast<double>(in_n - 1) /
static_cast<double>(out_n - 1);
304 for (
size_t i = 0; i < out_n; ++i) {
305 const double pos =
static_cast<double>(i) * step;
306 const auto idx =
static_cast<size_t>(pos);
307 const size_t idx1 = std::min(idx + 1, in_n - 1);
308 const double frac = pos -
static_cast<double>(idx);
309 out[i] = stretched[idx] + frac * (stretched[idx1] - stretched[idx]);
#define N(method_name, full_type_name)
Short-time Fourier domain primitives for MayaFlux::Kinesis.
std::vector< double > spectral_gate(std::span< const double > src, double threshold_db, uint32_t window_size, uint32_t hop_size)
Hard spectral gate: zero bins whose magnitude is below the threshold.
std::vector< double > apply_spectral(std::span< const double > src, uint32_t window_size, uint32_t hop_size, const SpectrumProcessor &processor)
Apply a per-frame spectrum processor via WOLA analysis-synthesis.
std::vector< double > spectral_filter(std::span< const double > src, double lo_hz, double hi_hz, double sample_rate, uint32_t window_size, uint32_t hop_size)
Hard bandpass filter: zero all bins outside [lo_hz, hi_hz].
std::vector< double > phase_vocoder_stretch(std::span< const double > src, double stretch_factor, uint32_t window_size, uint32_t analysis_hop)
Time-stretch via phase vocoder analysis-synthesis.
std::function< void(std::vector< std::complex< double > > &, size_t)> SpectrumProcessor
Callable type for per-frame spectrum modification Receives the one-sided complex spectrum (bins 0....
std::vector< double > harmonic_enhance(std::span< const double > src, double enhancement_factor, uint32_t window_size, uint32_t hop_size)
Linear spectral tilt: scale each bin by a factor that rises linearly from 1 at bin 0 to enhancement_f...
std::vector< double > spectral_invert(std::span< const double > src, uint32_t window_size, uint32_t hop_size)
Spectral phase inversion (conjugate all bins)
std::vector< double > pitch_shift(std::span< const double > src, double semitones, uint32_t window_size, uint32_t analysis_hop)
Pitch-shift by resampling around a phase vocoder stretch.