Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
audio_budget.hpp
Go to the documentation of this file.
1/**
2 * @file audio_budget.hpp
3 * @brief Audio callback cycle budget tracker
4 *
5 * Measures per-block CPU cycle consumption in audio callbacks using the
6 * DWT cycle counter. Tracks average (EWMA), peak, min, overrun count,
7 * and a per-block CPU load histogram (11 buckets, 10% each).
8 *
9 * Gated behind SBL_PROFILING_ENABLED. When the define is absent, all
10 * methods compile to no-ops and the class has zero data members -- no
11 * binary impact whatsoever.
12 *
13 * Usage:
14 * static sbl::profiling::AudioBudget budget;
15 * budget.init(48000, 48, 480'000'000);
16 *
17 * void audio_callback(int32_t* tx, const int32_t* rx, uint16_t frames) {
18 * budget.on_block_start();
19 * // ... DSP ...
20 * budget.on_block_end();
21 * }
22 *
23 * // In control loop:
24 * budget.avg_load() // [0.0, 1.0+]
25 * budget.peak_load() // worst-case since last reset
26 * budget.overruns() // count of callbacks that exceeded budget
27 */
28#pragma once
29#include <cstdint>
30
31#include <cstddef>
32
33#ifdef SBL_PROFILING_ENABLED
34#include <sbl/profiling/dwt.hpp>
35#endif
36
37namespace sbl::profiling {
38
40 static constexpr size_t HIST_BUCKETS = 11;
41
42public:
43 /// Configure budget from audio parameters.
44 /// budget_cycles = (cpu_hz / sample_rate) * block_size
45 void init(uint32_t sample_rate, uint16_t block_size, uint32_t cpu_hz) {
46#ifdef SBL_PROFILING_ENABLED
47 budget_ = (cpu_hz / sample_rate) * block_size;
48 reset();
49#else
50 (void)sample_rate; (void)block_size; (void)cpu_hz;
51#endif
52 }
53
54 /// Mark the start of audio processing (call first in callback)
56#ifdef SBL_PROFILING_ENABLED
57 start_ = cycles();
58#endif
59 }
60
61 /// Mark the end of audio processing (call last in callback)
62 void on_block_end() {
63#ifdef SBL_PROFILING_ENABLED
64 uint32_t elapsed = cycles() - start_;
65 ++count_;
66
67 if (elapsed > max_) max_ = elapsed;
68 if (elapsed < min_) min_ = elapsed;
69 if (elapsed > budget_) ++overruns_;
70
71 // EWMA smoothing, alpha = 1/128 (~130ms settling at 1ms blocks)
72 avg_ += (static_cast<float>(elapsed) - avg_) * (1.0f / 128.0f);
73
74 // Histogram: bucket index = load_percent / 10, clamped to [0, HIST_BUCKETS-1]
75 uint32_t pct = (elapsed * 100) / budget_;
76 uint32_t bucket = pct / 10;
77 if (bucket >= HIST_BUCKETS) bucket = HIST_BUCKETS - 1;
78 hist_[bucket]++;
79#endif
80 }
81
82 // --- Queries (safe to call from control context) ---
83
84 /// Average CPU load as fraction [0.0, 1.0+]
85 float avg_load() const {
86#ifdef SBL_PROFILING_ENABLED
87 return avg_ / static_cast<float>(budget_);
88#else
89 return 0.0f;
90#endif
91 }
92
93 /// Peak (worst-case) CPU load since last reset
94 float peak_load() const {
95#ifdef SBL_PROFILING_ENABLED
96 return static_cast<float>(max_) / static_cast<float>(budget_);
97#else
98 return 0.0f;
99#endif
100 }
101
102 /// Minimum CPU load since last reset
103 float min_load() const {
104#ifdef SBL_PROFILING_ENABLED
105 return static_cast<float>(min_) / static_cast<float>(budget_);
106#else
107 return 0.0f;
108#endif
109 }
110
111 /// Raw cycle counts
112 uint32_t avg_cycles() const {
113#ifdef SBL_PROFILING_ENABLED
114 return static_cast<uint32_t>(avg_);
115#else
116 return 0;
117#endif
118 }
119
120 uint32_t peak_cycles() const {
121#ifdef SBL_PROFILING_ENABLED
122 return max_;
123#else
124 return 0;
125#endif
126 }
127
128 uint32_t min_cycles() const {
129#ifdef SBL_PROFILING_ENABLED
130 return min_;
131#else
132 return 0;
133#endif
134 }
135
136 uint32_t budget_cycles() const {
137#ifdef SBL_PROFILING_ENABLED
138 return budget_;
139#else
140 return 0;
141#endif
142 }
143
144 /// Number of callbacks that exceeded the cycle budget
145 uint32_t overruns() const {
146#ifdef SBL_PROFILING_ENABLED
147 return overruns_;
148#else
149 return 0;
150#endif
151 }
152
153 /// Total callbacks measured
154 uint32_t count() const {
155#ifdef SBL_PROFILING_ENABLED
156 return count_;
157#else
158 return 0;
159#endif
160 }
161
162 // --- Histogram (10% buckets: 0-10%, 10-20%, ..., 90-100%, >100%) ---
163
164 /// Number of histogram buckets
165 static constexpr size_t hist_size() { return HIST_BUCKETS; }
166
167 /// Read histogram bucket count (0-indexed, last bucket is >100%)
168 uint32_t hist_bucket(size_t i) const {
169#ifdef SBL_PROFILING_ENABLED
170 return (i < HIST_BUCKETS) ? hist_[i] : 0;
171#else
172 (void)i; return 0;
173#endif
174 }
175
176 /// Reset statistics (budget preserved)
177 void reset() {
178#ifdef SBL_PROFILING_ENABLED
179 avg_ = 0.0f;
180 max_ = 0;
181 min_ = budget_;
182 overruns_ = 0;
183 count_ = 0;
184 for (auto& b : hist_) b = 0;
185#endif
186 }
187
188private:
189#ifdef SBL_PROFILING_ENABLED
190 uint32_t start_ = 0;
191 uint32_t budget_ = 0;
192 float avg_ = 0.0f;
193 uint32_t max_ = 0;
194 uint32_t min_ = 0;
195 uint32_t overruns_ = 0;
196 uint32_t count_ = 0;
197
198 uint32_t hist_[HIST_BUCKETS] = {};
199#endif
200};
201
202} // namespace sbl::profiling
float avg_load() const
Average CPU load as fraction [0.0, 1.0+].
uint32_t overruns() const
Number of callbacks that exceeded the cycle budget.
void on_block_start()
Mark the start of audio processing (call first in callback)
uint32_t avg_cycles() const
Raw cycle counts.
static constexpr size_t hist_size()
Number of histogram buckets.
float min_load() const
Minimum CPU load since last reset.
float peak_load() const
Peak (worst-case) CPU load since last reset.
uint32_t count() const
Total callbacks measured.
void on_block_end()
Mark the end of audio processing (call last in callback)
void init(uint32_t sample_rate, uint16_t block_size, uint32_t cpu_hz)
void reset()
Reset statistics (budget preserved)
uint32_t hist_bucket(size_t i) const
Read histogram bucket count (0-indexed, last bucket is >100%)
DWT cycle counter for ARM Cortex-M7.
CPU load monitoring.
uint32_t cycles()
Definition dwt.hpp:47