Skip to content

Commit 9a2ec94

Browse files
committed
Resample
1 parent 67c0a28 commit 9a2ec94

File tree

13 files changed

+326
-14
lines changed

13 files changed

+326
-14
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @file streams-generator-audiokit.ino
3+
* @brief Tesing I2S output on audiokit
4+
* @author Phil Schatzmann
5+
* @copyright GPLv3
6+
*/
7+
8+
#include "AudioTools.h"
9+
#include "AudioLibs/AudioKit.h"
10+
11+
uint16_t sample_rate=44100;
12+
uint8_t channels = 2; // The stream will have 2 channels
13+
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
14+
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
15+
AudioKitStream out;
16+
StreamCopy copier(out, sound); // copies sound into i2s
17+
18+
// Arduino Setup
19+
void setup(void) {
20+
// Open Serial
21+
Serial.begin(115200);
22+
AudioLogger::instance().begin(Serial, AudioLogger::Info);
23+
24+
// start I2S
25+
Serial.println("starting I2S...");
26+
auto config = out.defaultConfig(TX_MODE);
27+
config.sample_rate = sample_rate;
28+
config.channels = channels;
29+
config.bits_per_sample = 16;
30+
out.begin(config);
31+
32+
// Setup sine wave
33+
sineWave.begin(channels, sample_rate, N_B4);
34+
Serial.println("started...");
35+
}
36+
37+
// Arduino loop - copy sound to out
38+
void loop() {
39+
copier.copy();
40+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#pragma once
2+
3+
#define I2S_BUFFER_COUNT 8
4+
#define I2S_BUFFER_SIZE 256
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @file basic-a2dp-audioi2s.ino
3+
* @brief A2DP Sink with output to SPDIFStream
4+
*
5+
* @author Phil Schatzmann
6+
* @copyright GPLv3
7+
*/
8+
9+
/**
10+
* @file basic-a2dp-audioi2s.ino
11+
* @brief A2DP Sink with output to SPDIFStream
12+
*
13+
* @author Phil Schatzmann
14+
* @copyright GPLv3
15+
*/
16+
17+
#define USE_A2DP
18+
#include "AudioConfigLocal.h"
19+
#include "AudioTools.h"
20+
#include "AudioExperiments/Resample.h"
21+
22+
BluetoothA2DPSink a2dp_sink;
23+
I2SStream i2s;
24+
Resample<int16_t> out(i2s, 2, 2);
25+
26+
// Write data to SPDIF in callback
27+
void read_data_stream(const uint8_t *data, uint32_t length) {
28+
out.write(data, length);
29+
}
30+
31+
void setup() {
32+
Serial.begin(115200);
33+
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
34+
35+
// register callback
36+
a2dp_sink.set_stream_reader(read_data_stream, false);
37+
38+
// Start Bluetooth Audio Receiver
39+
a2dp_sink.set_auto_reconnect(false);
40+
a2dp_sink.start("a2dp-i2s");
41+
42+
// setup output
43+
auto cfg = i2s.defaultConfig();
44+
cfg.pin_data = 23;
45+
cfg.sample_rate = a2dp_sink.sample_rate()*2;
46+
cfg.channels = 2;
47+
cfg.bits_per_sample = 16;
48+
i2s.begin(cfg);
49+
50+
}
51+
52+
void loop() {
53+
delay(100);
54+
}

examples/examples-maximilian/04-AM2/04-AM2.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ void play(double *output) {
3131
//The frequency of the second new wave is the difference of the two original waves.
3232
//So you hear two new waves, one going up, one going down.
3333

34-
output[0]=mySine.sinewave(440)*myOtherSine.sinewave(myPhasor.phasor(0.01,0,440));
34+
output[0]=mySine.sinewave(440)*myOtherSine.sinewave(myPhasor.phasorBetween(0.01,0,440));
3535
output[1]=output[0];
3636

3737
}

examples/examples-maximilian/08-Counting2/08-Counting2.ino

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ void play(double *output) {
3737
// Once every second, CurrentCount counts from 1 until it gets to 9, then resets itself.
3838
// When it reaches 9 it resets, so the values you get are 1-8.
3939

40-
CurrentCount=myCounter.phasor(1, 1, 9);//phasor can take three arguments; frequency, start value and end value.
40+
CurrentCount=myCounter.phasor(1.0, 1.0, 9.0);//phasor can take three arguments; frequency, start value and end value.
4141

4242
// If we multiply the output of CurrentCount by 100, we get 100,200,300,400,500,600,700,800 in that order.
4343
// These become the frequency of the oscillator.

examples/examples-stream/streams-generator-i2s/streams-generator-i2s.ino

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
* @file streams-generator-i2s.ino
33
* @author Phil Schatzmann
44
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/examples-stream/streams-generator-i2s/README.md
5-
* @author Phil Schatzmann
65
* @copyright GPLv3
76
*/
87

98
#include "AudioTools.h"
109

11-
12-
1310
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
1411
uint16_t sample_rate=44100;
1512
uint8_t channels = 2; // The stream will have 2 channels
@@ -22,7 +19,7 @@ StreamCopy copier(out, sound); // copies sound into
2219
void setup(void) {
2320
// Open Serial
2421
Serial.begin(115200);
25-
AudioLogger::instance().begin(Serial, AudioLogger::Info);
22+
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
2623

2724
// start I2S
2825
Serial.println("starting I2S...");

src/AudioExperiments/Resample.h

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#pragma once
2+
3+
#include "AudioTools/AudioStreams.h"
4+
/**
5+
* @brief A simple implementation which changes the sample rate by the indicated factor.
6+
* To downlample we calculate the avarage of n (=factor) samples. To upsample we interpolate
7+
* the missing samples. If the indicated factor is positive we upsample if it is negative
8+
* we downsample.
9+
*
10+
* @tparam T
11+
*/
12+
template<typename T>
13+
class Resample : public AudioStreamX {
14+
public:
15+
/**
16+
* @brief Construct a new Converter Resample object
17+
*
18+
* @param channels number of channels (default 2)
19+
* @param factor use a negative value to downsample
20+
*/
21+
Resample(Print &out, int channels=2, int factor=2 ){
22+
this->channels = channels;
23+
this->factor = factor;
24+
this->p_out = &out;
25+
}
26+
27+
Resample(Stream &in, int channels=2, int factor=2 ){
28+
this->channels = channels;
29+
this->factor = factor;
30+
this->p_out = &in;
31+
this->p_in = &in;
32+
}
33+
34+
int availableForWrite() override { return p_out->availableForWrite(); }
35+
36+
/// Writes the data up or downsampled to the final destination
37+
size_t write(const uint8_t *src, size_t byte_count) override {
38+
if (byte_count%channels!=0){
39+
LOGE("Invalid buffer size: It must be multiple of %d", channels);
40+
return 0;
41+
}
42+
size_t bytes = 0;
43+
int sample_count = byte_count / sizeof(T);
44+
if (factor>1){
45+
allocateBuffer(sample_count*factor);
46+
bytes = upsample((T*)src, buffer, sample_count, channels, factor) * sizeof(T);
47+
p_out->write((uint8_t*)buffer, bytes);
48+
} else if (factor<1){
49+
allocateBuffer(sample_count/abs(factor));
50+
bytes = downsample((T*)src, buffer , sample_count, channels, abs(factor)) * sizeof(T);
51+
p_out->write((uint8_t*)buffer, bytes);
52+
} else {
53+
bytes = p_out->write(src, byte_count);
54+
}
55+
return bytes;
56+
}
57+
58+
int available() override { return p_in!=nullptr ? p_in->available() : 0; }
59+
60+
size_t readBytes(uint8_t *src, size_t length) override {
61+
if (p_in==nullptr) return 0;
62+
if (length%channels!=0){
63+
length = length / channels * channels;
64+
}
65+
size_t bytes = 0;
66+
int size = length / channels / sizeof(T);
67+
if (factor>1){
68+
int read_len = length/factor;
69+
allocateBuffer(read_len);
70+
read_len = p_in->readBytes((uint8_t*)buffer, read_len);
71+
bytes = upsample(buffer,(T*)src, read_len, channels, factor) * sizeof(T);
72+
} else if (factor<1){
73+
int abs_factor = abs(factor);
74+
int read_len = length*factor;
75+
allocateBuffer(read_len);
76+
read_len = p_in->readBytes((uint8_t*)buffer, read_len);
77+
bytes = downsample(buffer,(T*)src, read_len, channels, abs_factor) * sizeof(T);
78+
} else {
79+
bytes = p_in->readBytes(src, length);
80+
}
81+
return bytes;
82+
}
83+
84+
85+
protected:
86+
Print *p_out=nullptr;
87+
Stream *p_in=nullptr;
88+
T *buffer=nullptr;
89+
T *last_end=nullptr;
90+
int channels = 2;
91+
int factor = 1;
92+
int buffer_size = 0;
93+
94+
void allocateBuffer(int len) {
95+
if (len>buffer_size){
96+
if (buffer!=nullptr) delete []buffer;
97+
buffer = new T[len];
98+
}
99+
if (last_end==nullptr){
100+
last_end = new T[channels];
101+
}
102+
}
103+
104+
size_t downsample(T *from,T *to, int size, int channels, int factor ){
105+
if (size%factor!=0){
106+
LOGE("Incompatible buffer length for down sampling. If must be a factor of %d", factor);
107+
return 0;
108+
}
109+
for (int16_t j=0; j<size-factor; j+=channels){
110+
long total[channels];
111+
for (int8_t ch=0; ch<channels; ch++){
112+
for (int16_t f=0;f<factor;f++){
113+
total[j+ch] += from[j+ch+(f*channels)];
114+
}
115+
to[j+ch] = total[j+ch] / factor;
116+
}
117+
}
118+
return size/factor;
119+
}
120+
121+
/// We interpolate the missing samples
122+
size_t upsample(T *from, T* to, int sample_count, int channels, int factor ){
123+
int frame_count = sample_count/channels;
124+
size_t result = 0;
125+
int pos = 0;
126+
// we start with the last saved frame
127+
for (int16_t frame_pos=-1; frame_pos<frame_count-1; frame_pos++){
128+
for (int8_t ch=0; ch<channels; ch++){
129+
T actual_data = *p_data(frame_pos, ch, from);
130+
T next_data = *p_data(frame_pos+1, ch, from);
131+
float diff = (next_data - actual_data) / factor;
132+
pos =(frame_pos+1)*factor;
133+
*p_data(pos, ch, to) = actual_data;
134+
result++;
135+
for (int16_t f=1;f<factor;f++){
136+
pos = ((frame_pos+1)*factor)+f;
137+
*p_data(pos, ch, to) = actual_data + (diff*f);
138+
result++;
139+
}
140+
}
141+
}
142+
// save last frame_pos data
143+
pos = frame_count*factor;
144+
for (int8_t ch=0; ch<channels; ch++){
145+
T tmp = *p_data(frame_count-1, ch, from);
146+
last_end[ch] = tmp;
147+
}
148+
return pos*channels;
149+
}
150+
151+
// provide access to data
152+
T* p_data(int frame_pos, int channel, T*start){
153+
return frame_pos>=0 ? start+(frame_pos*channels)+channel : last_end+channel;
154+
}
155+
156+
};

src/AudioLibs/MaximilianDSP.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include "AudioConfig.h"
33
#include "maximilian.h"
4+
#include "libs/maxiClock.h"
45

56
// Maximilian play function - return an array of 2 channels
67
void play(double *channels);//run dac!
@@ -26,6 +27,7 @@ class Maximilian {
2627

2728
/// Setup Maximilian with audio parameters
2829
void begin(AudioBaseInfo cfg){
30+
this->cfg = cfg;
2931
maxiSettings::setup(cfg.sample_rate, cfg.channels, DEFAULT_BUFFER_SIZE);
3032
}
3133

@@ -43,14 +45,15 @@ class Maximilian {
4345
/// Copies the audio data from maximilian to the audio sink, Call this method from the Arduino Loop.
4446
void copy() {
4547
// fill buffer with data
46-
double out[2];
48+
double out[cfg.channels];
4749
uint16_t samples = buffer_size / sizeof(uint16_t);
4850
int16_t *p_samples = (int16_t *)p_buffer;
49-
for (uint16_t j=0;j<samples;j+=2){
51+
for (uint16_t j=0;j<samples;j+=cfg.channels){
5052
play(out);
51-
// convert to int16
52-
p_samples[j] = out[0]*32767*volume;
53-
p_samples[j+1] = out[1]*32767*volume;
53+
// convert all channels to int16
54+
for (int ch=0;ch<cfg.channels;ch++){
55+
p_samples[j+ch] = out[ch]*32767*volume;
56+
}
5457
}
5558
// write buffer to audio sink
5659
unsigned int result = p_sink->write(p_buffer, buffer_size);
@@ -62,6 +65,7 @@ class Maximilian {
6265
float volume=1.0;
6366
int buffer_size=256;
6467
Print *p_sink=nullptr;
68+
AudioBaseInfo cfg;
6569
};
6670

6771

src/AudioTools/AudioOutput.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class CsvStream : public AudioPrint, public AudioBaseInfoDependent {
6363
}
6464

6565
/// Constructor
66-
CsvStream(Print &out, int channels, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true) {
66+
CsvStream(Print &out, int channels=2, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true) {
6767
this->channels = channels;
6868
this->out_ptr = &out;
6969
this->active = active;
@@ -73,7 +73,6 @@ class CsvStream : public AudioPrint, public AudioBaseInfoDependent {
7373
void begin(){
7474
LOGD(LOG_METHOD);
7575
this->active = true;
76-
this->channels = 2;
7776
}
7877

7978
/// Provides the default configuration
@@ -135,7 +134,7 @@ class CsvStream : public AudioPrint, public AudioBaseInfoDependent {
135134
protected:
136135
T *data_ptr;
137136
Print *out_ptr = &Serial;
138-
int channels = 1;
137+
int channels = 2;
139138
bool active = false;
140139

141140
};

src/AudioTools/Converter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ class FormatConverter {
479479
};
480480

481481

482+
482483
/**
483484
* @brief Reads n numbers from an Arduino Stream
484485
*

0 commit comments

Comments
 (0)