Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
lfo.hpp
Go to the documentation of this file.
1// sbl/dsp/comp/lfo.hpp — Low Frequency Oscillator (Audio Stack — Compositions)
2//
3// Sub-audio modulation source. Composes PhaseAccumulator + WavetableReader
4// with amplitude scaling. Output is normalized bipolar (-depth to +depth).
5//
6// The LFO outputs a domain-neutral signal — it doesn't know what it's
7// modulating. The conversion to domain-specific units (Hz, octaves, etc.)
8// belongs at the modulation application point, not here. This matches the
9// analog modular convention: LFO outputs a voltage, the destination module
10// interprets it via its exponential converter (V/Oct) or linear VCA.
11//
12// Signal-unit inputs (FDP-052):
13// lfo.set_rate_su(0.5f); // [0,1] → 0.05–30 Hz (exponential)
14// lfo.set_depth_su(0.8f); // [0,1] → output amplitude
15//
16// Depth controls output amplitude: 1.0 = full ±1.0 swing. Do NOT encode
17// destination units into depth. Apply domain conversion at the destination:
18//
19// lfo.set_depth_su(1.0f); // ±1.0 normalized signal
20// filter.process(buf, n, lfo_buf, 2.0f); // ±2 octaves around smoothed center
21//
22// See FDP-031 (Signal layer) and FDP-052 for architectural rationale.
23
24#ifndef SBL_DSP_COMP_LFO_HPP_
25#define SBL_DSP_COMP_LFO_HPP_
26
27#include <cstdint>
28
33
34namespace sbl::dsp::comp {
35
36struct LfoTestAccess;
37
38template<uint16_t TableSize>
39class Lfo {
40public:
41 /// @note All public methods are ISR-safe — bounded computation, no I/O.
42
43 // ── Configuration ───────────────────────────────────────────
44
45 /** @brief Set wavetable (must have guard points for interpolation) */
46 void set_wavetable(const float* table) { reader_.set_table(table); }
47
48 /** @brief Set phase increment directly */
49 void set_increment(uint32_t inc) { phase_.set_increment(inc); }
50
51 // ── Signal-unit inputs ──────────────────────────────────────
52
53 /**
54 * @brief Set LFO rate from normalized [0,1] signal unit
55 * @param su Unipolar [0,1] — exponential map to 0.05–30 Hz
56 *
57 * Exponential mapping gives fine resolution at low rates (slow sweeps)
58 * and coarser control at high rates (fast vibrato/tremolo).
59 */
60 void set_rate_su(float su) {
61 if (su < 0.0f) su = 0.0f;
62 if (su > 1.0f) su = 1.0f;
63 float hz = kMinRateHz * math::fast_exp2f(su * kRateOctaves);
64 set_rate(hz);
65 }
66
67 /**
68 * @brief Set output amplitude from normalized [0,1] signal unit
69 * @param su Unipolar [0,1] — linear map to depth 0–1
70 */
71 void set_depth_su(float su) {
72 if (su < 0.0f) su = 0.0f;
73 if (su > 1.0f) su = 1.0f;
74 set_depth(su);
75 }
76
77 // ── Processing ──────────────────────────────────────────────
78
79 /**
80 * @brief Generate modulation signal (bipolar: -depth to +depth)
81 * @param out Output buffer (float)
82 * @param frames Number of samples
83 */
84 void process(float* out, uint16_t frames) {
85 for (uint16_t i = 0; i < frames; ++i) {
86 phase_.advance();
87 out[i] = reader_.read(phase_.phase()) * depth_;
88 }
89 }
90
91 /** @brief Hard sync — reset phase to zero */
92 void sync() { phase_.reset(); }
93
94 // ── Queries ─────────────────────────────────────────────────
95
96 /** @brief Current LFO value (bipolar, float) */
97 float value() const {
98 return reader_.read(phase_.phase()) * depth_;
99 }
100
101 /** @brief Current phase */
102 uint32_t phase() const { return phase_.phase(); }
103
104 /** @brief Current phase increment */
105 uint32_t increment() const { return phase_.increment(); }
106
107private:
108 friend struct LfoTestAccess;
109
110 // ── Rate mapping constants ──────────────────────────────────
111 static constexpr float kMinRateHz = 0.05f;
112 static constexpr float kMaxRateHz = 30.0f;
113 static constexpr float kRateOctaves = 9.228819f; // log2(30/0.05)
114
115 // ── Engineering-unit setters (private — use _su inputs) ─────
116
117 void set_rate(float freq_hz) {
118 phase_.set_increment(
120 }
121
122 void set_depth(float depth) { depth_ = depth; }
123
124 // ── State ───────────────────────────────────────────────────
127 float depth_ = 1.0f;
128};
129
130} // namespace sbl::dsp::comp
131
132#endif // SBL_DSP_COMP_LFO_HPP_
void process(float *out, uint16_t frames)
Generate modulation signal (bipolar: -depth to +depth)
Definition lfo.hpp:84
void sync()
Hard sync — reset phase to zero.
Definition lfo.hpp:92
void set_depth_su(float su)
Set output amplitude from normalized [0,1] signal unit.
Definition lfo.hpp:71
void set_rate_su(float su)
Set LFO rate from normalized [0,1] signal unit.
Definition lfo.hpp:60
void set_increment(uint32_t inc)
Set phase increment directly.
Definition lfo.hpp:49
friend struct LfoTestAccess
Definition lfo.hpp:108
uint32_t phase() const
Current phase.
Definition lfo.hpp:102
void set_wavetable(const float *table)
Set wavetable (must have guard points for interpolation)
Definition lfo.hpp:46
uint32_t increment() const
Current phase increment.
Definition lfo.hpp:105
float value() const
Current LFO value (bipolar, float)
Definition lfo.hpp:97
void set_increment(uint32_t inc)
Set the phase increment per sample.
static uint32_t freq_to_inc(float freq_hz, float sr=types::SAMPLE_RATE_F)
Convert frequency in Hz to phase increment.
uint32_t phase() const
Current phase value.
uint32_t increment() const
Current phase increment.
void advance()
Advance phase by one sample.
void set_table(const float *table)
Set the wavetable to read from (must have Size+1 guard points for linear)
float read(uint32_t phase) const
Read a single sample with linear interpolation.
Fast analytical approximations (Audio Stack — Atoms)
Fixed-point constants and audio sample types.
Compositions: subcircuits of primitives.
float fast_exp2f(float x)
Definition fast_math.hpp:99
float SAMPLE_RATE_F
Definition fixed.hpp:17
Phase accumulator for oscillators and LFOs.
Wavetable reader with interpolation.