arduino-audio-tools
AudioOutput.h
1 #pragma once
2 #include "AudioConfig.h"
3 #include "AudioTools/AudioTypes.h"
4 #include "AudioTools/BaseConverter.h"
5 #include "AudioTools/Buffers.h"
6 
7 namespace audio_tools {
8 
9 #if !defined(ARDUINO) || defined(IS_DESKTOP)
10 #define FLUSH_OVERRIDE override
11 #else
12 #define FLUSH_OVERRIDE
13 #endif
14 
20 class AudioOutput : public Print,
21  public AudioInfoSupport,
22  public AudioInfoSource {
23 public:
24  virtual ~AudioOutput() = default;
25 
26  virtual size_t write(const uint8_t *buffer, size_t size) override = 0;
27 
28  virtual size_t write(uint8_t ch) override {
29  if (tmp.isFull()) {
30  flush();
31  }
32  return tmp.write(ch);
33  }
34 
35  virtual int availableForWrite() override { return DEFAULT_BUFFER_SIZE; }
36 
37  // removed override because some old implementation did not define this method
38  // as virtual
39  virtual void flush() FLUSH_OVERRIDE {
40  if (tmp.available() > 0) {
41  write((const uint8_t *)tmp.address(), tmp.available());
42  }
43  }
44 
45  // overwrite to do something useful
46  virtual void setAudioInfo(AudioInfo newInfo) override {
47  TRACED();
48  if (cfg != newInfo){
49  cfg = newInfo;
50  cfg.logInfo();
51  }
52  AudioInfo out = audioInfoOut();
53  if (out) notifyAudioChange(out);
54  }
55 
57  virtual bool isDeletable() { return false; }
58 
59  virtual AudioInfo audioInfo() override { return cfg; }
60 
63  virtual void writeSilence(size_t len) {
64  int16_t zero = 0;
65  for (int j = 0; j < len / 2; j++) {
66  write((uint8_t *)&zero, 2);
67  }
68  }
69 
70  virtual bool begin(AudioInfo info) {
71  setAudioInfo(info);
72  return begin();
73  }
74 
75  virtual bool begin() {
76  is_active = true;
77  return true;
78  }
79  virtual void end() { is_active = false; }
80  operator bool() { return is_active; }
81 
82 protected:
83  int tmpPos = 0;
84  AudioInfo cfg;
85  SingleBuffer<uint8_t> tmp{MAX_SINGLE_CHARS};
86  bool is_active = false;
87 };
88 
96 class ModifyingOutput : public AudioOutput {
97  public:
99  virtual void setOutput(Print& out) = 0;
100 };
101 
102 
112 template <typename T> class CsvOutput : public AudioOutput {
113 public:
114  CsvOutput(int buffer_size = DEFAULT_BUFFER_SIZE, bool active = true) {
115  this->is_active = active;
116  }
117 
119  CsvOutput(Print &out, int channels = 2, int buffer_size = DEFAULT_BUFFER_SIZE,
120  bool active = true) {
121  this->out_ptr = &out;
122  this->is_active = active;
123  cfg.channels = channels;
124  }
125 
127  void setDelimiter(const char *del) { delimiter_str = del; }
128 
130  const char *delimiter() { return delimiter_str; }
131 
132  AudioInfo defaultConfig(RxTxMode mode) { return defaultConfig(); }
133 
136  AudioInfo info;
137  info.channels = 2;
138  info.sample_rate = 44100;
139  info.bits_per_sample = sizeof(T) * 8;
140  return info;
141  }
142 
144  bool begin(AudioInfo info) override { return begin(info.channels); }
145 
147  bool begin(int channels) {
148  TRACED();
149  cfg.channels = channels;
150  return begin();
151  }
152 
154  bool begin() override {
155  this->is_active = true;
156  // if (out_ptr == &Serial){
157  // Serial.setTimeout(60000);
158  // }
159  return true;
160  }
161 
163  virtual void setAudioInfo(AudioInfo info) override {
164  TRACEI();
165  this->is_active = true;
166  info.logInfo();
167  cfg = info;
168  };
169 
171  virtual size_t write(const uint8_t *data, size_t len) override {
172  LOGD("CsvOutput::write: %d", (int)len);
173  if (!is_active) {
174  LOGE("is not active");
175  return 0;
176  }
177 
178  if (cfg.channels == 0) {
179  LOGW("Channels not defined: using 2");
180  cfg.channels = 2;
181  }
182  size_t lenChannels = len / (sizeof(T) * cfg.channels);
183  if (lenChannels > 0) {
184  writeFrames((T *)data, lenChannels);
185  } else if (len == sizeof(T)) {
186  // if the write contains less then a frame we buffer the data
187  T *data_value = (T *)data;
188  out_ptr->print(data_value[0]);
189  channel++;
190  if (channel == cfg.channels) {
191  out_ptr->println();
192  channel = 0;
193  } else {
194  out_ptr->print(delimiter_str);
195  }
196  } else {
197  LOGE("Unsupported size: %d for channels %d and bits: %d", (int)len,
198  cfg.channels, cfg.bits_per_sample);
199  }
200  out_ptr->flush();
201  return len;
202  }
203 
204  int availableForWrite() override { return 1024; }
205 
206 protected:
207  T *data_ptr;
208  Print *out_ptr = &Serial;
209  int channel = 0;
210  const char *delimiter_str = ",";
211 
212  void writeFrames(T *data_ptr, int frameCount) {
213  for (size_t j = 0; j < frameCount; j++) {
214  for (int ch = 0; ch < cfg.channels; ch++) {
215  if (out_ptr != nullptr && data_ptr != nullptr) {
216  int value = *data_ptr;
217  out_ptr->print(value);
218  }
219  data_ptr++;
220  if (ch < cfg.channels - 1)
221  this->out_ptr->print(delimiter_str);
222  }
223  this->out_ptr->println();
224  }
225  }
226 };
227 
228 // legacy name
229 template <typename T> using CsvStream = CsvOutput<T>;
230 
237 class HexDumpOutput : public AudioOutput {
238 public:
239  HexDumpOutput(int buffer_size = DEFAULT_BUFFER_SIZE, bool active = true) {
240  this->is_active = active;
241  }
242 
244  HexDumpOutput(Print &out, int buffer_size = DEFAULT_BUFFER_SIZE,
245  bool active = true) {
246  this->out_ptr = &out;
247  this->is_active = active;
248  }
249 
250  bool begin() override {
251  TRACED();
252  this->is_active = true;
253  pos = 0;
254  return is_active;
255  }
256 
257  void flush() override {
258  out_ptr->println();
259  pos = 0;
260  }
261 
262  virtual size_t write(const uint8_t *data, size_t len) override {
263  if (!is_active)
264  return 0;
265  TRACED();
266  for (size_t j = 0; j < len; j++) {
267  out_ptr->print(data[j], HEX);
268  out_ptr->print(" ");
269  pos++;
270  if (pos == 8) {
271  out_ptr->print(" - ");
272  }
273  if (pos == 16) {
274  out_ptr->println();
275  pos = 0;
276  }
277  }
278  return len;
279  }
280 
281  //
282  AudioInfo defaultConfig(RxTxMode mode = TX_MODE) {
283  AudioInfo info;
284  return info;
285  }
286 
287 protected:
288  Print *out_ptr = &Serial;
289  int pos = 0;
290 };
291 
292 // legacy name
293 using HexDumpStream = HexDumpOutput;
294 
302 template <typename T>
303 class OutputMixer : public Print {
304 public:
305  OutputMixer() = default;
306 
307  OutputMixer(Print &finalOutput, int outputStreamCount) {
308  setOutput(finalOutput);
309  setOutputCount(outputStreamCount);
310  }
311 
312  void setOutput(Print &finalOutput) { p_final_output = &finalOutput; }
313 
314  void setOutputCount(int count) {
315  output_count = count;
316  buffers.resize(count);
317  for (int i = 0; i < count; i++) {
318  buffers[i] = nullptr;
319  }
320  weights.resize(count);
321  for (int i = 0; i < count; i++) {
322  weights[i] = 1.0;
323  }
324 
325  update_total_weights();
326  }
327 
330  void setWeight(int channel, float weight) {
331  if (channel < size()) {
332  weights[channel] = weight;
333  } else {
334  LOGE("Invalid channel %d - max is %d", channel, size() - 1);
335  }
336  update_total_weights();
337  }
338 
340  bool begin(int copy_buffer_size_bytes = DEFAULT_BUFFER_SIZE,
341  MemoryType memoryType = PS_RAM) {
342  is_active = true;
343  size_bytes = copy_buffer_size_bytes;
344  stream_idx = 0;
345  memory_type = memoryType;
346  allocate_buffers(size_bytes);
347  return true;
348  }
349 
351  void end() {
352  total_weights = 0.0;
353  is_active = false;
354  // release memory for buffers
355  free_buffers();
356  }
357 
359  int size() { return output_count; }
360 
361  size_t write(uint8_t) override { return 0; }
362 
365  size_t write(const uint8_t *buffer_c, size_t bytes) override {
366  size_t result = write(stream_idx, buffer_c, bytes);
367  // after writing the last stream we flush
368  stream_idx++;
369  if (stream_idx >= output_count) {
370  flushMixer();
371  }
372  return result;
373  }
374 
376  size_t write(int idx, const uint8_t *buffer_c, size_t bytes) {
377  LOGD("write idx %d: %d", stream_idx, bytes);
378  size_t result = 0;
379  RingBuffer<T> *p_buffer = idx < output_count ? buffers[idx] : nullptr;
380  assert(p_buffer != nullptr);
381  size_t samples = bytes / sizeof(T);
382  if (p_buffer->availableForWrite() < samples) {
383  LOGW("Available Buffer too small %d: requested: %d -> increase the "
384  "buffer size",
385  p_buffer->availableForWrite(), samples);
386  } else {
387  result = p_buffer->writeArray((T *)buffer_c, samples) * sizeof(T);
388  }
389  return result;
390  }
391 
393  int availableForWrite() override {
394  return is_active ? availableForWrite(stream_idx) : 0;
395  }
396 
398  int availableForWrite(int idx) {
399  RingBuffer<T> *p_buffer = buffers[idx];
400  if (p_buffer == nullptr)
401  return 0;
402  return p_buffer->availableForWrite();
403  }
404 
406  void flushMixer() {
407  LOGD("flush");
408  bool result = false;
409 
410  // determine ringbuffer with mininum available data
411  size_t samples = size_bytes / sizeof(T);
412  for (int j = 0; j < output_count; j++) {
413  samples = MIN(samples, (size_t)buffers[j]->available());
414  }
415 
416  if (samples > 0) {
417  result = true;
418  // mix data from ringbuffers to output
419  output.resize(samples);
420  memset(output.data(), 0, samples * sizeof(T));
421  for (int j = 0; j < output_count; j++) {
422  float weight = weights[j];
423  // sum up input samples to result samples
424  for (int i = 0; i < samples; i++) {
425  output[i] += weight * buffers[j]->read() / total_weights;
426  }
427  }
428 
429  // write output
430  LOGD("write to final out: %d", samples * sizeof(T));
431  p_final_output->write((uint8_t *)output.data(), samples * sizeof(T));
432  }
433  stream_idx = 0;
434  return;
435  }
436 
438  void resize(int size) {
439  if (size != size_bytes) {
440  allocate_buffers(size);
441  }
442  size_bytes = size;
443  }
444 
445 protected:
446  Vector<RingBuffer<T> *> buffers{0};
447  Vector<T> output{0};
448  Vector<float> weights{0};
449  Print *p_final_output = nullptr;
450  float total_weights = 0.0;
451  bool is_active = false;
452  int stream_idx = 0;
453  int size_bytes;
454  int output_count;
455  MemoryType memory_type;
456  void *p_memory = nullptr;
457 
458  void update_total_weights() {
459  total_weights = 0.0;
460  for (int j = 0; j < weights.size(); j++) {
461  total_weights += weights[j];
462  }
463  }
464 
465  void allocate_buffers(int size) {
466  // allocate ringbuffers for each output
467  for (int j = 0; j < output_count; j++) {
468  if (buffers[j] != nullptr) {
469  delete buffers[j];
470  }
471 #if defined(ESP32) && defined(ARDUINO)
472  if (memory_type == PS_RAM && ESP.getFreePsram() >= size) {
473  p_memory = ps_malloc(size);
474  LOGI("Buffer %d allocated %d bytes in PS_RAM", j, size);
475  } else {
476  p_memory = malloc(size);
477  LOGI("Buffer %d allocated %d bytes in RAM", j, size);
478  }
479  if (p_memory != nullptr) {
480  buffers[j] = new (p_memory) RingBuffer<T>(size / sizeof(T));
481  } else {
482  LOGE("Not enough memory to allocate %d bytes", size);
483  }
484 #else
485  buffers[j] = new RingBuffer<T>(size / sizeof(T));
486 #endif
487  }
488  }
489 
490  void free_buffers() {
491  // allocate ringbuffers for each output
492  for (int j = 0; j < output_count; j++) {
493  if (buffers[j] != nullptr) {
494  delete buffers[j];
495 #ifdef ESP32
496  if (p_memory != nullptr) {
497  free(p_memory);
498  }
499 #endif
500  buffers[j] = nullptr;
501  }
502  }
503  }
504 };
505 
513 class VolumeOutput : public AudioOutput {
514 public:
515  void setAudioInfo(AudioInfo info) {
516  this->info = info;
517  if (info.channels > 0) {
518  volumes.resize(info.channels);
519  volumes_tmp.resize(info.channels);
520  }
521  }
522 
523  size_t write(const uint8_t *buffer, size_t size) {
524  clear();
525  switch (info.bits_per_sample) {
526  case 16:
527  updateVolumes<int16_t>(buffer, size);
528  break;
529  case 24:
530  updateVolumes<int24_t>(buffer, size);
531  break;
532  case 32:
533  updateVolumes<int32_t>(buffer, size);
534  break;
535  default:
536  LOGE("Unsupported bits_per_sample: %d", info.bits_per_sample);
537  break;
538  }
539  return size;
540  }
541 
544  float volume() { return f_volume; }
545 
548  float volume(int channel) {
549  if (volumes.size() == 0) {
550  LOGE("begin not called!");
551  return 0.0f;
552  }
553  if (channel >= volumes.size()) {
554  LOGE("invalid channel %d", channel);
555  return 0.0f;
556  }
557  return volumes[channel];
558  }
559 
561  void clear() {
562  f_volume_tmp = 0;
563  for (int j = 0; j < info.channels; j++) {
564  volumes_tmp[j] = 0;
565  }
566  }
567 
568 protected:
569  AudioInfo info;
570  float f_volume_tmp = 0;
571  float f_volume = 0;
572  Vector<float> volumes{0};
573  Vector<float> volumes_tmp{0};
574 
575  template <typename T> void updateVolumes(const uint8_t *buffer, size_t size) {
576  T *bufferT = (T *)buffer;
577  int samplesCount = size / sizeof(T);
578  for (int j = 0; j < samplesCount; j++) {
579  float tmp = abs(static_cast<float>(bufferT[j]));
580  updateVolume(tmp, j);
581  }
582  commit();
583  }
584 
585  void updateVolume(float tmp, int j) {
586  if (tmp > f_volume_tmp) {
587  f_volume_tmp = tmp;
588  }
589  if (volumes_tmp.size() > 0 && info.channels > 0) {
590  int ch = j % info.channels;
591  if (tmp > volumes_tmp[ch]) {
592  volumes_tmp[ch] = tmp;
593  }
594  }
595  }
596 
597  void commit() {
598  f_volume = f_volume_tmp;
599  for (int j = 0; j < info.channels; j++) {
600  volumes[j] = volumes_tmp[j];
601  }
602  }
603 };
604 
605 // legacy name
606 using VolumePrint = VolumeOutput;
607 
612 class MemoryOutput : public AudioOutput {
613 public:
614  MemoryOutput(uint8_t *start, int len) {
615  p_start = start;
616  p_next = start;
617  max_size = len;
618  is_active = true;
619  if (p_next == nullptr) {
620  LOGE("start must not be null");
621  }
622  }
623 
624  bool begin() {
625  is_active = true;
626  p_next = p_start;
627  pos = 0;
628  return true;
629  }
630 
631  size_t write(const uint8_t *buffer, size_t len) override {
632  if (p_next == nullptr)
633  return 0;
634  if (pos + len <= max_size) {
635  memcpy(p_next, buffer, len);
636  pos += len;
637  p_next += len;
638  return len;
639  } else {
640  LOGE("Buffer too small: pos:%d, size: %d ", pos, (int)max_size);
641  return 0;
642  }
643  }
644 
645  int availableForWrite() override { return max_size - pos; }
646 
647  int size() { return max_size; }
648 
649 protected:
650  int pos = 0;
651  uint8_t *p_start = nullptr;
652  uint8_t *p_next = nullptr;
653  size_t max_size;
654 };
655 
656 // legacy name
657 using MemoryPrint = MemoryOutput;
658 
668 public:
669  ChannelSplitOutput() = default;
670 
671  ChannelSplitOutput(Print &out, int channel) { addOutput(out, channel); }
672 
675  void addOutput(Print &out, int channel) {
677  def.channel = channel;
678  def.p_out = &out;
679  out_channels.push_back(def);
680  }
681 
682  size_t write(const uint8_t *buffer, size_t size) override {
683  switch(cfg.bits_per_sample){
684  case 16:
685  return writeT<int16_t>(buffer, size);
686  case 24:
687  return writeT<int24_t>(buffer, size);
688  case 32:
689  return writeT<int32_t>(buffer, size);
690  default:
691  return 0;
692  }
693  }
694 
695 protected:
697  Print *p_out = nullptr;
698  int channel;
699  };
701 
702  template <typename T>
703  size_t writeT(const uint8_t *buffer, size_t size) {
704  int sample_count = size / sizeof(T);
705  int result_size = sample_count / cfg.channels;
706  T *data = (T *)buffer;
707  T result[result_size];
708 
709  for (int ch = 0; ch < out_channels.size(); ch++) {
710  ChannelSelectionOutputDef &def = out_channels[ch];
711  // extract mono result
712  int i = 0;
713  for (int j = def.channel; j < sample_count; j += cfg.channels) {
714  result[i++] = data[j];
715  }
716  // write mono result
717  size_t written =
718  def.p_out->write((uint8_t *)result, result_size * sizeof(T));
719  if (written != result_size * sizeof(T)) {
720  LOGW("Could not write all samples");
721  }
722  }
723  return size;
724  }
725 
726 };
727 
728 
729 } // namespace audio_tools
Supports the subscription to audio change notifications.
Definition: AudioTypes.h:155
Supports changes to the sampling rate, bits and channels.
Definition: AudioTypes.h:136
virtual AudioInfo audioInfoOut()
provides the actual output AudioInfo: this is usually the same as audioInfo() unless we use a transfo...
Definition: AudioTypes.h:143
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
virtual bool isDeletable()
If true we need to release the related memory in the destructor.
Definition: AudioOutput.h:57
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioOutput.h:46
virtual void writeSilence(size_t len)
Definition: AudioOutput.h:63
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition: AudioOutput.h:59
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
Simple functionality to extract mono streams from a multichannel (e.g. stereo) signal.
Definition: AudioOutput.h:667
void addOutput(Print &out, int channel)
Definition: AudioOutput.h:675
Stream Wrapper which can be used to print the values as readable ASCII to the screen to be analyzed i...
Definition: AudioOutput.h:112
virtual size_t write(const uint8_t *data, size_t len) override
Writes the data - formatted as CSV - to the output stream.
Definition: AudioOutput.h:171
AudioInfo defaultConfig()
Provides the default configuration.
Definition: AudioOutput.h:135
bool begin(int channels)
Starts the processing with the defined number of channels.
Definition: AudioOutput.h:147
CsvOutput(Print &out, int channels=2, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true)
Constructor.
Definition: AudioOutput.h:119
virtual void setAudioInfo(AudioInfo info) override
defines the number of channels
Definition: AudioOutput.h:163
const char * delimiter()
Provides the current column delimiter.
Definition: AudioOutput.h:130
bool begin(AudioInfo info) override
Starts the processing with the defined number of channels.
Definition: AudioOutput.h:144
bool begin() override
(Re)start (e.g. if channels is set in constructor)
Definition: AudioOutput.h:154
void setDelimiter(const char *del)
Defines an alternative (column) delimiter. The default is ,.
Definition: AudioOutput.h:127
Creates a Hex Dump.
Definition: AudioOutput.h:237
HexDumpOutput(Print &out, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true)
Constructor.
Definition: AudioOutput.h:244
Writes to a preallocated memory.
Definition: AudioOutput.h:612
Abstract class: Objects can be put into a pipleline.
Definition: AudioOutput.h:96
virtual void setOutput(Print &out)=0
Defines/Changes the output target.
Mixing of multiple outputs to one final output.
Definition: AudioOutput.h:303
size_t write(int idx, const uint8_t *buffer_c, size_t bytes)
Write the data for an individual stream idx which will be mixed together.
Definition: AudioOutput.h:376
bool begin(int copy_buffer_size_bytes=DEFAULT_BUFFER_SIZE, MemoryType memoryType=PS_RAM)
Starts the processing.
Definition: AudioOutput.h:340
int availableForWrite() override
Provides the bytes available to write for the current stream buffer.
Definition: AudioOutput.h:393
size_t write(const uint8_t *buffer_c, size_t bytes) override
Definition: AudioOutput.h:365
void flushMixer()
Force output to final destination.
Definition: AudioOutput.h:406
void end()
Remove all input streams.
Definition: AudioOutput.h:351
void setWeight(int channel, float weight)
Definition: AudioOutput.h:330
int availableForWrite(int idx)
Provides the bytes available to write for the indicated stream index.
Definition: AudioOutput.h:398
void resize(int size)
Resizes the buffer to the indicated number of bytes.
Definition: AudioOutput.h:438
int size()
Number of stremams to which are mixed together.
Definition: AudioOutput.h:359
Definition: NoArduino.h:58
Implements a typed Ringbuffer.
Definition: Buffers.h:298
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: Buffers.h:365
T * address() override
Provides address to beginning of the buffer.
Definition: Buffers.h:243
bool write(T sample) override
write add an entry to the buffer
Definition: Buffers.h:188
int available() override
provides the number of entries that are available to read
Definition: Buffers.h:213
bool isFull() override
checks if the buffer is full
Definition: Buffers.h:220
Vector implementation which provides the most important methods as defined by std::vector....
Definition: Vector.h:21
A simple class to determine the volume.
Definition: AudioOutput.h:513
float volume()
Definition: AudioOutput.h:544
void setAudioInfo(AudioInfo info)
Defines the input AudioInfo.
Definition: AudioOutput.h:515
void clear()
Resets the actual volume.
Definition: AudioOutput.h:561
float volume(int channel)
Definition: AudioOutput.h:548
MemoryType
Memory types.
Definition: AudioTypes.h:33
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
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