Skip to content

Commit da17b01

Browse files
committed
Support for PortAudio
1 parent dc04935 commit da17b01

File tree

11 files changed

+303
-53
lines changed

11 files changed

+303
-53
lines changed

examples/desktop/CMakeLists.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cmake_minimum_required(VERSION 3.0)
2+
3+
# set the project name
4+
project(desktop)
5+
6+
# Build with arduino-audio-tools
7+
FetchContent_Declare(arduino_audio_tools GIT_REPOSITORY "https://github.com/pschatzmann/arduino-audio-tools.git" GIT_TAG main )
8+
FetchContent_GetProperties(arduino_audio_tools)
9+
if(NOT arduino_audio_tools_POPULATED)
10+
FetchContent_Populate(arduino_audio_tools)
11+
add_subdirectory(${arduino_audio_tools_SOURCE_DIR})
12+
endif()
13+
14+
# Build with Linux Arduino Emulator
15+
FetchContent_Declare(arduino_emulator GIT_REPOSITORY "https://github.com/pschatzmann/Arduino-Emulator.git" GIT_TAG main )
16+
FetchContent_GetProperties(arduino_emulator)
17+
if(NOT arduino_emulator_POPULATED)
18+
FetchContent_Populate(arduino_emulator)
19+
add_subdirectory(${arduino_emulator_SOURCE_DIR})
20+
endif()
21+
22+
# Build with Portaudio
23+
FetchContent_Declare(portaudio GIT_REPOSITORY "https://github.com/pschatzmann/Arduino-Emulator.git" GIT_TAG main )
24+
FetchContent_GetProperties(portaudio)
25+
if(NOT portaudio_POPULATED)
26+
FetchContent_Populate(portaudio)
27+
add_subdirectory(${portaudio_SOURCE_DIR})
28+
endif()
29+
30+
31+
# build sketch to executable
32+
add_executable (desktop desktop_example.cpp)
33+

examples/desktop/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Desktop Implementation
2+
3+
We provide some generic output which will work on Linux, Windows and OS/X
4+
The provided cmake is downloading all dependencies and builds an executable from the sketch

examples/desktop/desktop_example.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "Arduino.h"
2+
#include "AudioTools.h"
3+
4+
using namespace audio_tools;
5+
6+
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
7+
uint16_t sample_rate=44100;
8+
uint8_t channels = 2; // The stream will have 2 channels
9+
SineWaveGenerator<sound_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
10+
GeneratedSoundStream<sound_t> in(sineWave, channels); // Stream generated from sine wave
11+
DefaultStream out = DefaultStream::instance() ; // A2DP input - A2DPStream is a singleton!
12+
StreamCopy copier(out, in); // copy in to out
13+
14+
// Arduino Setup
15+
void setup(void) {
16+
Serial.begin(115200);
17+
18+
// We send the test signal via A2DP - so we conect to the MyMusic Bluetooth Speaker
19+
out.begin(TX_MODE, "MyMusic");
20+
21+
Serial.println("A2DP is connected now...");
22+
23+
// Setup sine wave
24+
sineWave.begin(sample_rate, B4);
25+
26+
}
27+
28+
// Arduino loop
29+
void loop() {
30+
if (out)
31+
copier.copy();
32+
}

sandbox/streams-mozzi-a2dp/streams-mozzi-a2dp.ino

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @file streams-generator-a2dp.ino
2+
* @file streams-mozzi-a2dp.ino
33
* @author Phil Schatzmann
44
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-generator-a2dp/README.md
55
*
@@ -19,11 +19,8 @@
1919

2020
using namespace audio_tools;
2121

22-
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
23-
uint8_t channels = 2; // The stream will have 2 channels
24-
MozziGenerator mozzi(CONTROL_RATE); // subclass of SoundGenerator
25-
GeneratedSoundStream<sound_t> in(mozzi, channels); // Stream generated with mozzi
26-
A2DPStream out = A2DPStream::instance() ; // A2DP output - A2DPStream is a singleton!
22+
MozziStream in; // Stream generated with mozzi
23+
A2DPStream out = A2DPStream::instance() ; // A2DP output - A2DPStream is a singleton!
2724
StreamCopy copier(out, in); // copy in to out
2825

2926
/// Copied from AMsynth.ino
@@ -45,24 +42,33 @@ Q24n8 mod_freq; // unsigned long with 24 integer bits and 8 fractional bits
4542
// for random notes
4643
Q8n0 octave_start_note = 42;
4744

