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
audio_capture.hpp
Go to the documentation of this file.
1#ifndef SBL_COMMON_DIAGNOSTICS_AUDIO_CAPTURE_HPP_
2#define SBL_COMMON_DIAGNOSTICS_AUDIO_CAPTURE_HPP_
3
4#include <cstdint>
5
6#if __has_include(<ff.h>)
7#include <ff.h>
8#define SBL_HAS_FATFS 1
9#endif
10
11namespace sbl::diagnostics {
12
13/// Always-on audio capture ring buffer for host-side analysis.
14///
15/// Continuously records the last MaxFrames of audio into a circular buffer.
16/// No arming needed — the buffer always has the latest audio. To read:
17///
18/// 1. Freeze the buffer (via SWD write or app trigger) — audio keeps playing
19/// 2. Read pos_ to know the ring head position
20/// 3. Read buf_left_/buf_right_ via sbl-debugger
21/// 4. Unfreeze to resume capture
22/// 5. Unroll the ring: [pos_..MaxFrames) then [0..pos_) = chronological order
23///
24/// The sbl-debugger `audio_capture` tool handles all of this automatically.
25///
26/// Example:
27/// static sbl::diagnostics::AudioCapture<4096> s_audio_capture;
28///
29/// void audio_callback(int32_t* tx, const int32_t* rx, uint16_t frames) {
30/// render(left_buf, right_buf, frames);
31/// s_audio_capture.feed(left_buf, right_buf, frames);
32/// sbl::dsp::types::interleave_from_float(left_buf, right_buf, tx, frames);
33/// }
34///
35/// Cost: ~1us per 48-frame block when active. Single branch when frozen.
36///
37/// Template parameter MaxFrames controls buffer size and capture duration:
38/// 2048 -> 16 KB stereo, 42.7 ms @ 48kHz
39/// 4096 -> 32 KB stereo, 85.3 ms @ 48kHz (default)
40/// 8192 -> 64 KB stereo, 170.7 ms @ 48kHz
41/// 16384 -> 128 KB stereo, 341.3 ms @ 48kHz
42template <uint16_t MaxFrames = 4096>
44public:
45 /// Call from audio callback every block. Always-on ring buffer write.
46 /// No-op when frozen. ~1us for 48 stereo frames.
47 void feed(const float* left, const float* right, uint16_t frames) {
48 if (frozen_) return;
49
50 for (uint16_t i = 0; i < frames; ++i) {
51 buf_left_[pos_] = left[i];
52 buf_right_[pos_] = right[i];
53 pos_ = (pos_ + 1 < MaxFrames) ? pos_ + 1 : 0;
54 }
55
56 // Track total writes so the reader knows the buffer is full
57 if (!ready_) {
58 written_ += frames;
59 if (written_ >= MaxFrames) ready_ = true;
60 }
61 }
62
63 /// Freeze the buffer — stops writing. Audio callback continues normally.
64 /// Safe to call from any context (ISR, main loop, SWD write to frozen_).
65 void freeze() { frozen_ = true; }
66
67 /// Resume writing after a freeze.
68 void unfreeze() { frozen_ = false; }
69
70#ifdef SBL_HAS_FATFS
71 /// Dump the ring buffer to a 32-bit float WAV file via FatFs.
72 /// Freezes capture during write, then resumes. Returns true on success.
73 /// On platforms without a filesystem, f_open fails and this returns false.
74 bool dump_to_file(const char* path, uint32_t sample_rate = 48000) {
75 if (!ready_) return false;
76 freeze();
77
78 FIL fil;
79 if (f_open(&fil, path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) {
80 unfreeze();
81 return false;
82 }
83
84 // WAV header — 32-bit IEEE float, stereo
85 constexpr uint16_t channels = 2;
86 constexpr uint16_t bits = 32;
87 constexpr uint16_t fmt_pcm_float = 3;
88 uint32_t data_size = MaxFrames * channels * sizeof(float);
89 uint32_t file_size = 36 + data_size;
90 uint32_t fmt_size = 16;
91 uint32_t byte_rate = sample_rate * channels * (bits / 8);
92 uint16_t block_align = channels * (bits / 8);
93 UINT bw;
94
95 f_write(&fil, "RIFF", 4, &bw);
96 f_write(&fil, &file_size, 4, &bw);
97 f_write(&fil, "WAVE", 4, &bw);
98 f_write(&fil, "fmt ", 4, &bw);
99 f_write(&fil, &fmt_size, 4, &bw);
100 f_write(&fil, &fmt_pcm_float, 2, &bw);
101 f_write(&fil, &channels, 2, &bw);
102 f_write(&fil, &sample_rate, 4, &bw);
103 f_write(&fil, &byte_rate, 4, &bw);
104 f_write(&fil, &block_align, 2, &bw);
105 f_write(&fil, &bits, 2, &bw);
106 f_write(&fil, "data", 4, &bw);
107 f_write(&fil, &data_size, 4, &bw);
108
109 // Unroll ring: [pos..MaxFrames) then [0..pos), interleaved stereo
110 uint16_t p = pos_;
111 for (uint16_t pass = 0; pass < 2; ++pass) {
112 uint16_t start = (pass == 0) ? p : 0;
113 uint16_t end = (pass == 0) ? MaxFrames : p;
114 for (uint16_t i = start; i < end; ++i) {
115 f_write(&fil, &buf_left_[i], sizeof(float), &bw);
116 f_write(&fil, &buf_right_[i], sizeof(float), &bw);
117 }
118 }
119
120 f_close(&fil);
121 unfreeze();
122 return true;
123 }
124#endif // SBL_HAS_FATFS
125
126 // ── State fields — readable/writable via sbl-debugger ──
127
128 volatile bool frozen_ = false; // When true, feed() is a no-op
129 volatile bool ready_ = false; // True once buffer has been fully written
130 volatile uint16_t pos_ = 0; // Ring head: next write position
131 volatile uint32_t written_ = 0; // Total samples written (for ready_ detection)
132
133 // ── Sample buffers — readable via sbl-debugger read_memory ──
134
135 float buf_left_[MaxFrames] = {};
136 float buf_right_[MaxFrames] = {};
137
138 static constexpr uint16_t max_frames = MaxFrames;
139};
140
141} // namespace sbl::diagnostics
142
143#endif // SBL_COMMON_DIAGNOSTICS_AUDIO_CAPTURE_HPP_
static constexpr uint16_t max_frames
void feed(const float *left, const float *right, uint16_t frames)
void unfreeze()
Resume writing after a freeze.
Diagnostics and profiling.