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
mixer.hpp
Go to the documentation of this file.
1// sbl/dsp/modules/mixer.hpp — Mixing console module (Audio Stack — Modules)
2//
3// Ableton session view-inspired bus topology: Audio channels, Return channels,
4// and a master bus. Composes Channel widgets with summing and master output.
5//
6// Three channel types:
7// Audio — Sound sources (voices, inputs). Have sends to returns.
8// Return — FX processors (delay, reverb). Can also send to other returns.
9// Group — Submix bus (future — not implemented).
10//
11// Solo-in-place: soloing an audio channel still lets its FX tails through.
12// Audio and return solo scopes are independent (Ableton behavior).
13//
14// Return channels must be ordered so upstream returns (delay) precede
15// downstream returns (reverb) for return-to-return sends to work correctly.
16// This is a documented convention, not enforced.
17//
18// Effect callbacks:
19// Each return slot holds an optional FxProcess callback. The mixer calls it
20// after accumulating sends into the return's input buffers (buf_l/buf_r).
21// The callback processes audio in-place (e.g., delay, reverb). After the
22// callback, the return channel's process() applies gain + pan for master.
23// Returns without a callback pass the dry accumulated signal through.
24//
25// Single process(n) call replaces the old clear/route_sends/sum_to_master/
26// apply_master sequence:
27// 1. Clear return inputs and master bus
28// 2. Process all audio channels (gain + pan)
29// 3. Route audio channel sends to return inputs
30// 4. Process returns in order (call FX, then route return-to-return sends)
31// 5. Sum to master (respecting mute/solo)
32// 6. Apply master gain (fader * bus_gain * output_trim)
33//
34// Usage:
35// sbl::dsp::modules::Mixer<48, 3, 2> mixer;
36// mixer.audio[0].add_send(0, 0.4f); // voice 0 → delay return
37// mixer.audio[0].add_send(1, 0.2f); // voice 0 → reverb return
38// mixer.set_return_processor(0, &delay, delay_process);
39// mixer.set_return_processor(1, &reverb, reverb_process);
40//
41// // In audio callback:
42// mixer.audio[0].write_input_mono(voice_buf, n);
43// mixer.process(n);
44// // mixer.master_l/r ready for DAC
45
46#ifndef SBL_DSP_MODULES_MIXER_HPP_
47#define SBL_DSP_MODULES_MIXER_HPP_
48
49#include <cstdint>
50
52
53namespace sbl::dsp::modules {
54
55/// Effect processor callback signature.
56/// Called with the return channel's accumulated input buffers.
57/// Must process audio in-place.
58using FxProcess = void(*)(void* ctx, float* buf_l, float* buf_r, uint16_t n);
59
60template <uint16_t MaxFrames, uint8_t NumAudio, uint8_t NumReturn, uint8_t MaxSends = 4>
61class Mixer {
62public:
64
65 Chan audio[NumAudio]; // Audio channels (voices, inputs)
66
67 // Master output buffers
68 float master_l[MaxFrames] = {};
69 float master_r[MaxFrames] = {};
70
71 // ── Master ports ─────────────────────────────────────────────────
72
73 /// Port: master fader level [0,1].
74 void set_master_fader_su(float su) { master_fader_.set_level_su(su); }
75
76 // ── Master parameters ────────────────────────────────────────────
77
78 void set_bus_gain(float g) { bus_gain_ = g; }
79 void set_output_trim(float t) { output_trim_ = t; }
80 void set_master_taper(comp::Taper t) { master_fader_.set_taper(t); }
81
82 // ── Return channel access ────────────────────────────────────────
83
84 /// Access a return channel by index.
85 Chan& ret(uint8_t idx) { return returns_[idx].channel; }
86 const Chan& ret(uint8_t idx) const { return returns_[idx].channel; }
87
88 /// Register an effect processor callback for a return channel.
89 /// The callback is called with the accumulated send input and must
90 /// process audio in-place.
91 void set_return_processor(uint8_t idx, void* ctx, FxProcess fn) {
92 if (idx < NumReturn) {
93 returns_[idx].processor = fn;
94 returns_[idx].processor_ctx = ctx;
95 }
96 }
97
98 // ── Processing — single call replaces old multi-step sequence ────
99
100 /// Process the entire mixer for one block.
101 ///
102 /// Prerequisites: audio channels have been written via write_input_mono/stereo.
103 ///
104 /// Steps:
105 /// 1. Clear return input buffers and master bus
106 /// 2. Process all audio channels (fader gain + pan)
107 /// 3. Route audio channel sends to return inputs
108 /// 4. For each return in order:
109 /// a. Call effect processor (if registered)
110 /// b. Process return channel (fader gain + pan)
111 /// c. Route return-to-return sends
112 /// 5. Sum all channels to master bus (mute/solo)
113 /// 6. Apply master gain (fader * bus_gain * output_trim)
114 void process(uint16_t n) {
115 // 1. Clear return input buffers and master bus
116 for (uint8_t i = 0; i < NumReturn; ++i)
117 clear_buf(returns_[i].channel.buf_l, returns_[i].channel.buf_r, n);
118 clear_buf(master_l, master_r, n);
119
120 // 2. Process all audio channels (gain + pan)
121 for (uint8_t i = 0; i < NumAudio; ++i)
122 audio[i].process(n);
123
124 // 3. Route audio channel sends to return inputs
125 for (uint8_t i = 0; i < NumAudio; ++i)
126 route_channel_sends(audio[i], n);
127
128 // 4. Process returns in order
129 for (uint8_t i = 0; i < NumReturn; ++i) {
130 auto& slot = returns_[i];
131
132 // 4a. Call effect processor (operates on accumulated input in-place)
133 if (slot.processor) {
134 slot.processor(slot.processor_ctx,
135 slot.channel.buf_l, slot.channel.buf_r, n);
136 }
137
138 // 4b. Process return channel (gain + pan → post_l/post_r)
139 // After this, buf_l/buf_r contain the wet signal (post-effect,
140 // pre-fader) and post_l/post_r contain the master-ready signal.
141 slot.channel.process(n);
142
143 // 4c. Route return-to-return sends (serial FX chains)
144 route_channel_sends(slot.channel, n);
145 }
146
147 // 5. Sum to master (respecting mute/solo)
148 sum_to_master(n);
149
150 // 6. Apply master gain
151 apply_master_gain(n);
152 }
153
154 // ── Accessors ────────────────────────────────────────────────────
155
156 comp::Fader& master_fader() { return master_fader_; }
157
158private:
159 struct ReturnSlot {
160 Chan channel;
161 FxProcess processor = nullptr;
162 void* processor_ctx = nullptr;
163 };
164
165 comp::Fader master_fader_; // master level
166 float bus_gain_ = 0.707f; // -3dB headroom
167 float output_trim_ = 0.5f; // DAC calibration
168 ReturnSlot returns_[NumReturn] = {};
169
170 // ── Private helpers ──────────────────────────────────────────────
171
172 static void clear_buf(float* l, float* r, uint16_t n) {
173 for (uint16_t i = 0; i < n; ++i) {
174 l[i] = 0.0f;
175 r[i] = 0.0f;
176 }
177 }
178
179 /// Route one channel's sends to return channel input buffers.
180 /// Reads from buf_l/buf_r (pre-pan). Gain depends on send type:
181 /// PreFader: send_level only
182 /// PostFader: fader_gain * send_level
183 void route_channel_sends(Chan& ch, uint16_t n) {
184 for (uint8_t s = 0; s < ch.send_count(); ++s) {
185 auto& send = ch.send(s);
186 // Advance the send fader smoother by the block's worth of samples
187 // for control-rate smoothed gain. Advances even when skipped.
188 float send_gain = send.level.tick(n);
189 if (send_gain < 0.001f) continue;
190
191 Chan& tgt = returns_[send.target].channel;
192 const float* src_l = ch.buf_l;
193 const float* src_r = (ch.format() == widgets::Format::Mono)
194 ? ch.buf_l
195 : ch.buf_r;
196
197 float gain = (send.type == widgets::SendType::PreFader)
198 ? send_gain
199 : ch.fader_gain() * send_gain;
200
201 for (uint16_t i = 0; i < n; ++i) {
202 tgt.buf_l[i] += src_l[i] * gain;
203 tgt.buf_r[i] += src_r[i] * gain;
204 }
205 }
206 }
207
208 /// Sum all channels to master bus. Applies per-channel post output,
209 /// respects mute/solo. Solo-in-place: audio and return solo
210 /// scopes are independent.
211 void sum_to_master(uint16_t n) {
212 // Detect solo state per scope
213 bool any_audio_solo = false;
214 for (uint8_t i = 0; i < NumAudio; ++i)
215 if (audio[i].soloed()) any_audio_solo = true;
216
217 bool any_ret_solo = false;
218 for (uint8_t i = 0; i < NumReturn; ++i)
219 if (returns_[i].channel.soloed()) any_ret_solo = true;
220
221 // Sum audio channels (from post-processed output)
222 for (uint8_t ch = 0; ch < NumAudio; ++ch) {
223 if (audio[ch].muted()) continue;
224 if (any_audio_solo && !audio[ch].soloed()) continue;
225
226 const float* src_l = audio[ch].post_l();
227 const float* src_r = audio[ch].post_r();
228
229 for (uint16_t i = 0; i < n; ++i) {
230 master_l[i] += src_l[i];
231 master_r[i] += src_r[i];
232 }
233 }
234
235 // Sum return channels (solo-in-place: returns independent of audio solo)
236 for (uint8_t ch = 0; ch < NumReturn; ++ch) {
237 if (returns_[ch].channel.muted()) continue;
238 if (any_ret_solo && !returns_[ch].channel.soloed()) continue;
239
240 const float* src_l = returns_[ch].channel.post_l();
241 const float* src_r = returns_[ch].channel.post_r();
242
243 for (uint16_t i = 0; i < n; ++i) {
244 master_l[i] += src_l[i];
245 master_r[i] += src_r[i];
246 }
247 }
248 }
249
250 /// Apply bus gain (headroom), master fader, and output trim (DAC calibration).
251 void apply_master_gain(uint16_t n) {
252 float master_gain = master_fader_.tick(n);
253 float gain = master_gain * bus_gain_ * output_trim_;
254 for (uint16_t i = 0; i < n; ++i) {
255 master_l[i] *= gain;
256 master_r[i] *= gain;
257 }
258 }
259};
260
261} // namespace sbl::dsp::modules
262
263#endif // SBL_DSP_MODULES_MIXER_HPP_
Mixer channel strip (Audio Stack — Widgets)
float tick(uint16_t n)
Definition fader.hpp:86
void set_taper(Taper t)
Definition fader.hpp:55
float level() const
Raw su level (before taper mapping).
Definition fader.hpp:95
void set_level_su(float su)
Definition fader.hpp:46
Chan & ret(uint8_t idx)
Access a return channel by index.
Definition mixer.hpp:85
void set_return_processor(uint8_t idx, void *ctx, FxProcess fn)
Definition mixer.hpp:91
widgets::Channel< MaxFrames, MaxSends > Chan
Definition mixer.hpp:63
float master_l[MaxFrames]
Definition mixer.hpp:68
void process(uint16_t n)
Definition mixer.hpp:114
const Chan & ret(uint8_t idx) const
Definition mixer.hpp:86
Chan audio[NumAudio]
Definition mixer.hpp:65
float master_r[MaxFrames]
Definition mixer.hpp:69
comp::Fader & master_fader()
Definition mixer.hpp:156
void set_master_fader_su(float su)
Port: master fader level [0,1].
Definition mixer.hpp:74
void set_output_trim(float t)
Definition mixer.hpp:79
void set_bus_gain(float g)
Definition mixer.hpp:78
void set_master_taper(comp::Taper t)
Definition mixer.hpp:80
const float * post_r() const
Definition channel.hpp:154
const float * post_l() const
Definition channel.hpp:153
Complete musical tools.
Definition ladder.hpp:59
void(*)(void *ctx, float *buf_l, float *buf_r, uint16_t n) FxProcess
Definition mixer.hpp:58