libDaisy
Hardware Library for Daisy
Loading...
Searching...
No Matches
WavPlayer.h
Go to the documentation of this file.
1#pragma once
2
3#include "daisy.h"
4#include "ff.h"
5#include "WavParser.h"
6#include "FileReader.h"
7
8namespace daisy
9{
34template <size_t workspace_bytes>
36{
37 public:
40
51
53 struct FileInfo
54 {
57 };
58
60 Result Init(const char* name)
61 {
63 auto res = Open(name);
64 if(res != Result::Ok)
65 return res;
66
67 for(size_t i = 0; i < kMaxAudioChannels; i++)
68 {
69 current_sample_[i] = 0.f;
70 previous_sample_[i] = 0.f;
71 }
72 pos_acc_ = 0.f;
73 playback_speed_ = 1.f;
74 looping_ = false;
75 playing_ = false;
76
77 return Result::Ok;
78 }
79
81 Result Open(const char* name)
82 {
83 if(is_open_)
84 f_close(&file_);
85 auto sta = f_open(&file_, name, (FA_OPEN_EXISTING | FA_READ));
86 switch(sta)
87 {
88 case FR_OK: break;
89 case FR_NO_FILE:
90 case FR_NO_PATH: return Result::FileNotFoundError;
91 default: return Result::DiskError;
92 }
93 is_open_ = true;
94
95 daisy::FileReader reader(&file_);
96 daisy::WavParser parser;
97 if(!parser.parse(reader))
98 return Result::DiskError;
99 const auto& info = parser.info();
100 file_info_.channels = info.numChannels;
101 file_info_.samplerate = info.sampleRate;
102 auto bd = info.bitsPerSample;
103 file_info_.data_size_bytes = parser.dataSize();
104 file_info_.length
105 = file_info_.data_size_bytes / ((bd / 8) * file_info_.channels);
106 file_info_.data_start = parser.dataOffset();
107
108 // Compute frame size (bytes per sample frame)
109 frame_bytes_ = (file_info_.channels) * (bd / 8);
110 if(frame_bytes_ == 0)
111 return Result::DiskError;
112
113 // Seek to start of data
114 if(f_lseek(&file_, file_info_.data_start) != FR_OK)
115 return Result::DiskError;
116
117 // Prime FIFO with frame-aligned read
118 std::fill(buff_raw_, buff_raw_ + kRxSizeSamples, 0);
119 UINT bytes_read = 0;
120 size_t bytes_to_read
121 = std::min((size_t)workspace_bytes, file_info_.data_size_bytes);
122 // Align down to full frames
123 bytes_to_read -= (bytes_to_read % frame_bytes_);
124 if(bytes_to_read > 0)
125 {
126 if(f_read(&file_, (void*)buff_raw_, bytes_to_read, &bytes_read)
127 != FR_OK)
128 {
129 f_close(&file_);
130 return Result::DiskError;
131 }
132 if(bytes_read != bytes_to_read)
133 {
134 f_close(&file_);
135 return Result::DiskError;
136 }
137 }
138
139 buff_fifo_.Clear();
140 size_t samps_to_write = bytes_read / sizeof(int16_t);
141 // Align push count to whole frames (multiples of channels)
142 if(file_info_.channels > 0)
143 samps_to_write -= (samps_to_write % file_info_.channels);
144 for(size_t i = 0; i < samps_to_write; i++)
145 {
146 if(!buff_fifo_.PushBack(buff_raw_[i]))
147 break;
148 }
149
150 position_ = 0;
151 pending_read_req_ = false;
152 pending_seek_req_ = false;
153 bytes_left_in_chunk_ = (bytes_read <= file_info_.data_size_bytes)
154 ? (file_info_.data_size_bytes - bytes_read)
155 : 0;
156
157 return Result::Ok;
158 }
159
160
163 {
164 f_close(&file_);
165 file_info_.channels = 0;
166 file_info_.data_start = 0;
167 file_info_.length = 0;
168 file_info_.samplerate = 0;
169 is_open_ = false;
170 playing_ = false;
171 return Result::Ok;
172 }
173
179 {
180 while(!request_fifo_.IsEmpty())
181 {
182 auto req = request_fifo_.PopFront();
183 switch(req.type)
184 {
185 case IoRequest::Type::Read:
186 {
187 size_t bytes_requested = req.data * sizeof(int16_t);
188 // Align to full frames
189 bytes_requested -= (bytes_requested % frame_bytes_);
190 if(bytes_requested == 0)
191 {
192 pending_read_req_ = false;
193 break;
194 }
195
196 UINT total_bytes_read = 0;
197 UINT bytes_read = 0;
198
199 std::fill(buff_raw_, buff_raw_ + kRxSizeSamples, 0);
200
201 // Read up to end of data chunk
202 size_t first_span
203 = std::min(bytes_requested, bytes_left_in_chunk_);
204 // Align first_span to frame boundary as well
205 first_span -= (first_span % frame_bytes_);
206 if(first_span > 0)
207 {
208 if(f_read(&file_,
209 (void*)buff_raw_,
210 first_span,
211 &bytes_read)
212 != FR_OK)
213 return Result::DiskError;
214 if(bytes_read != first_span)
215 return Result::DiskError;
216 total_bytes_read += bytes_read;
217 bytes_left_in_chunk_ -= bytes_read;
218 }
219
220 // If need more and looping, wrap and read more (frame-aligned)
221 if(total_bytes_read < bytes_requested && looping_)
222 {
223 if(f_lseek(&file_, file_info_.data_start) != FR_OK)
224 return Result::DiskError;
225
226 size_t remaining_bytes
227 = bytes_requested - total_bytes_read;
228 // Align remaining as well (it already is, but keep consistent)
229 remaining_bytes -= (remaining_bytes % frame_bytes_);
230 if(remaining_bytes > 0)
231 {
232 UINT bytes_read2 = 0;
233 char* tbuff
234 = ((char*)(buff_raw_) + total_bytes_read);
235 // Do not exceed the chunk size on wrap
236 size_t span2 = std::min(remaining_bytes,
237 file_info_.data_size_bytes);
238 // Align span2
239 span2 -= (span2 % frame_bytes_);
240 if(span2 > 0)
241 {
242 if(f_read(&file_,
243 (void*)tbuff,
244 span2,
245 &bytes_read2)
246 != FR_OK)
247 return Result::DiskError;
248 if(bytes_read2 != span2)
249 return Result::DiskError;
250 total_bytes_read += bytes_read2;
251 bytes_left_in_chunk_
252 = (bytes_read2
253 <= file_info_.data_size_bytes)
254 ? (file_info_.data_size_bytes
255 - bytes_read2)
256 : 0;
257 }
258 }
259 }
260
261 // Push into FIFO; align to full frames
262 size_t samps_to_write = total_bytes_read / sizeof(int16_t);
263 if(file_info_.channels > 0)
264 samps_to_write
265 -= (samps_to_write % file_info_.channels);
266
267 for(size_t i = 0; i < samps_to_write; i++)
268 {
269 if(!buff_fifo_.PushBack(buff_raw_[i]))
270 {
271 pending_read_req_ = false;
273 }
274 }
275 pending_read_req_ = false;
276 }
277 break;
278 case IoRequest::Type::Seek:
279 {
280 size_t dest_bytes = req.data * sizeof(int16_t);
281 // Clamp and align to frame boundary
282 if(dest_bytes > file_info_.data_size_bytes)
283 dest_bytes = file_info_.data_size_bytes;
284 dest_bytes -= (dest_bytes % frame_bytes_);
285
286 if(f_lseek(&file_, file_info_.data_start + dest_bytes)
287 != FR_OK)
288 return Result::DiskError;
289
290 bytes_left_in_chunk_
291 = file_info_.data_size_bytes - dest_bytes;
292 pending_seek_req_ = false;
293 }
294 break;
295 default: break;
296 }
297 }
298 return Result::Ok;
299 }
300
325 Result Stream(float* samples, size_t num_channels)
326 {
327 auto channels = file_info_.channels;
328
329 for(size_t i = 0; i < num_channels; i++)
330 samples[i] = 0.f;
331
332 if(!buff_fifo_.IsEmpty() && playing_)
333 {
334 size_t ch_out = std::min(channels, num_channels);
335 for(size_t i = 0; i < ch_out; i++)
336 {
337 samples[i]
338 = previous_sample_[i]
339 + pos_acc_ * (current_sample_[i] - previous_sample_[i]);
340 }
341
342 pos_acc_ += playback_speed_;
343 while(pos_acc_ >= 1.f)
344 {
345 position_ += 1;
346 pos_acc_ -= 1.f;
347 for(size_t i = 0; i < channels; i++)
348 {
349 previous_sample_[i] = current_sample_[i];
350 current_sample_[i] = s162f(buff_fifo_.PopFront());
351 }
352 }
353 }
354
355 if(position_ >= (file_info_.length > 0 ? file_info_.length : 1))
356 {
357 position_ = 0;
358 if(!looping_)
359 {
360 playing_ = false;
361 }
362 else
363 {
364 pos_acc_ = 0.f;
365 for(size_t i = 0; i < kMaxAudioChannels; i++)
366 previous_sample_[i] = current_sample_[i];
367 }
368 }
369
370 // Request new samples in whole frames
371 bool requested_new_samps = false;
372 if(buff_fifo_.GetNumElements() < kRxFifoThreshold && !pending_read_req_)
373 {
374 size_t free_slots = (kRxSizeSamples - buff_fifo_.GetNumElements());
375 size_t rx_qty = (free_slots > 1) ? (free_slots - 1) : 0;
376 // Align to multiples of channels
377 if(file_info_.channels > 0)
378 rx_qty -= (rx_qty % file_info_.channels);
379
380 if(rx_qty > 0)
381 {
382 request_fifo_.PushBack(
383 IoRequest(IoRequest::Type::Read, rx_qty));
384 pending_read_req_ = true;
385 requested_new_samps = true;
386 }
387 }
388 if(requested_new_samps)
390 else if(buff_fifo_.IsEmpty() && playing_)
392 else
393 return Result::Ok;
394 }
395
397 void Restart()
398 {
399 buff_fifo_.Clear();
400 request_fifo_.Clear();
401
402 pos_acc_ = 0.f;
403 for(size_t i = 0; i < kMaxAudioChannels; i++)
404 current_sample_[i] = previous_sample_[i] = 0.f;
405
406 bytes_left_in_chunk_ = file_info_.data_size_bytes;
407
408 request_fifo_.PushBack(IoRequest(IoRequest::Type::Seek, 0));
409
410 // Request a frame-aligned quantity
411 size_t req_samps = kRxSizeSamples;
412 if(file_info_.channels > 0)
413 req_samps -= (req_samps % file_info_.channels);
414 if(req_samps > 0)
415 request_fifo_.PushBack(IoRequest(IoRequest::Type::Read, req_samps));
416
417 pending_read_req_ = true;
418 pending_seek_req_ = true;
419 position_ = 0;
420 playing_ = true;
421 }
422
424 inline size_t GetDurationInSamples() const
425 {
426 return file_info_.length > 0 ? file_info_.length : 1;
427 }
428
430 inline size_t GetChannels() const { return file_info_.channels; }
431
433 inline uint32_t GetPosition() const { return position_; }
434
436 inline float GetNormalizedPosition() const
437 {
438 size_t duration = GetDurationInSamples();
439 return static_cast<float>(position_) / static_cast<float>(duration);
440 }
441
445 inline void SetLooping(bool state) { looping_ = state; }
446
448 inline bool GetLooping() const { return looping_; }
449
450 inline void SetPlaying(bool state) { playing_ = state; }
451 inline bool GetPlaying() const { return playing_; }
452
457 inline void SetPlaybackSpeedRatio(const float speed)
458 {
459 if(speed >= 0.f)
460 {
461 playback_speed_ = speed;
462 }
463 }
464
468 inline void SetPlaybackSpeedSemitones(const float semitones)
469 {
470 playback_speed_ = std::pow(2.f, semitones / 12.f);
471 }
472
473 private:
477 struct IoRequest
478 {
479 enum class Type
480 {
481 Read,
482 Seek,
483 Unknown,
484 };
486 Type type;
487
492 size_t data;
493
495 IoRequest(Type t, size_t val)
496 {
497 type = t;
498 data = val;
499 }
500
501 IoRequest() : type(Type::Unknown), data(0) {}
502
503 ~IoRequest() {}
504 };
505
507 static const constexpr size_t kRxSizeSamples
508 = (workspace_bytes / sizeof(int16_t));
509
511 static const constexpr size_t kRxFifoThreshold = ((kRxSizeSamples / 4) * 3);
512
514 static const constexpr size_t kMaxAudioChannels = 8;
515
519 daisy::FIFO<IoRequest, 8> request_fifo_;
520
523
529 int16_t buff_raw_[kRxSizeSamples];
530
533 size_t position_; //< position within audio data (in samples)
534 FileInfo file_info_; //< Info for the currently open file
535
537 bool looping_, playing_;
538 float playback_speed_;
539
541 float current_sample_[kMaxAudioChannels];
542
544 float previous_sample_[kMaxAudioChannels];
545
548 FIL file_;
549 bool is_open_;
550 bool pending_read_req_;
551 bool pending_seek_req_;
552 float pos_acc_;
553 size_t bytes_left_in_chunk_; // remaining bytes in WAV data chunk
554 size_t frame_bytes_; // bytes per sample frame (channels * bytes-per-sample)
555};
556
557
558} // namespace daisy
bool PushBack(const T &elementToAdd)
Definition FIFO.h:49
T PopFront()
Definition FIFO.h:101
size_t GetNumElements() const
Definition FIFO.h:171
void Clear()
Definition FIFO.h:45
bool IsEmpty() const
Definition FIFO.h:165
Definition FIFO.h:299
Definition WavParser.h:64
uint32_t dataSize() const
Definition WavParser.h:134
const WavFormatInfo & info() const
Definition WavParser.h:132
uint32_t dataOffset() const
Definition WavParser.h:133
bool parse(IReader &r)
Definition WavParser.h:79
Definition WavPlayer.h:36
size_t GetChannels() const
Definition WavPlayer.h:430
void SetPlaybackSpeedRatio(const float speed)
Definition WavPlayer.h:457
WavPlayer()
Definition WavPlayer.h:38
Result Open(const char *name)
Definition WavPlayer.h:81
uint32_t GetPosition() const
Definition WavPlayer.h:433
bool GetPlaying() const
Definition WavPlayer.h:451
Result Stream(float *samples, size_t num_channels)
Definition WavPlayer.h:325
~WavPlayer()
Definition WavPlayer.h:39
void Restart()
Definition WavPlayer.h:397
void SetLooping(bool state)
Definition WavPlayer.h:445
size_t GetDurationInSamples() const
Definition WavPlayer.h:424
void SetPlaybackSpeedSemitones(const float semitones)
Definition WavPlayer.h:468
bool GetLooping() const
Definition WavPlayer.h:448
Result Prepare()
Definition WavPlayer.h:178
Result Close()
Definition WavPlayer.h:162
Result
Definition WavPlayer.h:43
void SetPlaying(bool state)
Definition WavPlayer.h:450
float GetNormalizedPosition() const
Definition WavPlayer.h:436
Result Init(const char *name)
Definition WavPlayer.h:60
FORCE_INLINE float s162f(int16_t x)
Definition daisy_core.h:120
Hardware defines and helpers for daisy field platform.
Definition index.h:2
Definition WavPlayer.h:54
size_t length
Definition WavPlayer.h:55
size_t data_size_bytes
Definition WavPlayer.h:56
size_t channels
Definition WavPlayer.h:55
size_t data_start
Definition WavPlayer.h:55
size_t samplerate
Definition WavPlayer.h:55