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
86
87 setAudioInfo(cfg);
88
89 return true;
90 }
91
92 bool begin(AudioInfo from, AudioInfo to) {
93 if (from.bits_per_sample != to.bits_per_sample){
94 LOGE("invalid bits_per_sample: %d", (int) to.bits_per_sample);
95 return false;
96 }
97 if (from.channels != to.channels){
98 LOGE("invalid channels: %d", (int) to.channels);
99 return false;
100 }
101 return begin(from, (sample_rate_t)to.sample_rate);
102 }
103
104 bool begin(AudioInfo from, int toRate) {
105 return begin(from, (sample_rate_t)toRate);
106 }
107
108 bool begin(AudioInfo from, sample_rate_t toRate) {
109 ResampleConfig rcfg;
110 rcfg.copyFrom(from);
111 rcfg.to_sample_rate = toRate;
112 rcfg.step_size = getStepSize(from.sample_rate, toRate);
113 return begin(rcfg);
114 }
115
116 virtual bool begin(AudioInfo info) {
117 if (to_sample_rate != 0) return begin(info, to_sample_rate);
118 return begin(info, step_size);
119 }
120
121 bool begin() override {
122 return begin(audioInfo());
123 }
124
125 bool begin(AudioInfo info, float step) {
126 ResampleConfig rcfg;
127 rcfg.copyFrom(info);
128 step_size = step;
129 return begin(rcfg);
130 }
131
132 void setAudioInfo(AudioInfo newInfo) override {
133 // update the step size if a fixed to_sample_rate has been defined
134 if (to_sample_rate != 0) {
136 }
137 // notify about changes
138 LOGI("-> ResampleStream:")
140 }
141
143 AudioInfo out = audioInfo();
144 if (to_sample_rate != 0) {
146 } else {
148 }
149 return out;
150 }
151
153 void setStepSize(float step) {
154 LOGI("setStepSize: %f", step);
155 step_size = step;
156#if PREFER_FIXEDPOINT
157 step_size_fixed = (int32_t) lround(step * 65536.0f);
158#endif
159 }
160
161 void setTargetSampleRate(int rate) { to_sample_rate = rate; }
162
165 float getStepSize(float sampleRateFrom, float sampleRateTo) {
166 return sampleRateFrom / sampleRateTo;
167 }
168
170 float getStepSize() { return step_size; }
171
172 // int availableForWrite() override { return p_print->availableForWrite(); }
173
174 size_t write(const uint8_t *data, size_t len) override {
175 LOGD("ResampleStream::write: %d", (int)len);
176 //addNotifyOnFirstWrite();
177 size_t written = 0;
178 switch (info.bits_per_sample) {
179 case 16:
180#if PREFER_FIXEDPOINT
181 return writeFixed(p_print, data, len, written);
182#else
183 return write<int16_t>(p_print, data, len, written);
184#endif
185 case 24:
186 return write<int24_t>(p_print, data, len, written);
187 case 32:
188 return write<int32_t>(p_print, data, len, written);
189 default:
190 TRACEE();
191 }
192 return 0;
193 }
194
196 void setBuffered(bool active) { is_buffer_active = active; }
197
200 if (p_out != nullptr && !out_buffer.isEmpty()) {
201 TRACED();
202#if USE_PRINT_FLUSH
203 p_out->flush();
204#endif
206 if (rc != out_buffer.available()) {
207 LOGE("write error %d vs %d", rc, out_buffer.available());
208 }
210 }
211 }
212
213 float getByteFactor() override { return 1.0f / step_size; }
214
215 protected:
217 float idx = 0;
218 bool is_first = true;
219 float step_size = 1.0;
222#if PREFER_FIXEDPOINT
223 // Q16.16 fixed-point read position and step size, used for the 16-bit fast
224 // path: avoids soft-float add/div/round() on FPU-less MCUs (e.g. RP2040).
225 int32_t idx_fixed = 0;
226 int32_t step_size_fixed = 1 << 16;
227#endif
228 // optional buffering
231 Print *p_out = nullptr;
232
235 int bytes_per_sample = cfg.bits_per_sample / 8;
236 int last_samples_size = cfg.channels * bytes_per_sample;
237 last_samples.resize(last_samples_size);
238 memset(last_samples.data(), 0, last_samples_size);
239 }
240
242 template <typename T>
243 size_t write(Print *p_out, const uint8_t *buffer, size_t bytes,
244 size_t &written) {
245 this->p_out = p_out;
246 if (step_size == 1.0f) {
247 written = p_out->write(buffer, bytes);
248 return written;
249 }
250 // prevent npe
251 if (info.channels == 0) {
252 LOGE("channels is 0");
253 return 0;
254 }
255 T *data = (T *)buffer;
256 int samples = bytes / sizeof(T);
257 size_t frames = samples / info.channels;
258 written = 0;
259
260 // avoid noise if audio does not start with 0
261 if (is_first) {
262 is_first = false;
263 setupLastSamples<T>(data, 0);
264 }
265
266 T frame[info.channels];
267 size_t frame_size = sizeof(frame);
268
269 // process all samples
270 while (idx < frames - 1) {
271 for (int ch = 0; ch < info.channels; ch++) {
272 T result = getValue<T>(data, idx, ch);
273 frame[ch] = result;
274 }
275
276 if (is_buffer_active) {
277 // if buffer is full we send it to output
278 if (out_buffer.availableForWrite() <= frame_size) {
279 flush();
280 }
281
282 // we use a buffer to minimize the number of output calls
283 int tmp_written =
284 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
285 written += tmp_written;
286 if (frame_size != tmp_written) {
287 TRACEE();
288 }
289 } else {
290 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
291 written += tmp;
292 if (tmp != frame_size) {
293 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
294 }
295 }
296
297 idx += step_size;
298 }
299
300 flush();
301
302 // save last samples to be made available at index position -1;
303 setupLastSamples<T>(data, frames - 1);
304 idx -= frames;
305
306 if (bytes != (written * step_size)) {
307 LOGD("write: %d vs %d", (int)bytes, (int)written);
308 }
309
310 // returns requested bytes to avoid rewriting of processed bytes
311 return frames * info.channels * sizeof(T);
312 }
313
314#if PREFER_FIXEDPOINT
317 size_t writeFixed(Print *p_out, const uint8_t *buffer, size_t bytes,
318 size_t &written) {
319 this->p_out = p_out;
320 if (step_size == 1.0f) {
321 written = p_out->write(buffer, bytes);
322 return written;
323 }
324 // prevent npe
325 if (info.channels == 0) {
326 LOGE("channels is 0");
327 return 0;
328 }
329 int16_t *data = (int16_t *)buffer;
330 int samples = bytes / sizeof(int16_t);
331 size_t frames = samples / info.channels;
332 written = 0;
333
334 // avoid noise if audio does not start with 0
335 if (is_first) {
336 is_first = false;
337 setupLastSamples<int16_t>(data, 0);
338 }
339
340 int16_t frame[info.channels];
341 size_t frame_size = sizeof(frame);
342
343 int32_t frames_fixed = ((int32_t)frames - 1) << 16;
344 // process all samples
345 while (idx_fixed < frames_fixed) {
346 for (int ch = 0; ch < info.channels; ch++) {
347 frame[ch] = getValueFixed(data, idx_fixed, ch);
348 }
349
350 if (is_buffer_active) {
351 // if buffer is full we send it to output
352 if (out_buffer.availableForWrite() <= frame_size) {
353 flush();
354 }
355
356 // we use a buffer to minimize the number of output calls
357 int tmp_written =
358 out_buffer.writeArray((const uint8_t *)&frame, frame_size);
359 written += tmp_written;
360 if (frame_size != tmp_written) {
361 TRACEE();
362 }
363 } else {
364 int tmp = p_out->write((const uint8_t *)&frame, frame_size);
365 written += tmp;
366 if (tmp != frame_size) {
367 LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
368 }
369 }
370
371 idx_fixed += step_size_fixed;
372 }
373
374 flush();
375
376 // save last samples to be made available at index position -1;
377 setupLastSamples<int16_t>(data, frames - 1);
378 idx_fixed -= (int32_t)frames << 16;
379
380 // returns requested bytes to avoid rewriting of processed bytes
381 return frames * info.channels * sizeof(int16_t);
382 }
383
386 int16_t getValueFixed(int16_t *data, int32_t frame_idx_fixed, int channel) {
387 // arithmetic shift floors toward -infinity, which correctly handles the
388 // [-1, 0) range without the truncate-then-patch needed for float
389 int32_t frame_idx0 = frame_idx_fixed >> 16;
390 int32_t frame_idx1 = frame_idx0 + 1;
391 int16_t val0 = lookup<int16_t>(data, frame_idx0, channel);
392 int16_t val1 = lookup<int16_t>(data, frame_idx1, channel);
393
394 // top 8 bits of the 16-bit fraction: keeps diff * weight within 32 bits
395 // (a single MULS on Cortex-M0+) instead of needing a 64-bit intermediate
396 int32_t weight = (frame_idx_fixed >> 8) & 0xFF;
397 int32_t diff = (int32_t)val1 - (int32_t)val0;
398 // round-half-up instead of floor, to avoid a systematic bias
399 int32_t result = (int32_t)val0 + (((diff * weight) + (1 << 7)) >> 8);
400 return (int16_t)result;
401 }
402#endif
403
405 template <typename T>
406 T getValue(T *data, float frame_idx, int channel) {
407 // interpolate value
408 int frame_idx0 = frame_idx;
409 // e.g. index -0.5 should be determined from -1 to 0 range
410 if (frame_idx0 == 0 && frame_idx < 0) frame_idx0 = -1;
411 int frame_idx1 = frame_idx0 + 1;
412 T val0 = lookup<T>(data, frame_idx0, channel);
413 T val1 = lookup<T>(data, frame_idx1, channel);
414
415 // direct 2-point lerp: frame_idx1 - frame_idx0 is always 1, so the
416 // generic mapT() divide by (in_max - in_min) is a wasted division here;
417 // and a manual +/-0.5 round avoids a libm round() call.
418 float frac = frame_idx - frame_idx0;
419 float result = val0 + frac * (val1 - val0);
420 LOGD("getValue idx: %d:%d / val: %d:%d / %f -> %f", frame_idx0, frame_idx1,
421 (int)val0, (int)val1, frame_idx, result)
422 return (T)(result + (result >= 0 ? 0.5f : -0.5f));
423 }
424
426 template <typename T>
427 T lookup(T *data, int frame, int channel) {
428 if (frame >= 0) {
429 return data[frame * info.channels + channel];
430 } else {
431 // index -1 (get last sample from previos run)
432 T *pt_last_samples = (T *)last_samples.data();
433 return pt_last_samples[channel];
434 }
435 }
437 template <typename T>
438 void setupLastSamples(T *data, int frame) {
439 for (int ch = 0; ch < info.channels; ch++) {
440 T *pt_last_samples = (T *)last_samples.data();
441 pt_last_samples[ch] = data[(frame * info.channels) + ch];
442 LOGD("setupLastSamples ch:%d - %d", ch, (int)pt_last_samples[ch])
443 }
444 }
445};
446
447} // 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:256
virtual void setStream(Stream &stream) override
Defines/Changes the input & output.
Definition AudioIO.h:258
virtual void setOutput(AudioOutput &print)
Defines/Changes the output target and registers for audio change notifications.
Definition AudioIO.h:272
Print * p_print
Definition AudioIO.h:334
void setupReader()
Definition AudioIO.h:336
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:243
bool begin(AudioInfo info, float step)
Definition ResampleStream.h:125
void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition ResampleStream.h:132
bool begin(AudioInfo from, int toRate)
Definition ResampleStream.h:104
bool begin(AudioInfo from, AudioInfo to)
Definition ResampleStream.h:92
float getStepSize(float sampleRateFrom, float sampleRateTo)
Definition ResampleStream.h:165
ResampleStream(Print &out)
Support for resampling via write.
Definition ResampleStream.h:38
virtual bool begin(AudioInfo info)
Definition ResampleStream.h:116
bool is_first
Definition ResampleStream.h:218
bool begin(AudioInfo from, sample_rate_t toRate)
Definition ResampleStream.h:108
ResampleStream(AudioOutput &out)
Definition ResampleStream.h:41
float step_size
Definition ResampleStream.h:219
void setTargetSampleRate(int rate)
Definition ResampleStream.h:161
void setBuffered(bool active)
Activates buffering to avoid small incremental writes.
Definition ResampleStream.h:196
bool is_buffer_active
Definition ResampleStream.h:229
size_t write(const uint8_t *data, size_t len) override
Definition ResampleStream.h:174
void setStepSize(float step)
influence the sample rate
Definition ResampleStream.h:153
float idx
Definition ResampleStream.h:217
Vector< uint8_t > last_samples
Definition ResampleStream.h:216
float getByteFactor() override
Definition ResampleStream.h:213
bool begin(ResampleConfig cfg)
Definition ResampleStream.h:63
SingleBuffer< uint8_t > out_buffer
Definition ResampleStream.h:230
Print * p_out
Definition ResampleStream.h:231
float getStepSize()
Returns the actual step size.
Definition ResampleStream.h:170
AudioInfo audioInfoOut() override
Definition ResampleStream.h:142
bool begin() override
Definition ResampleStream.h:121
void setupLastSamples(T *data, int frame)
store last samples to provide values for index -1
Definition ResampleStream.h:438
void flush()
When buffering is active, writes the buffered audio to the output.
Definition ResampleStream.h:199
void setupLastSamples(AudioInfo cfg)
Sets up the buffer for the rollover samples.
Definition ResampleStream.h:234
ResampleStream(Stream &io)
Support for resampling via write and read.
Definition ResampleStream.h:47
int bytes_per_frame
Definition ResampleStream.h:221
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:406
ResampleStream(AudioStream &io)
Definition ResampleStream.h:51
int to_sample_rate
Definition ResampleStream.h:220
T lookup(T *data, int frame, int channel)
lookup value for indicated frame & channel: index starts with -1;
Definition ResampleStream.h:427
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:184
int available() override
provides the number of entries that are available to read
Definition Buffers.h:245
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:250
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:213
T * data()
Provides address of actual data.
Definition Buffers.h:296
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:317
void reset() override
clears the buffer
Definition Buffers.h:298
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
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