48-
45+
// Setup Mozzi and A2DP
4946
void setup(){
5047
Serial.begin(115200);
48+
AudioLogger::instance().begin(Serial, AudioLogger::Debug);
5149

52-
if (mozzi.config().sample_rate!=44100){
53-
Serial.println("Please set the AUDIO_RATE in the mozzi_config.h to 44100");
54-
stop();
55-
}
50+
// setup MozziStream
51+
auto cfg = in.defaultConfig();
52+
cfg.channels = 2;
53+
cfg.updateAudio = updateAudio;
54+
cfg.updateControl = updateControl;
55+
in.begin(cfg);
5656

5757
// We send the test signal via A2DP - so we conect to the MyMusic Bluetooth Speaker
5858
out.begin(TX_MODE, "MyMusic");
5959
Serial.println("A2DP is connected now...");
6060

61+
// setup Mozzi
6162
ratio = float_to_Q8n8(3.0f); // define modulation ratio in float and convert to fixed-point
6263
kNoteChangeDelay.set(200); // note duration ms, within resolution of CONTROL_RATE
6364
aModDepth.setFreq(13.f); // vary mod depth to highlight am effects
6465
randSeed(); // reseed the random generator for different results each time the sketch runs
65-
in.begin();
66+
}
67+
68+
// Arduino loop
69+
void loop() {
70+
if (out)
71+
copier.copy();
6672
}
6773

