Updates to BaseConverter and continous ADC of ESP32 (#1621)

* Converter Updates

Updated Binning converter. Handling of partial bins. Results in binsize less samples
Added ChannelDiff converter. Computes pairwise difference between channels. results in half the channels
Added ChannelAvg converter. Computes pairwise average between channels. Results in half the channels

* Testing and Debugging of Diff, Bin and BinDiff
This commit is contained in:
Urs Utzinger 2024-07-18 20:02:39 -07:00 committed by GitHub
parent 0e6f90c44d
commit 0f3329630d
9 changed files with 794 additions and 77 deletions

View File

@ -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

View File

@ -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<int16_t> sineWave1(32000); // subclass of SoundGenerator with max amplitude of 32000
SineWaveGenerator<int16_t> sineWave2(32000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound1(sineWave1); // stream generated from sine wave1
GeneratedSoundStream<int16_t> sound2(sineWave2); // stream generated from sine wave2
InputMerge<int16_t> imerge; // merge two inputs to stereo
ChannelAvg averager; // channel averager
ConverterStream<int16_t> averaged_stream(imerge, averager); // pipe the sound to the averager
CsvOutput<int16_t> 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();
}

View File

@ -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<int16_t> sineWave1(32000); // subclass of SoundGenerator with max amplitude of 32000
SineWaveGenerator<int16_t> sineWave2(32000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound1(sineWave1); // stream generated from sine wave1
GeneratedSoundStream<int16_t> sound2(sineWave2); // stream generated from sine wave2
InputMerge<int16_t> imerge; // merge two inputs to stereo
Bin binner; // channel averager
ConverterStream<int16_t> binned_stream(imerge, binner); // pipe the sound to the averager
CsvOutput<int16_t> 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();
}

View File

@ -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<int16_t> sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000
SineWaveGenerator<int16_t> sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound1(sineWave1); // stream generated from sine wave
GeneratedSoundStream<int16_t> sound2(sineWave2); // stream generated from sine wave
InputMerge<int16_t> imerge;
ChannelBinDiff bindiffer; // Binning each channel by average length, setup see below
ConverterStream<int16_t> converted_stream(imerge, bindiffer); // pipe the merged sound to the converter
CsvOutput<int16_t> 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();
}

View File

@ -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<int16_t> sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000
SineWaveGenerator<int16_t> sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound1(sineWave1); // stream generated from sine wave
GeneratedSoundStream<int16_t> sound2(sineWave2); // stream generated from sine wave
InputMerge<int16_t> imerge;
Decimate decimater; // decimate by 4 on 1 channel
ConverterStream<int16_t> decimated_stream(imerge, decimater); // pipe the sound to the binner
CsvOutput<int16_t> 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();
}

View File

@ -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<int16_t> sineWave1(16000); // subclass of SoundGenerator with max amplitude of 32000
SineWaveGenerator<int16_t> sineWave2(16000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound1(sineWave1); // stream generated from sine wave1
GeneratedSoundStream<int16_t> sound2(sineWave2); // stream generated from sine wave2
InputMerge<int16_t> imerge; // merge two inputs to stereo
ChannelDiff differ; // channel averager
ConverterStream<int16_t> diffed_stream(imerge, differ); // pipe the sound to the averager
CsvOutput<int16_t> 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();
}

View File

@ -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) {

View File

@ -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 <typename T>
struct AppropriateSumType;
template <>
struct AppropriateSumType<int8_t> {
using type = int16_t;
};
template <>
struct AppropriateSumType<int16_t> {
using type = int32_t;
};
template <>
struct AppropriateSumType<int24_t> {
using type = int32_t;
};
template <>
struct AppropriateSumType<int32_t> {
using type = int64_t;
struct AppropriateSumType {
using type = T;
};
/**
* @brief Provides reduced sampling rates through binning: typed implementation
* @ingroup convert
*/
template <>
struct AppropriateSumType<int8_t> {
using type = int16_t;
};
template <>
struct AppropriateSumType<int16_t> {
using type = int32_t;
};
template <>
struct AppropriateSumType<int24_t> {
using type = int32_t; // Assuming int24_t is a custom 24-bit integer type
};
template <>
struct AppropriateSumType<int32_t> {
using type = int64_t;
};
template <typename T>
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<T>::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<T>(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<T>(sums[ch] / binSize);
*p_target++ = avg;
for (int ch = 0; ch < channels; ch++) {
p_target[result_size / sizeof(T)] = static_cast<T>(sums[ch] / binSize);
result_size += sizeof(T);
}
} else {
*p_target++ = static_cast<T>(sums[ch]);
for (int ch = 0; ch < channels; ch++) {
p_target[result_size / sizeof(T)] = static_cast<T>(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<T>(sums[ch] / binSize);
result_size += sizeof(T);
}
} else {
for (int ch = 0; ch < channels; ch++) {
p_target[result_size / sizeof(T)] = static_cast<T>(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 <typename T>
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<int8_t> cd8;
return cd8.convert(target, src, size);
}
case 16: {
ChannelDiffT<int16_t> cd16;
return cd16.convert(target, src, size);
}
case 24: {
ChannelDiffT<int24_t> cd24;
return cd24.convert(target, src, size);
}
case 32: {
ChannelDiffT<int32_t> 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 <typename T>
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<int8_t> ca8;
return ca8.convert(target, src, size);
}
case 16: {
ChannelAvgT<int16_t> ca16;
return ca16.convert(target, src, size);
}
case 24: {
ChannelAvgT<int24_t> ca24;
return ca24.convert(target, src, size);
}
case 32: {
ChannelAvgT<int32_t> 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 <typename T>
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<T>::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<T>((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<T>((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<T>((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<T>((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<int8_t> bd8(binSize, channels, average);
return bd8.convert(target, src, size);
}
case 16: {
ChannelBinDiffT<int16_t> bd16(binSize, channels, average);
return bd16.convert(target, src, size);
}
case 24: {
ChannelBinDiffT<int24_t> bd24(binSize, channels, average);
return bd24.convert(target, src, size);
}
case 32: {
ChannelBinDiffT<int32_t> 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<T> &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]);