libDaisy
Hardware Library for Daisy
Loading...
Searching...
No Matches
WavParser.h
Go to the documentation of this file.
1#pragma once
2
3// Minimal, allocation-free WAV (RIFF) parser suitable for embedded (e.g.,
4// STM32). Supports canonical PCM / IEEE float WAV, handles JUNK and unknown
5// chunks by skipping.
6// Does not load sample data; records data offset & length
7// so caller can stream.
8//
9// Limitations / Assumptions:
10// - Little-endian host or platform where manual LE decoding is used (safe on
11// STM32/Cortex-M).
12// - No dynamic allocation; fixed maximum number of metadata entries.
13// - Ignores extensible format extra fields beyond what is necessary for basic
14// parsing.
15// - Caller provides an abstract Reader (seek + read) so this can work with
16// FatFS, cstdio, raw data, etc.
17//
18// Typical usage:
19// FileReader fr(fopen("file.wav", "rb"));
20// WavParser parser;
21// if(parser.parse(fr)) {
22// // Use parser.info() to get format, sampleRate, etc.
23// // Use parser.dataOffset(), parser.dataSize() to stream audio.
24// }
25//
26
27#include <cstddef>
28#include <cstdint>
29#include "FileReader.h"
30
31namespace daisy
32{
34{
35 uint16_t audioFormat = 0; // 1 = PCM, 3 = IEEE float, 0xFFFE = extensible
36 uint16_t numChannels = 0;
37 uint32_t sampleRate = 0;
38 uint32_t byteRate = 0;
39 uint16_t blockAlign = 0;
40 uint16_t bitsPerSample = 0;
41 // For extensible (0xFFFE)
42 uint16_t validBitsPerSample = 0; // if provided
43 uint32_t channelMask = 0; // if provided
44 uint16_t subFormat = 0; // wFormatTag of the sub-format GUID (first 2 bytes)
45};
46
48{
49 uint32_t fourcc = 0; // chunk id
50 uint32_t size = 0; // payload size (before padding)
51 uint32_t offset = 0; // file offset of chunk data
52};
53
54// Utility to form a FourCC constant at compile time: FCC("RIFF") not constexpr
55// in pre-C++20 easily.
56constexpr uint32_t make_fourcc(char a, char b, char c, char d)
57{
58 return (uint32_t(uint8_t(a))) | (uint32_t(uint8_t(b)) << 8)
59 | (uint32_t(uint8_t(c)) << 16) | (uint32_t(uint8_t(d)) << 24);
60}
61
62
64{
65 public:
66 static constexpr uint32_t FOURCC_RIFF = make_fourcc('R', 'I', 'F', 'F');
67 static constexpr uint32_t FOURCC_WAVE = make_fourcc('W', 'A', 'V', 'E');
68 static constexpr uint32_t FOURCC_FMT = make_fourcc('f', 'm', 't', ' ');
69 static constexpr uint32_t FOURCC_DATA = make_fourcc('d', 'a', 't', 'a');
70 static constexpr uint32_t FOURCC_JUNK = make_fourcc('J', 'U', 'N', 'K');
71 static constexpr uint32_t FOURCC_FACT = make_fourcc('f', 'a', 'c', 't');
72 static constexpr uint32_t FOURCC_LIST = make_fourcc('L', 'I', 'S', 'T');
73 static constexpr uint32_t FOURCC_INFO = make_fourcc('I', 'N', 'F', 'O');
74
75 static constexpr int MAX_METADATA_CHUNKS = 16; // tunable
76
77 WavParser() = default;
78
79 bool parse(IReader& r)
80 {
81 reset();
82 if(!read_riff_header(r))
83 return false;
84 while(r.position() + 8 <= fileSize_)
85 {
86 ChunkHeader ch;
87 if(!read_chunk_header(r, ch))
88 return false;
89 if(ch.id == FOURCC_FMT)
90 {
91 if(!parse_fmt_chunk(r, ch))
92 return false;
93 }
94 else if(ch.id == FOURCC_DATA)
95 {
96 dataOffset_ = r.position();
97 dataSize_ = ch.size;
98 // Skip data (we only record offset). Allow early break if we've got
99 // fmt.
100 if(!skip_chunk_payload(r, ch.size))
101 return false;
102 haveData_ = true;
103 }
104 else
105 {
106 // Store metadata if room
107 if(metadataCount_ < MAX_METADATA_CHUNKS)
108 {
109 metadata_[metadataCount_].fourcc = ch.id;
110 metadata_[metadataCount_].size = ch.size;
111 metadata_[metadataCount_].offset = r.position();
112 metadataCount_++;
113 }
114 if(!skip_chunk_payload(r, ch.size))
115 return false;
116 }
117
118 // Chunks are padded to even size
119 if(ch.size & 1)
120 {
121 uint8_t pad;
122 if(r.read(&pad, 1) != 1)
123 break;
124 }
125
126 if(haveFmt_ && haveData_)
127 break; // parsed what we need
128 }
129 return haveFmt_ && haveData_;
130 }
131
132 const WavFormatInfo& info() const { return fmt_; }
133 uint32_t dataOffset() const { return dataOffset_; }
134 uint32_t dataSize() const { return dataSize_; }
135 const MetadataEntry* metadata() const { return metadata_; }
136 int metadataCount() const { return metadataCount_; }
137
138 private:
139 struct ChunkHeader
140 {
141 uint32_t id;
142 uint32_t size;
143 };
144
145 void reset()
146 {
147 fmt_ = WavFormatInfo{};
148 haveFmt_ = false;
149 haveData_ = false;
150 dataOffset_ = 0;
151 dataSize_ = 0;
152 metadataCount_ = 0;
153 fileSize_ = 0;
154 }
155
156 static uint16_t rd_u16(const uint8_t* b)
157 {
158 return uint16_t(b[0]) | (uint16_t(b[1]) << 8);
159 }
160 static uint32_t rd_u32(const uint8_t* b)
161 {
162 return uint32_t(b[0]) | (uint32_t(b[1]) << 8) | (uint32_t(b[2]) << 16)
163 | (uint32_t(b[3]) << 24);
164 }
165
166 bool read_exact(IReader& r, void* dst, size_t n)
167 {
168 return r.read(dst, n) == n;
169 }
170
171 bool read_riff_header(IReader& r)
172 {
173 uint8_t hdr[12];
174 if(!read_exact(r, hdr, 12))
175 return false;
176 uint32_t riff = rd_u32(hdr + 0);
177 uint32_t fileSizeMinus8 = rd_u32(hdr + 4); // size of file - 8
178 uint32_t wave = rd_u32(hdr + 8);
179 if(riff != FOURCC_RIFF || wave != FOURCC_WAVE)
180 return false;
181 fileSize_ = fileSizeMinus8 + 8; // nominal
182 if(r.size() != 0)
183 fileSize_ = r.size(); // trust reader if known
184 return true;
185 }
186
187 bool read_chunk_header(IReader& r, ChunkHeader& ch)
188 {
189 uint8_t buf[8];
190 if(!read_exact(r, buf, 8))
191 return false;
192 ch.id = rd_u32(buf);
193 ch.size = rd_u32(buf + 4);
194 return true;
195 }
196
197 bool skip_chunk_payload(IReader& r, uint32_t sz)
198 {
199 // Seek ahead instead of reading to avoid buffer.
200 uint32_t target = r.position() + sz;
201 return r.seek(target);
202 }
203
204 bool parse_fmt_chunk(IReader& r, const ChunkHeader& ch)
205 {
206 if(ch.size < 16)
207 return false;
208 uint8_t core[16];
209 if(!read_exact(r, core, 16))
210 return false;
211 fmt_.audioFormat = rd_u16(core + 0);
212 fmt_.numChannels = rd_u16(core + 2);
213 fmt_.sampleRate = rd_u32(core + 4);
214 fmt_.byteRate = rd_u32(core + 8);
215 fmt_.blockAlign = rd_u16(core + 12);
216 fmt_.bitsPerSample = rd_u16(core + 14);
217 uint32_t consumed = 16;
218
219 if(fmt_.audioFormat != 1 && fmt_.audioFormat != 3
220 && fmt_.audioFormat != 0xFFFE)
221 {
222 // unsupported basic format
223 skip_rest_of_chunk(r, ch, consumed);
224 return false;
225 }
226
227 if(ch.size > consumed)
228 {
229 // Read the remaining bytes (small), up to a cap we care about
230 uint32_t remain = ch.size - consumed;
231 // We'll process extension for extensible
232 if(fmt_.audioFormat == 0xFFFE && remain >= 2)
233 {
234 uint8_t extSizeBuf[2];
235 if(!read_exact(r, extSizeBuf, 2))
236 return false;
237 consumed += 2;
238 uint16_t extSize = rd_u16(extSizeBuf);
239 if(extSize >= 22 && remain >= 2 + 22)
240 { // extensible has at least 22 bytes after cbSize
241 uint8_t ext[22];
242 if(!read_exact(r, ext, 22))
243 return false;
244 consumed += 22;
245 fmt_.validBitsPerSample = rd_u16(ext + 0);
246 fmt_.channelMask = rd_u32(ext + 2);
247 fmt_.subFormat = rd_u16(
248 ext
249 + 6); // first two bytes of GUID contain the actual format tag
250 // skip any rest of ext
251 if(extSize > 22)
252 {
253 uint32_t skip = extSize - 22;
254 if(!skip_bytes(r, skip))
255 return false;
256 consumed += skip;
257 }
258 }
259 else
260 {
261 // skip remainder if not long enough
262 if(!skip_bytes(r, remain - 2))
263 return false; // we already read extSizeBuf
264 consumed = ch.size; // consumed all
265 }
266 }
267 else
268 {
269 // skip any unneeded extended bytes for PCM / float
270 if(!skip_bytes(r, remain))
271 return false;
272 consumed = ch.size;
273 }
274 }
275 haveFmt_ = true;
276 return true;
277 }
278
279 bool skip_bytes(IReader& r, uint32_t count)
280 {
281 uint32_t target = r.position() + count;
282 return r.seek(target);
283 }
284
285 bool
286 skip_rest_of_chunk(IReader& r, const ChunkHeader& ch, uint32_t consumed)
287 {
288 if(consumed < ch.size)
289 return skip_bytes(r, ch.size - consumed);
290 return true;
291 }
292
293 WavFormatInfo fmt_{};
294 bool haveFmt_ = false;
295 bool haveData_ = false;
296 uint32_t dataOffset_ = 0;
297 uint32_t dataSize_ = 0;
298 MetadataEntry metadata_[MAX_METADATA_CHUNKS];
299 int metadataCount_ = 0;
300 uint32_t fileSize_ = 0;
301};
302
303} // namespace daisy
Definition FileReader.h:15
virtual uint32_t position() const =0
virtual size_t read(void *dst, size_t bytes)=0
Definition WavParser.h:64
uint32_t dataSize() const
Definition WavParser.h:134
static constexpr uint32_t FOURCC_INFO
Definition WavParser.h:73
static constexpr int MAX_METADATA_CHUNKS
Definition WavParser.h:75
static constexpr uint32_t FOURCC_DATA
Definition WavParser.h:69
static constexpr uint32_t FOURCC_LIST
Definition WavParser.h:72
const WavFormatInfo & info() const
Definition WavParser.h:132
WavParser()=default
int metadataCount() const
Definition WavParser.h:136
static constexpr uint32_t FOURCC_JUNK
Definition WavParser.h:70
static constexpr uint32_t FOURCC_FMT
Definition WavParser.h:68
static constexpr uint32_t FOURCC_WAVE
Definition WavParser.h:67
static constexpr uint32_t FOURCC_RIFF
Definition WavParser.h:66
static constexpr uint32_t FOURCC_FACT
Definition WavParser.h:71
uint32_t dataOffset() const
Definition WavParser.h:133
const MetadataEntry * metadata() const
Definition WavParser.h:135
bool parse(IReader &r)
Definition WavParser.h:79
Hardware defines and helpers for daisy field platform.
Definition index.h:2
constexpr uint32_t make_fourcc(char a, char b, char c, char d)
Definition WavParser.h:56
Definition WavParser.h:48
uint32_t offset
Definition WavParser.h:51
uint32_t size
Definition WavParser.h:50
uint32_t fourcc
Definition WavParser.h:49
Definition WavParser.h:34
uint16_t bitsPerSample
Definition WavParser.h:40
uint16_t numChannels
Definition WavParser.h:36
uint16_t blockAlign
Definition WavParser.h:39
uint32_t byteRate
Definition WavParser.h:38
uint16_t audioFormat
Definition WavParser.h:35
uint32_t sampleRate
Definition WavParser.h:37
uint16_t validBitsPerSample
Definition WavParser.h:42
uint32_t channelMask
Definition WavParser.h:43
uint16_t subFormat
Definition WavParser.h:44