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