Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectral_morph.hpp
Go to the documentation of this file.
1#pragma once
2
11
12#include <algorithm>
13#include <cmath>
14#include <complex>
15#include <cstddef>
16#include <limits>
17#include <type_traits>
18
19namespace pulp::signal {
20
25
30
31template <typename SampleType = float>
33public:
34 static_assert(std::is_floating_point_v<SampleType>);
35
36 static constexpr int maximum_channels = 64;
37 static constexpr int minimum_bins = 129;
38 static constexpr int maximum_bins = 8193;
39
45
46 static bool supports_configuration(int channels, int num_bins) noexcept {
49 return false;
50 const int fft_size = 2 * (num_bins - 1);
51 return (fft_size & (fft_size - 1)) == 0;
52 }
53
56 bool prepare(int channels, int num_bins) noexcept {
57 if (!supports_configuration(channels, num_bins)) return false;
58 channels_ = channels;
59 num_bins_ = num_bins;
60 prepared_ = true;
61 return true;
62 }
63
66 void reset() noexcept {}
67
68 bool set_config(Config config) noexcept {
69 if (!valid(config.magnitude) || !valid(config.phase)) return false;
70 config_ = config;
71 return true;
72 }
73
74 Config config() const noexcept { return config_; }
75 bool prepared() const noexcept { return prepared_; }
76 int channels() const noexcept { return channels_; }
77 int num_bins() const noexcept { return num_bins_; }
78
83 bool process(const std::complex<SampleType>* const* a,
84 const std::complex<SampleType>* const* b,
85 std::complex<SampleType>* const* out,
86 int channels, int num_bins,
87 SampleType magnitude_amount,
88 SampleType phase_amount) const noexcept {
89 if (channels != channels_ || num_bins != num_bins_) return false;
90 return process_partition(a, b, out, channels, 0, num_bins,
91 magnitude_amount, phase_amount);
92 }
93
96 bool process_partition(const std::complex<SampleType>* const* a,
97 const std::complex<SampleType>* const* b,
98 std::complex<SampleType>* const* out,
99 int channels, int first_bin, int bin_count,
100 SampleType magnitude_amount,
101 SampleType phase_amount) const noexcept {
102 if (!prepared_ || channels != channels_ || first_bin < 0 || bin_count < 0
103 || first_bin > num_bins_ || bin_count > num_bins_ - first_bin
104 || !std::isfinite(magnitude_amount) || !std::isfinite(phase_amount)
105 || a == nullptr || b == nullptr || out == nullptr)
106 return false;
107 for (int ch = 0; ch < channels_; ++ch)
108 if (a[ch] == nullptr || b[ch] == nullptr || out[ch] == nullptr)
109 return false;
110
111 const SampleType magnitude_mix =
112 std::clamp(magnitude_amount, SampleType{0}, SampleType{1});
113 const SampleType phase_mix =
114 std::clamp(phase_amount, SampleType{0}, SampleType{1});
115
116 for (int ch = 0; ch < channels_; ++ch) {
117 for (int bin = first_bin; bin < first_bin + bin_count; ++bin) {
118 const bool self_conjugate = bin == 0 || bin == num_bins_ - 1;
119 const auto input_a = a[ch][bin];
120 const auto input_b = b[ch][bin];
121 DecodedBin da = decode(input_a);
122 DecodedBin db = decode(input_b);
123 if (!da.finite && !db.finite) {
124 out[ch][bin] = {};
125 continue;
126 }
127 if (magnitude_mix == SampleType{0} && phase_mix == SampleType{0}) {
128 const auto selected = da.finite ? input_a : input_b;
129 const auto& selected_decoded = da.finite ? da : db;
130 if (!selected_decoded.saturated
131 && (!self_conjugate || selected.imag() == SampleType{0})) {
132 out[ch][bin] = selected;
133 continue;
134 }
135 }
136 if (magnitude_mix == SampleType{1} && phase_mix == SampleType{1}) {
137 const auto selected = db.finite ? input_b : input_a;
138 const auto& selected_decoded = db.finite ? db : da;
139 if (!selected_decoded.saturated
140 && (!self_conjugate || selected.imag() == SampleType{0})) {
141 out[ch][bin] = selected;
142 continue;
143 }
144 }
145 if (!da.finite) da = db;
146 if (!db.finite) db = da;
147
148 // Zero has no phase. Borrow the non-zero endpoint's phase so a
149 // magnitude fade never rotates through an arbitrary angle.
150 if (da.magnitude == SampleType{0} && db.magnitude > SampleType{0})
151 da.phase = db.phase;
152 if (db.magnitude == SampleType{0} && da.magnitude > SampleType{0})
153 db.phase = da.phase;
154
155 const SampleType magnitude = interpolate_magnitude(
156 da.magnitude, db.magnitude, magnitude_mix, config_.magnitude);
157 if (magnitude == SampleType{0}) {
158 out[ch][bin] = {};
159 continue;
160 }
161 if (self_conjugate) {
162 // DC and Nyquist are their own conjugates and therefore
163 // cannot carry an imaginary phase. Opposite signs have no
164 // continuous real-only path at constant magnitude, so the
165 // phase amount selects A below 0.5 and B at/above 0.5.
166 const SampleType selected_phase =
167 phase_mix < SampleType{0.5} ? da.phase : db.phase;
168 const SampleType sign = std::cos(selected_phase) < SampleType{0}
169 ? SampleType{-1}
170 : SampleType{1};
171 out[ch][bin] = {sign * magnitude, SampleType{0}};
172 continue;
173 }
174 const SampleType phase = interpolate_phase(
175 da.phase, db.phase, phase_mix, config_.phase);
176 out[ch][bin] = std::polar(magnitude, phase);
177 }
178 }
179 return true;
180 }
181
182private:
183 struct DecodedBin {
184 SampleType magnitude = SampleType{0};
185 SampleType phase = SampleType{0};
186 bool finite = false;
187 bool saturated = false;
188 };
189
190 static constexpr SampleType pi() noexcept {
191 return static_cast<SampleType>(3.14159265358979323846264338327950288L);
192 }
193
194 static bool valid(SpectralMagnitudeInterpolation policy) noexcept {
197 }
198
199 static bool valid(SpectralPhaseInterpolation policy) noexcept {
202 }
203
204 static DecodedBin decode(std::complex<SampleType> value) noexcept {
205 if (!std::isfinite(value.real()) || !std::isfinite(value.imag())) return {};
206 const SampleType real = value.real();
207 const SampleType imag = value.imag();
208 const SampleType scale = std::max(std::abs(real), std::abs(imag));
209 if (scale == SampleType{0})
210 return {SampleType{0}, SampleType{0}, true, false};
211
212 const SampleType scaled_magnitude = std::hypot(real / scale, imag / scale);
213 const SampleType maximum = std::numeric_limits<SampleType>::max();
214 const bool saturated = scale > maximum / scaled_magnitude;
215 const SampleType magnitude = saturated
216 ? maximum
217 : scale * scaled_magnitude;
218 return {magnitude, std::atan2(imag, real), true, saturated};
219 }
220
221 static SampleType interpolate_magnitude(
222 SampleType a, SampleType b, SampleType amount,
223 SpectralMagnitudeInterpolation policy) noexcept {
224 if (amount == SampleType{0}) return a;
225 if (amount == SampleType{1}) return b;
227 return std::fma(amount, b - a, a);
228
229 const SampleType result = std::hypot(
230 std::sqrt(SampleType{1} - amount) * a,
231 std::sqrt(amount) * b);
232 return std::isfinite(result)
233 ? result
234 : std::numeric_limits<SampleType>::max();
235 }
236
237 static SampleType shortest_phase(SampleType a, SampleType b,
238 SampleType amount) noexcept {
239 if (amount == SampleType{0}) return a;
240 if (amount == SampleType{1}) return b;
241 const SampleType delta = std::remainder(b - a, SampleType{2} * pi());
242 return std::fma(amount, delta, a);
243 }
244
245 static SampleType interpolate_phase(
246 SampleType a, SampleType b, SampleType amount,
247 SpectralPhaseInterpolation policy) noexcept {
249 return shortest_phase(a, b, amount);
250 if (amount == SampleType{0}) return a;
251 if (amount == SampleType{1}) return b;
252
253 const SampleType real = std::fma(amount, std::cos(b) - std::cos(a),
254 std::cos(a));
255 const SampleType imag = std::fma(amount, std::sin(b) - std::sin(a),
256 std::sin(a));
257 if (std::hypot(real, imag) <= std::numeric_limits<SampleType>::epsilon())
258 return shortest_phase(a, b, amount);
259 return std::atan2(imag, real);
260 }
261
262 Config config_;
263 int channels_ = 0;
264 int num_bins_ = 0;
265 bool prepared_ = false;
266};
267
270
271} // namespace pulp::signal
Definition spectral_morph.hpp:32
static bool supports_configuration(int channels, int num_bins) noexcept
Definition spectral_morph.hpp:46
bool set_config(Config config) noexcept
Definition spectral_morph.hpp:68
Config config() const noexcept
Definition spectral_morph.hpp:74
bool process_partition(const std::complex< SampleType > *const *a, const std::complex< SampleType > *const *b, std::complex< SampleType > *const *out, int channels, int first_bin, int bin_count, SampleType magnitude_amount, SampleType phase_amount) const noexcept
Morph [first_bin, first_bin + bin_count).
Definition spectral_morph.hpp:96
void reset() noexcept
Stateless lifecycle symmetry for frame-processing chains.
Definition spectral_morph.hpp:66
bool process(const std::complex< SampleType > *const *a, const std::complex< SampleType > *const *b, std::complex< SampleType > *const *out, int channels, int num_bins, SampleType magnitude_amount, SampleType phase_amount) const noexcept
Morph an entire coherent frame group.
Definition spectral_morph.hpp:83
int channels() const noexcept
Definition spectral_morph.hpp:76
static constexpr int maximum_bins
Definition spectral_morph.hpp:38
int num_bins() const noexcept
Definition spectral_morph.hpp:77
bool prepare(int channels, int num_bins) noexcept
Fix the coherent frame geometry.
Definition spectral_morph.hpp:56
bool prepared() const noexcept
Definition spectral_morph.hpp:75
static constexpr int maximum_channels
Definition spectral_morph.hpp:36
static constexpr int minimum_bins
Definition spectral_morph.hpp:37
@ b
Definition pitch.hpp:31
Definition additive_bank.hpp:218
SpectralMorphT< double > SpectralMorph64
Definition spectral_morph.hpp:269
SpectralMorphT< float > SpectralMorph
Definition spectral_morph.hpp:268
@ maximum
Definition spectrum_trace.hpp:34
@ a
Definition formant_filter_bank.hpp:20
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
SpectralMagnitudeInterpolation
Definition spectral_morph.hpp:21
@ equal_power
Definition spectral_morph.hpp:23
@ linear
Definition spectral_morph.hpp:22
SpectralPhaseInterpolation
Definition spectral_morph.hpp:26
@ normalized_vector
Definition spectral_morph.hpp:28
@ shortest_arc
Definition spectral_morph.hpp:27
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
Definition spectral_morph.hpp:40
SpectralMagnitudeInterpolation magnitude
Definition spectral_morph.hpp:41
SpectralPhaseInterpolation phase
Definition spectral_morph.hpp:43