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_print->availableForWrite(); }
174
175 size_t write(const uint8_t *data, size_t len) override {
176 LOGD("ResampleStream::write: %d", (int)len);
177 //addNotifyOnFirstWrite();
178 int frame_bytes = info.bits_per_sample / 8 * info.channels;
179 if (frame_bytes <= 0) {
180 TRACEE();
181 return 0;
182 }
183
184 const uint8_t *proc_data = data;
185 size_t proc_len = len;
186 // Combine any partial-frame remainder carried over from a previous
187 // call (e.g. the source delivered a byte count that wasn't a whole
188 // number of frames - a short/malformed USB packet, for instance) with
189 // the new data, instead of processing len on its own. write<T>()/
190 // writeFixed() below only ever consume whole frames; without this,
191 // any remainder they leave behind would be silently dropped by the
192 // caller (see TransformationReader::fillResultQueue(), which advances
193 // past exactly `len` bytes on the source regardless of how much we
194 // report back) - permanently shifting every following sample by the
195 // gap and turning a single hiccup into session-long scrambled audio.
196 if (carry_len > 0) {
198 memcpy(combine_buffer.data(), carry.data(), carry_len);
199 memcpy(combine_buffer.data() + carry_len, data, len);
200 proc_data = combine_buffer.data();
201 proc_len = combine_buffer.size();
202 }
203
204 size_t whole = (proc_len / (size_t)frame_bytes) * (size_t)frame_bytes;
205 size_t leftover = proc_len - whole;
206
207 if (whole > 0) {
208 size_t written = 0;
209 switch (info.bits_per_sample) {
210 case 16:
211#if PREFER_FIXEDPOINT
212 writeFixed(p_print, proc_data, whole, written);
213#else
214 write<int16_t>(p_print, proc_data, whole, written);
215#endif
216 break;
217 case 24:
218 write<int24_t>(p_print, proc_data, whole, written);
219 break;
220 case 32:
221 write<int32_t>(p_print, proc_data, whole, written);
222 break;
223 default:
224 TRACEE();
225 return 0;
226 }
227 }
228
229 carry_len = leftover;
230 if (leftover > 0) {
231 carry.resize(leftover);
232 memcpy(carry.data(), proc_data + whole, leftover);
233 }
234
235 // The remainder is safely carried forward (to be completed by the
236 // next call) rather than dropped, so all of the caller's new bytes
237 // count as accounted for.
238 return len;
239 }
240
242 void setBuffered(bool active) { is_buffer_active = active; }
243
246 if (p_out != nullptr && !out_buffer.isEmpty()) {
247 TRACED();
248#if USE_PRINT_FLUSH
249 p_out->flush();
250#endif
252 if (rc != out_buffer.available()) {
253 LOGE("write error %d vs %d", rc, out_buffer.available());
254 }
256 }
257 }
258
259 float getByteFactor() override { return 1.0f / step_size; }
260
261 protected:
263 float idx = 0;
264 bool is_first = true;
265 float step_size = 1.0;
268 // Partial-frame remainder left over from a write() whose byte count
269 // wasn't a whole number of frames - see write() for why this must be
270 // carried forward rather than dropped.
272 size_t carry_len = 0;
273 Vector<uint8_t> combine_buffer{0}; // scratch space for carry+new data
274#if PREFER_FIXEDPOINT
275 // Q16.16 fixed-point read position and step size, used for the 16-bit fast
276 // path: avoids soft-float add/div/round() on FPU-less MCUs (e.g. RP2040).
277 int32_t idx_fixed = 0;
278 int32_t step_size_fixed = 1 << 16;
279#endif
280 // optional buffering
283 Print *p_out = nullptr;
284
287 int bytes_per_sample = cfg.bits_per_sample / 8;
288 int last_samples_size = cfg.channels * bytes_per_sample;
289 last_samples.resize(last_samples_size);
290 memset(last_samples.data(), 0, last_samples_size);
291 }
292
294 template <typename T>
295 size_t write(Print *p_out, const uint8_t *buffer, size_t bytes,
296 size_t &written) {
297 this->p_out = p_out;
298 if (step_size == 1.0f) {
299 written = p_out->write(buffer, bytes);
300 return written;
301 }
302 // prevent npe
303 if (info.channels == 0) {
304 LOGE("channels is 0");
305 return 0;
306 }
307 T *data = (T *)buffer;
308 int samples = bytes / sizeof(T);
309 size_t frames = samples / info.channels;
310 written = 0;
311
312 // avoid noise if audio does not start with 0
313 if (is_first) {
314 is_first = false;
315 setupLastSamples<T>(data, 0);
316 }
317
318 T frame[info.channels];
319 size_t frame_size = sizeof(frame);
320
321 // process all samples
322 while (idx < frames - 1) {
323 for (int ch = 0; ch < info.channels; ch++) {
324 T result = getValue<T>(data, idx, ch);
325 frame[ch] = result;
326 }
327
328 if (is_buffer_active) {
329 // if buffer is full we send it to output
330 if (out_buffer.availableForWrite() <= frame_size) {
331 flush();
332 }
333
334 // we use a buffer to minimize the number of output calls
335 int tmp_written =
336 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
337 written += tmp_written;
338 if (frame_size != tmp_written) {
339 TRACEE();
340 }
341 } else {
342 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
343 written += tmp;
344 if (tmp != frame_size) {
345 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
346 }
347 }
348
349 idx += step_size;
350 }
351
352 flush();
353
354 // save last samples to be made available at index position -1;
355 setupLastSamples<T>(data, frames - 1);
356 idx -= frames;
357
358 if (bytes != (written * step_size)) {
359 LOGD("write: %d vs %d", (int)bytes, (int)written);
360 }
361
362 // returns requested bytes to avoid rewriting of processed bytes
363 return frames * info.channels * sizeof(T);
364 }
365
366#if PREFER_FIXEDPOINT
369 size_t writeFixed(Print *p_out, const uint8_t *buffer, size_t bytes,
370 size_t &written) {
371 this->p_out = p_out;
372 if (step_size == 1.0f) {
373 written = p_out->write(buffer, bytes);
374 return written;
375 }
376 // prevent npe
377 if (info.channels == 0) {
378 LOGE("channels is 0");
379 return 0;
380 }
381 int16_t *data = (int16_t *)buffer;
382 int samples = bytes / sizeof(int16_t);
383 size_t frames = samples / info.channels;
384 written = 0;
385
386 // avoid noise if audio does not start with 0
387 if (is_first) {
388 is_first = false;
389 setupLastSamples<int16_t>(data, 0);
390 }
391
392 int16_t frame[info.channels];
393 size_t frame_size = sizeof(frame);
394
395 int32_t frames_fixed = ((int32_t)frames - 1) << 16;
396 // process all samples
397 while (idx_fixed < frames_fixed) {
398 for (int ch = 0; ch < info.channels; ch++) {
399 frame[ch] = getValueFixed(data, idx_fixed, ch);
400 }
401
402 if (is_buffer_active) {
403 // if buffer is full we send it to output
404 if (out_buffer.availableForWrite() <= frame_size) {
405 flush();
406 }
407
408 // we use a buffer to minimize the number of output calls
409 int tmp_written =
410 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
411 written += tmp_written;
412 if (frame_size != tmp_written) {
413 TRACEE();
414 }
415 } else {
416 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
417 written += tmp;
418 if (tmp != frame_size) {
419 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
420 }
421 }
422
423 idx_fixed += step_size_fixed;
424 }
425
426 flush();
427
428 // save last samples to be made available at index position -1;
429 setupLastSamples<int16_t>(data, frames - 1);
430 idx_fixed -= (int32_t)frames << 16;
431
432 // returns requested bytes to avoid rewriting of processed bytes
433 return frames * info.channels * sizeof(int16_t);
434 }
435
438 int16_t getValueFixed(int16_t *data, int32_t frame_idx_fixed, int channel) {
439 // arithmetic shift floors toward -infinity, which correctly handles the
440 // [-1, 0) range without the truncate-then-patch needed for float
441 int32_t frame_idx0 = frame_idx_fixed >> 16;
442 int32_t frame_idx1 = frame_idx0 + 1;
443 int16_t val0 = lookup<int16_t>(data, frame_idx0, channel);
444 int16_t val1 = lookup<int16_t>(data, frame_idx1, channel);
445
446 // top 8 bits of the 16-bit fraction: keeps diff * weight within 32 bits
447 // (a single MULS on Cortex-M0+) instead of needing a 64-bit intermediate
448 int32_t weight = (frame_idx_fixed >> 8) & 0xFF;
449 int32_t diff = (int32_t)val1 - (int32_t)val0;
450 // round-half-up instead of floor, to avoid a systematic bias
451 int32_t result = (int32_t)val0 + (((diff * weight) + (1 << 7)) >> 8);
452 return (int16_t)result;
453 }
454#endif
455
457 template <typename T>
458 T getValue(T *data, float frame_idx, int channel) {
459 // interpolate value
460 int frame_idx0 = frame_idx;
461 // e.g. index -0.5 should be determined from -1 to 0 range
462 if (frame_idx0 == 0 && frame_idx < 0) frame_idx0 = -1;
463 int frame_idx1 = frame_idx0 + 1;
464 T val0 = lookup<T>(data, frame_idx0, channel);
465 T val1 = lookup<T>(data, frame_idx1, channel);
466
467 // direct 2-point lerp: frame_idx1 - frame_idx0 is always 1, so the
468 // generic mapT() divide by (in_max - in_min) is a wasted division here;
469 // and a manual +/-0.5 round avoids a libm round() call.
470 float frac = frame_idx - frame_idx0;
471 float result = val0 + frac * (val1 - val0);
472 LOGD("getValue idx: %d:%d / val: %d:%d / %f -> %f", frame_idx0, frame_idx1,
473 (int)val0, (int)val1, frame_idx, result)
474 return (T)(result + (result >= 0 ? 0.5f : -0.5f));
475 }
476
478 template <typename T>
479 T lookup(T *data, int frame, int channel) {
480 if (frame >= 0) {
481 return data[frame * info.channels + channel];
482 } else {
483 // index -1 (get last sample from previos run)
484 T *pt_last_samples = (T *)last_samples.data();
485 return pt_last_samples[channel];
486 }
487 }
489 template <typename T>
490 void setupLastSamples(T *data, int frame) {
491 for (int ch = 0; ch < info.channels; ch++) {
492 T *pt_last_samples = (T *)last_samples.data();
493 pt_last_samples[ch] = data[(frame * info.channels) + ch];
494 LOGD("setupLastSamples ch:%d - %d", ch, (int)pt_last_samples[ch])
495 }
496 }
497};
498
499} // 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_print
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
size_t write(Print *p_out, const uint8_t *buffer, size_t bytes, size_t &written)
Writes the buffer to defined output after resampling.
Definition ResampleStream.h:295
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:264
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:265
void setTargetSampleRate(int rate)
Definition ResampleStream.h:162
void setBuffered(bool active)
Activates buffering to avoid small incremental writes.
Definition ResampleStream.h:242
bool is_buffer_active
Definition ResampleStream.h:281
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:263
Vector< uint8_t > last_samples
Definition ResampleStream.h:262
float getByteFactor() override
Definition ResampleStream.h:259
bool begin(ResampleConfig cfg)
Definition ResampleStream.h:63
SingleBuffer< uint8_t > out_buffer
Definition ResampleStream.h:282
Print * p_out
Definition ResampleStream.h:283
size_t carry_len
Definition ResampleStream.h:272
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:490
void flush()
When buffering is active, writes the buffered audio to the output.
Definition ResampleStream.h:245
void setupLastSamples(AudioInfo cfg)
Sets up the buffer for the rollover samples.
Definition ResampleStream.h:286
ResampleStream(Stream &io)
Support for resampling via write and read.
Definition ResampleStream.h:47
int bytes_per_frame
Definition ResampleStream.h:267
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:458
ResampleStream(AudioStream &io)
Definition ResampleStream.h:51
int to_sample_rate
Definition ResampleStream.h:266
T lookup(T *data, int frame, int channel)
lookup value for indicated frame & channel: index starts with -1;
Definition ResampleStream.h:479
Vector< uint8_t > carry
Definition ResampleStream.h:271
Vector< uint8_t > combine_buffer
Definition ResampleStream.h:273
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:189
int available() override
provides the number of entries that are available to read
Definition Buffers.h:250
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:255
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:218
T * data()
Provides address of actual data.
Definition Buffers.h:301
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:322
void reset() override
clears the buffer
Definition Buffers.h:303
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:316
int size()
Definition Vector.h:178
uint32_t sample_rate_t
Type alias for sample rate values.
Definition AudioTypes.h:19
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:51
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:101
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57
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