Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
wavetable.hpp
Go to the documentation of this file.
1#pragma once
2
21
22#include <algorithm>
23#include <cmath>
24#include <cstddef>
25#include <vector>
26
27namespace pulp::signal {
28
32template <typename SampleType = float>
34 std::vector<SampleType> samples;
35 SampleType max_frequency_hz = SampleType{22050.0f};
36};
37
40
53template <typename SampleType = float>
55public:
59 static constexpr std::size_t kCrossfadeSamples = 128;
60
61 WavetableT() = default;
62
66 explicit WavetableT(std::vector<WavetableEntryT<SampleType>> bands) {
67 bands_.reserve(bands.size());
68 for (auto& b : bands) {
69 if (!b.samples.empty()) bands_.push_back(std::move(b));
70 }
71 std::sort(bands_.begin(), bands_.end(),
74 return a.max_frequency_hz < b.max_frequency_hz;
75 });
76 target_band_ = select_band_for(frequency_);
77 crossfade_source_ = target_band_;
78 }
79
80 void set_sample_rate(SampleType sr) {
81 if (sr > SampleType{0.0f}) sample_rate_ = sr;
82 }
83 void set_frequency(SampleType hz) {
84 if (hz <= SampleType{0.0f}) return;
85 frequency_ = hz;
86 const int new_band = select_band_for(frequency_);
87 if (new_band != target_band_) {
88 // Begin a crossfade. If a previous fade is still in flight,
89 // capture its current source + target + remaining samples
90 // so the new fade can blend FROM the actually-audible mix
91 // rather than jumping back to the previous target. Rapid
92 // retunes must preserve continuity so the crossfade does
93 // not introduce the click it is meant to prevent.
94 if (crossfade_samples_remaining_ > 0) {
95 in_flight_source_ = crossfade_source_;
96 in_flight_target_ = target_band_;
97 in_flight_remaining_ = crossfade_samples_remaining_;
98 has_in_flight_ = true;
99 } else {
100 has_in_flight_ = false;
101 }
102 crossfade_source_ = target_band_;
103 target_band_ = new_band;
104 crossfade_samples_remaining_ = kCrossfadeSamples;
105 }
106 }
107
116 void set_frequency_immediate(SampleType hz) {
117 if (hz <= SampleType{0.0f}) return;
118 frequency_ = hz;
119 target_band_ = select_band_for(frequency_);
120 crossfade_source_ = target_band_;
121 crossfade_samples_remaining_ = 0;
122 has_in_flight_ = false;
123 in_flight_remaining_ = 0;
124 }
125
126 void reset() {
127 phase_ = 0.0f;
128 crossfade_samples_remaining_ = 0;
129 crossfade_source_ = target_band_;
130 has_in_flight_ = false;
131 in_flight_remaining_ = 0;
132 }
133
136 SampleType next() {
137 if (bands_.empty()) return SampleType{0.0f};
138
139 const SampleType new_sample = sample_band(target_band_, phase_);
140 SampleType out = new_sample;
141 if (crossfade_samples_remaining_ > 0) {
142 const SampleType t = SampleType{1.0f} -
143 (static_cast<SampleType>(crossfade_samples_remaining_) /
144 static_cast<SampleType>(kCrossfadeSamples));
145 // Source for the new fade. If a previous fade was still
146 // in flight when this one started, sample the in-flight
147 // blend at its progress ratio so the new fade begins from
148 // the actually-audible mix instead of the in-flight target.
149 SampleType source_sample;
150 if (has_in_flight_ && in_flight_remaining_ > 0) {
151 const SampleType old_t = SampleType{1.0f}
152 - (static_cast<SampleType>(in_flight_remaining_)
153 / static_cast<SampleType>(kCrossfadeSamples));
154 const SampleType old_src = sample_band(in_flight_source_, phase_);
155 const SampleType old_tgt = sample_band(in_flight_target_, phase_);
156 source_sample = old_src * (SampleType{1.0f} - old_t) + old_tgt * old_t;
157 --in_flight_remaining_;
158 if (in_flight_remaining_ == 0) has_in_flight_ = false;
159 } else {
160 source_sample = sample_band(crossfade_source_, phase_);
161 has_in_flight_ = false;
162 }
163 out = source_sample * (SampleType{1.0f} - t) + new_sample * t;
164 --crossfade_samples_remaining_;
165 }
166
167 const SampleType dt = frequency_ / sample_rate_;
168 phase_ += dt;
169 while (phase_ >= SampleType{1.0f}) phase_ -= SampleType{1.0f};
170 while (phase_ < SampleType{0.0f}) phase_ += SampleType{1.0f};
171 return out;
172 }
173
174 std::size_t band_count() const { return bands_.size(); }
175 int current_band() const { return target_band_; }
176 bool is_crossfading() const { return crossfade_samples_remaining_ > 0; }
177
183 SampleType band_max_frequency_hz(std::size_t i) const {
184 return i < bands_.size() ? bands_[i].max_frequency_hz : SampleType{0.0f};
185 }
186
192 static inline WavetableT make_sine(
193 std::size_t table_length = 2048,
194 SampleType reference_sample_rate = SampleType{48000.0f});
195 static inline WavetableT make_saw(
196 std::size_t bands = 10,
197 std::size_t table_length = 2048,
198 SampleType reference_sample_rate = SampleType{48000.0f});
199 static inline WavetableT make_square(
200 std::size_t bands = 10,
201 std::size_t table_length = 2048,
202 SampleType reference_sample_rate = SampleType{48000.0f});
204 std::size_t bands = 10,
205 std::size_t table_length = 2048,
206 SampleType reference_sample_rate = SampleType{48000.0f});
207
208private:
209 int select_band_for(SampleType hz) const {
210 if (bands_.empty()) return 0;
211 for (std::size_t i = 0; i < bands_.size(); ++i) {
212 if (hz <= bands_[i].max_frequency_hz) return static_cast<int>(i);
213 }
214 return static_cast<int>(bands_.size() - 1);
215 }
216 SampleType sample_band(int band, SampleType phase) const {
217 if (band < 0 || static_cast<std::size_t>(band) >= bands_.size())
218 return SampleType{0.0f};
219 const auto& table = bands_[band].samples;
220 if (table.empty()) return SampleType{0.0f};
221 const SampleType n = static_cast<SampleType>(table.size());
222 const SampleType pos = phase * n;
223 const std::size_t i0 = static_cast<std::size_t>(std::floor(pos)) % table.size();
224 const std::size_t i1 = (i0 + 1) % table.size();
225 const SampleType frac = pos - std::floor(pos);
226 return table[i0] * (SampleType{1.0f} - frac) + table[i1] * frac;
227 }
228
229 std::vector<WavetableEntryT<SampleType>> bands_;
230 SampleType sample_rate_ = SampleType{48000.0f};
231 SampleType frequency_ = SampleType{440.0f};
232 SampleType phase_ = SampleType{0.0f};
233 int target_band_ = 0;
234 int crossfade_source_ = 0;
235 std::size_t crossfade_samples_remaining_ = 0;
236 // Captured in-flight fade state so rapid retunes blend FROM the
237 // actually-audible mix.
238 bool has_in_flight_ = false;
239 int in_flight_source_ = 0;
240 int in_flight_target_ = 0;
241 std::size_t in_flight_remaining_ = 0;
242};
243
250template <typename SampleType = float>
252public:
253 WavetableBankT() = default;
254 explicit WavetableBankT(std::vector<WavetableT<SampleType>> waveforms)
255 : tables_(std::move(waveforms)) {}
256
257 void set_sample_rate(SampleType sr) {
258 for (auto& t : tables_) t.set_sample_rate(sr);
259 }
260 void set_frequency(SampleType hz) {
261 for (auto& t : tables_) t.set_frequency(hz);
262 }
263
266 void set_frequency_immediate(SampleType hz) {
267 for (auto& t : tables_) t.set_frequency_immediate(hz);
268 }
269 void set_position(SampleType pos) {
270 position_ = std::clamp(pos, SampleType{0.0f}, SampleType{1.0f});
271 }
272
273 SampleType next() {
274 if (tables_.empty()) return SampleType{0.0f};
275 if (tables_.size() == 1) return tables_.front().next();
276
277 const SampleType scaled =
278 position_ * static_cast<SampleType>(tables_.size() - 1);
279 const std::size_t lo = static_cast<std::size_t>(std::floor(scaled));
280 const std::size_t hi = std::min(lo + 1, tables_.size() - 1);
281 const SampleType frac = scaled - static_cast<SampleType>(lo);
282
283 // Advance every table at the same frequency so morphing does
284 // not introduce phase wobble between adjacent waveforms.
285 SampleType lo_sample = SampleType{0.0f};
286 SampleType hi_sample = SampleType{0.0f};
287 for (std::size_t i = 0; i < tables_.size(); ++i) {
288 const SampleType s = tables_[i].next();
289 if (i == lo) lo_sample = s;
290 if (i == hi) hi_sample = s;
291 }
292 return lo_sample * (SampleType{1.0f} - frac) + hi_sample * frac;
293 }
294
295 void reset() {
296 for (auto& t : tables_) t.reset();
297 }
298
299 std::size_t size() const { return tables_.size(); }
300
301private:
302 std::vector<WavetableT<SampleType>> tables_;
303 SampleType position_ = SampleType{0.0f};
304};
305
310
311// ── Factory implementations ────────────────────────────────────────────────
312
313namespace detail {
314
315template <typename SampleType>
316constexpr SampleType kWavetableTwoPi =
317 static_cast<SampleType>(6.283185307179586476925286766559L);
318
322template <typename SampleType>
323inline std::vector<SampleType> build_wavetable_band_ceilings(
324 std::size_t num_bands, SampleType reference_sample_rate) {
325 if (num_bands == 0 || !(reference_sample_rate > SampleType{0.0f})) return {};
326 const SampleType nyquist = reference_sample_rate * SampleType{0.5f};
327 constexpr SampleType kBaseFreq = SampleType{20.0f};
328 std::vector<SampleType> ceilings;
329 ceilings.reserve(num_bands);
330 const SampleType ratio =
331 std::pow(nyquist / kBaseFreq,
332 SampleType{1.0f} / static_cast<SampleType>(num_bands));
333 SampleType ceiling = kBaseFreq;
334 for (std::size_t b = 0; b < num_bands; ++b) {
335 ceiling *= ratio;
336 ceilings.push_back(std::min(ceiling, nyquist));
337 }
338 return ceilings;
339}
340
341template <typename SampleType, typename HarmonicAmp>
342inline std::vector<SampleType> generate_wavetable(std::size_t length,
343 std::size_t max_harmonic,
344 HarmonicAmp&& harmonic_amp) {
345 std::vector<SampleType> samples(length, SampleType{0.0f});
346 if (length == 0 || max_harmonic == 0) return samples;
347 for (std::size_t i = 0; i < length; ++i) {
348 const SampleType phase =
349 static_cast<SampleType>(i) / static_cast<SampleType>(length);
350 SampleType s = SampleType{0.0f};
351 for (std::size_t k = 1; k <= max_harmonic; ++k) {
352 const SampleType amp = harmonic_amp(k);
353 if (amp == SampleType{0.0f}) continue;
354 s += amp * std::sin(kWavetableTwoPi<SampleType> *
355 static_cast<SampleType>(k) * phase);
356 }
357 samples[i] = s;
358 }
359 SampleType peak = SampleType{0.0f};
360 for (SampleType s : samples) peak = std::max(peak, std::fabs(s));
361 if (peak > SampleType{0.0f}) {
362 const SampleType inv = SampleType{1.0f} / peak;
363 for (SampleType& s : samples) s *= inv;
364 }
365 return samples;
366}
367
368template <typename SampleType, typename HarmonicAmp>
369inline std::vector<WavetableEntryT<SampleType>> build_wavetable_band_stack(
370 std::size_t num_bands,
371 std::size_t table_length,
372 SampleType reference_sample_rate,
373 HarmonicAmp&& harmonic_amp) {
374 if (num_bands == 0) return {};
375 // Non-positive sample rates produce clamped_ceiling <= 0 and then
376 // a NaN/-inf in the std::floor -> size_t cast (UB). Public factories
377 // accept arbitrary inputs, so short-circuit on invalid sample rates here.
378 if (!(reference_sample_rate > SampleType{0.0f})) return {};
379 const SampleType nyquist = reference_sample_rate * SampleType{0.5f};
380 std::vector<WavetableEntryT<SampleType>> bands;
381 bands.reserve(num_bands);
382 const auto ceilings =
383 build_wavetable_band_ceilings(num_bands, reference_sample_rate);
384 for (const SampleType clamped_ceiling : ceilings) {
385 std::size_t max_harmonic = static_cast<std::size_t>(
386 std::floor(nyquist / clamped_ceiling));
387 if (max_harmonic < 1) max_harmonic = 1;
388 bands.push_back(WavetableEntryT<SampleType>{
389 generate_wavetable<SampleType>(table_length, max_harmonic,
390 harmonic_amp),
391 clamped_ceiling,
392 });
393 }
394 return bands;
395}
396
397} // namespace detail
398
399template <typename SampleType>
401 std::size_t table_length,
402 SampleType reference_sample_rate) {
403 // Guard against non-positive sample rates so the returned Wavetable has a
404 // well-defined empty band stack rather than a band with a negative ceiling.
405 if (!(reference_sample_rate > SampleType{0.0f})) return WavetableT();
406 std::vector<WavetableEntryT<SampleType>> bands;
407 bands.push_back(WavetableEntryT<SampleType>{
409 table_length, /*max_harmonic=*/1,
410 [](std::size_t k) {
411 return k == 1 ? SampleType{1.0f} : SampleType{0.0f};
412 }),
413 reference_sample_rate * SampleType{0.5f},
414 });
415 return WavetableT(std::move(bands));
416}
417
418template <typename SampleType>
420 std::size_t bands,
421 std::size_t table_length,
422 SampleType reference_sample_rate) {
424 bands, table_length, reference_sample_rate,
425 [](std::size_t k) {
426 return SampleType{1.0f} / static_cast<SampleType>(k);
427 }));
428}
429
430template <typename SampleType>
432 std::size_t bands,
433 std::size_t table_length,
434 SampleType reference_sample_rate) {
436 bands, table_length, reference_sample_rate,
437 [](std::size_t k) {
438 return (k % 2 == 1)
439 ? SampleType{1.0f} / static_cast<SampleType>(k)
440 : SampleType{0.0f};
441 }));
442}
443
444template <typename SampleType>
446 std::size_t bands,
447 std::size_t table_length,
448 SampleType reference_sample_rate) {
450 bands, table_length, reference_sample_rate,
451 [](std::size_t k) {
452 if (k % 2 == 0) return SampleType{0.0f};
453 const SampleType kk = static_cast<SampleType>(k);
454 const SampleType sign = ((k - 1) / 2) % 2 == 0
455 ? SampleType{1.0f}
456 : SampleType{-1.0f};
457 return sign / (kk * kk);
458 }));
459}
460
461} // namespace pulp::signal
Linear-interpolated morph across N Wavetables.
Definition wavetable.hpp:251
void set_sample_rate(SampleType sr)
Definition wavetable.hpp:257
WavetableBankT(std::vector< WavetableT< SampleType > > waveforms)
Definition wavetable.hpp:254
void set_position(SampleType pos)
Definition wavetable.hpp:269
SampleType next()
Definition wavetable.hpp:273
void set_frequency(SampleType hz)
Definition wavetable.hpp:260
std::size_t size() const
Definition wavetable.hpp:299
void reset()
Definition wavetable.hpp:295
void set_frequency_immediate(SampleType hz)
Snap every table to the band for hz with no crossfade — the first frequency after construction/reset,...
Definition wavetable.hpp:266
Wavetable oscillator with band-switching.
Definition wavetable.hpp:54
std::size_t band_count() const
Definition wavetable.hpp:174
void set_sample_rate(SampleType sr)
Definition wavetable.hpp:80
void set_frequency_immediate(SampleType hz)
Select the band for hz with NO crossfade — snap straight to it.
Definition wavetable.hpp:116
bool is_crossfading() const
Definition wavetable.hpp:176
static WavetableT make_saw(std::size_t bands=10, std::size_t table_length=2048, SampleType reference_sample_rate=SampleType{48000.0f})
Definition wavetable.hpp:419
void set_frequency(SampleType hz)
Definition wavetable.hpp:83
static WavetableT make_triangle(std::size_t bands=10, std::size_t table_length=2048, SampleType reference_sample_rate=SampleType{48000.0f})
Definition wavetable.hpp:445
int current_band() const
Definition wavetable.hpp:175
SampleType next()
Generate the next sample.
Definition wavetable.hpp:136
WavetableT(std::vector< WavetableEntryT< SampleType > > bands)
Construct from a list of bands.
Definition wavetable.hpp:66
static WavetableT make_square(std::size_t bands=10, std::size_t table_length=2048, SampleType reference_sample_rate=SampleType{48000.0f})
Definition wavetable.hpp:431
void reset()
Definition wavetable.hpp:126
SampleType band_max_frequency_hz(std::size_t i) const
Upper alias-free playback frequency of band i — its max_frequency_hz, the ceiling select_band_for com...
Definition wavetable.hpp:183
static WavetableT make_sine(std::size_t table_length=2048, SampleType reference_sample_rate=SampleType{48000.0f})
Compose a stack of harmonic-based bands for one of the four classical shapes (sine,...
Definition wavetable.hpp:400
static constexpr std::size_t kCrossfadeSamples
Definition wavetable.hpp:59
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
std::vector< SampleType > generate_wavetable(std::size_t length, std::size_t max_harmonic, HarmonicAmp &&harmonic_amp)
Definition wavetable.hpp:342
std::vector< SampleType > build_wavetable_band_ceilings(std::size_t num_bands, SampleType reference_sample_rate)
Shared ceiling plan for synthesized and authored wavetable stacks.
Definition wavetable.hpp:323
constexpr SampleType kWavetableTwoPi
Definition wavetable.hpp:316
std::vector< WavetableEntryT< SampleType > > build_wavetable_band_stack(std::size_t num_bands, std::size_t table_length, SampleType reference_sample_rate, HarmonicAmp &&harmonic_amp)
Definition wavetable.hpp:369
Definition additive_bank.hpp:218
@ peak
Sample peak. Literal, fastest-responding.
Definition feedforward_compressor.hpp:119
WavetableBankT< float > WavetableBank
Definition wavetable.hpp:308
WavetableBankT< double > WavetableBank64
Definition wavetable.hpp:309
@ ratio
base_frequency_hz * ratio.
Definition fm_operator_engine.hpp:20
WavetableEntryT< float > WavetableEntry
Definition wavetable.hpp:38
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
WavetableEntryT< double > WavetableEntry64
Definition wavetable.hpp:39
WavetableT< double > Wavetable64
Definition wavetable.hpp:307
WavetableT< float > Wavetable
Definition wavetable.hpp:306
@ n
Definition input_events.hpp:40
STL namespace.
One single-cycle wavetable plus the maximum playback frequency it remains bandlimited at.
Definition wavetable.hpp:33
std::vector< float > samples
Definition wavetable.hpp:34
float max_frequency_hz
Definition wavetable.hpp:35