6874
void updateControl(){
@@ -113,9 +119,3 @@ AudioOutput_t updateAudio(){
113119
int32_t mod = (128u+ aModulator.next()) * ((byte)128+ aModDepth.next());
114120
return MonoOutput::fromNBit(24, mod * aCarrier.next());
115121
}
116-
117-
// Arduino loop
118-
void loop() {
119-
if (out)
120-
copier.copy();
121-
}

sandbox/streams-mozzi-webserver/streams-mozzi-webserver.ino

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
using namespace audio_tools;
2020

21-
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
22-
uint8_t channels = 2; // The stream will have 2 channels
2321
MozziStream mozzi;
2422
AudioWAVServer server("ssid","password");
2523

@@ -49,10 +47,13 @@ void setup(){
4947

5048
// configure mozzi
5149
auto cfg = mozzi.defaultConfig();
50+
cfg.channels = 1;
51+
cfg.updateAudio = updateAudio;
52+
cfg.updateControl = updateControl;
5253
mozzi.begin(cfg);
5354

54-
// We connect to the server
55-
server.begin(mozzi, cfg.sample_rate, cfg.channels);
55+
// We start to the server
56+
server.begin(mozzi, cfg.sample_rate, cfg.channels, cfg.bits_per_sample);
5657

5758
// Start Mozzi
5859
ratio = float_to_Q8n8(3.0f); // define modulation ratio in float and convert to fixed-point

src/AudioConfig.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,14 @@
101101
#define SOFT_MUTE_VALUE LOW
102102
#endif
103103

104+
105+
/**
106+
* -------------------------------------------------------------------------
107+
* @brief typedefs for DefaultStream
108+
*
109+
*/
110+
#if defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
111+
typedef PortAudioStream DefaultStream
112+
#elif defined(ESP32) || defined(ESP8266) || defined(__SAMD21G18A__)
113+
typedef A2DPStream DefaultStream
114+
#endif

src/AudioMozzi.h

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ namespace audio_tools {
1212
*
1313
*/
1414
struct MozziConfig : AudioBaseInfo {
15-
uint16_t control_rate=0;
15+
uint16_t control_rate=CONTROL_RATE;
1616
void (*updateControl)() = nullptr; //&::updateControl;
1717
AudioOutput_t (*updateAudio)() = nullptr; // = &::updateAudio;
1818

1919
MozziConfig(){
2020
channels = AUDIO_CHANNELS;
2121
sample_rate = AUDIO_RATE;
22-
bits_per_sample = sizeof(AudioOutputStorage_t)*8;
22+
bits_per_sample = 16;
2323
}
2424
};
2525
/**
@@ -33,7 +33,7 @@ struct MozziConfig : AudioBaseInfo {
3333
* @copyright GPLv3
3434
*/
3535

36-
class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
36+
class MozziGenerator : public SoundGenerator<int16_t> {
3737
public:
3838
MozziGenerator(){
3939
LOGD(__FUNCTION__);
@@ -48,6 +48,7 @@ class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
4848
}
4949

5050
void begin(MozziConfig config){
51+
SoundGenerator<int16_t>::begin();
5152
info = config;
5253
if (info.control_rate==0){
5354
info.control_rate = CONTROL_RATE;
@@ -65,14 +66,14 @@ class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
6566
}
6667

6768
/// Provides a single sample
68-
virtual AudioOutputStorage_t readSample() {
69+
virtual int16_t readSample() {
6970
if (info.updateAudio==nullptr){
70-
LOGE("The updateAudio method has not been defined!");
71-
end();
71+
LOGE("The updateAudio method has not been defined in the configuration !");
72+
stop();
7273
return 0;
7374
}
7475

75-
// return prior right value from buffer
76+
// return value from buffer -> channel 2
7677
if (is_read_buffer_filled){
7778
// for stereo output we might have the value already
7879
is_read_buffer_filled = false;
@@ -83,12 +84,13 @@ class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
8384
if (--control_counter<0){
8485
control_counter = control_counter_max;
8586
if (info.updateControl!=nullptr){
87+
LOGD("updateControl");
8688
info.updateControl();
8789
}
8890
}
8991

9092
// return left value
91-
AudioOutputStorage_t result = updateSample();
93+
int16_t result = updateSample();
9294
return result;
9395
}
9496

@@ -100,30 +102,30 @@ class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
100102
bool is_read_buffer_filled = false;
101103

102104

103-
AudioOutputStorage_t updateSample(){
105+
// we make sure that we return one sample for each
106+
// requested number of channels sequentially
107+
int16_t updateSample(){
104108
AudioOutput out = info.updateAudio();
105109
// requested mono
106-
AudioOutputStorage_t result = 0;
110+
int16_t result = 0;
107111
#if (AUDIO_CHANNELS == MONO)
108-
// generated stereo
109-
if (sizeof(out) == sizeof(AudioOutputStorage_t)*2){
110-
result = out[0]/2 + out[1]/2;
111-
// generated mono
112-
} else if (sizeof(out) == sizeof(AudioOutputStorage_t)){
112+
if (info.channels==2){
113+
result = out[0];
114+
read_buffer = out[0];
115+
is_read_buffer_filled = true;
116+
} else if (info.channels==1){
113117
result = out[0];
114118
}
115119

116120
// requested stereo
117121
#elif (AUDIO_CHANNELS == STEREO)
118122
result = out[0];
119-
// generated stereo
120-
if (sizeof(out) == sizeof(AudioOutputStorage_t)*2){
123+
if (info.channels==2){
124+
result = out[0];
121125
read_buffer = out[1];
122126
is_read_buffer_filled = true;
123-
// generated mono
124-
} else if (sizeof(out) == sizeof(AudioOutputStorage_t)){
125-
read_buffer = out[0];
126-
is_read_buffer_filled = true;
127+
} else if (info.channels==1){
128+
result = out[0]/2 + out[1]/2;
127129
}
128130
#endif
129131
return result;
@@ -172,14 +174,11 @@ class MozziStream : public Stream {
172174
config = cfg;
173175
Mozzi.setAudioRate(config.sample_rate);
174176
// in output mode we do not allocate any unnecessary functionality
175-
if (input_ptr == nullptr && config.control_rate>0){
176-
input_ptr = new MozziGenerator(config);
177-
}
178177
if (cfg.channels != config.channels){
179178
LOGE("You need to change the AUDIO_CHANNELS in mozzi_config.h to %d", cfg.channels);
180179
}
181180

182-
Mozzi.start(0);
181+
Mozzi.start(config.control_rate);
183182
}
184183

185184
void end(){
@@ -205,6 +204,7 @@ class MozziStream : public Stream {
205204
virtual size_t write(const uint8_t *buffer, size_t size) {
206205
for (size_t j=0;j<size;j++){
207206
if (write(buffer[j])<=0){
207+
LOGE("Could not write all data: %d of %d", j, size);
208208
return j;
209209
}
210210
}
@@ -216,7 +216,8 @@ class MozziStream : public Stream {
216216
}
217217

218218
virtual size_t readBytes(char *buffer, size_t length){
219-
return input_ptr==nullptr ? 0 : input_ptr->readBytes((uint8_t*)buffer, length);
219+
LOGD("readBytes: %d", length);
220+
return get_input_ptr()->readBytes((uint8_t*)buffer, length, config.channels);
220221
}
221222

222223
virtual int read(){
@@ -240,6 +241,17 @@ class MozziStream : public Stream {
240241
uint8_t buffer[64];
241242
int buffer_pos;
242243

244+
MozziGenerator *get_input_ptr(){
245+
if (input_ptr==nullptr){
246+
// allocate generator only when requested
247+
if (config.control_rate>0){
248+
config.control_rate = CONTROL_RATE;
249+
}
250+
input_ptr = new MozziGenerator(config);
251+
}
252+
return input_ptr;
253+
}
254+
243255
// writes individual bytes and puts them together as MonoOutput or StereoOutput
244256
void writeChar(uint8_t c){
245257
buffer[buffer_pos++] = c;

src/AudioTools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "AudioTools/Streams.h"
2121
#include "AudioTools/AudioCopy.h"
2222
#include "AudioTools/AudioPWM.h"
23+
#include "AudioTools/PortAudioStream.h"
2324

2425
#ifdef USE_URL_ARDUINO
2526
// Arduino network support (incl ESP32)

src/AudioTools/AudioCopy.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class StreamCopyT {
6161
size_t samples = bytes_to_read / sizeof(T);
6262
bytes_to_read = samples * sizeof(T);
6363
size_t bytes_read = from->readBytes(buffer, bytes_to_read);
64-
result = write(bytes_read,delayCount);
64+
result = write(bytes_read, delayCount);
6565
}
6666
LOGI("StreamCopy::copy %u -> %u bytes - in %d hops", bytes_to_read, result, delayCount);
6767
return result;
@@ -123,14 +123,14 @@ class StreamCopyT {
123123
while(total<len){
124124
size_t written = to->write(buffer+total, len-total);
125125
total += written;
126-
yield();
127126
delayCount++;
128127

129-
if (retry++ > 10){
128+
if (retry++ > 20){
130129
break;
131130
}
132131

133132
if (retry>1) {
133+
delay(5);
134134
LOGI("try write - %d ",retry);
135135
}
136136

0 commit comments

Comments
 (0)