Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
frame.hpp
Go to the documentation of this file.
1// sbl/signal/frame.hpp — Stereo buffer management (Signal layer)
2//
3// Manages the stereo float buffer pair that every audio callback needs.
4// Eliminates manual buffer declaration, zeroing, gain staging, and DMA
5// interleave boilerplate.
6//
7// Frame is the workhorse of the Signal layer — the connective tissue
8// between widgets in a processing chain. Every method is a trivial loop
9// that the compiler will vectorize. Zero abstraction cost.
10//
11// Part of the Audio Stack: Atoms → Signal → Widgets
12// See FDP-031 for architecture rationale.
13//
14// Usage:
15// sbl::signal::Frame<48> frame;
16//
17// void audio_callback(int32_t* tx, const int32_t* rx, uint16_t frames) {
18// uint16_t n = frame.begin(frames);
19//
20// osc.process(frame.left, n); // source → left channel
21// filter.process(frame.left, n); // in-place filter
22// env.process(env_buf, n);
23// frame.apply(env_buf, n); // VCA: L *= env, R *= env
24// frame.mix_mono(bass_buf, 0.5f, n); // mix bass at -6 dB
25// delay.process(frame.left, frame.right, n);
26//
27// frame.end(tx, n); // float → int32 stereo DMA
28// }
29
30#pragma once
31
32#include <cstdint>
33
34#include <sbl/dsp/convert.hpp>
35
36namespace sbl::signal {
37
38template<uint16_t MaxFrames = 48>
39struct Frame {
40 float left[MaxFrames];
41 float right[MaxFrames];
42
43 /// Begin a processing block: clamp frame count and zero buffers.
44 ///
45 /// Call this at the top of every audio callback. Returns the actual
46 /// frame count to use (clamped to MaxFrames).
47 ///
48 /// @param requested Frame count from DMA callback
49 /// @return Actual frame count (min of requested and MaxFrames)
50 uint16_t begin(uint16_t requested) {
51 uint16_t n = (requested > MaxFrames) ? MaxFrames : requested;
52 for (uint16_t i = 0; i < n; ++i) {
53 left[i] = 0.0f;
54 right[i] = 0.0f;
55 }
56 return n;
57 }
58
59 /// Finalize block: float → int32 stereo interleave to DMA tx buffer.
60 ///
61 /// This is the DMA boundary — NaN guard, clamp to [-1.0, 1.0], and
62 /// 24-bit scaling all happen inside interleave_from_float().
63 ///
64 /// @param tx DMA transmit buffer (interleaved stereo int32)
65 /// @param n Frame count
66 void end(int32_t* tx, uint16_t n) const {
68 }
69
70 /// Scale both channels by a constant gain.
71 ///
72 /// Use for headroom management (e.g., 1/sqrt(N) for N summed voices).
73 ///
74 /// @param gain Gain factor
75 /// @param n Frame count
76 void scale(float gain, uint16_t n) {
77 for (uint16_t i = 0; i < n; ++i) {
78 left[i] *= gain;
79 right[i] *= gain;
80 }
81 }
82
83 /// Apply per-sample modulation to both channels (stereo VCA).
84 ///
85 /// Multiplies both L and R by a control signal — typically an
86 /// envelope output [0.0, 1.0].
87 ///
88 /// @param mod Control signal buffer (e.g., envelope output)
89 /// @param n Frame count
90 void apply(const float* mod, uint16_t n) {
91 for (uint16_t i = 0; i < n; ++i) {
92 left[i] *= mod[i];
93 right[i] *= mod[i];
94 }
95 }
96
97 /// Mix a mono source into both channels at a given gain.
98 ///
99 /// Adds src * gain to both L and R. Use for mixing a mono sub-voice
100 /// into a stereo frame (e.g., bass at -6 dB = gain 0.5).
101 ///
102 /// @param src Mono source buffer
103 /// @param gain Mix gain (e.g., 0.5 for -6 dB)
104 /// @param n Frame count
105 void mix_mono(const float* src, float gain, uint16_t n) {
106 for (uint16_t i = 0; i < n; ++i) {
107 float s = src[i] * gain;
108 left[i] += s;
109 right[i] += s;
110 }
111 }
112
113 /// Mix another stereo frame into this one at a given gain.
114 ///
115 /// @param other Source frame
116 /// @param gain Mix gain
117 /// @param n Frame count
118 void mix(const Frame& other, float gain, uint16_t n) {
119 for (uint16_t i = 0; i < n; ++i) {
120 left[i] += other.left[i] * gain;
121 right[i] += other.right[i] * gain;
122 }
123 }
124
125 /// Mix separate stereo buffers into this frame at a given gain.
126 ///
127 /// @param src_l Left source buffer
128 /// @param src_r Right source buffer
129 /// @param gain Mix gain
130 /// @param n Frame count
131 void mix_stereo(const float* src_l, const float* src_r, float gain,
132 uint16_t n) {
133 for (uint16_t i = 0; i < n; ++i) {
134 left[i] += src_l[i] * gain;
135 right[i] += src_r[i] * gain;
136 }
137 }
138
139 /// Snapshot current buffer state into separate L/R buffers.
140 ///
141 /// Use for explicit feedback paths: tap the signal before effects,
142 /// then mix the tapped (and possibly processed) signal back in on
143 /// the next block.
144 ///
145 /// @param dst_l Destination left buffer
146 /// @param dst_r Destination right buffer
147 /// @param n Frame count
148 void tap(float* dst_l, float* dst_r, uint16_t n) const {
149 for (uint16_t i = 0; i < n; ++i) {
150 dst_l[i] = left[i];
151 dst_r[i] = right[i];
152 }
153 }
154
155 /// Zero both channels.
156 ///
157 /// @param n Frame count
158 void clear(uint16_t n) {
159 for (uint16_t i = 0; i < n; ++i) {
160 left[i] = 0.0f;
161 right[i] = 0.0f;
162 }
163 }
164};
165
166} // namespace sbl::signal
void interleave_from_float(const float *left, const float *right, int32_t *interleaved, uint16_t frames)
Definition convert.hpp:86
void mix_stereo(const float *src_l, const float *src_r, float gain, uint16_t n)
Definition frame.hpp:131
uint16_t begin(uint16_t requested)
Definition frame.hpp:50
void mix_mono(const float *src, float gain, uint16_t n)
Definition frame.hpp:105
float left[MaxFrames]
Definition frame.hpp:40
void apply(const float *mod, uint16_t n)
Definition frame.hpp:90
void clear(uint16_t n)
Definition frame.hpp:158
void end(int32_t *tx, uint16_t n) const
Definition frame.hpp:66
void mix(const Frame &other, float gain, uint16_t n)
Definition frame.hpp:118
void tap(float *dst_l, float *dst_r, uint16_t n) const
Definition frame.hpp:148
float right[MaxFrames]
Definition frame.hpp:41
void scale(float gain, uint16_t n)
Definition frame.hpp:76