Line data Source code
1 : #include "audio/effects/octaver.h"
2 : #include "audio/effects/effect_factory.h"
3 : #include <cmath>
4 :
5 : namespace Amplitron {
6 :
7 2 : static EffectRegistrar<Octaver> reg("Octaver");
8 :
9 : // Param indices
10 : static constexpr int P_OCT_DOWN = 0;
11 : static constexpr int P_OCT_UP = 1;
12 : static constexpr int P_DRY = 2;
13 :
14 : // Hysteresis threshold for the flip-flop zero-crossing detector.
15 : // The divider only flips when prev_sample_ < -FLIP_HYSTERESIS and the current
16 : // sample > +FLIP_HYSTERESIS, preventing chatter caused by near-zero noise.
17 : //
18 : // Value choice: 0.002 ≈ -54 dBFS — comfortably above the noise floor of a
19 : // typical USB audio interface while remaining well below the smallest musically
20 : // significant guitar signal. At this threshold the per-sample slope of the
21 : // slowest guitar fundamental (E2 ≈ 82 Hz at 48 kHz) still crosses the ±H band
22 : // reliably, so the divider tracks pitch without false negatives. Tune upward
23 : // only if the target hardware has an unusually noisy input stage.
24 : static constexpr float FLIP_HYSTERESIS = 0.002f;
25 :
26 52 : Octaver::Octaver() {
27 117 : params_ = {
28 39 : {"Oct -1", 0.5f, 0.0f, 1.0f, 0.5f, "",
29 13 : "Level of the sub-octave (one octave below). Produces a thick, organ-like low tone."},
30 13 : {"Oct +1", 0.0f, 0.0f, 1.0f, 0.0f, "",
31 13 : "Level of the upper octave (one octave above). Adds a bright, shimmery harmonic."},
32 13 : {"Dry", 0.7f, 0.0f, 1.0f, 0.7f, "",
33 13 : "Level of the original dry signal blended with the octave voices."},
34 117 : };
35 52 : set_sample_rate(DEFAULT_SAMPLE_RATE);
36 78 : }
37 :
38 75 : void Octaver::set_sample_rate(int sample_rate) {
39 75 : Effect::set_sample_rate(sample_rate);
40 50 : reset();
41 62 : }
42 :
43 408 : void Octaver::process(float* buffer, int num_samples) {
44 408 : if (!enabled_) return;
45 :
46 : // One-pole smoothing coefficient (~10 ms time constant)
47 405 : const float alpha = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.010f));
48 :
49 : // Envelope follower coefficients
50 405 : const float env_attack = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.002f)); // 2 ms
51 405 : const float env_release = 1.0f - std::exp(-1.0f / (sample_rate_ * 0.020f)); // 20 ms
52 :
53 : // DC blocker coefficient (high-pass at ~20 Hz)
54 405 : const float dc_coeff = 1.0f - (TWO_PI * 20.0f / sample_rate_);
55 :
56 205269 : for (int i = 0; i < num_samples; ++i) {
57 204864 : const float dry = buffer[i];
58 :
59 : // Smooth parameters
60 204864 : oct_down_smooth_ += alpha * (params_[P_OCT_DOWN].value - oct_down_smooth_);
61 204864 : oct_up_smooth_ += alpha * (params_[P_OCT_UP].value - oct_up_smooth_);
62 204864 : dry_smooth_ += alpha * (params_[P_DRY].value - dry_smooth_);
63 :
64 : // --- Envelope follower (tracks input amplitude) ---
65 204864 : const float abs_in = std::fabs(dry);
66 204864 : if (abs_in > envelope_) {
67 79845 : envelope_ += env_attack * (abs_in - envelope_);
68 26615 : } else {
69 125019 : envelope_ += env_release * (abs_in - envelope_);
70 : }
71 :
72 : // --- Oct-1: Flip-flop divider ---
73 : // Detect positive-going zero crossing with hysteresis: require the
74 : // previous sample to have been clearly negative and the current sample
75 : // to be clearly positive before toggling. This suppresses chatter from
76 : // near-zero noise that would otherwise cause false flips.
77 204864 : if (prev_sample_ < -FLIP_HYSTERESIS && dry > FLIP_HYSTERESIS) {
78 1899 : flipflop_ = -flipflop_;
79 633 : }
80 204864 : prev_sample_ = dry;
81 :
82 : // Square wave at half frequency, shaped by envelope
83 204864 : float oct_down = flipflop_ * envelope_;
84 :
85 : // --- Oct+1: Full-wave rectification + DC removal ---
86 204864 : float rectified = std::fabs(dry);
87 :
88 : // DC blocker (1st-order high-pass): removes the DC offset from rectification
89 204864 : float dc_out = rectified - dc_x1_ + dc_coeff * dc_y1_;
90 204864 : dc_x1_ = rectified;
91 204864 : dc_y1_ = dc_out;
92 :
93 204864 : float oct_up = dc_out;
94 :
95 : // --- Mix ---
96 273152 : buffer[i] = dry * dry_smooth_
97 204864 : + oct_down * oct_down_smooth_
98 204864 : + oct_up * oct_up_smooth_;
99 :
100 : // Safety clamp
101 204864 : buffer[i] = clamp(buffer[i], -1.0f, 1.0f);
102 68288 : }
103 136 : }
104 :
105 192 : void Octaver::reset() {
106 192 : prev_sample_ = 0.0f;
107 192 : flipflop_ = 1.0f;
108 192 : dc_x1_ = 0.0f;
109 192 : dc_y1_ = 0.0f;
110 192 : envelope_ = 0.0f;
111 192 : oct_down_smooth_ = params_[P_OCT_DOWN].value;
112 192 : oct_up_smooth_ = params_[P_OCT_UP].value;
113 192 : dry_smooth_ = params_[P_DRY].value;
114 192 : }
115 :
116 : } // namespace Amplitron
|