Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
scanning_osc.hpp
Go to the documentation of this file.
1// sbl/widgets/source/scanning_osc.hpp — Scanning wavetable oscillator (Audio Stack — Widgets)
2//
3// Crossfades between multiple registered wavetables via a scan position.
4// Composes PhaseAccumulator + lut::crossfade into a morphing oscillator.
5//
6// Usage:
7// sbl::widgets::source::ScanningOsc<256, 4> osc;
8// osc.add_table(sbl::dsp::lut::saw_256);
9// osc.add_table(sbl::dsp::lut::square_256);
10// osc.add_table(sbl::dsp::lut::triangle_256);
11// osc.add_table(sbl::dsp::lut::sin_256);
12// osc.set_scan(0.0f); // Pure saw
13// osc.set_scan(1.0f); // Pure sin
14// osc.set_scan(0.5f); // Crossfade midpoint
15// osc.set_frequency(440.0f);
16// osc.process(buf, frames);
17
18#pragma once
19
20#include <cstdint>
21
22#include <sbl/dsp/phase.hpp>
23#include <sbl/dsp/lut.hpp>
24#include <sbl/dsp/pitch.hpp>
25
26namespace sbl::widgets::source {
27
28template<uint16_t TableSize, uint8_t MaxTables = 8>
30public:
31 /// @note All public methods are ISR-safe — bounded computation, no I/O.
32
33 /**
34 * @brief Register a wavetable in scan order
35 * @param table Pointer to float table data (must have guard points)
36 * @return true if added, false if at capacity
37 */
38 bool add_table(const float* table) {
39 if (count_ >= MaxTables) return false;
40 tables_[count_++] = table;
41 return true;
42 }
43
44 /**
45 * @brief Set scan position
46 * @param position 0.0 = first table, 1.0 = last table
47 */
48 void set_scan(float position) { scan_pos_ = position; }
49
50 /**
51 * @brief Set frequency in Hz
52 * @param freq_hz Frequency in Hz (e.g., 440.0f)
53 * @param sr Sample rate (default 48000)
54 */
55 void set_frequency(float freq_hz, float sr = 48000.0f) {
57 }
58
59 /**
60 * @brief Set frequency from MIDI note number (requires FPU)
61 * @param midi_note MIDI note (69 = A4 = 440 Hz)
62 * @param sample_rate Sample rate
63 */
64 void set_note(float midi_note, float sample_rate = 48000.0f) {
65 phase_.set_increment(dsp::note_to_phase_increment(midi_note, sample_rate));
66 }
67
68 /** @brief Set phase increment directly */
69 void set_increment(uint32_t inc) { phase_.set_increment(inc); }
70
71 /** @brief Set output amplitude (0.0 = silence, 1.0 = full scale) */
72 void set_amplitude(float amp) { amplitude_ = amp; }
73
74 /**
75 * @brief Generate audio samples
76 * @param out Output buffer (float, [-1.0, 1.0])
77 * @param frames Number of samples
78 */
79 void process(float* out, uint16_t frames) {
80 if (count_ == 0) {
81 for (uint16_t i = 0; i < frames; ++i) out[i] = 0.0f;
82 return;
83 }
84
85 // Precompute scan table pair and balance
86 uint8_t idx_a;
87 uint8_t idx_b;
88 float balance;
89 compute_scan(idx_a, idx_b, balance);
90
91 const float* ta = tables_[idx_a];
92 const float* tb = tables_[idx_b];
93
94 for (uint16_t i = 0; i < frames; ++i) {
95 phase_.advance();
96 float s;
97 if (idx_a == idx_b) {
99 } else {
100 s = dsp::lut::crossfade<TableSize>(ta, tb, phase_.phase(), balance);
101 }
102 out[i] = s * amplitude_;
103 }
104 }
105
106 /** @brief Hard sync — reset phase to zero */
107 void sync() { phase_.reset(); }
108
109 /** @brief Current phase */
110 uint32_t phase() const { return phase_.phase(); }
111
112 /** @brief Current phase increment */
113 uint32_t increment() const { return phase_.increment(); }
114
115 /** @brief Number of registered tables */
116 uint8_t table_count() const { return count_; }
117
118private:
119 void compute_scan(uint8_t& idx_a, uint8_t& idx_b, float& balance) const {
120 if (count_ == 1) {
121 idx_a = 0;
122 idx_b = 0;
123 balance = 0.0f;
124 return;
125 }
126 // Scale scan_pos [0.0, 1.0] across (count_-1) intervals
127 float pos = scan_pos_;
128 if (pos < 0.0f) pos = 0.0f;
129 if (pos > 1.0f) pos = 1.0f;
130 float scaled = pos * static_cast<float>(count_ - 1);
131 idx_a = static_cast<uint8_t>(scaled);
132 if (idx_a >= count_ - 1) {
133 idx_a = count_ - 2;
134 idx_b = count_ - 1;
135 balance = 1.0f;
136 } else {
137 idx_b = idx_a + 1;
138 balance = scaled - static_cast<float>(idx_a);
139 }
140 }
141
142 dsp::PhaseAccumulator phase_;
143 const float* tables_[MaxTables] = {};
144 float amplitude_ = 1.0f;
145 float scan_pos_ = 0.0f;
146 uint8_t count_ = 0;
147};
148
149} // namespace sbl::widgets::source
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
uint32_t phase() const
Current phase.
bool add_table(const float *table)
Register a wavetable in scan order.
void set_frequency(float freq_hz, float sr=48000.0f)
Set frequency in Hz.
void set_amplitude(float amp)
Set output amplitude (0.0 = silence, 1.0 = full scale)
void sync()
Hard sync — reset phase to zero.
uint8_t table_count() const
Number of registered tables.
void set_increment(uint32_t inc)
Set phase increment directly.
void set_scan(float position)
Set scan position.
uint32_t increment() const
Current phase increment.
void set_note(float midi_note, float sample_rate=48000.0f)
Set frequency from MIDI note number (requires FPU)
void process(float *out, uint16_t frames)
Generate audio samples.
uint16_t lookup_linear(const uint16_t *table, uint32_t phase)
Definition lut.hpp:39
uint32_t note_to_phase_increment(float midi_note, float sample_rate)
Definition pitch.hpp:58
Sound source widgets.
Definition noise.hpp:15