Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
wt.hpp
Go to the documentation of this file.
1#pragma once
2
47
49
50#include <algorithm>
51#include <cmath>
52#include <cstddef>
53#include <utility>
54#include <vector>
55
56namespace pulp::signal::osc {
57
60public:
64 static constexpr double kDefaultScanTimeMs = 5.0;
65
71 void set_wavetable_set(std::vector<Wavetable64> tables) {
72 ceilings_.clear();
73 if (!tables.empty()) {
74 const Wavetable64& first = tables.front();
75 ceilings_.reserve(first.band_count());
76 for (std::size_t b = 0; b < first.band_count(); ++b)
77 ceilings_.push_back(first.band_max_frequency_hz(b));
78 }
79 table_count_ = tables.size();
80 bank_ = WavetableBank64(std::move(tables));
81 bank_.set_sample_rate(sample_rate_);
82 // The fresh bank starts on its default band with nothing audible to fade
83 // FROM, so the next frequency must SNAP to its band, not crossfade up from
84 // the default โ€” otherwise swapping the wavetable set mid-note replays the
85 // aliased band-switch onset that `reset()`'s snap exists to prevent.
86 pending_band_snap_ = true;
87 update_scan_coeff();
88 }
89
92 void prepare(double sample_rate) {
93 if (sample_rate > 0.0) sample_rate_ = sample_rate;
94 bank_.set_sample_rate(sample_rate_);
95 update_scan_coeff();
96 }
97
109 void reset() {
110 bank_.reset();
111 pending_band_snap_ = true;
112 scan_position_ = scan_target_;
113 bank_.set_position(scan_position_);
114 }
115
119 void set_position(double pos) {
120 scan_target_ = std::clamp(pos, 0.0, 1.0);
121 }
122
123 double position() const { return scan_position_; }
124
128 void set_scan_time_ms(double ms) {
129 scan_time_ms_ = ms > 0.0 ? ms : 0.0;
130 update_scan_coeff();
131 }
132 double scan_time_ms() const { return scan_time_ms_; }
133
138 double next(double increment) {
139 const double frequency = increment * sample_rate_;
140 if (frequency > 0.0) {
141 // The first frequency after construction/reset snaps to its band with
142 // no crossfade: a fresh voice has no previously audible band to fade
143 // FROM, so a crossfade here would play the default band's harmonics at
144 // the new pitch for 128 samples โ€” an aliased onset on every note-on.
145 // Every later frequency crossfades as normal (a fade between two real
146 // playing bands).
147 if (pending_band_snap_) {
148 bank_.set_frequency_immediate(frequency);
149 pending_band_snap_ = false;
150 } else {
151 bank_.set_frequency(frequency);
152 }
153 }
154
155 // One-pole slew of the scan position toward the target. At coeff 1
156 // (scan_time_ms == 0) this is an exact jump; otherwise it is a smooth,
157 // click-free approach.
158 scan_position_ += (scan_target_ - scan_position_) * scan_coeff_;
159 bank_.set_position(scan_position_);
160
161 return bank_.next();
162 }
163
165 std::size_t band_count() const { return ceilings_.size(); }
168 double band_max_frequency_hz(std::size_t i) const {
169 return i < ceilings_.size() ? ceilings_[i] : 0.0;
170 }
171
172 std::size_t table_count() const { return table_count_; }
173
174private:
178 void update_scan_coeff() {
179 if (scan_time_ms_ <= 0.0 || sample_rate_ <= 0.0) {
180 scan_coeff_ = 1.0;
181 return;
182 }
183 const double tau_samples = scan_time_ms_ * 0.001 * sample_rate_;
184 scan_coeff_ = 1.0 - std::exp(-1.0 / tau_samples);
185 }
186
187 WavetableBank64 bank_;
188 std::vector<double> ceilings_;
189 std::size_t table_count_ = 0;
190
191 double sample_rate_ = 48000.0;
192 double scan_target_ = 0.0;
193 double scan_position_ = 0.0;
194 double scan_time_ms_ = kDefaultScanTimeMs;
195 double scan_coeff_ = 1.0;
196 bool pending_band_snap_ = true;
197};
198
199} // namespace pulp::signal::osc
Modern wavetable oscillator over a set of band-limited WavetableTs.
Definition wt.hpp:59
static constexpr double kDefaultScanTimeMs
Default scan slew time (ms): the one-pole time constant that turns a stepped position control into a ...
Definition wt.hpp:64
double scan_time_ms() const
Definition wt.hpp:132
void set_wavetable_set(std::vector< Wavetable64 > tables)
Install the table set (off the audio thread).
Definition wt.hpp:71
void reset()
Reset phase and crossfade state to the start of the cycle, and snap the scan position to its target (...
Definition wt.hpp:109
std::size_t table_count() const
Number of tables the scan morphs across.
Definition wt.hpp:172
double next(double increment)
Generate one sample and advance by increment cycles (frequency รท sample rate).
Definition wt.hpp:138
void set_scan_time_ms(double ms)
Scan slew time, in ms.
Definition wt.hpp:128
void set_position(double pos)
Target scan position across the table set, 0..1 (0 = first table, 1 = last).
Definition wt.hpp:119
std::size_t band_count() const
Number of bands in the table set's readout (from the first table).
Definition wt.hpp:165
void prepare(double sample_rate)
Set the sample rate.
Definition wt.hpp:92
double band_max_frequency_hz(std::size_t i) const
Upper alias-free playback frequency of band i (its ceiling).
Definition wt.hpp:168
double position() const
The audible (slewed) scan position โ€” what the bank is actually morphing at.
Definition wt.hpp:123
Definition blep.hpp:72
@ first
Definition dither.hpp:23
WavetableBankT< double > WavetableBank64
Definition wavetable.hpp:309
@ i
Definition formant_filter_bank.hpp:20
WavetableT< double > Wavetable64
Definition wavetable.hpp:307
Wavetable oscillator with band-switching + bank morphing.