Sound Byte Libs 29c5ff3
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/widgets/mod/lfo.hpp — Low Frequency Oscillator (Audio Stack — Widgets)
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// set_depth() controls output amplitude: 1.0 (default) = full ±1.0 swing.
13// Do NOT encode destination units into depth (e.g., set_depth(200.0f) for
14// "±200 Hz" — this breaks composability). Instead, use depth for amplitude
15// scaling and apply domain conversion at the destination:
16//
17// // Correct: normalized LFO + exponential modulation at destination
18// lfo.set_depth(1.0f); // ±1.0 normalized signal (default)
19// filter.process_modulated(buf, n, lfo_buf, cutoff, 2.0f); // ±2 octaves
20//
21// // Also correct: attenuated LFO for subtle modulation
22// lfo.set_depth(0.3f); // ±0.3 normalized signal
23// filter.process_modulated(buf, n, lfo_buf, cutoff, 4.0f); // ±1.2 octaves
24//
25// See FDP-031 (Signal layer) and RPT-014 for architectural rationale.
26//
27// Usage:
28// sbl::widgets::mod::Lfo<1024> lfo;
29// lfo.set_wavetable(sbl::dsp::lut::sin_1024);
30// lfo.set_rate(2.0f); // 2 Hz
31// lfo.set_depth(0.5f); // ±0.5 output amplitude
32// lfo.process(buf, frames);
33
34#pragma once
35
36#include <cstdint>
37
38#include <sbl/dsp/phase.hpp>
39#include <sbl/dsp/wavetable.hpp>
40
41namespace sbl::widgets::mod {
42
43template<uint16_t TableSize>
44class Lfo {
45public:
46 /// @note All public methods are ISR-safe — bounded computation, no I/O.
47
48 /** @brief Set wavetable (must have guard points for interpolation) */
49 void set_wavetable(const float* table) { reader_.set_table(table); }
50
51 /**
52 * @brief Set LFO rate in Hz
53 * @param freq_hz Rate in Hz (e.g., 2.0f)
54 * @param sr Sample rate (default 48000)
55 */
56 void set_rate(float freq_hz, float sr = 48000.0f) {
58 }
59
60 /** @brief Set phase increment directly */
61 void set_increment(uint32_t inc) { phase_.set_increment(inc); }
62
63 /**
64 * @brief Set output amplitude (depth)
65 *
66 * Controls the bipolar output range: output = wavetable * depth.
67 * Default is 1.0 — full ±1.0 normalized swing. Use values in [0, 1]
68 * for amplitude scaling. The caller applies domain conversion
69 * (exponential for frequency, linear for amplitude) at the
70 * modulation destination.
71 *
72 * @param depth Output amplitude (1.0 = full ±1.0 swing)
73 */
74 void set_depth(float depth) { depth_ = depth; }
75
76 /**
77 * @brief Generate modulation signal (bipolar: -depth to +depth)
78 * @param out Output buffer (float)
79 * @param frames Number of samples
80 */
81 void process(float* out, uint16_t frames) {
82 for (uint16_t i = 0; i < frames; ++i) {
83 phase_.advance();
84 out[i] = reader_.read(phase_.phase()) * depth_;
85 }
86 }
87
88 /** @brief Hard sync — reset phase to zero */
89 void sync() { phase_.reset(); }
90
91 /** @brief Current LFO value (bipolar, float) */
92 float value() const {
93 return reader_.read(phase_.phase()) * depth_;
94 }
95
96 /** @brief Current phase */
97 uint32_t phase() const { return phase_.phase(); }
98
99 /** @brief Current phase increment */
100 uint32_t increment() const { return phase_.increment(); }
101
102private:
105 float depth_ = 1.0f;
106};
107
108} // namespace sbl::widgets::mod
void reset()
Reset phase to zero.
Definition phase.hpp:58
void set_increment(uint32_t inc)
Set the phase increment per sample.
Definition phase.hpp:33
uint32_t increment() const
Current phase increment.
Definition phase.hpp:55
void advance()
Advance phase by one sample.
Definition phase.hpp:36
static uint32_t freq_to_inc(float freq_hz, float sr=48000.0f)
Convert frequency in Hz to phase increment.
Definition phase.hpp:70
uint32_t phase() const
Current phase value.
Definition phase.hpp:52
float read(uint32_t phase) const
Read a single sample with linear interpolation.
Definition wavetable.hpp:39
void set_table(const float *table)
Set the wavetable to read from (must have Size+1 guard points for linear)
Definition wavetable.hpp:29
uint32_t increment() const
Current phase increment.
Definition lfo.hpp:100
void set_wavetable(const float *table)
Set wavetable (must have guard points for interpolation)
Definition lfo.hpp:49
void set_depth(float depth)
Set output amplitude (depth)
Definition lfo.hpp:74
void process(float *out, uint16_t frames)
Generate modulation signal (bipolar: -depth to +depth)
Definition lfo.hpp:81
float value() const
Current LFO value (bipolar, float)
Definition lfo.hpp:92
void set_rate(float freq_hz, float sr=48000.0f)
Set LFO rate in Hz.
Definition lfo.hpp:56
void set_increment(uint32_t inc)
Set phase increment directly.
Definition lfo.hpp:61
void sync()
Hard sync — reset phase to zero.
Definition lfo.hpp:89
uint32_t phase() const
Current phase.
Definition lfo.hpp:97
Modulation source widgets.
Definition envelope.hpp:19