diff --git a/examples/README_ESP32.md b/examples/README_ESP32.md index b01fbf109..338de3156 100644 --- a/examples/README_ESP32.md +++ b/examples/README_ESP32.md @@ -69,7 +69,7 @@ adcConfig.adc_channels[1] = ADC_CHANNEL_5; ``` ## ADC unit 1 channels on common ESP32 boards -Audio tools supports ADC Unit 1 only. +Audio tools continous ADC framewaork supports ADC Unit 1 only. ### Sparkfun ESP32 Thing Plus (ESP32) - A2, ADC1_CH6 @@ -78,6 +78,14 @@ Audio tools supports ADC Unit 1 only. - 32, ADC1_CH4 - 33, ADC1_CH5 +### Sparkfun ESP32 Thing Plus C (ESP32) +- A2, ADC1_CH6 +- A3, ADC1_CH3 +- A4, ADC1_CH0 +- A5, ADC1_CH7 +- 32/6, ADC1_CH4 +- 33/10, ADC1_CH5 + ### Sparkfun ESP32 Qwiic Pocket Development (ESP32C6) - 2, ADC1_CH2 - 3, ADC1_CH3 diff --git a/examples/examples-stream/streams_generator_bin_serial/streams_generator_bin_serial.ino b/examples/examples-stream/streams-generator-bin-serial/streams-generator-bin-serial.ino similarity index 100% rename from examples/examples-stream/streams_generator_bin_serial/streams_generator_bin_serial.ino rename to examples/examples-stream/streams-generator-bin-serial/streams-generator-bin-serial.ino diff --git a/examples/tests/conversion/channel-converter-avg/channel-converter-avg.ino b/examples/tests/conversion/channel-converter-avg/channel-converter-avg.ino new file mode 100644 index 000000000..648552d96 --- /dev/null +++ b/examples/tests/conversion/channel-converter-avg/channel-converter-avg.ino @@ -0,0 +1,48 @@ +/** + * @file channel-converter-avg.ino + * @brief Test calculating pairwise average of channels + * @author Urs Utzinger + * @copyright GPLv3 + **/ + +#include "AudioTools.h" + +AudioInfo info1(44100, 1, 16); +AudioInfo info2(44100, 2, 16); +SineWaveGenerator sineWave1(32000); // subclass of SoundGenerator with max amplitude of 32000 +SineWaveGenerator sineWave2(32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound1(sineWave1); // stream generated from sine wave1 +GeneratedSoundStream sound2(sineWave2); // stream generated from sine wave2 +InputMerge imerge; // merge two inputs to stereo +ChannelAvg averager; // channel averager +ConverterStream averaged_stream(imerge, averager); // pipe the sound to the averager +CsvOutput serial_out(Serial); // serial output +StreamCopy copier(serial_out, averaged_stream); // stream the binner output to serial port + +// Arduino Setup +void setup(void) { + + // Open Serial + Serial.begin(115200); + while(!Serial); // wait for Serial to be ready + + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + // Setup sine waves + sineWave1.begin(info1, N_B4); + sineWave2.begin(info1, N_B5); + + // Merge input to stereo + imerge.add(sound1); + imerge.add(sound2); + imerge.begin(info2); + + // Define CSV Output + serial_out.begin(info1); + +} + +// Arduino loop - copy sound to out with conversion +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/tests/conversion/channel-converter-bin/channel-converter-bin.ino b/examples/tests/conversion/channel-converter-bin/channel-converter-bin.ino new file mode 100644 index 000000000..c2da8da11 --- /dev/null +++ b/examples/tests/conversion/channel-converter-bin/channel-converter-bin.ino @@ -0,0 +1,57 @@ +/** + * @file channel-converter-avg.ino + * @brief Test calculating average of two channels + * @author Urs Utzinger + * @copyright GPLv3 + **/ + +#include "AudioTools.h" + +#define BINSIZE 4 + +AudioInfo info1(44100, 1, 16); +AudioInfo info2(44100, 2, 16); +AudioInfo info_out(44100/BINSIZE, 2, 16); +SineWaveGenerator sineWave1(32000); // subclass of SoundGenerator with max amplitude of 32000 +SineWaveGenerator sineWave2(32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound1(sineWave1); // stream generated from sine wave1 +GeneratedSoundStream sound2(sineWave2); // stream generated from sine wave2 +InputMerge imerge; // merge two inputs to stereo +Bin binner; // channel averager +ConverterStream binned_stream(imerge, binner); // pipe the sound to the averager +CsvOutput serial_out(Serial); // serial output +StreamCopy copier(serial_out, binned_stream); // stream the binner output to serial port + +// Arduino Setup +void setup(void) { + + // Open Serial + Serial.begin(115200); + while(!Serial); // wait for Serial to be ready + + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + // Setup sine waves + sineWave1.begin(info1, N_B4); + sineWave2.begin(info1, N_B5); + + // Merge input to stereo + imerge.add(sound1); + imerge.add(sound2); + imerge.begin(info2); + + // Configure binning + binner.setChannels(2); + binner.setBits(16); + binner.setBinSize(BINSIZE); + binner.setAverage(true); + + // Define CSV Output + serial_out.begin(info_out); + +} + +// Arduino loop - copy sound to out with conversion +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/tests/conversion/channel-converter-bindiff/channel-converter-bindiff.ino b/examples/tests/conversion/channel-converter-bindiff/channel-converter-bindiff.ino new file mode 100644 index 000000000..c8b70a1f8 --- /dev/null +++ b/examples/tests/conversion/channel-converter-bindiff/channel-converter-bindiff.ino @@ -0,0 +1,56 @@ +/** + * @file channel-converter-bin-diff.ino + * @author Urs Utzinger + * @brief On two channels reduce number of samples by binning, then compute difference between two channels + * @copyright GPLv3 + **/ + +#include "AudioTools.h" + +#define BINSIZE 4 + +AudioInfo info1(44100, 1, 16); +AudioInfo info2(44100, 2, 16); +AudioInfo info_out(44100/BINSIZE, 1, 16); +SineWaveGenerator sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000 +SineWaveGenerator sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound1(sineWave1); // stream generated from sine wave +GeneratedSoundStream sound2(sineWave2); // stream generated from sine wave +InputMerge imerge; +ChannelBinDiff bindiffer; // Binning each channel by average length, setup see below +ConverterStream converted_stream(imerge, bindiffer); // pipe the merged sound to the converter +CsvOutput serial_out(Serial); // serial output +StreamCopy copier(serial_out, converted_stream); // stream the binner output to serial port + +// Arduino Setup +void setup(void) { + // Open Serial + Serial.begin(115200); + while(!Serial); + + AudioLogger::instance().begin(Serial, AudioLogger::Debug); // Info, Warning, Error, Debug + + // Setup sine wave + sineWave1.begin(info1, N_B4); + sineWave2.begin(info1, N_B5); + + // Merge input to stereo + imerge.add(sound1); + imerge.add(sound2); + imerge.begin(info2); + + // Setup binning + bindiffer.setChannels(2); + bindiffer.setBits(16); + bindiffer.setBinSize(BINSIZE); + bindiffer.setAverage(true); + + // Define CSV Output + serial_out.begin(info_out); + +} + +// Arduino loop - copy sound to out with conversion +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/tests/conversion/channel-converter-decimate/channel-converter-decimate.ino b/examples/tests/conversion/channel-converter-decimate/channel-converter-decimate.ino new file mode 100644 index 000000000..90602cc03 --- /dev/null +++ b/examples/tests/conversion/channel-converter-decimate/channel-converter-decimate.ino @@ -0,0 +1,54 @@ +/** + * @file channel-converter-decimate.ino + * @author Urs Utzinger + * @brief Reduce samples by binning; which is summing consecutive samples and optionally dividing by the number of samples summed. + * @copyright GPLv3 + **/ + +#include "AudioTools.h" +#define FACTOR 4 + +AudioInfo info1(44100, 1, 16); +AudioInfo info2(44100, 2, 16); +AudioInfo infoO(44100/FACTOR, 2, 16); +SineWaveGenerator sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000 +SineWaveGenerator sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound1(sineWave1); // stream generated from sine wave +GeneratedSoundStream sound2(sineWave2); // stream generated from sine wave +InputMerge imerge; +Decimate decimater; // decimate by 4 on 1 channel +ConverterStream decimated_stream(imerge, decimater); // pipe the sound to the binner +CsvOutput serial_out(Serial); // serial output +StreamCopy copier(serial_out, decimated_stream); // stream the binner output to serial port + +// Arduino Setup +void setup(void) { + + // Open Serial + Serial.begin(115200); + while(!Serial); + + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + // + decimater.setChannels(2); + decimater.setFactor(FACTOR); + + // Merge input to stereo + imerge.add(sound1); + imerge.add(sound2); + imerge.begin(info2); + + // Setup sine wave + sineWave1.begin(info1, N_B4); + sineWave2.begin(info1, N_B5); + + // Define CSV Output + serial_out.begin(infoO); + + } + +// Arduino loop - copy sound to out with conversion +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/tests/conversion/channel-converter-diff/channel-converter-diff.ino b/examples/tests/conversion/channel-converter-diff/channel-converter-diff.ino new file mode 100644 index 000000000..ae08b254b --- /dev/null +++ b/examples/tests/conversion/channel-converter-diff/channel-converter-diff.ino @@ -0,0 +1,48 @@ +/** + * @file channel-converter-diff.ino + * @brief Test calculating parwise difference of channels + * @author Urs Utzinger + * @copyright GPLv3 + **/ + +#include "AudioTools.h" + +AudioInfo info1(44100, 1, 16); +AudioInfo info2(44100, 2, 16); +SineWaveGenerator sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000 +SineWaveGenerator sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound1(sineWave1); // stream generated from sine wave1 +GeneratedSoundStream sound2(sineWave2); // stream generated from sine wave2 +InputMerge imerge; // merge two inputs to stereo +ChannelDiff differ; // channel averager +ConverterStream diffed_stream(imerge, differ); // pipe the sound to the averager +CsvOutput serial_out(Serial); // serial output +StreamCopy copier(serial_out, diffed_stream); // stream the binner output to serial port + +// Arduino Setup +void setup(void) { + + // Open Serial + Serial.begin(115200); + while(!Serial); // wait for Serial to be ready + + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + // Setup sine waves + sineWave1.begin(info1, N_B4); + sineWave2.begin(info1, N_B5); + + // Merge input to stereo + imerge.add(sound1); + imerge.add(sound2); + imerge.begin(info2); + + // Define CSV Output + serial_out.begin(info1); + +} + +// Arduino loop - copy sound to out with conversion +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/src/AudioAnalog/AnalogConfigESP32V1.h b/src/AudioAnalog/AnalogConfigESP32V1.h index 649eb2f57..6d3f0224a 100644 --- a/src/AudioAnalog/AnalogConfigESP32V1.h +++ b/src/AudioAnalog/AnalogConfigESP32V1.h @@ -12,7 +12,7 @@ #if CONFIG_IDF_TARGET_ESP32 # define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 # define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1 -# define ADC_CHANNELS {ADC_CHANNEL_6, ADC_CHANNEL_7} +# define ADC_CHANNELS {ADC_CHANNEL_7, ADC_CHANNEL_0, ADC_CHANNEL_3, ADC_CHANNEL_6, ADC_CHANNEL_4, ADC_CHANNEL_5} # define AUDIO_ADC_GET_CHANNEL(p_data) ((p_data)->type1.channel) # define AUDIO_ADC_GET_DATA(p_data) ((p_data)->type1.data) # define HAS_ESP32_DAC @@ -21,20 +21,20 @@ # define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2 # define AUDIO_ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel) # define AUDIO_ADC_GET_DATA(p_data) ((p_data)->type2.data) -# define ADC_CHANNELS {ADC_CHANNEL_2, ADC_CHANNEL_3} +# define ADC_CHANNELS {ADC_CHANNEL_7, ADC_CHANNEL_0, ADC_CHANNEL_3, ADC_CHANNEL_6, ADC_CHANNEL_4, ADC_CHANNEL_5} # define HAS_ESP32_DAC #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 # define ADC_CONV_MODE ADC_CONV_ALTER_UNIT //ESP32C3 only supports alter mode # define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2 # define AUDIO_ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel) # define AUDIO_ADC_GET_DATA(p_data) ((p_data)->type2.data) -# define ADC_CHANNELS {ADC_CHANNEL_2, ADC_CHANNEL_3} +# define ADC_CHANNELS {ADC_CHANNEL_7, ADC_CHANNEL_0, ADC_CHANNEL_3, ADC_CHANNEL_6, ADC_CHANNEL_4, ADC_CHANNEL_5} #elif CONFIG_IDF_TARGET_ESP32S3 # define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 # define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2 # define AUDIO_ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel) # define AUDIO_ADC_GET_DATA(p_data) ((p_data)->type2.data) -# define ADC_CHANNELS {ADC_CHANNEL_2, ADC_CHANNEL_3} // or channel 4 & 5 +# define ADC_CHANNELS {ADC_CHANNEL_8, ADC_CHANNEL_9, ADC_CHANNEL_4, ADC_CHANNEL_5, ADC_CHANNEL_4, ADC_CHANNEL_5} #endif // #define GET_ADC_UNIT_FROM_CHANNEL(x) ((x >> 3) & 0x1) @@ -77,8 +77,7 @@ class AnalogConfigESP32V1 : public AudioInfo { adc_digi_output_format_t adc_output_type = ADC_OUTPUT_TYPE; uint8_t adc_attenuation = ADC_ATTEN_DB_12; // full voltage range of 3.9V uint8_t adc_bit_width = SOC_ADC_DIGI_MAX_BITWIDTH; - /// ESP32: ADC_CHANNEL_6, ADC_CHANNEL_7; others ADC_CHANNEL_2, ADC_CHANNEL_3 - adc_channel_t adc_channels[2] = ADC_CHANNELS; + adc_channel_t adc_channels[6] = ADC_CHANNELS; /// Default constructor AnalogConfigESP32V1(RxTxMode rxtxMode=TX_MODE) { diff --git a/src/AudioTools/BaseConverter.h b/src/AudioTools/BaseConverter.h index 7e6d1808c..991a57f88 100644 --- a/src/AudioTools/BaseConverter.h +++ b/src/AudioTools/BaseConverter.h @@ -39,7 +39,7 @@ class BaseConverter { */ class NOPConverter : public BaseConverter { public: - virtual size_t convert(uint8_t(*src), size_t size) { return size; }; + size_t convert(uint8_t(*src), size_t size) override { return size; }; }; /** @@ -110,7 +110,7 @@ class ConverterAutoCenterT : public BaseConverter { this->is_dynamic = isDynamic; } - size_t convert(uint8_t(*src), size_t byte_count) { + size_t convert(uint8_t(*src), size_t byte_count) override { size_t size = byte_count / channels / sizeof(T); T *sample = (T *)src; setup((T *)src, size); @@ -194,7 +194,10 @@ class ConverterAutoCenter : public BaseConverter { begin(channels, bitsPerSample); } ~ConverterAutoCenter() { - if (p_converter != nullptr) delete p_converter; + if (p_converter != nullptr) { + delete p_converter; + p_converter = nullptr; + } } void begin(int channels, int bitsPerSample, bool isDynamic = false) { @@ -241,7 +244,7 @@ class ConverterSwitchLeftAndRight : public BaseConverter { public: ConverterSwitchLeftAndRight(int channels = 2) { this->channels = channels; } - size_t convert(uint8_t *src, size_t byte_count) { + size_t convert(uint8_t *src, size_t byte_count) override { if (channels == 2) { int size = byte_count / channels / sizeof(T); T *sample = (T *)src; @@ -361,7 +364,7 @@ class ConverterToInternalDACFormat : public BaseConverter { public: ConverterToInternalDACFormat(int channels = 2) { this->channels = channels; } - size_t convert(uint8_t *src, size_t byte_count) { + size_t convert(uint8_t *src, size_t byte_count) override { int size = byte_count / channels / sizeof(T); T *sample = (T *)src; for (int i = 0; i < size; i++) { @@ -486,16 +489,21 @@ class DecimateT : public BaseConverter { DecimateT(int factor, int channels) { setChannels(channels); setFactor(factor); + count = 0; // Initialize count to 0 } + /// Defines the number of channels void setChannels(int channels) { this->channels = channels; } - /// Sets the factor: e.g. with 4 we keep every forth sample + /// Sets the factor: e.g. with 4 we keep every fourth sample void setFactor(int factor) { this->factor = factor; } size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } size_t convert(uint8_t *target, uint8_t *src, size_t size) { + + assert(size % (sizeof(T) * channels) == 0); // Ensure proper buffer size + int frame_count = size / (sizeof(T) * channels); T *p_target = (T *)target; T *p_source = (T *)src; @@ -504,23 +512,24 @@ class DecimateT : public BaseConverter { for (int i = 0; i < frame_count; i++) { if (++count == factor) { count = 0; - // only keep even samples + // Only keep every "factor" samples for (int ch = 0; ch < channels; ch++) { - *p_target++ = p_source[i + ch]; + *p_target++ = p_source[i * channels + ch]; // Corrected indexing result_size += sizeof(T); } } } + // LOGI("%d: %d -> %d ",factor, (int)size, (int)result_size); return result_size; } - operator bool() { return factor > 1; }; + operator bool() { return factor > 1; } protected: int channels = 2; int factor = 1; - uint16_t count = 0; + uint16_t count; }; /** @@ -570,106 +579,175 @@ class Decimate : public BaseConverter { }; /** - * @brief Provides a reduced sampling rate through binning + * @brief We reduce the number of samples in a datastream by summing (binning) or averaging. + * This will result in the same number of channels but binSize times less samples. + * If Average is true the sum is divided by binSize. + * @author Urs Utzinger * @ingroup convert + * @tparam T */ // Helper template to define the integer type for the summation based on input // data type T template -struct AppropriateSumType; -template <> -struct AppropriateSumType { - using type = int16_t; -}; -template <> -struct AppropriateSumType { - using type = int32_t; -}; -template <> -struct AppropriateSumType { - using type = int32_t; -}; -template <> -struct AppropriateSumType { - using type = int64_t; +struct AppropriateSumType { + using type = T; }; -/** - * @brief Provides reduced sampling rates through binning: typed implementation - * @ingroup convert - */ +template <> +struct AppropriateSumType { + using type = int16_t; +}; + +template <> +struct AppropriateSumType { + using type = int32_t; +}; + +template <> +struct AppropriateSumType { + using type = int32_t; // Assuming int24_t is a custom 24-bit integer type +}; + +template <> +struct AppropriateSumType { + using type = int64_t; +}; template class BinT : public BaseConverter { public: + BinT() = default; BinT(int binSize, int channels, bool average) { setChannels(channels); setBinSize(binSize); - setAverage(average); + setAverage(average); + this->partialBinSize = 0; + this->partialBin = new T[channels]; + std::fill(this->partialBin, this->partialBin + channels, 0); // Initialize partialBin with zeros } - /// Defines the number of channels + + ~BinT() { + delete[] this->partialBin; + } + void setChannels(int channels) { this->channels = channels; } - - /// Sets the bins: e.g. with 4 we sum 4 sample void setBinSize(int binSize) { this->binSize = binSize; } - - /// Enables averaging: e.g. when true it divides the sum by number of bins void setAverage(bool average) { this->average = average; } size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } size_t convert(uint8_t *target, uint8_t *src, size_t size) { - int frame_count = size / (sizeof(T) * channels); + // The binning takes the following into account + // 1) if size is too small it will add up data to partialBin and return 0 size + // 2) if there is sufficient data to fill Bins but there is partial data remaining it will be added to the partial Bin + // 3) if there was previous partial Bin it will be filled with the new data + // 4) if there is insufficient new data to fill the partial Bin it will fill the partial Bin with the new data + LOGD("Binning %d samples of %d size buffer", size / sizeof(T), size); + + assert(size % (sizeof(T) * channels) == 0); // Ensure proper buffer size + + int sample_count = size / (sizeof(T) * channels); // new available samples in each channel + int total_samples = partialBinSize + sample_count; // total samples available for each channel including previous number of sample in partial bin + int bin_count = total_samples / binSize; // number of bins we can make + int remaining_samples = total_samples % binSize; // remaining samples after binning T *p_target = (T *)target; T *p_source = (T *)src; size_t result_size = 0; - // Allocate stack memory for sums to avoid dynamic allocation overhead. - // Ensure you have enough stack space or adjust accordingly for your - // environment. + // Allocate sum for each channel with appropriate type typename AppropriateSumType::type sums[channels]; + int current_sample = 0; // current sample index + + // Is there a partial bin from the previous call? + // ---- + if (partialBinSize > 0) { + + int samples_needed = binSize - partialBinSize; + bool have_enough_samples = (samples_needed < sample_count); + int samples_to_bin = have_enough_samples ? samples_needed : sample_count; - for (int i = 0; i < frame_count; i += binSize) { - // Initialize sums for each channel to the first element in the bin for (int ch = 0; ch < channels; ch++) { - sums[ch] = (i * channels + ch < frame_count * channels) - ? p_source[i * channels + ch] - : static_cast(0); + sums[ch] = partialBin[ch]; } - // Sum up binSize number of samples for each channel, starting from the - // second sample in the bin - for (int j = 1; j < binSize && (i + j) < frame_count; j++) { + for (int j = 0; j < samples_to_bin; j++) { for (int ch = 0; ch < channels; ch++) { - sums[ch] += p_source[(i + j) * channels + ch]; + sums[ch] += p_source[current_sample * channels + ch]; } + current_sample++; } - // Compute average or sum for each channel and write to target buffer - for (int ch = 0; ch < channels; ch++) { + if (have_enough_samples) { + // Store the completed bin if (average) { - T avg = static_cast(sums[ch] / binSize); - *p_target++ = avg; + for (int ch = 0; ch < channels; ch++) { + p_target[result_size / sizeof(T)] = static_cast(sums[ch] / binSize); + result_size += sizeof(T); + } } else { - *p_target++ = static_cast(sums[ch]); + for (int ch = 0; ch < channels; ch++) { + p_target[result_size / sizeof(T)] = static_cast(sums[ch]); + result_size += sizeof(T); + } } + partialBinSize = 0; + } else { + // Not enough samples to complete the bin, update partialBin + for (int ch = 0; ch < channels; ch++) { + partialBin[ch] = sums[ch]; + } + partialBinSize += current_sample; + return result_size; } - result_size += sizeof(T) * channels; } - // LOGI("%d: %d -> %d avg:%s", binSize, (int)size, (int)result_size, average - // ? "on" : "off"); + // Fill bins + // ---- + for (int i = 0; i < bin_count; i++) { + for (int ch = 0; ch < channels; ch++) { + sums[ch] = p_source[current_sample * channels + ch]; // Initialize sums with first value in the input buffer + } + + for (int j = 1; j < binSize; j++) { + for (int ch = 0; ch < channels; ch++) { + sums[ch] += p_source[(current_sample + j) * channels + ch]; + } + } + current_sample += binSize; + + // Store the bin result + if (average) { + for (int ch = 0; ch < channels; ch++) { + p_target[result_size / sizeof(T)] = static_cast(sums[ch] / binSize); + result_size += sizeof(T); + } + } else { + for (int ch = 0; ch < channels; ch++) { + p_target[result_size / sizeof(T)] = static_cast(sums[ch]); + result_size += sizeof(T); + } + } + } + + // Store the remaining samples in the partial bin + // ---- + for (int i = 0; i < remaining_samples; i++) { + for (int ch = 0; ch < channels; ch++) { + partialBin[ch] += p_source[(current_sample + i) * channels + ch]; + } + } + partialBinSize = remaining_samples; + return result_size; } - operator bool() { return binSize > 1; }; - protected: int channels = 2; int binSize = 1; bool average = true; - uint16_t count = 0; + T *partialBin; + int partialBinSize; }; /** @@ -681,17 +759,15 @@ class Bin : public BaseConverter { public: Bin() = default; Bin(int binSize, int channels, bool average, int bits_per_sample) { - setBinSize(binSize); setChannels(channels); + setBinSize(binSize); setAverage(average); - setBits(bits_per_sample); + setBits(bits_per_sample); } - /// Defines the number of channels + void setChannels(int channels) { this->channels = channels; } void setBits(int bits) { this->bits = bits; } - /// Sets the binning size: e.g. with 4 we sum 4 samples void setBinSize(int binSize) { this->binSize = binSize; } - /// Enables averaging: e.g. when true it divides the sum by number of bins void setAverage(bool average) { this->average = average; } size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } @@ -718,11 +794,8 @@ class Bin : public BaseConverter { return 0; } } - return 0; } - operator bool() { return binSize > 1; }; - protected: int channels = 2; int bits = 16; @@ -730,6 +803,380 @@ class Bin : public BaseConverter { bool average = false; }; +/** + * @brief We calculate the difference between pairs of channels in a datastream. + * E.g. if we have 4 channels we end up with 2 channels. + * The channels will be + * channel_1 - channel_2 + * channel_3 - channel_4 + * This is similar to background subtraction between two channels but will + * also work for quadric, sexic or octic audio. + * This will not work if you provide single channel data! + * @author Urs Utzinger + * @ingroup convert + * @tparam T + */ + +template +class ChannelDiffT : public BaseConverter { + public: + ChannelDiffT() {} + + size_t convert(uint8_t *src, size_t size) override { return convert(src, src, size); } + + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + LOGD("convert %d samples of %d size buffer", size / sizeof(T), size); + + // Ensure the buffer size is even for pairs of channels + assert(size % (sizeof(T) * 2) == 0); + + int sample_count = size / (sizeof(T) * 2); // Each pair of channels produces one output sample + T *p_result = (T *)target; + T *p_source = (T *)src; + + for (int i = 0; i < sample_count; i++) { + *p_result++ = *p_source++ - *p_source++; + } + + return sizeof(T) * sample_count; + } +}; + +class ChannelDiff : public BaseConverter { + public: + ChannelDiff() = default; + ChannelDiff(int bitsPerSample) { + setBits(bitsPerSample); + } + void setBits(int bits) { this->bits = bits; } + + size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + switch (bits) { + case 8: { + ChannelDiffT cd8; + return cd8.convert(target, src, size); + } + case 16: { + ChannelDiffT cd16; + return cd16.convert(target, src, size); + } + case 24: { + ChannelDiffT cd24; + return cd24.convert(target, src, size); + } + case 32: { + ChannelDiffT cd32; + return cd32.convert(target, src, size); + } + default: { + LOGE("Number of bits %d not supported.", bits); + return 0; + } + } + } + + protected: + int bits = 16; +}; + +/** + * @brief We average pairs of channels in a datastream. + * E.g. if we have 4 channels we end up with 2 channels. + * The channels will be + * (channel_1 + channel_2)/2 + * (channel_3 - channel_4)/2. + * This is equivalent of stereo to mono conversion but will + * also work for quadric, sexic or octic audio. + * This will not work if you provide single channel data! + * @author Urs Utzinger + * @ingroup convert + * @tparam T + */ +template +class ChannelAvgT : public BaseConverter { + public: + ChannelAvgT() {} + + size_t convert(uint8_t *src, size_t size) override { return convert(src, src, size); } + + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + LOGD("convert %d samples of %d size buffer", size / sizeof(T), size); + + assert(size % (sizeof(T) * 2) == 0); // Ensure even number of samples for pairs + + int sample_count = size / (sizeof(T) * 2); // Each pair of channels produces one output sample + T *p_result = (T *)target; + T *p_source = (T *)src; + + for (int i = 0; i < sample_count; i++) { + *p_result++ = (*p_source++ + *p_source++) / 2; // Average the pair of channels + } + + return sizeof(T) * sample_count; + } +}; + +class ChannelAvg : public BaseConverter { + public: + ChannelAvg() = default; + ChannelAvg(int bitsPerSample) { + setBits(bitsPerSample); + } + void setBits(int bits) { this->bits = bits; } + + size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + switch (bits) { + case 8: { + ChannelAvgT ca8; + return ca8.convert(target, src, size); + } + case 16: { + ChannelAvgT ca16; + return ca16.convert(target, src, size); + } + case 24: { + ChannelAvgT ca24; + return ca24.convert(target, src, size); + } + case 32: { + ChannelAvgT ca32; + return ca32.convert(target, src, size); + } + default: { + LOGE("Number of bits %d not supported.", bits); + return 0; + } + } + } + + protected: + int bits = 16; +}; + +/** + * @brief We first bin the channels then we calculate the difference between pairs of channels in a datastream. + * E.g. For binning, if we bin 4 samples in each channel we will have 4 times less samples per channel + * E.g. For subtracting if we have 4 channels we end up with 2 channels. + * The channels will be + * channel_1 - channel_2 + * channel_3 - channel_4 + * This is the same as combining binning and subtracting channels. + * This will not work if you provide single channel data! + * @author Urs Utzinger + * @ingroup convert + * @tparam T + */ + +template +class ChannelBinDiffT : public BaseConverter { + public: + ChannelBinDiffT() = default; + ChannelBinDiffT(int binSize, int channels, bool average) { + setChannels(channels); + setBinSize(binSize); + setAverage(average); + this->partialBinSize = 0; + this->partialBin = new T[channels]; + std::fill(this->partialBin, this->partialBin + channels, 0); // Initialize partialBin with zeros + } + + ~ChannelBinDiffT() { + delete[] this->partialBin; + } + + void setChannels(int channels) { + assert((channels % 2) == 0); // Ensure even channel size + this->channels = channels; + } + void setBinSize(int binSize) { this->binSize = binSize; } + void setAverage(bool average) { this->average = average; } + + size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } + + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + // The binning works the same as in the BinT class + // Here we add subtraction before we store the bins + LOGD("Binning and Subtracting %d samples of %d size buffer", size / sizeof(T), size); + + assert(size % (sizeof(T) * channels) == 0); // Ensure proper buffer size + + int sample_count = size / (sizeof(T) * channels); // new available samples in each channel + int total_samples = partialBinSize + sample_count; // total samples available for each channel including previous number of sample in partial bin + int bin_count = total_samples / binSize; // number of bins we can make + int remaining_samples = total_samples % binSize; // remaining samples after binning + T *p_target = (T *)target; + T *p_source = (T *)src; + size_t result_size = 0; + + // Allocate sum for each channel with appropriate type + typename AppropriateSumType::type sums[channels]; + int current_sample = 0; // current sample index + + // Is there a partial bin from the previous call? + // ---- + if (partialBinSize > 0) { + + LOGD("Deal with partial bins"); + + int samples_needed = binSize - partialBinSize; + bool have_enough_samples = (samples_needed < sample_count); + int samples_to_bin = have_enough_samples ? samples_needed : sample_count; + + // initialize + for (int ch = 0; ch < channels; ch++) { + sums[ch] = partialBin[ch]; + } + + // continue binning + for (int j = 0; j < samples_to_bin; j++) { + for (int ch = 0; ch < channels; ch++) { + sums[ch] += p_source[current_sample * channels + ch]; + } + current_sample++; + } + + // store the bin results or update the partial bin + if (have_enough_samples) { + // Subtract two channels and store the completed bin + if (average) { + for (int ch = 0; ch < channels; ch+=2) { + p_target[result_size / sizeof(T)] = static_cast((sums[ch] - sums[ch+1]) / binSize); + result_size += sizeof(T); + } + } else { + for (int ch = 0; ch < channels; ch+=2) { + p_target[result_size / sizeof(T)] = static_cast((sums[ch] - sums[ch+1])); + result_size += sizeof(T); + } + } + partialBinSize = 0; + LOGD("Partial bins are empty"); + + } else { + // Not enough samples to complete the bin, update partialBin + for (int ch = 0; ch < channels; ch++) { + partialBin[ch] = sums[ch]; + } + partialBinSize += current_sample; + LOGD("Partial bins were updated"); + return result_size; + } + } + + // Fill bins + // ---- + LOGD("Fillin bins"); + for (int i = 0; i < bin_count; i++) { + + LOGD("Current sample %d", current_sample); + + for (int ch = 0; ch < channels; ch++) { + sums[ch] = p_source[current_sample * channels + ch]; // Initialize sums with first value in the input buffer + } + + for (int j = 1; j < binSize; j++) { + for (int ch = 0; ch < channels; ch++) { + sums[ch] += p_source[(current_sample + j) * channels + ch]; + } + } + current_sample += binSize; + + // Finish binning, then subtact two channel and store the result + if (average) { + for (int ch = 0; ch < channels; ch+=2) { + p_target[result_size / sizeof(T)] = static_cast((sums[ch]-sums[ch+1]) / binSize); + result_size += sizeof(T); + } + } else { + for (int ch = 0; ch < channels; ch+=2) { + p_target[result_size / sizeof(T)] = static_cast((sums[ch]-sums[ch+1])); + result_size += sizeof(T); + } + } + } + + // Store the remaining samples in the partial bin + // ---- + LOGD("Updating partial bins"); + for (int i = 0; i < remaining_samples; i++) { + for (int ch = 0; ch < channels; ch++) { + partialBin[ch] += p_source[(current_sample + i) * channels + ch]; + } + } + partialBinSize = remaining_samples; + + return result_size; + } + + protected: + int channels = 2; + int binSize = 4; + bool average = true; + T *partialBin; + int partialBinSize; +}; + +/** + * @brief Provides combination of binning and subtracting channels + * @author Urs Utzinger + * @ingroup convert + * @tparam T + */ + +class ChannelBinDiff : public BaseConverter { + public: + ChannelBinDiff() = default; + ChannelBinDiff(int binSize, int channels, bool average, int bits_per_sample) { + setChannels(channels); + setBinSize(binSize); + setAverage(average); + setBits(bits_per_sample); + } + + void setChannels(int channels) { + assert((channels % 2) == 0); // Ensure even channel size + this->channels = channels; + } + void setBits(int bits) { this->bits = bits; } + void setBinSize(int binSize) { this->binSize = binSize; } + void setAverage(bool average) { this->average = average; } + + size_t convert(uint8_t *src, size_t size) { return convert(src, src, size); } + size_t convert(uint8_t *target, uint8_t *src, size_t size) { + switch (bits) { + case 8: { + ChannelBinDiffT bd8(binSize, channels, average); + return bd8.convert(target, src, size); + } + case 16: { + ChannelBinDiffT bd16(binSize, channels, average); + return bd16.convert(target, src, size); + } + case 24: { + ChannelBinDiffT bd24(binSize, channels, average); + return bd24.convert(target, src, size); + } + case 32: { + ChannelBinDiffT bd32(binSize, channels, average); + return bd32.convert(target, src, size); + } + default: { + LOGE("Number of bits %d not supported.", bits); + return 0; + } + } + } + + protected: + int channels = 2; + int bits = 16; + int binSize = 4; + bool average = true; +}; + + /** * @brief Increases the channel count * @ingroup convert @@ -1018,7 +1465,7 @@ class Converter1Channel : public BaseConverter { public: Converter1Channel(Filter &filter) { this->p_filter = &filter; } - size_t convert(uint8_t *src, size_t size) { + size_t convert(uint8_t *src, size_t size) override { T *data = (T *)src; for (size_t j = 0; j < size; j++) { data[j] = p_filter->process(data[j]);