arduino-audio-tools
ResampleStream.h
1 #pragma once
2 
3 #include "AudioTools/CoreAudio/AudioIO.h"
4 
5 #if USE_PRINT_FLUSH
6 # define PRINT_FLUSH_OVERRIDE override
7 #else
8 # define PRINT_FLUSH_OVERRIDE
9 #endif
10 
11 namespace audio_tools {
12 
18 struct ResampleConfig : public AudioInfo {
19  float step_size = 1.0f;
21  int to_sample_rate = 0;
22  int buffer_size = DEFAULT_BUFFER_SIZE;
23 };
24 
34  public:
35  ResampleStream() = default;
36 
38  ResampleStream(Print &out) { setOutput(out); }
42  setAudioInfo(out.audioInfo());
43  setOutput(out);
44  }
45 
48 
52  setAudioInfo(io.audioInfo());
53  setStream(io);
54  }
55 
58  ResampleConfig cfg;
59  cfg.copyFrom(audioInfo());
60  return cfg;
61  }
62 
63  bool begin(ResampleConfig cfg) {
64  LOGI("begin step_size: %f", cfg.step_size);
65  //is_output_notify = false;
66  to_sample_rate = cfg.to_sample_rate;
67  out_buffer.resize(cfg.buffer_size);
68 
69  setupLastSamples(cfg);
70  setStepSize(cfg.step_size);
71  is_first = true;
72  idx = 0;
73  // step_dirty = true;
74  bytes_per_frame = info.bits_per_sample / 8 * info.channels;
75 
76  setupReader();
77 
78  setAudioInfo(cfg);
79 
80  return true;
81  }
82 
83  bool begin(AudioInfo from, AudioInfo to) {
84  if (from.bits_per_sample != to.bits_per_sample){
85  LOGE("invalid bits_per_sample: %d", (int) to.bits_per_sample);
86  return false;
87  }
88  if (from.channels != to.channels){
89  LOGE("invalid channels: %d", (int) to.channels);
90  return false;
91  }
92  return begin(from, (sample_rate_t)to.sample_rate);
93  }
94 
95  bool begin(AudioInfo from, int toRate) {
96  return begin(from, (sample_rate_t)toRate);
97  }
98 
99  bool begin(AudioInfo from, sample_rate_t toRate) {
100  ResampleConfig rcfg;
101  rcfg.copyFrom(from);
102  rcfg.to_sample_rate = toRate;
103  rcfg.step_size = getStepSize(from.sample_rate, toRate);
104  return begin(rcfg);
105  }
106 
107  virtual bool begin(AudioInfo info) {
108  if (to_sample_rate != 0) return begin(info, to_sample_rate);
109  return begin(info, step_size);
110  }
111 
112  bool begin() override {
113  return begin(audioInfo());
114  }
115 
116  bool begin(AudioInfo info, float step) {
117  ResampleConfig rcfg;
118  rcfg.copyFrom(info);
119  step_size = step;
120  return begin(rcfg);
121  }
122 
123  void setAudioInfo(AudioInfo newInfo) override {
124  // update the step size if a fixed to_sample_rate has been defined
125  if (to_sample_rate != 0) {
126  setStepSize(getStepSize(newInfo.sample_rate, to_sample_rate));
127  }
128  // notify about changes
129  LOGI("-> ResampleStream:")
130  AudioStream::setAudioInfo(newInfo);
131  }
132 
133  AudioInfo audioInfoOut() override {
134  AudioInfo out = audioInfo();
135  if (to_sample_rate != 0) {
136  out.sample_rate = to_sample_rate;
137  } else {
138  out.sample_rate = out.sample_rate * step_size;
139  }
140  return out;
141  }
142 
144  void setStepSize(float step) {
145  LOGI("setStepSize: %f", step);
146  step_size = step;
147  }
148 
149  void setTargetSampleRate(int rate) { to_sample_rate = rate; }
150 
153  float getStepSize(float sampleRateFrom, float sampleRateTo) {
154  return sampleRateFrom / sampleRateTo;
155  }
156 
158  float getStepSize() { return step_size; }
159 
160  // int availableForWrite() override { return p_print->availableForWrite(); }
161 
162  size_t write(const uint8_t *data, size_t len) override {
163  LOGD("ResampleStream::write: %d", (int)len);
164  //addNotifyOnFirstWrite();
165  size_t written = 0;
166  switch (info.bits_per_sample) {
167  case 16:
168  return write<int16_t>(p_print, data, len, written);
169  case 24:
170  return write<int24_t>(p_print, data, len, written);
171  case 32:
172  return write<int32_t>(p_print, data, len, written);
173  default:
174  TRACEE();
175  }
176  return 0;
177  }
178 
180  void setBuffered(bool active) { is_buffer_active = active; }
181 
183  void flush() PRINT_FLUSH_OVERRIDE {
184  if (p_out != nullptr && !out_buffer.isEmpty()) {
185  TRACED();
186 #if USE_PRINT_FLUSH
187  p_out->flush();
188 #endif
189  int rc = p_out->write(out_buffer.data(), out_buffer.available());
190  if (rc != out_buffer.available()) {
191  LOGE("write error %d vs %d", rc, out_buffer.available());
192  }
193  out_buffer.reset();
194  }
195  }
196 
197  float getByteFactor() { return 1.0 / step_size; }
198 
199  protected:
200  Vector<uint8_t> last_samples{0};
201  float idx = 0;
202  bool is_first = true;
203  float step_size = 1.0;
204  int to_sample_rate = 0;
205  int bytes_per_frame = 0;
206  // optional buffering
207  bool is_buffer_active = USE_RESAMPLE_BUFFER;
208  SingleBuffer<uint8_t> out_buffer{0};
209  Print *p_out = nullptr;
210 
213  int bytes_per_sample = cfg.bits_per_sample / 8;
214  int last_samples_size = cfg.channels * bytes_per_sample;
215  last_samples.resize(last_samples_size);
216  memset(last_samples.data(), 0, last_samples_size);
217  }
218 
220  template <typename T>
221  size_t write(Print *p_out, const uint8_t *buffer, size_t bytes,
222  size_t &written) {
223  this->p_out = p_out;
224  if (step_size == 1.0) {
225  written = p_out->write(buffer, bytes);
226  return written;
227  }
228  // prevent npe
229  if (info.channels == 0) {
230  LOGE("channels is 0");
231  return 0;
232  }
233  T *data = (T *)buffer;
234  int samples = bytes / sizeof(T);
235  size_t frames = samples / info.channels;
236  written = 0;
237 
238  // avoid noise if audio does not start with 0
239  if (is_first) {
240  is_first = false;
241  setupLastSamples<T>(data, 0);
242  }
243 
244  T frame[info.channels];
245  size_t frame_size = sizeof(frame);
246 
247  // process all samples
248  while (idx < frames - 1) {
249  for (int ch = 0; ch < info.channels; ch++) {
250  T result = getValue<T>(data, idx, ch);
251  frame[ch] = result;
252  }
253 
254  if (is_buffer_active) {
255  // if buffer is full we send it to output
256  if (out_buffer.availableForWrite() <= frame_size) {
257  flush();
258  }
259 
260  // we use a buffer to minimize the number of output calls
261  int tmp_written =
262  out_buffer.writeArray((const uint8_t *)&frame, frame_size);
263  written += tmp_written;
264  if (frame_size != tmp_written) {
265  TRACEE();
266  }
267  } else {
268  int tmp = p_out->write((const uint8_t *)&frame, frame_size);
269  written += tmp;
270  if (tmp != frame_size) {
271  LOGE("Failed to write %d bytes: %d", (int)frame_size, tmp);
272  }
273  }
274 
275  idx += step_size;
276  }
277 
278  flush();
279 
280  // save last samples to be made available at index position -1;
281  setupLastSamples<T>(data, frames - 1);
282  idx -= frames;
283 
284  if (bytes != (written * step_size)) {
285  LOGD("write: %d vs %d", (int)bytes, (int)written);
286  }
287 
288  // returns requested bytes to avoid rewriting of processed bytes
289  return frames * info.channels * sizeof(T);
290  }
291 
293  template <typename T>
294  T getValue(T *data, float frame_idx, int channel) {
295  // interpolate value
296  int frame_idx0 = frame_idx;
297  // e.g. index -0.5 should be determined from -1 to 0 range
298  if (frame_idx0 == 0 && frame_idx < 0) frame_idx0 = -1;
299  int frame_idx1 = frame_idx0 + 1;
300  T val0 = lookup<T>(data, frame_idx0, channel);
301  T val1 = lookup<T>(data, frame_idx1, channel);
302 
303  float result = mapT<float>(frame_idx, frame_idx0, frame_idx1, val0, val1);
304  LOGD("getValue idx: %d:%d / val: %d:%d / %f -> %f", frame_idx0, frame_idx1,
305  (int)val0, (int)val1, frame_idx, result)
306  return (float)round(result);
307  }
308 
310  template <typename T>
311  T lookup(T *data, int frame, int channel) {
312  if (frame >= 0) {
313  return data[frame * info.channels + channel];
314  } else {
315  // index -1 (get last sample from previos run)
316  T *pt_last_samples = (T *)last_samples.data();
317  return pt_last_samples[channel];
318  }
319  }
321  template <typename T>
322  void setupLastSamples(T *data, int frame) {
323  for (int ch = 0; ch < info.channels; ch++) {
324  T *pt_last_samples = (T *)last_samples.data();
325  pt_last_samples[ch] = data[(frame * info.channels) + ch];
326  LOGD("setupLastSamples ch:%d - %d", ch, (int)pt_last_samples[ch])
327  }
328  }
329 };
330 
331 } // namespace audio_tools
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition: AudioOutput.h:59
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition: BaseStream.h:109
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: BaseStream.h:117
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition: BaseStream.h:140
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
Definition: NoArduino.h:58
Base class for chained converting streams.
Definition: AudioIO.h:142
virtual void setStream(Stream &stream) override
Defines/Changes the input & output.
Definition: AudioIO.h:144
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:221
void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: ResampleStream.h:123
float getStepSize(float sampleRateFrom, float sampleRateTo)
Definition: ResampleStream.h:153
ResampleStream(Print &out)
Support for resampling via write.
Definition: ResampleStream.h:38
ResampleStream(AudioOutput &out)
Definition: ResampleStream.h:41
void setBuffered(bool active)
Activates buffering to avoid small incremental writes.
Definition: ResampleStream.h:180
void setStepSize(float step)
influence the sample rate
Definition: ResampleStream.h:144
float getStepSize()
Returns the actual step size.
Definition: ResampleStream.h:158
AudioInfo audioInfoOut() override
provides the actual output AudioInfo: this is usually the same as audioInfo() unless we use a transfo...
Definition: ResampleStream.h:133
void setupLastSamples(T *data, int frame)
store last samples to provide values for index -1
Definition: ResampleStream.h:322
void flush()
When buffering is active, writes the buffered audio to the output.
Definition: ResampleStream.h:183
void setupLastSamples(AudioInfo cfg)
Sets up the buffer for the rollover samples.
Definition: ResampleStream.h:212
ResampleStream(Stream &io)
Support for resampling via write and read.
Definition: ResampleStream.h:47
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:294
ResampleStream(AudioStream &io)
Definition: ResampleStream.h:51
T lookup(T *data, int frame, int channel)
lookup value for indicated frame & channel: index starts with -1;
Definition: ResampleStream.h:311
T * data()
Provides address of actual data.
Definition: Buffers.h:252
int available() override
provides the number of entries that are available to read
Definition: Buffers.h:219
int availableForWrite() override
provides the number of entries that are available to write
Definition: Buffers.h:224
void reset() override
clears the buffer
Definition: Buffers.h:254
Definition: NoArduino.h:125
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
void copyFrom(AudioInfo info)
Same as set.
Definition: AudioTypes.h:107
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:59
Optional Configuration object. The critical information is the channels and the step_size....
Definition: ResampleStream.h:18
int to_sample_rate
Optional fixed target sample rate.
Definition: ResampleStream.h:21