MayaFlux 0.3.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Spectral.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * @file Spectral.hpp
5 * @brief Short-time Fourier domain primitives for MayaFlux::Kinesis
6 *
7 * Pure functions operating on contiguous double-precision spans.
8 * No MayaFlux type dependencies. Domain-agnostic: the same primitives
9 * serve audio, control, and any other sampled sequence.
10 *
11 * All functions that modify frequency content use weighted overlap-add
12 * (WOLA) synthesis with a Hann analysis window. The synthesis window is
13 * the same Hann window; at 75% overlap (hop = N/4) the OLA normalisation
14 * factor is constant and the reconstruction is exact up to numerical noise.
15 *
16 * Eigen FFT dependency is confined to the .cpp translation unit.
17 */
18
20
21/**
22 * @brief Callable type for per-frame spectrum modification
23 * Receives the one-sided complex spectrum (bins 0..N/2 inclusive)
24 * and the frame index. Modifications are written back in-place.
25 */
26using SpectrumProcessor = std::function<void(std::vector<std::complex<double>>&, size_t)>;
27
28// ============================================================================
29// Core STFT engine
30// ============================================================================
31
32/**
33 * @brief Apply a per-frame spectrum processor via WOLA analysis-synthesis
34 *
35 * Frames the input with a Hann window, calls @p processor on each frame's
36 * one-sided spectrum, reconstructs via IFFT, and accumulates with WOLA
37 * normalisation. Output length equals input length.
38 *
39 * @param src Input samples
40 * @param window_size FFT frame size (power of 2, >= 64)
41 * @param hop_size Hop between analysis frames
42 * @param processor Per-frame spectrum callback
43 * @return Processed output, same length as src
44 */
45[[nodiscard]] std::vector<double> apply_spectral(
46 std::span<const double> src,
47 uint32_t window_size,
48 uint32_t hop_size,
49 const SpectrumProcessor& processor);
50
51// ============================================================================
52// Spectral transforms
53// ============================================================================
54
55/**
56 * @brief Hard bandpass filter: zero all bins outside [lo_hz, hi_hz]
57 * @param src Input samples
58 * @param lo_hz Lower frequency bound (Hz)
59 * @param hi_hz Upper frequency bound (Hz)
60 * @param sample_rate Sample rate (Hz)
61 * @param window_size FFT frame size
62 * @param hop_size Analysis hop
63 * @return Filtered output
64 */
65[[nodiscard]] std::vector<double> spectral_filter(
66 std::span<const double> src,
67 double lo_hz,
68 double hi_hz,
69 double sample_rate,
70 uint32_t window_size = 1024,
71 uint32_t hop_size = 256);
72
73/**
74 * @brief Spectral phase inversion (conjugate all bins)
75 * @param src Input samples
76 * @param window_size FFT frame size
77 * @param hop_size Analysis hop
78 * @return Phase-inverted output
79 */
80[[nodiscard]] std::vector<double> spectral_invert(
81 std::span<const double> src,
82 uint32_t window_size = 1024,
83 uint32_t hop_size = 256);
84
85/**
86 * @brief Linear spectral tilt: scale each bin by a factor that rises
87 * linearly from 1 at bin 0 to @p enhancement_factor at the Nyquist bin
88 *
89 * This is a first-order spectral emphasis, not a true harmonic detector;
90 * caller should be aware that it brightens high-frequency energy
91 * uniformly regardless of harmonic * structure.
92 *
93 * @param src Input samples
94 * @param enhancement_factor Gain at the Nyquist bin (> 1 brightens, < 1 darkens)
95 * @param window_size FFT frame size
96 * @param hop_size Analysis hop
97 * @return Enhanced output
98 */
99[[nodiscard]] std::vector<double> harmonic_enhance(
100 std::span<const double> src,
101 double enhancement_factor,
102 uint32_t window_size = 1024,
103 uint32_t hop_size = 256);
104
105/**
106 * @brief Hard spectral gate: zero bins whose magnitude is below the threshold
107 * @param src Input samples
108 * @param threshold_db Gate threshold in dBFS; bins below are zeroed
109 * @param window_size FFT frame size
110 * @param hop_size Analysis hop
111 * @return Gated output
112 */
113[[nodiscard]] std::vector<double> spectral_gate(
114 std::span<const double> src,
115 double threshold_db,
116 uint32_t window_size = 1024,
117 uint32_t hop_size = 256);
118
119// ============================================================================
120// Phase vocoder operations
121// ============================================================================
122
123/**
124 * @brief Time-stretch via phase vocoder analysis-synthesis
125 *
126 * Produces output of length approximately src.size() * stretch_factor.
127 * Spectral envelopes and pitched content are preserved without aliasing.
128 * Sharp transients exhibit mild pre-echo at high stretch ratios (> 2×),
129 * which is inherent to the standard phase vocoder algorithm.
130 *
131 * Recommended parameters for audio at 48 kHz:
132 * window_size = 2048, analysis_hop = 512 (75% overlap).
133 *
134 * @param src Input samples
135 * @param stretch_factor >1 = slower/longer, <1 = faster/shorter; 1 returns copy
136 * @param window_size FFT frame size (power of 2, >= 64)
137 * @param analysis_hop Analysis hop Ha; synthesis hop Hs = Ha * stretch_factor
138 * @return Time-stretched output
139 */
140[[nodiscard]] std::vector<double> phase_vocoder_stretch(
141 std::span<const double> src,
142 double stretch_factor,
143 uint32_t window_size = 2048,
144 uint32_t analysis_hop = 512);
145
146/**
147 * @brief Pitch-shift by resampling around a phase vocoder stretch
148 *
149 * Stretches by 1/pitch_ratio, then resamples back to the original length
150 * using linear interpolation. This preserves duration while shifting pitch,
151 * which is the standard phase vocoder pitch-shift approach.
152 *
153 * @param src Input samples
154 * @param semitones Pitch shift in semitones (positive = up, negative = down)
155 * @param window_size FFT frame size
156 * @param analysis_hop Analysis hop
157 * @return Pitch-shifted output, same length as src
158 */
159[[nodiscard]] std::vector<double> pitch_shift(
160 std::span<const double> src,
161 double semitones,
162 uint32_t window_size = 2048,
163 uint32_t analysis_hop = 512);
164
165} // namespace MayaFlux::Kinesis::Discrete
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.
Definition Spectral.cpp:172
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.
Definition Spectral.cpp:59
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].
Definition Spectral.cpp:123
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.
Definition Spectral.cpp:193
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....
Definition Spectral.hpp:26
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...
Definition Spectral.cpp:155
std::vector< double > spectral_invert(std::span< const double > src, uint32_t window_size, uint32_t hop_size)
Spectral phase inversion (conjugate all bins)
Definition Spectral.cpp:143
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.
Definition Spectral.cpp:282