arduino-audio-tools
Loading...
Searching...
No Matches
ResampleStream.h
Go to the documentation of this file.
1#pragma once
2
4
5#if USE_PRINT_FLUSH
6# define PRINT_FLUSH_OVERRIDE override
7#else
8# define PRINT_FLUSH_OVERRIDE
9#endif
10
11namespace audio_tools {
12
18struct ResampleConfig : public AudioInfo {
19 float step_size = 1.0f;
23};
24
34 public:
35 ResampleStream() = default;
36
43 setOutput(out);
44 }
45
48
55
59 cfg.copyFrom(audioInfo());
60 return cfg;
61 }
62
64 LOGI("begin step_size: %f", cfg.step_size);
65 //is_output_notify = false;
68
71 is_first = true;
72 idx = 0;
73#if PREFER_FIXEDPOINT
74 idx_fixed = 0;
75 // Q16.16 step_size_fixed is an int32_t: a ratio at or beyond +/-32768
76 // overflows it. Real resampling ratios are nowhere near this (e.g.
77 // 192000/8000 = 24), so this only catches misconfiguration.
78 if (cfg.step_size >= 32768.0f || cfg.step_size <= -32768.0f) {
79 LOGE("step_size %f is out of range for fixed point (+/-32768) - falling back is not implemented, output will wrap", cfg.step_size);
80 }
81#endif
82 // step_dirty = true;
84 carry_len = 0;
85
87
88 setAudioInfo(cfg);
89
90 return true;
91 }
92
93 bool begin(AudioInfo from, AudioInfo to) {
94 if (from.bits_per_sample != to.bits_per_sample){
95 LOGE("invalid bits_per_sample: %d", (int) to.bits_per_sample);
96 return false;
97 }
98 if (from.channels != to.channels){
99 LOGE("invalid channels: %d", (int) to.channels);
100 return false;
101 }
102 return begin(from, (sample_rate_t)to.sample_rate);
103 }
104
105 bool begin(AudioInfo from, int toRate) {
106 return begin(from, (sample_rate_t)toRate);
107 }
108
109 bool begin(AudioInfo from, sample_rate_t toRate) {
110 ResampleConfig rcfg;
111 rcfg.copyFrom(from);
112 rcfg.to_sample_rate = toRate;
113 rcfg.step_size = getStepSize(from.sample_rate, toRate);
114 return begin(rcfg);
115 }
116
117 virtual bool begin(AudioInfo info) {
118 if (to_sample_rate != 0) return begin(info, to_sample_rate);
119 return begin(info, step_size);
120 }
121
122 bool begin() override {
123 return begin(audioInfo());
124 }
125
126 bool begin(AudioInfo info, float step) {
127 ResampleConfig rcfg;
128 rcfg.copyFrom(info);
129 step_size = step;
130 return begin(rcfg);
131 }
132
133 void setAudioInfo(AudioInfo newInfo) override {
134 // update the step size if a fixed to_sample_rate has been defined
135 if (to_sample_rate != 0) {
137 }
138 // notify about changes
139 LOGI("-> ResampleStream:")
141 }
142
144 AudioInfo out = audioInfo();
145 if (to_sample_rate != 0) {
147 } else {
149 }
150 return out;
151 }
152
154 void setStepSize(float step) {
155 LOGI("setStepSize: %f", step);
156 step_size = step;
157#if PREFER_FIXEDPOINT
158 step_size_fixed = (int32_t) lround(step * 65536.0f);
159#endif
160 }
161
162 void setTargetSampleRate(int rate) { to_sample_rate = rate; }
163
166 float getStepSize(float sampleRateFrom, float sampleRateTo) {
167 return sampleRateFrom / sampleRateTo;
168 }
169
171 float getStepSize() { return step_size; }
172
173 // int availableForWrite() override { return p_out->availableForWrite(); }
174
175 size_t write(const uint8_t *data, size_t len) override {
176 LOGD("ResampleStream::write: %d", (int)len);
177 //addNotifyOnFirstWrite();
178 if (p_out == nullptr) {
179 LOGE("output not defined");
180 return 0;
181 }
182 int frame_bytes = info.bits_per_sample / 8 * info.channels;
183 if (frame_bytes <= 0) {
184 TRACEE();
185 return 0;
186 }
187
188 const uint8_t *proc_data = data;
189 size_t proc_len = len;
190 // Combine any partial-frame remainder carried over from a previous
191 // call (e.g. the source delivered a byte count that wasn't a whole
192 // number of frames - a short/malformed USB packet, for instance) with
193 // the new data, instead of processing len on its own. write<T>()/
194 // writeFixed() below only ever consume whole frames; without this,
195 // any remainder they leave behind would be silently dropped by the
196 // caller (see TransformationReader::fillResultQueue(), which advances
197 // past exactly `len` bytes on the source regardless of how much we
198 // report back) - permanently shifting every following sample by the
199 // gap and turning a single hiccup into session-long scrambled audio.
200 if (carry_len > 0) {
202 memcpy(combine_buffer.data(), carry.data(), carry_len);
203 memcpy(combine_buffer.data() + carry_len, data, len);
204 proc_data = combine_buffer.data();
205 proc_len = combine_buffer.size();
206 }
207
208 size_t whole = (proc_len / (size_t)frame_bytes) * (size_t)frame_bytes;
209 size_t leftover = proc_len - whole;
210
211 if (whole > 0) {
212 size_t written = 0;
213 switch (info.bits_per_sample) {
214 case 16:
215#if PREFER_FIXEDPOINT
216 writeFixed(proc_data, whole, written);
217#else
218 write<int16_t>(proc_data, whole, written);
219#endif
220 break;
221 case 24:
222 write<int24_t>(proc_data, whole, written);
223 break;
224 case 32:
225 write<int32_t>(proc_data, whole, written);
226 break;
227 default:
228 TRACEE();
229 return 0;
230 }
231 }
232
233 carry_len = leftover;
234 if (leftover > 0) {
235 carry.resize(leftover);
236 memcpy(carry.data(), proc_data + whole, leftover);
237 }
238
239 // The remainder is safely carried forward (to be completed by the
240 // next call) rather than dropped, so all of the caller's new bytes
241 // count as accounted for.
242 return len;
243 }
244
246 void setBuffered(bool active) { is_buffer_active = active; }
247
250 if (p_out != nullptr && !out_buffer.isEmpty()) {
251 TRACED();
252#if USE_PRINT_FLUSH
253 p_out->flush();
254#endif
256 if (rc != out_buffer.available()) {
257 LOGE("write error %d vs %d", rc, out_buffer.available());
258 }
260 }
261 }
262
263 float getByteFactor() override { return 1.0f / step_size; }
264
265 protected:
267 float idx = 0;
268 bool is_first = true;
269 float step_size = 1.0;
272 // Partial-frame remainder left over from a write() whose byte count
273 // wasn't a whole number of frames - see write() for why this must be
274 // carried forward rather than dropped.
276 size_t carry_len = 0;
277 Vector<uint8_t> combine_buffer{0}; // scratch space for carry+new data
278#if PREFER_FIXEDPOINT
279 // Q16.16 fixed-point read position and step size, used for the 16-bit fast
280 // path: avoids soft-float add/div/round() on FPU-less MCUs (e.g. RP2040).
281 int32_t idx_fixed = 0;
282 int32_t step_size_fixed = 1 << 16;
283#endif
284 // optional buffering
287
290 int bytes_per_sample = cfg.bits_per_sample / 8;
291 int last_samples_size = cfg.channels * bytes_per_sample;
292 last_samples.resize(last_samples_size);
293 memset(last_samples.data(), 0, last_samples_size);
294 }
295
297 template <typename T>
298 size_t write(const uint8_t *buffer, size_t bytes, size_t &written) {
299 if (step_size == 1.0f) {
300 written = p_out->write(buffer, bytes);
301 return written;
302 }
303 // prevent npe
304 if (info.channels == 0) {
305 LOGE("channels is 0");
306 return 0;
307 }
308 T *data = (T *)buffer;
309 int samples = bytes / sizeof(T);
310 size_t frames = samples / info.channels;
311 written = 0;
312
313 // avoid noise if audio does not start with 0
314 if (is_first) {
315 is_first = false;
316 setupLastSamples<T>(data, 0);
317 }
318
319 T frame[info.channels];
320 size_t frame_size = sizeof(frame);
321
322 // process all samples
323 while (idx < frames - 1) {
324 for (int ch = 0; ch < info.channels; ch++) {
325 T result = getValue<T>(data, idx, ch);
326 frame[ch] = result;
327 }
328
329 if (is_buffer_active) {
330 // if buffer is full we send it to output
331 if (out_buffer.availableForWrite() <= frame_size) {
332 flush();
333 }
334
335 // we use a buffer to minimize the number of output calls
336 int tmp_written =
337 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
338 written += tmp_written;
339 if (frame_size != tmp_written) {
340 TRACEE();
341 }
342 } else {
343 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
344 written += tmp;
345 if (tmp != frame_size) {
346 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
347 }
348 }
349
350 idx += step_size;
351 }
352
353 flush();
354
355 // save last samples to be made available at index position -1;
356 setupLastSamples<T>(data, frames - 1);
357 idx -= frames;
358
359 if (bytes != (written * step_size)) {
360 LOGD("write: %d vs %d", (int)bytes, (int)written);
361 }
362
363 // returns requested bytes to avoid rewriting of processed bytes
364 return frames * info.channels * sizeof(T);
365 }
366
367#if PREFER_FIXEDPOINT
370 size_t writeFixed(const uint8_t *buffer, size_t bytes, size_t &written) {
371 if (step_size == 1.0f) {
372 written = p_out->write(buffer, bytes);
373 return written;
374 }
375 // prevent npe
376 if (info.channels == 0) {
377 LOGE("channels is 0");
378 return 0;
379 }
380 int16_t *data = (int16_t *)buffer;
381 int samples = bytes / sizeof(int16_t);
382 size_t frames = samples / info.channels;
383 written = 0;
384
385 // avoid noise if audio does not start with 0
386 if (is_first) {
387 is_first = false;
388 setupLastSamples<int16_t>(data, 0);
389 }
390
391 int16_t frame[info.channels];
392 size_t frame_size = sizeof(frame);
393
394 int32_t frames_fixed = ((int32_t)frames - 1) << 16;
395 // process all samples
396 while (idx_fixed < frames_fixed) {
397 for (int ch = 0; ch < info.channels; ch++) {
398 frame[ch] = getValueFixed(data, idx_fixed, ch);
399 }
400
401 if (is_buffer_active) {
402 // if buffer is full we send it to output
403 if (out_buffer.availableForWrite() <= frame_size) {
404 flush();
405 }
406
407 // we use a buffer to minimize the number of output calls
408 int tmp_written =
409 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
410 written += tmp_written;
411 if (frame_size != tmp_written) {
412 TRACEE();
413 }
414 } else {
415 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
416 written += tmp;
417 if (tmp != frame_size) {
418 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
419 }
420 }
421
422 idx_fixed += step_size_fixed;
423 }
424
425 flush();
426
427 // save last samples to be made available at index position -1;
428 setupLastSamples<int16_t>(data, frames - 1);
429 idx_fixed -= (int32_t)frames << 16;
430
431 // returns requested bytes to avoid rewriting of processed bytes
432 return frames * info.channels * sizeof(int16_t);
433 }
434
437 int16_t getValueFixed(int16_t *data, int32_t frame_idx_fixed, int channel) {
438 // arithmetic shift floors toward -infinity, which correctly handles the
439 // [-1, 0) range without the truncate-then-patch needed for float
440 int32_t frame_idx0 = frame_idx_fixed >> 16;
441 int32_t frame_idx1 = frame_idx0 + 1;
442 int16_t val0 = lookup<int16_t>(data, frame_idx0, channel);
443 int16_t val1 = lookup<int16_t>(data, frame_idx1, channel);
444
445 // top 8 bits of the 16-bit fraction: keeps diff * weight within 32 bits
446 // (a single MULS on Cortex-M0+) instead of needing a 64-bit intermediate
447 int32_t weight = (frame_idx_fixed >> 8) & 0xFF;
448 int32_t diff = (int32_t)val1 - (int32_t)val0;
449 // round-half-up instead of floor, to avoid a systematic bias
450 int32_t result = (int32_t)val0 + (((diff * weight) + (1 << 7)) >> 8);
451 return (int16_t)result;
452 }
453#endif
454
456 template <typename T>
457 T getValue(T *data, float frame_idx, int channel) {
458 // interpolate value
459 int frame_idx0 = frame_idx;
460 // e.g. index -0.5 should be determined from -1 to 0 range
461 if (frame_idx0 == 0 && frame_idx < 0) frame_idx0 = -1;
462 int frame_idx1 = frame_idx0 + 1;
463 T val0 = lookup<T>(data, frame_idx0, channel);
464 T val1 = lookup<T>(data, frame_idx1, channel);
465
466 // direct 2-point lerp: frame_idx1 - frame_idx0 is always 1, so the
467 // generic mapT() divide by (in_max - in_min) is a wasted division here;
468 // and a manual +/-0.5 round avoids a libm round() call.
469 float frac = frame_idx - frame_idx0;
470 float result = val0 + frac * (val1 - val0);
471 LOGD("getValue idx: %d:%d / val: %d:%d / %f -> %f", frame_idx0, frame_idx1,
472 (int)val0, (int)val1, frame_idx, result)
473 return (T)(result + (result >= 0 ? 0.5f : -0.5f));
474 }
475
477 template <typename T>
478 T lookup(T *data, int frame, int channel) {
479 if (frame >= 0) {
480 return data[frame * info.channels + channel];
481 } else {
482 // index -1 (get last sample from previos run)
483 T *pt_last_samples = (T *)last_samples.data();
484 return pt_last_samples[channel];
485 }
486 }
488 template <typename T>
489 void setupLastSamples(T *data, int frame) {
490 for (int ch = 0; ch < info.channels; ch++) {
491 T *pt_last_samples = (T *)last_samples.data();
492 pt_last_samples[ch] = data[(frame * info.channels) + ch];
493 LOGD("setupLastSamples ch:%d - %d", ch, (int)pt_last_samples[ch])
494 }
495 }
496};
497
498} // namespace audio_tools
#define TRACED()
Definition AudioLoggerIDF.h:31
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define TRACEE()
Definition AudioLoggerIDF.h:34
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define PRINT_FLUSH_OVERRIDE
Definition AudioOutput.h:15
#define USE_RESAMPLE_BUFFER
Definition AudioToolsConfig.h:158
#define DEFAULT_BUFFER_SIZE
Definition avr.h:20
Definition Arduino.h:56
virtual size_t write(const uint8_t *data, size_t len)
Definition Arduino.h:120
virtual void flush()
Definition Arduino.h:130
Definition Arduino.h:136
Abstract Audio Ouptut class.
Definition AudioOutput.h:25
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition AudioOutput.h:62
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
AudioInfo info
Definition BaseStream.h:171
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:128
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition BaseStream.h:151
bool isEmpty()
Definition Buffers.h:87
Base class for chained converting streams.
Definition AudioIO.h:268
virtual void setStream(Stream &stream) override
Defines/Changes the input & output.
Definition AudioIO.h:270
virtual void setOutput(AudioOutput &print)
Defines/Changes the output target and registers for audio change notifications.
Definition AudioIO.h:284
Print * p_out
Definition AudioIO.h:346
void setupReader()
Definition AudioIO.h:348
Dynamic Resampling. We can use a variable factor to speed up or slow down the playback.
Definition ResampleStream.h:33
bool begin(AudioInfo info, float step)
Definition ResampleStream.h:126
void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition ResampleStream.h:133
bool begin(AudioInfo from, int toRate)
Definition ResampleStream.h:105
bool begin(AudioInfo from, AudioInfo to)
Definition ResampleStream.h:93
float getStepSize(float sampleRateFrom, float sampleRateTo)
Definition ResampleStream.h:166
ResampleStream(Print &out)
Support for resampling via write.
Definition ResampleStream.h:38
virtual bool begin(AudioInfo info)
Definition ResampleStream.h:117
bool is_first
Definition ResampleStream.h:268
bool begin(AudioInfo from, sample_rate_t toRate)
Definition ResampleStream.h:109
ResampleStream(AudioOutput &out)
Definition ResampleStream.h:41
float step_size
Definition ResampleStream.h:269
size_t write(const uint8_t *buffer, size_t bytes, size_t &written)
Writes the buffer to defined output after resampling.
Definition ResampleStream.h:298
void setTargetSampleRate(int rate)
Definition ResampleStream.h:162
void setBuffered(bool active)
Activates buffering to avoid small incremental writes.
Definition ResampleStream.h:246
bool is_buffer_active
Definition ResampleStream.h:285
size_t write(const uint8_t *data, size_t len) override
Definition ResampleStream.h:175
void setStepSize(float step)
influence the sample rate
Definition ResampleStream.h:154
float idx
Definition ResampleStream.h:267
Vector< uint8_t > last_samples
Definition ResampleStream.h:266
float getByteFactor() override
Definition ResampleStream.h:263
bool begin(ResampleConfig cfg)
Definition ResampleStream.h:63
SingleBuffer< uint8_t > out_buffer
Definition ResampleStream.h:286
size_t carry_len
Definition ResampleStream.h:276
float getStepSize()
Returns the actual step size.
Definition ResampleStream.h:171
AudioInfo audioInfoOut() override
Definition ResampleStream.h:143
bool begin() override
Definition ResampleStream.h:122
void setupLastSamples(T *data, int frame)
store last samples to provide values for index -1
Definition ResampleStream.h:489
void flush()
When buffering is active, writes the buffered audio to the output.
Definition ResampleStream.h:249
void setupLastSamples(AudioInfo cfg)
Sets up the buffer for the rollover samples.
Definition ResampleStream.h:289
ResampleStream(Stream &io)
Support for resampling via write and read.
Definition ResampleStream.h:47
int bytes_per_frame
Definition ResampleStream.h:271
ResampleConfig defaultConfig()
Provides the default configuraiton.
Definition ResampleStream.h:57
T getValue(T *data, float frame_idx, int channel)
get the interpolated value for indicated (float) index value
Definition ResampleStream.h:457
ResampleStream(AudioStream &io)
Definition ResampleStream.h:51
int to_sample_rate
Definition ResampleStream.h:270
T lookup(T *data, int frame, int channel)
lookup value for indicated frame & channel: index starts with -1;
Definition ResampleStream.h:478
Vector< uint8_t > carry
Definition ResampleStream.h:275
Vector< uint8_t > combine_buffer
Definition ResampleStream.h:277
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:194
int available() override
provides the number of entries that are available to read
Definition Buffers.h:255
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:260
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:223
T * data()
Provides address of actual data.
Definition Buffers.h:306
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:327
void reset() override
clears the buffer
Definition Buffers.h:308
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:321
int size()
Definition Vector.h:178
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:56
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:106
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:58
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:60
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:62
Optional Configuration object. The critical information is the channels and the step_size....
Definition ResampleStream.h:18
float step_size
Definition ResampleStream.h:19
int buffer_size
Definition ResampleStream.h:22
int to_sample_rate
Optional fixed target sample rate.
Definition ResampleStream.h:21