Skip to content

Commit e7c6c89

Browse files
committed
Resample
1 parent 9913454 commit e7c6c89

File tree

6 files changed

+189
-15
lines changed

6 files changed

+189
-15
lines changed

examples/sandbox/basic-a2dp-resample-i2s/basic-a2dp-resample-i2s.ino

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#define USE_A2DP
1010
#include "AudioConfigLocal.h"
1111
#include "AudioTools.h"
12-
#include "AudioExperiments/Resample.h"
1312

1413
BluetoothA2DPSink a2dp_sink;
1514
I2SStream i2s;

src/AudioTools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "AudioAnalog/AnalogAudio.h"
2020
#include "AudioTools/AudioLogger.h"
2121
#include "AudioTools/AudioStreams.h"
22+
#include "AudioTools/Resample.h"
2223
#include "AudioTools/AudioOutput.h"
2324
#include "AudioTools/AudioCopy.h"
2425
#include "AudioMetaData/MetaData.h"

src/AudioExperiments/Resample.h renamed to src/AudioTools/Resample.h

Lines changed: 180 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,58 @@
11
#pragma once
22

33
#include "AudioTools/AudioStreams.h"
4+
5+
namespace audio_tools {
6+
47
/**
5-
* @brief A simple implementation which changes the sample rate by the indicated factor.
8+
* @brief A simple implementation which changes the sample rate by the indicated integer factor.
69
* To downlample we calculate the avarage of n (=factor) samples. To upsample we interpolate
710
* the missing samples. If the indicated factor is positive we upsample if it is negative
811
* we downsample.
9-
*
10-
* @tparam T
12+
* @author Phil Schatzmann
13+
* @copyright GPLv3
14+
* @tparam T data type of audio data
1115
*/
1216
template<typename T>
1317
class Resample : public AudioStreamX {
1418
public:
19+
/**
20+
* @brief Construct a new Resample object
21+
* call setOut and begin to setup the required parameters
22+
*/
23+
Resample() = default;
1524
/**
1625
* @brief Construct a new Converter Resample object
1726
*
1827
* @param channels number of channels (default 2)
1928
* @param factor use a negative value to downsample
2029
*/
2130
Resample(Print &out, int channels=2, int factor=2 ){
31+
setOut(out);
32+
begin(channels, factor);
33+
}
34+
/**
35+
* @brief Construct a new Resample object
36+
*
37+
* @param in
38+
* @param channels
39+
* @param factor
40+
*/
41+
Resample(Stream &in, int channels=2, int factor=2 ){
42+
setIn(in);
43+
begin(channels, factor);
44+
}
45+
46+
void begin(int channels=2, int factor=2) {
2247
this->channels = channels;
2348
this->factor = factor;
49+
}
50+
51+
void setOut(Print &out){
2452
this->p_out = &out;
2553
}
2654

27-
Resample(Stream &in, int channels=2, int factor=2 ){
28-
this->channels = channels;
29-
this->factor = factor;
55+
void setIn(Stream &in){
3056
this->p_out = &in;
3157
this->p_in = &in;
3258
}
@@ -88,7 +114,6 @@ class Resample : public AudioStreamX {
88114
return byte_count;
89115
}
90116

91-
92117
protected:
93118
Print *p_out=nullptr;
94119
Stream *p_in=nullptr;
@@ -170,5 +195,152 @@ class Resample : public AudioStreamX {
170195
T* p_data(int frame_pos, int channel, T*start){
171196
return frame_pos>=0 ? start+(frame_pos*channels)+channel : last_end+channel;
172197
}
198+
};
199+
200+
enum ResamplePrecision { Low, Medium, High, VeryHigh};
201+
202+
/**
203+
* @brief Class to determine a combination of upsample and downsample rates to achieve any ratio
204+
* @author Phil Schatzmann
205+
* @copyright GPLv3
206+
*/
207+
class ResampleParameterEstimator {
208+
public:
209+
210+
ResampleParameterEstimator() = default;
211+
212+
ResampleParameterEstimator(int fromRate, int toRate, ResamplePrecision precision = Medium){
213+
begin(fromRate, toRate, precision);
214+
}
215+
216+
void begin(int fromRate, int toRate, ResamplePrecision precision = Medium){
217+
this->from_rate = fromRate;
218+
this->to_rate = toRate;
219+
this->precision = precision;
220+
// update result values
221+
calculate();
222+
}
223+
224+
/// prposed factor for upsampling
225+
int factor() {
226+
return fact;
227+
}
228+
229+
/// propose divisor for downsampling
230+
int divisor() {
231+
return div;
232+
}
233+
234+
/// original sample rate
235+
int fromRate(){
236+
return from_rate;
237+
}
238+
239+
/// target sample rate
240+
int toRate(){
241+
return to_rate;
242+
}
243+
244+
/// effective target sample rate by upsampling and then downsampling at different factors
245+
float toRateEffective() {
246+
return to_rate_eff;
247+
}
248+
249+
/// same as factor
250+
int upsample() {
251+
return factor();
252+
}
253+
254+
/// same as division but provides negative number to indicate that we need to downsample
255+
int downsample() {
256+
return - divisor();
257+
}
258+
259+
/// Determines a supported downsampling write size
260+
size_t supportedSize(size_t len){
261+
return (len / div) * div;
262+
}
263+
264+
protected:
265+
int fact=0, div=0;
266+
int from_rate=0, to_rate=0;
267+
float diff=10000000.0;
268+
float to_rate_eff=0;
269+
int div_array[28] = {1, 2, 3, 5, 7, 10, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 100};
270+
int limits[4] = {6, 12, 18, 27 };
271+
int precision=1;
272+
273+
// find the parameters with the lowest difference
274+
void calculate() {
275+
int limit_max = limits[precision];
276+
for (int j=0;j<limit_max;j++){
277+
int tmp_div = div_array[j];
278+
int tmp_fact = rintf(static_cast<float>(to_rate) * tmp_div / from_rate);
279+
float tmp_diff = static_cast<float>(to_rate) - (static_cast<float>(from_rate) * tmp_fact / tmp_div);
280+
LOGD("div: %d, fact %d -> diff: %f", tmp_div,tmp_fact,tmp_diff);
281+
if (abs(tmp_diff)<abs(diff)){
282+
fact = tmp_fact;
283+
div = tmp_div;
284+
diff = tmp_diff;
285+
if (diff==0.0){
286+
break;
287+
}
288+
}
289+
}
290+
to_rate_eff = static_cast<float>(from_rate) * fact / div;
291+
LOGI("div: %d, fact %d -> rate: %d, rate_eff: %f", div,fact,to_rate, to_rate_eff);
292+
}
293+
};
294+
295+
/**
296+
* @brief Stream class which can be used to resample between different sample rates.
297+
* @author Phil Schatzmann
298+
* @copyright GPLv3
299+
* @tparam T data type of audio data
300+
*/
301+
template<typename T>
302+
class ResampleStream : public AudioStreamX {
303+
public:
304+
ResampleStream(Print &out, ResamplePrecision precision = Medium){
305+
this->precision = precision;
306+
up.setOut(down); // we upsample first
307+
down.setOut(out); // so that we can downsample to the requested rate
308+
}
309+
310+
ResampleStream(Stream &in, ResamplePrecision precision = Medium){
311+
this->precision = precision;
312+
up.seIn(down); // we upsample first
313+
down.setIn(in); // so that we can downsample to the requested rate
314+
}
315+
316+
/// Defines the channels and sample rates
317+
void begin(int channels, int fromRate, int toRate){
318+
calc.begin(fromRate, toRate, precision);
319+
up.begin(channels, calc.upsample());
320+
down.begin(channels, calc.downsample());
321+
}
322+
323+
/// Determines the number of bytes which are available for write
324+
int availableForWrite() override { return up.availableForWrite(); }
325+
326+
/// Writes the data up or downsampled to the final destination
327+
size_t write(const uint8_t *src, size_t byte_count) override {
328+
return up.write(src, byte_count);
329+
}
330+
/// Determines the available bytes from the final source stream
331+
int available() override { up.available(); }
332+
333+
/// Reads the up/downsampled bytes
334+
size_t readBytes(uint8_t *src, size_t byte_count) override {
335+
return up.readBytes(src, byte_count);
336+
}
337+
338+
protected:
339+
ResampleParameterEstimator calc;
340+
Resample<T> up;
341+
Resample<T> down;
342+
ResamplePrecision precision;
343+
344+
};
173345

174-
};
346+
} // namespace

src/AudioTools/noise.wav

Whitespace-only changes.

tests/filter/filter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ NoiseGenerator<int16_t> noise(32000); // subclass of
1212
GeneratedSoundStream<int16_t> in_stream(noise); // Stream generated from sine wave
1313
FilteredStream<int16_t, float> in_filtered(in_stream, channels); // Defiles the filter as BaseConverter
1414
PortAudioStream out; // Output to Desktop
15-
StreamCopy copier(out, in_stream); // copies sound to out
15+
StreamCopy copier(out, in_stream, 1012); // copies sound to out
1616

1717

1818
void setup(){

tests/resample/resample.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
// Simple wrapper for Arduino sketch to compilable with cpp in cmake
22
#include "Arduino.h"
33
#include "AudioTools.h"
4-
#include "AudioExperiments/Resample.h"
54

65
uint16_t sample_rate=44100;
76
uint8_t channels = 2; // The stream will have 2 channels
87
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
98
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
10-
CsvStream<int16_t> csv(Serial, channels); // Output to Serial
11-
Resample<int16_t> out(csv, channels, -2); // We double the output sample rate
12-
StreamCopy copier(out, sound); // copies sound to out
9+
CsvStream<int16_t> csv(Serial, channels); // Output to Serial
10+
ResampleStream<int16_t> out(csv); // We double the output sample rate
11+
StreamCopy copier(out, sound, 1012); // copies sound to out
1312

1413
// Arduino Setup
1514
void setup(void) {
1615
// Open Serial
1716
Serial.begin(115200);
18-
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
17+
AudioLogger::instance().begin(Serial, AudioLogger::Info);
1918

2019
// Define CSV Output
2120
auto config = csv.defaultConfig();
2221
config.sample_rate = sample_rate;
2322
config.channels = channels;
2423
csv.begin(config);
2524

25+
// Resample
26+
out.begin(channels, sample_rate, 48000);
27+
2628
// Setup sine wave
2729
sineWave.begin(channels, sample_rate, N_B4);
2830
Serial.println("started...");

0 commit comments

Comments
 (0)