41template <
typename SampleType =
float>
45 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
55 update_internal_rate();
63 tail_samples_remaining_ = 0;
64 nonlinear_activity_this_sample_ =
false;
65 ahd_triggers_.fill(
false);
66 ahd_trigger_cursor_ = 0;
67 ahd_phase_ = AhdPhase::inactive;
68 ahd_gain_ = ahd_enabled_ ? 0.0 : 1.0;
79 if (factor == oversampling_)
return;
80 oversampling_ = factor;
81 update_internal_rate();
103 return halfband_latency_samples() +
104 halfband_latency_samples() / 2;
119 return tail_samples_remaining_ > 0 || lofi_.has_tail();
125 void set_drive(
double amount) { drive_ = std::clamp(amount, 0.0, 1.0); }
128 void set_fold(
double amount) { fold_ = std::clamp(amount, 0.0, 1.0); }
131 void set_level(
double level) { level_ = std::max(level, 0.0); }
136 void set_ahd_ms(
double attack_ms,
double hold_ms,
double decay_ms) {
137 ahd_attack_ms_ = std::max(attack_ms, 0.0);
138 ahd_hold_ms_ = std::max(hold_ms, 0.0);
139 ahd_decay_ms_ = std::max(decay_ms, 0.01);
145 ahd_enabled_ = enabled;
147 ahd_triggers_.fill(
false);
148 ahd_phase_ = AhdPhase::inactive;
154 double ahd_gain() const noexcept {
return ahd_gain_; }
160 if (oversampling_ != other.oversampling_) {
161 oversampling_ = other.oversampling_;
162 update_internal_rate();
165 drive_ = other.drive_;
167 level_ = other.level_;
168 lofi_.sync_configuration_from(other.lofi_);
169 if (ahd_attack_ms_ != other.ahd_attack_ms_ ||
170 ahd_hold_ms_ != other.ahd_hold_ms_ ||
171 ahd_decay_ms_ != other.ahd_decay_ms_) {
172 ahd_attack_ms_ = other.ahd_attack_ms_;
173 ahd_hold_ms_ = other.ahd_hold_ms_;
174 ahd_decay_ms_ = other.ahd_decay_ms_;
177 if (ahd_enabled_ != other.ahd_enabled_)
184 ahd_triggers_.fill(
false);
185 ahd_phase_ = AhdPhase::inactive;
191 const int size =
static_cast<int>(ahd_triggers_.size());
192 const int arrival = (ahd_trigger_cursor_ +
delay) % size;
193 ahd_triggers_[
static_cast<std::size_t
>(arrival)] =
true;
205 nonlinear_activity_this_sample_ =
false;
206 SampleType processed = input;
207 switch (oversampling_) {
209 processed = process_nonlinear(input);
214 stage_a_.upsample(input, even, odd);
216 stage_a_.downsample(process_nonlinear(even), process_nonlinear(odd));
222 stage_a_.upsample(input, even, odd);
223 processed = stage_a_.downsample(process_inner_x2(even),
224 process_inner_x2(odd));
230 tail_samples_remaining_ =
232 }
else if (tail_samples_remaining_ > 0) {
233 --tail_samples_remaining_;
235 const double envelope = process_ahd();
236 return static_cast<SampleType
>(
237 static_cast<double>(processed) * level_ * envelope);
241 static constexpr int halfband_latency_samples() noexcept {
245 int factor_value() const noexcept {
246 return static_cast<int>(oversampling_);
249 void update_internal_rate() {
250 lofi_.set_sample_rate(sample_rate_ *
static_cast<double>(factor_value()));
254 SampleType process_inner_x2(SampleType input) {
257 stage_b_.upsample(input, even, odd);
258 return stage_b_.downsample(process_nonlinear(even), process_nonlinear(odd));
261 SampleType process_nonlinear(SampleType input) {
262 double x =
static_cast<double>(input);
264 const double k = 1.0 + fold_ * 4.0;
265 x = std::sin(0.5 * 3.14159265358979323846 * k * x);
268 x = std::tanh((1.0 + drive_ * 9.0) * x);
270 x =
static_cast<double>(lofi_.process(
static_cast<SampleType
>(x)));
271 nonlinear_activity_this_sample_ |= std::abs(x) > 1
e-20;
272 return static_cast<SampleType
>(
x);
275 enum class AhdPhase {
282 void update_ahd_times() {
283 ahd_attack_samples_ =
284 static_cast<int>(0.001 * ahd_attack_ms_ * sample_rate_);
286 static_cast<int>(0.001 * ahd_hold_ms_ * sample_rate_);
288 std::max(1,
static_cast<int>(0.001 * ahd_decay_ms_ * sample_rate_));
290 ahd_attack_samples_ > 0 ? 1.0 / ahd_attack_samples_ : 1.0;
291 ahd_decay_multiplier_ =
292 std::pow(1
e-6, 1.0 /
static_cast<double>(ahd_decay_samples_));
295 void begin_ahd_decay() {
296 ahd_phase_ = AhdPhase::decay;
297 ahd_remaining_ = ahd_decay_samples_;
302 if (ahd_attack_samples_ > 0) {
303 ahd_phase_ = AhdPhase::attack;
304 ahd_remaining_ = ahd_attack_samples_;
306 }
else if (ahd_hold_samples_ > 0) {
307 ahd_phase_ = AhdPhase::hold;
308 ahd_remaining_ = ahd_hold_samples_;
315 double process_ahd() {
316 if (!ahd_enabled_)
return 1.0;
317 if (ahd_triggers_[
static_cast<std::size_t
>(ahd_trigger_cursor_)]) {
318 ahd_triggers_[
static_cast<std::size_t
>(ahd_trigger_cursor_)] =
322 const double current =
323 ahd_phase_ == AhdPhase::inactive ? 0.0 : ahd_gain_;
324 switch (ahd_phase_) {
325 case AhdPhase::inactive:
327 case AhdPhase::attack:
328 ahd_gain_ = std::min(ahd_gain_ + ahd_attack_step_, 1.0);
329 if (--ahd_remaining_ <= 0) {
331 if (ahd_hold_samples_ > 0) {
332 ahd_phase_ = AhdPhase::hold;
333 ahd_remaining_ = ahd_hold_samples_;
340 if (--ahd_remaining_ <= 0) begin_ahd_decay();
342 case AhdPhase::decay:
343 ahd_gain_ *= ahd_decay_multiplier_;
344 if (--ahd_remaining_ <= 0) {
346 ahd_phase_ = AhdPhase::inactive;
350 ahd_trigger_cursor_ =
351 (ahd_trigger_cursor_ + 1) %
352 static_cast<int>(ahd_triggers_.size());
356 LofiChainT<SampleType> lofi_;
357 detail::LinearPhaseOversamplingStage2x<SampleType> stage_a_;
358 detail::LinearPhaseOversamplingStage2x<SampleType> stage_b_;
359 double sample_rate_ = 44100.0;
363 double ahd_attack_ms_ = 0.0;
364 double ahd_hold_ms_ = 0.0;
365 double ahd_decay_ms_ = 1000.0;
366 double ahd_gain_ = 1.0;
367 double ahd_attack_step_ = 1.0;
368 double ahd_decay_multiplier_ = 1.0;
369 int ahd_attack_samples_ = 0;
370 int ahd_hold_samples_ = 0;
371 int ahd_decay_samples_ = 1;
372 int ahd_remaining_ = 0;
373 bool ahd_enabled_ =
false;
374 static constexpr int kMaxLatency =
376 std::array<bool, kMaxLatency + 1> ahd_triggers_{};
377 int ahd_trigger_cursor_ = 0;
378 AhdPhase ahd_phase_ = AhdPhase::inactive;
380 std::size_t tail_samples_remaining_ = 0;
381 bool nonlinear_activity_this_sample_ =
false;
The three lo-fi stages in the order the hardware they model applies them: quantise the word,...
Definition lofi_chain.hpp:251
The output stage every percussion voice ends with: fold, saturate, degrade, set the level,...
Definition output_stage.hpp:42
void set_drive(double amount)
Saturation amount, 0 (clean) to 1.
Definition output_stage.hpp:125
bool ahd_enabled() const noexcept
Definition output_stage.hpp:153
void trigger()
Start the AHD for a new hit. Every drum voice calls this from note-on.
Definition output_stage.hpp:182
void set_level(double level)
Output gain, linear.
Definition output_stage.hpp:131
void reset()
Definition output_stage.hpp:59
void prepare(double sample_rate)
Definition output_stage.hpp:44
LofiChainT< SampleType > & lofi()
The degradation stages, exposed so a voice can configure bit depth, hold rate, and dead zone without ...
Definition output_stage.hpp:201
void set_fold(double amount)
Wavefolding amount, 0 to 1.
Definition output_stage.hpp:128
void sync_configuration_from(const OutputStageT &other)
Mirror another stage's controls without copying its FIR, lo-fi, tail, or envelope history.
Definition output_stage.hpp:159
bool has_tail() const noexcept
True while the linear-phase pair still owns delayed output after the source layers have finished.
Definition output_stage.hpp:118
int latency_samples() const noexcept
Constant group delay at the host rate.
Definition output_stage.hpp:111
static constexpr OutputOversampling kDefaultOversampling
Definition output_stage.hpp:95
void set_oversampling(OutputOversampling factor)
Changes the output quality outside the audio callback.
Definition output_stage.hpp:78
OutputOversampling oversampling() const noexcept
Definition output_stage.hpp:85
double ahd_gain() const noexcept
Definition output_stage.hpp:154
void set_ahd_enabled(bool enabled)
Definition output_stage.hpp:144
static constexpr int latency_samples_for(OutputOversampling factor) noexcept
Definition output_stage.hpp:97
SampleType process(SampleType input)
Definition output_stage.hpp:204
void reset_nonlinear_state()
Restarts hit-scoped degradation without discarding samples already travelling through the linear-phas...
Definition output_stage.hpp:73
void set_ahd_ms(double attack_ms, double hold_ms, double decay_ms)
Configure and enable the dedicated post-saturation attack/hold/decay VCA.
Definition output_stage.hpp:136
const LofiChainT< SampleType > & lofi() const
Definition output_stage.hpp:202
OutputOversampling
Realtime quality choices for the shared drum output nonlinearity.
Definition output_stage.hpp:20
@ x2
Definition output_stage.hpp:22
@ x4
Definition output_stage.hpp:23
@ bypass
Definition output_stage.hpp:21
OutputStageT< double > OutputStage64
Definition output_stage.hpp:385
OutputStageT< float > OutputStage
Definition output_stage.hpp:384
constexpr double kHouseHalfBandStopbandDb
Definition oversampling_fir.hpp:22
@ delay
Definition envelope.hpp:57
constexpr std::size_t kHouseHalfBandTaps
Pulp's low-latency linear-phase half-band design.
Definition oversampling_fir.hpp:21
constexpr double kHouseHalfBandStageTwoPassband
Definition oversampling_fir.hpp:24
@ e
Definition formant_filter_bank.hpp:20
constexpr double kHouseHalfBandStageOnePassband
Definition oversampling_fir.hpp:23
@ k
Definition input_events.hpp:40
@ x
Definition input_events.hpp:41
Linear-phase 2x FIR stage used by the realtime oversampler.