mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-21 10:27:27 +00:00
Stream support
This commit is contained in:
parent
b3891a80dd
commit
9e9f0c8593
126
README.md
126
README.md
@ -1,44 +1,120 @@
|
||||
# Arduino Audio Tools
|
||||
|
||||
Some basic C++ classes that can be used for Audio Processing privided as Arduino Library
|
||||
Some basic __header-only C++ classes__ that can be used for __Audio Processing__ provided as __Arduino Library__:
|
||||
|
||||
- Additional Stream implementations: MemoryStream - ESP32 only: UrlStream, I2SStream
|
||||
- a simple I2S class (to read and write to the internal I2S) [ESP32 only]
|
||||
- a simple ADC class (to read analog data with the help of I2S) [ESP32 only]
|
||||
- a simple I2S class (to read and write to the internal I2S)
|
||||
- a simple ADC class (to read analog data with the help of I2S)
|
||||
- Additional Stream implementations: MemoryStream, UrlStream, I2SStream, A2DPStream, PrintStream
|
||||
- Converters
|
||||
- Musical Notes (with Frequencies of notes)
|
||||
- Musical Notes (with frequencies of notes)
|
||||
- SineWaveGenerator (to generate some sine tone)
|
||||
- NBuffer (Multi buffer for writing and reading of (audio) data)
|
||||
- TimerAlarmRepeating (e.g. for sampling audio data using exact times) [ESP32 only]
|
||||
- A Wav Encoder and Decoder
|
||||
- AudioOutputWithCallback class to provide callback integration with ESP8266Audio
|
||||
- AudioOutputWithCallback class to provide callback integration e.g. with ESP8266Audio
|
||||
|
||||
This functionality provides the glue which makes different audio processing components and libraries work together.
|
||||
We also provide plenty of examples that demonstrate how to implement the different scenarios.
|
||||
We also provide plenty of examples that demonstrate how to implement the different scenarios. The __design philosophy__ is based on the Arduino conventions: we use the ```begin()``` and ```end()``` methods to start and stop the processing and we propagate the __use of Streams__. We all know the Arduino Streams. We use them to write out print messages and sometimes we use them to read the output from Serial devices. The same thing applies to my “Audio Streams”: You can read audio data from “Audio Sources” and you write them to “Audio Sinks”.
|
||||
|
||||
As “Audio Sources” we will have e.g.:
|
||||
|
||||
- Analog Microphones – AnalogStream
|
||||
- Digital Microphonse – I2SStream
|
||||
- Files on the Internet – UrlStream
|
||||
- Generated Sound – GeneratedSoundStream
|
||||
- Mobile Phone A2DP Bluetooth – A2DPStream
|
||||
- Binary Data in Flash Memory – MemoryStream
|
||||
- SD Files
|
||||
|
||||
As “Audio Sinks” we will have e.g:
|
||||
|
||||
- external DAC – I2SStream
|
||||
- an Amplifier – AnalogStream
|
||||
- Bluetooth Speakers – A2DPStream
|
||||
- Serial to display the data as CSV – CsvStream.
|
||||
- SD Files
|
||||
|
||||
Here is an simple example which streams a file from the Flash Memory and writes it to I2S:
|
||||
|
||||
```
|
||||
#include "AudioTools.h"
|
||||
#include "StarWars30.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
uint8_t channels = 2;
|
||||
uint16_t sample_rate = 22050;
|
||||
|
||||
MemoryStream music(StarWars30_raw, StarWars30_raw_len);
|
||||
I2SStream i2s; // Output to I2S
|
||||
StreamCopyT<int16_t> copier(i2s, music); // copies sound into i2s
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200);
|
||||
|
||||
I2SConfig config = i2s.defaultConfig(TX_MODE);
|
||||
config.sample_rate = sample_rate;
|
||||
config.channels = channels;
|
||||
config.bits_per_sample = 16;
|
||||
i2s.begin(config);
|
||||
}
|
||||
|
||||
void loop(){
|
||||
if (!copier.copy2()){
|
||||
i2s.end();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
A complete list of the supported Audio Stream classes and scenarios can be found in the [Scenarios Document](Scenarios.md)
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
The examples follow the following naming convention: "scenario type"-"source"-"destination". For the scenario types we might have __base__ (using basic api functionality), __stream__ for examples using Streams and __test__ for the test cases.
|
||||
|
||||
For the __source__ we currently have __adc__ for analog input devices like analog microphones, __i2s__ for digital input devices (e.g. digital microphones), __file__ for SD files and __a2dp__ for input from Bluetooth A2DP (e.g. from a Mobile Phone).
|
||||
|
||||
For the __destination__ we use __dac__ for analog output (e.g. to an amplifier), __i2s__ for digital output devices (e.g. an external DAC), __file__ for SD files and __a2dp__ for output to Bluetooth A2DP (e.g. a Bluetooth Speaker).
|
||||
|
||||
|
||||
Here is the list of examples:
|
||||
|
||||
#### Stream API
|
||||
|
||||
- [streams-url_raw-serial](/examples/streams-url_raw-serial) Displaying a music file from the internet on the Serial Plotter
|
||||
- [streams-generator-serial](/examples/streams-generator-serial) Displaying generated sound on the Serial Plotter
|
||||
- [streams-adc-serial](/examples/streams-adc-serial) Displaying input from analog microphone on the Serial Plotter
|
||||
- [streams-i2s-a2dp](examples/streams-i2s-a2dp) - Sample analog sound and write it to a A2DP Bluetooth source
|
||||
- [streams-file_raw-a2dp](examples/streams-file_raw-a2dp) - Read Raw File from SD card write it A2DP Bluetooth
|
||||
- [streams-adc-a2dp](examples/streams-adc-a2dp) - Sample analog sound from analog microphone and send it to Bluetooth Speaker
|
||||
- [streams-memory_raw-i2s_external_dac](examples/streams-memory_raw-i2s_external_dac) - Play music form Flash Memory via I2S to External DAC
|
||||
|
||||
... these are just a few examples, but you can combine any Input Stream with any Output Stream as you like...
|
||||
|
||||
#### Basic API
|
||||
|
||||
- [base-adc-serial](examples/base-adc-serial) - Sample analog sound and write it to Serial
|
||||
- [base-adc-a2dp](examples/base-adc-a2dp) - Sample analog sound and write it to a A2DP Bluetooth source
|
||||
- [base-file_raw-serial](examples/base-file_raw-serial) - Read Raw File from SD card to and write it to Serial
|
||||
- [base-file_raw-a2dp](examples/base-file_raw-a2dp) - Read Raw File from SD card write it A2DP Bluetooth
|
||||
- [base-file_mp3-a2dp](examples/base-file_mp3-a2dp) - Stream MP3 File from SD card to A2DP Bluetooth using the ESP8266Audio library
|
||||
- [base-i2s-serial](examples/base-i2s-serial) - Sample digital sound and write it to Serial
|
||||
- [base-i2s-a2dp](examples/base-i2s-a2dp) - Sample analog sound and write it to a A2DP Bluetooth source
|
||||
|
||||
The design philosophy is based on the Arduino conventions: we use the ```begin()``` and ```end()``` methods to start and stop the processing and we propagate the use of Streams.
|
||||
|
||||
|
||||
## Optional Libraries
|
||||
|
||||
Dependent on the example you might need to install some of the following libraries:
|
||||
|
||||
- [ESP32-A2DP Library](https://github.com/pschatzmann/ESP32-A2DP)
|
||||
- [ESP8266Audio](https://github.com/earlephilhower/ESP8266Audio)
|
||||
- [SD Library](https://www.arduino.cc/en/reference/SD)
|
||||
- [arduino-fdk-aac](https://github.com/pschatzmann/arduino-fdk-aac)
|
||||
- [ESP32-A2DP Library](https://github.com/pschatzmann/ESP32-A2DP) to support A2DP Bluetooth Audio
|
||||
- [ESP8266Audio]( ) to play different audio Formats
|
||||
- [SD Library](https://www.arduino.cc/en/reference/SD) to read and write files.
|
||||
- [arduino-fdk-aac](https://github.com/pschatzmann/arduino-fdk-aac) to encode or decode AAC
|
||||
|
||||
|
||||
# Examples
|
||||
|
||||
- [adc-a2dp](examples/adc-a2dp) - Stream Analog input to A2DP Bluetooth
|
||||
- [adc-serial](examples/adc-serial) - Stream Analog input to Serial
|
||||
- [file_raw-a2dp](examples/file_raw-a2dp) - Stream Row File from SD card to A2DP Bluetooth
|
||||
- [file_raw-serial](examples/file_raw-serial) - Stream Raw File from SD card to Serial
|
||||
- [file_mp3-a2dp](examples/file_mp3-a2dp) - Stream MP3 File from SD card to A2DP Bluetooth using the ESP8266Audio library
|
||||
- [i2s-a2dp](examples/i2s-a2dp) - Stream I2S Input to A2DP Bluetooth
|
||||
- [i2s-serial](examples/i2s-serial) - Stream I2S Input to Serial
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
@ -52,7 +128,9 @@ git clone pschatzmann/arduino-audio-tools.git
|
||||
|
||||
## Documentation
|
||||
|
||||
Here is the generated [Class documentation](https://pschatzmann.github.io/arduino-audio-tools/html/annotated.html). You might find further information in [one of my blogs](https://www.pschatzmann.ch/home/category/machine-sound/)
|
||||
Here is the generated [Class documentation](https://pschatzmann.github.io/arduino-audio-tools/html/annotated.html).
|
||||
|
||||
You also might find further information in [one of my blogs](https://www.pschatzmann.ch/home/category/machine-sound/)
|
||||
|
||||
## Project Status
|
||||
|
||||
@ -63,7 +141,7 @@ This is currently work in progress:
|
||||
| Analog input - ADC | tested |
|
||||
| I2S | tested |
|
||||
| Files (RAW, MP3...) | tested |
|
||||
| Streams | open |
|
||||
| Streams | tested |
|
||||
| WAV encoding/deconding | open |
|
||||
| AAC encoding/deconding | open |
|
||||
| int24_t | tested |
|
||||
|
53
Scenarios.md
Normal file
53
Scenarios.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Schenarios
|
||||
|
||||
### Supported Architectures
|
||||
|
||||
Unfortunatly Arduino does not provide an I2S functionality which is standrdized acress the different processors. There is only an official documentation for SAMD21 processors. The full functionality of the library is currently only available on the ESP32:
|
||||
|
||||
|
||||
| Processor | I2SStream | ADCStream | A2DP | UrlStream | Other |
|
||||
|----------------|-----------|-----------|--------|-----------|--------|
|
||||
| ESP32 | + | + | + | + | + |
|
||||
| ESP8266 | * | * | | | + |
|
||||
| SAMD21 | * | | | | + |
|
||||
| Raspberry Pico | | | | | + |
|
||||
|
||||
|
||||
+ supported
|
||||
* not tested
|
||||
|
||||
|
||||
### Supported Schenarios
|
||||
|
||||
Here are the related Stream classes with their supported operations that can be used:
|
||||
|
||||
| Class | Read | Write | Comments |
|
||||
|-------------------------|------|-------|--------------------|
|
||||
| I2SStream | + | + | i2s |
|
||||
| AnalogAudioStream | + | + | adc, dac |
|
||||
| MemoryStream | + | + | memory |
|
||||
| UrlStream | + | | url |
|
||||
| A2DPStream | + | + | a2dp |
|
||||
| GeneratedSoundStream | + | | gen |
|
||||
| AudioOutputWithCallback | + | + | |
|
||||
| CsvStream | | + | csv |
|
||||
| File | + | + | From SD library |
|
||||
| Serial, ... | + | + | Std Arduino |
|
||||
|
||||
|
||||
|
||||
In theory we should be able to support the following scenarios:
|
||||
|
||||
| Input | dac | i2s | file | a2dp | Serial | csv |
|
||||
|--------|------|-----|------|------|--------|------|
|
||||
| adc | + | + | + | + | + | + |
|
||||
| i2s | + | + | + | + | + | + |
|
||||
| file | + | + | + | + | + | + |
|
||||
| a2dp | + | + | + | - | + | + |
|
||||
| Serial | + | + | + | + | + | + |
|
||||
| memory | + | + | + | + | + | + |
|
||||
| url | + | + | + | - | + | + |
|
||||
| gen | + | + | + | + | + | + |
|
||||
|
||||
|
||||
|
BIN
docs/.DS_Store
vendored
BIN
docs/.DS_Store
vendored
Binary file not shown.
@ -17,17 +17,17 @@ using namespace audio_tools;
|
||||
* @brief We use a mcp6022 analog microphone as input and send the data to A2DP
|
||||
*/
|
||||
|
||||
ADC adc;
|
||||
AnalogAudio adc;
|
||||
BluetoothA2DPSource a2dp_source;
|
||||
// The data has a center of around 26427, so we we need to shift it down to bring the center to 0
|
||||
FilterScaler<int16_t> scaler(1.0, -26427, 32700 );
|
||||
ConverterScaler<int16_t> scaler(1.0, -26427, 32700 );
|
||||
|
||||
// callback used by A2DP to provide the sound data
|
||||
int32_t get_sound_data(Channels* data, int32_t len) {
|
||||
arrayOf2int16_t *data_arrays = (arrayOf2int16_t *) data;
|
||||
// the ADC provides data in 16 bits
|
||||
size_t result_len = adc.read(data_arrays, len);
|
||||
scaler.process(data_arrays, result_len);
|
||||
scaler.convert(data_arrays, result_len);
|
||||
return result_len;
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ void setup(void) {
|
||||
|
||||
// start i2s input with default configuration
|
||||
Serial.println("starting I2S-ADC...");
|
||||
adc.begin(adc.defaultConfig());
|
||||
adc.begin(adc.defaultConfig(RX_MODE));
|
||||
|
||||
// start the bluetooth
|
||||
Serial.println("starting A2DP...");
|
@ -17,11 +17,11 @@ using namespace audio_tools;
|
||||
* @brief We use a mcp6022 analog microphone on GPIO34 and write it to Serial
|
||||
*/
|
||||
|
||||
ADC adc;
|
||||
AnalogAudio adc;
|
||||
const int32_t max_buffer_len = 512;
|
||||
int16_t buffer[max_buffer_len][2];
|
||||
// The data has a center of around 26427, so we we need to shift it down to bring the center to 0
|
||||
FilterScaler<int16_t> scaler(1.0, -26427, 32700 );
|
||||
ConverterScaler<int16_t> scaler(1.0, -26427, 32700 );
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
@ -29,7 +29,7 @@ void setup(void) {
|
||||
|
||||
// start i2s input with default configuration
|
||||
Serial.println("starting I2S-ADC...");
|
||||
adc.begin(adc.defaultConfig());
|
||||
adc.begin(adc.defaultConfig(RX_MODE));
|
||||
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ void setup(void) {
|
||||
void loop() {
|
||||
size_t len = adc.read(buffer, max_buffer_len);
|
||||
// move center to 0 and scale the values
|
||||
scaler.process(buffer, len);
|
||||
scaler.convert(buffer, len);
|
||||
|
||||
for (int j=0;j<len;j++){
|
||||
Serial.print(buffer[j][0]);
|
@ -11,7 +11,7 @@
|
||||
#include <SD.h>
|
||||
#include "AudioFileSourceSD.h"
|
||||
#include "AudioGeneratorMP3.h"
|
||||
#include "ESP8266AudioSupport.h"
|
||||
#include "AudioESP8266.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
#include "AudioTools.h"
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file i2s-a2dp.ino
|
||||
* @file base-i2s-a2dp.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/i2s-a2dp/README.md
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/base-i2s-a2dp/README.md
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
@ -10,14 +10,15 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AudioTools.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
|
||||
BluetoothA2DPSource a2dp_source;
|
||||
I2S<int32_t> i2s;
|
||||
ChannelConverter<int32_t> converter(&convertFrom32To16);
|
||||
FilterFillLeftAndRight<int32_t> bothChannels;
|
||||
CallbackConverter<int32_t,int16_t> converter(&convertFrom32To16);
|
||||
ConverterFillLeftAndRight<int32_t> bothChannels;
|
||||
const size_t max_buffer_len = 1024;
|
||||
int32_t buffer[max_buffer_len][2];
|
||||
|
||||
@ -29,10 +30,10 @@ int32_t get_sound_data(Channels* data, int32_t len) {
|
||||
size_t result_len = i2s.read(buffer, req_len);
|
||||
|
||||
// we have data only in 1 channel but we want to fill both
|
||||
bothChannels.process(buffer, result_len);
|
||||
bothChannels.convert(buffer, result_len);
|
||||
|
||||
// convert buffer to int16 for A2DP
|
||||
converter.convert(buffer, data, result_len);
|
||||
converter.convert(buffer, (int16_t(*)[2]) data, result_len);
|
||||
return result_len;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
|
||||
* @file i2s-serial.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/i2s-serial/README.md
|
||||
@ -28,8 +28,8 @@ void setup(void) {
|
||||
|
||||
// start i2s input with default configuration
|
||||
Serial.println("starting I2S...");
|
||||
I2SConfig<int32_t> config = i2s.defaultConfig(RX_MODE);
|
||||
config.i2s.sample_rate = 16000;
|
||||
I2SConfig config = i2s.defaultConfig(RX_MODE);
|
||||
config.sample_rate = 16000;
|
||||
i2s.begin(config);
|
||||
}
|
||||
|
22
examples/streams-adc-serial/README.md
Normal file
22
examples/streams-adc-serial/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Reading an Analog Signal
|
||||
|
||||
We can read an analog signal from a microphone. To test the functionality I am using a MCP6022 microphone module.
|
||||
|
||||
![MCP6022](https://pschatzmann.github.io/arduino-audio-tools/resources/mcp6022.jpeg)
|
||||
![MCP6022](https://pschatzmann.github.io/arduino-audio-tools/resources/mcp6022-1.jpeg)
|
||||
|
||||
The MCP6022 is a anlog microphone which operates at 3.3 V
|
||||
We sample the sound signal with the help of the ESP32 I2S ADC input functionality.
|
||||
|
||||
### Pins:
|
||||
|
||||
| MCP6022 | ESP32
|
||||
|---------|---------------
|
||||
| VCC | 3.3
|
||||
| GND | GND
|
||||
| OUT | GPIO34
|
||||
|
||||
|
||||
Here is the result on the Arduino Serial Plotter:
|
||||
|
||||
![serial-plotter](https://pschatzmann.github.io/arduino-audio-tools/resources/serial-plotter-sine.png)
|
32
examples/streams-adc-serial/streams-adc-serial.ino
Normal file
32
examples/streams-adc-serial/streams-adc-serial.ino
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @file streams-adc-serial.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-adc-serial/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
uint8_t channels = 2;
|
||||
AnalogAudioStream microphone; // analog microphone
|
||||
CsvStream<int16_t> printer(Serial, channels); // ASCII output stream
|
||||
StreamCopy copier(printer, microphone); // copies microphone into printer
|
||||
ConverterAutoCenter<int16_t> center; // make sure the avg of the signal is 0
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
// Open Serial
|
||||
Serial.begin(115200);
|
||||
|
||||
AnalogConfig cfg = microphone.defaultConfig(RX_MODE);
|
||||
microphone.begin(cfg);
|
||||
}
|
||||
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
copier.copy(center);
|
||||
}
|
32
examples/streams-generator-i2s_external_dac/README.md
Normal file
32
examples/streams-generator-i2s_external_dac/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Digital output via I2S to a external DAC
|
||||
|
||||
To test the I2S output I'm using the generate sine signal and write it to I2S.
|
||||
|
||||
For my tests I am using the 24-bit PCM5102 PCM5102A Stereo DAC Digital-to-analog Converter PLL Voice Module pHAT
|
||||
![DAC](https://pschatzmann.github.io/arduino-audio-tools/resources/dac.jpeg)
|
||||
|
||||
|
||||
### External DAC:
|
||||
|
||||
I am just using the default pins defined by the framework. However I could change them with the help of the config object. The mute pin can be defined in the constructor of the I2SStream - by not defining anything we use the default which is GPIO23
|
||||
|
||||
|
||||
| DAC | ESP32
|
||||
| --------| ---------------
|
||||
| VDD | 5V
|
||||
| GND | GND
|
||||
| SD | OUT (GPIO22)
|
||||
| L/R | GND
|
||||
| WS | WS (GPIO15)
|
||||
| SCK | BCK (GPIO14)
|
||||
| FMT | GND
|
||||
| XSMT | GPIO23
|
||||
|
||||
|
||||
- DEMP - De-emphasis control for 44.1kHz sampling rate(1): Off (Low) / On (High)
|
||||
- FLT - Filter select : Normal latency (Low) / Low latency (High)
|
||||
- SCK - System clock input (probably SCL on your board).
|
||||
- FMT - Audio format selection : I2S (Low) / Left justified (High)
|
||||
- XSMT - Soft mute control(1): Soft mute (Low) / soft un-mute (High)
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file streams-generator-i2s.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-generator-i2s/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
|
||||
uint16_t sample_rate=44100;
|
||||
uint8_t channels = 2; // The stream will have 2 channels
|
||||
SineWaveGenerator<sound_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
|
||||
GeneratedSoundStream<sound_t> sound(sineWave, channels); // Stream generated from sine wave
|
||||
I2SStream out; // Output to I2S
|
||||
StreamCopy copier(out, sound); // copies sound into i2s
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
// Open Serial
|
||||
Serial.begin(115200);
|
||||
|
||||
// Setup sine wave
|
||||
sineWave.begin(sample_rate, B4);
|
||||
|
||||
// Open I2S
|
||||
I2SConfig config = out.defaultConfig(TX_MODE);
|
||||
config.sample_rate = sample_rate;
|
||||
config.channels = channels;
|
||||
config.bits_per_sample = sizeof(sound_t)*8;
|
||||
out.begin(config);
|
||||
}
|
||||
|
||||
// Arduino loop - copy sound to out
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
12
examples/streams-generator-serial/README.md
Normal file
12
examples/streams-generator-serial/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Display Generated Sound
|
||||
|
||||
Sometimes it is quite useful to be able to generate a test tone.
|
||||
We can use the GeneratedSoundStream class together with a SoundGenerator class. In my example I use a SineWaveGenerator.
|
||||
|
||||
In order to make sure that we use a consistent data type in the sound processing I also used a ```typedef int16_t sound_t;```. You could experiment with some other data types.
|
||||
|
||||
To be able to verify the result I used ```CsvStream<sound_t> printer(Serial, channels);``` which generates CSV output and sends it to Serial.
|
||||
|
||||
Here is the result on the Arduino Serial Plotter:
|
||||
|
||||
![serial-plotter](https://pschatzmann.github.io/arduino-audio-tools/resources/serial-plotter-sine.png)
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @file streams-url_raw-serial.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-url_raw-serial/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
|
||||
uint16_t sample_rate=44100;
|
||||
uint8_t channels = 2; // The stream will have 2 channels
|
||||
SineWaveGenerator<sound_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
|
||||
GeneratedSoundStream<sound_t> sound(sineWave, channels); // Stream generated from sine wave
|
||||
CsvStream<sound_t> printer(Serial, channels); // ASCII stream
|
||||
StreamCopy copier(printer, sound); // copies sound into printer
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
// Open Serial
|
||||
Serial.begin(115200);
|
||||
|
||||
// Setup sine wave
|
||||
sineWave.begin(sample_rate, B4);
|
||||
}
|
||||
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
38
examples/streams-memory_raw-i2s_external_dac/README.md
Normal file
38
examples/streams-memory_raw-i2s_external_dac/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Digital output via I2S to a external DAC
|
||||
|
||||
Somtimes we want to store the sound file in memory. [Audacity](https://www.audacityteam.org/) might help you out here: export with the file name audio.raw as RAW signed 16 bit PCM and copy it to the SD card. In the example I was just using one channel to save memory!.
|
||||
|
||||
Then you can convert the file with xxd into a C file that contains the data in an array. In the Sketch I am using the __MemoryStream class__ which turns the array into a Stream.
|
||||
|
||||
Unlike in the other examples I am using the typed __StreamCopyT<int16_t>__ class together with the __copy2()__ method. This reads the one channel input and copies it as 2 channels to the destination I2S stream.
|
||||
|
||||
Please note that you must compile this sketch with the __Partition Scheme: Huge App__!
|
||||
|
||||
|
||||
For my tests I am using the 24-bit PCM5102 PCM5102A Stereo DAC Digital-to-analog Converter PLL Voice Module pHAT
|
||||
![DAC](https://pschatzmann.github.io/arduino-audio-tools/resources/dac.jpeg)
|
||||
|
||||
### External DAC:
|
||||
|
||||
I am just using the default pins defined by the framework. However I could change them with the help of the config object. The mute pin can be defined in the constructor of the I2SStream - by not defining anything we use the default which is GPIO23
|
||||
|
||||
|
||||
| DAC | ESP32
|
||||
| --------| ---------------
|
||||
| VDD | 5V
|
||||
| GND | GND
|
||||
| SD | OUT (GPIO22)
|
||||
| L/R | GND
|
||||
| WS | WS (GPIO15)
|
||||
| SCK | BCK (GPIO14)
|
||||
| FMT | GND
|
||||
| XSMT | GPIO23
|
||||
|
||||
|
||||
- DEMP - De-emphasis control for 44.1kHz sampling rate(1): Off (Low) / On (High)
|
||||
- FLT - Filter select : Normal latency (Low) / Low latency (High)
|
||||
- SCK - System clock input (probably SCL on your board).
|
||||
- FMT - Audio format selection : I2S (Low) / Left justified (High)
|
||||
- XSMT - Soft mute control(1): Soft mute (Low) / soft un-mute (High)
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file streams-memory_raw-i2s_external_dac.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief Compile with Partition Scheme Hughe APP!
|
||||
* @version 0.1
|
||||
* @date 2021-01-24
|
||||
*
|
||||
* @copyright Copyright (c) 2021
|
||||
*
|
||||
*/
|
||||
#include "AudioTools.h"
|
||||
#include "StarWars30.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
uint8_t channels = 2;
|
||||
uint16_t sample_rate = 22050;
|
||||
|
||||
I2SStream i2s; // Output to I2S
|
||||
MemoryStream music(StarWars30_raw, StarWars30_raw_len);
|
||||
StreamCopyT<int16_t> copier(i2s, music); // copies sound into i2s
|
||||
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200);
|
||||
|
||||
I2SConfig config = i2s.defaultConfig(TX_MODE);
|
||||
config.sample_rate = sample_rate;
|
||||
config.channels = channels;
|
||||
config.bits_per_sample = 16;
|
||||
i2s.begin(config);
|
||||
|
||||
}
|
||||
|
||||
void loop(){
|
||||
if (!copier.copy2()){
|
||||
i2s.end();
|
||||
stop();
|
||||
}
|
||||
}
|
8
examples/streams-url_raw-serial/README.md
Normal file
8
examples/streams-url_raw-serial/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Display RAW Stream
|
||||
|
||||
Sometimes it is handy to check out the data on the screen with the help of the Arduino Serial Monitor and Serial Plotter.
|
||||
We read the raw binary data from an UrlStream.
|
||||
|
||||
As output stream we use a CsvStream: this class transforms the data into CSV and prints the result to Serial. Finally we can use the Arduino Serial Plotter to view the result as chart:
|
||||
|
||||
![serial-plotter](https://pschatzmann.github.io/arduino-audio-tools/resources/serial-plotter-01.png)
|
39
examples/streams-url_raw-serial/streams-url_raw-serial.ino
Normal file
39
examples/streams-url_raw-serial/streams-url_raw-serial.ino
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file streams-url_raw-serial.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-url_raw-serial/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "WiFi.h"
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
UrlStream music; // Music Stream
|
||||
int channels = 2; // The stream has 2 channels
|
||||
CsvStream<int16_t> printer(Serial, channels); // ASCII stream
|
||||
StreamCopy copier(printer, music); // copies music into printer
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
// Open Serial
|
||||
Serial.begin(115200);
|
||||
|
||||
// connect to WIFI
|
||||
WiFi.begin("network-name", "password");
|
||||
while (WiFi.status() != WL_CONNECTED){
|
||||
Serial.print(".");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
// open music stream - it contains 2 channels of int16_t data
|
||||
music.begin("https://pschatzmann.github.io/arduino-audio-tools/resources/audio.raw");
|
||||
}
|
||||
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
copier.copy();
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
|
||||
These folder contains some experiments, tests and work in progress, so do not expect them to work.
|
||||
These folder contains some work in progress, experiments and tests, so do not expect them to work.
|
||||
But it might still serve as a source of inspiration...
|
@ -1,22 +0,0 @@
|
||||
# SD Module
|
||||
|
||||
![sd](https://pschatzmann.github.io/arduino-audio-tools/doc/resources/sd-module.jpeg)
|
||||
|
||||
We are reading a raw audio file from the SD card and send it to a Bluetooth A2DP device. The audio file must be available using 16 bit integers with 2 channels.
|
||||
|
||||
The SD module is connected with the help of the SPI bus
|
||||
|
||||
### Pins:
|
||||
|
||||
We use SPI2 (HSPI):
|
||||
|
||||
| MCP6022 | ESP32
|
||||
|---------|---------------
|
||||
| VCC | 5V
|
||||
| GND | GND
|
||||
| CS | CS GP15
|
||||
| SCK | SCK GP14
|
||||
| MOSI | MOSI GP13
|
||||
| MISO | MISO GP12
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
I2S i2s; // I2S output destination
|
||||
File sound_file;
|
||||
AACDecoder decoder(i2s);
|
||||
const int sd_ss_pin = 4;
|
||||
const char* file_name = "audio.aac"
|
||||
uint8_t buffer[512];
|
||||
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Setup SD and open file
|
||||
SD.begin(sd_ss_pin);
|
||||
sound_file = SD.open(file_name, FILE_READ);
|
||||
|
||||
i2s.begin();
|
||||
decoder.begin();
|
||||
}
|
||||
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
if (sound_file.available()>0){
|
||||
int len = sound_file.readBytes(buffer, 512);
|
||||
decoder.write(buffer, len);
|
||||
}
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @file file_raw-external_dac.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/file_raw-external_dac/README.md
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
File sound_file;
|
||||
I2S<int16_t> i2s;
|
||||
I2SStream i2s_stream(i2s);
|
||||
StreamCopy streamCopy(i2s_stream, sound_file, 1024);
|
||||
const char* file_name = "/audio.raw";
|
||||
const int sd_ss_pin = 5;
|
||||
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Setup SD and open file
|
||||
SD.begin(sd_ss_pin);
|
||||
sound_file = SD.open(file_name, FILE_READ);
|
||||
|
||||
// start I2S with external DAC
|
||||
Serial.println("starting I2S...");
|
||||
i2s.begin(i2s.defaultConfig(TX_MODE));
|
||||
}
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
if (streamCopy.copy()){
|
||||
Serial.print(".");
|
||||
} else {
|
||||
Serial.println();
|
||||
Serial.println("Copy ended");
|
||||
delay(10000);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
# Stream SD File to I2S internal DAC
|
||||
|
||||
We are reading a raw audio file from the SD card and write the data to the analog pins of the ESP32 using the I2S interface. The audio file must be available using 16 bit integers with 2 channels.
|
||||
|
||||
[Audacity](https://www.audacityteam.org/) might help you out here: export with the file name audio.raw as RAW signed 16 bit PCM and copy it to the SD card. In my example I was using the file [audio.raw](https://pschatzmann.github.io/arduino-audio-tools/resources/audio.raw).
|
||||
|
||||
### SD Pins:
|
||||
|
||||
The SD module is connected with the help of the SPI bus
|
||||
|
||||
![sd](https://pschatzmann.github.io/arduino-audio-tools/resources/sd-module.jpeg)
|
||||
|
||||
We connect the SD to the ESP32:
|
||||
|
||||
| SD | ESP32
|
||||
|---------|---------------
|
||||
| VCC | 5V
|
||||
| GND | GND
|
||||
| CS | CS GP5
|
||||
| SCK | SCK GP18
|
||||
| MOSI | MOSI GP23
|
||||
| MISO | MISO GP19
|
||||
|
||||
|
||||
### Amplifier Pins:
|
||||
|
||||
To hear the sound we connect the ESP32 to an amplifier module: The analog output is available on GPIO25 & GPIO26. You could also use some earphones.
|
||||
|
||||
|
||||
| Amp | ESP32
|
||||
|---------|---------------
|
||||
| + | 5V
|
||||
| - | GND
|
||||
| L | GPIO25
|
||||
| R | GPIO26
|
||||
| T | GND
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* @file file_raw-internal_dac.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/file_raw-internal_dac/README.md
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
File sound_file;
|
||||
I2S<int16_t> i2s;
|
||||
I2SStream i2s_stream(i2s);
|
||||
StreamCopy streamCopy(i2s_stream, sound_file, 1024);
|
||||
const char* file_name = "/audio.raw";
|
||||
const int sd_ss_pin = 5;
|
||||
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Setup SD and open file
|
||||
SD.begin(sd_ss_pin);
|
||||
sound_file = SD.open(file_name, FILE_READ);
|
||||
|
||||
// start I2S with internal DAC -> GPIO25 & GPIO26
|
||||
Serial.println("starting I2S...");
|
||||
I2SConfig<int16_t> config = i2s.defaultConfig(TX_MODE);
|
||||
config.i2s.mode = static_cast<i2s_mode_t>( I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN);
|
||||
i2s.begin(config);
|
||||
}
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
if (streamCopy.copy()){
|
||||
Serial.print(".");
|
||||
} else {
|
||||
Serial.println();
|
||||
Serial.println("Copy ended");
|
||||
delay(10000);
|
||||
}
|
||||
}
|
21
sandbox/streams-adc-a2dp/README.md
Normal file
21
sandbox/streams-adc-a2dp/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Stream Analog input to A2DP Bluetooth
|
||||
|
||||
We can read an analog signal from a microphone and and send it to a Bluetooth A2DP device. To test the functionality I am using a MCP6022 microphone module.
|
||||
|
||||
![MCP6022](https://pschatzmann.github.io/arduino-audio-tools/resources/mcp6022.jpeg)
|
||||
![MCP6022](https://pschatzmann.github.io/arduino-audio-tools/resources/mcp6022-1.jpeg)
|
||||
|
||||
The MCP6022 is a anlog microphone which operates at 3.3 V
|
||||
We sample the sound signal with the help of the ESP32 I2S ADC input functionality.
|
||||
|
||||
In this implementation we are using the Streams !
|
||||
|
||||
### Pins:
|
||||
|
||||
| MCP6022 | ESP32
|
||||
|---------|---------------
|
||||
| VCC | 3.3
|
||||
| GND | GND
|
||||
| OUT | GPIO34
|
||||
|
||||
|
44
sandbox/streams-adc-a2dp/adc-a2dp.ino
Normal file
44
sandbox/streams-adc-a2dp/adc-a2dp.ino
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file adc-a2dp.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/adc-a2dp/README.md
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
/**
|
||||
* @brief We use a mcp6022 analog microphone as input and send the data to A2DP
|
||||
*/
|
||||
|
||||
ADC adc;
|
||||
ADCStream adcStream(adc);
|
||||
A2DPStream a2dpStream;
|
||||
StreamCopy copier(a2dp, adcStream, 512);
|
||||
|
||||
// The data has a center of around 26427, so we we need to shift it down to bring the center to 0
|
||||
ConverterScaler<int16_t> scaler(1.0, -26427, 32700 );
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// start i2s input with default configuration
|
||||
Serial.println("starting ADC...");
|
||||
adc.begin(adc.defaultConfig());
|
||||
|
||||
// start the bluetooth
|
||||
Serial.println("starting A2DP...");
|
||||
a2dpStream.start("MyMusic");
|
||||
}
|
||||
|
||||
// Arduino loop - repeated processing
|
||||
void loop() {
|
||||
copier.copy(scaler);
|
||||
}
|
33
sandbox/streams-i2s-a2dp/README.md
Normal file
33
sandbox/streams-i2s-a2dp/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
# Stream I2S Input to A2DP Bluetooth
|
||||
|
||||
## General Description:
|
||||
We implement a A2DP source: We stream the sound input which we read in from the I2S interface to a A2DP sink. We can use any device which provides the sound data via I2S. In order to test the functionality we use the INMP441 microphone.
|
||||
|
||||
In this Sketch we are using Streams!
|
||||
|
||||
![INMP441](https://pschatzmann.github.io/arduino-audio-tools/resources/inmp441.jpeg)
|
||||
|
||||
The INMP441 is a high-performance, low power, digital-output, omnidirectional MEMS microphone with a bottom port. The complete INMP441 solution consists of a MEMS sensor, signal conditioning, an analog-to-digital converter, anti-aliasing filters, power management, and an industry-standard 24-bit I²S interface. The I²S interface allows the INMP441 to connect directly to digital processors, such as DSPs and microcontrollers, without the need for an audio codec in the system.
|
||||
|
||||
## Pins
|
||||
|
||||
| INMP441 | ESP32
|
||||
| --------| ---------------
|
||||
| VDD | 3.3
|
||||
| GND | GND
|
||||
| SD | IN (GPIO32)
|
||||
| L/R | GND
|
||||
| WS | WS (GPIO15)
|
||||
| SCK | BCK (GPIO14)
|
||||
|
||||
|
||||
SCK: Serial data clock for I²S interface
|
||||
WS: Select serial data words for the I²S interface
|
||||
L/R: Left / right channel selection
|
||||
When set to low, the microphone emits signals on the left channel of the I²S frame.
|
||||
When the high level is set, the microphone will send signals on the right channel.
|
||||
ExSD: Serial data output of the I²S interface
|
||||
VCC: input power 1.8V to 3.3V
|
||||
GND: Power groundHigh PSR: -75 dBFS.
|
||||
|
39
sandbox/streams-i2s-a2dp/i2s-a2dp.ino
Normal file
39
sandbox/streams-i2s-a2dp/i2s-a2dp.ino
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file i2s-a2dp.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/i2s-a2dp/README.md
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
I2S<int32_t> i2s;
|
||||
I2SStream i2sStream(i2s); // Access I2S as stream
|
||||
const A2DPStream &a2dpStream = A2DPStream.instance(); // access A2DP as stream
|
||||
ChannelConverter<int32_t> converter(&convertFrom32To16);
|
||||
ConverterFillLeftAndRight<int32_t> bothChannels;
|
||||
StreamCopy copier(i2sStream, a2dpStream, 1024); // copy a2dpStream to i2sStream
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// start i2s input with default configuration
|
||||
Serial.println("starting I2S...");
|
||||
i2s.begin(i2s.defaultConfig(RX_MODE));
|
||||
|
||||
// start the bluetooth
|
||||
Serial.println("starting A2DP...");
|
||||
a2dpStream.begin("MyMusic");
|
||||
}
|
||||
|
||||
// Arduino loop - copy data
|
||||
void loop() {
|
||||
copier.copy(bothChannels, converter)
|
||||
}
|
110110
sandbox/streams-memory_raw-file_wav/StarWars30.h
Normal file
110110
sandbox/streams-memory_raw-file_wav/StarWars30.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,26 +1,9 @@
|
||||
# Stream SD File to I2S external DAC
|
||||
# Stream URL to I2S external DAC
|
||||
|
||||
We are reading a raw audio file from the SD card and write the data to the I2S interface. The audio file must be available using 16 bit integers with 2 channels.
|
||||
We are reading a raw audio file from the Intenet and write the data to the I2S interface. The audio file must be available using 16 bit integers with 2 channels.
|
||||
|
||||
[Audacity](https://www.audacityteam.org/) might help you out here: export with the file name audio.raw as RAW signed 16 bit PCM and copy it to the SD card. In my example I was using the file [audio.raw](https://pschatzmann.github.io/arduino-audio-tools/resources/audio.raw).
|
||||
|
||||
### SD Pins:
|
||||
|
||||
The SD module is connected with the help of the SPI bus
|
||||
|
||||
![sd](https://pschatzmann.github.io/arduino-audio-tools/resources/sd-module.jpeg)
|
||||
|
||||
We connect the SD to the ESP32:
|
||||
|
||||
| SD | ESP32
|
||||
|---------|---------------
|
||||
| VCC | 5V
|
||||
| GND | GND
|
||||
| CS | CS GP5
|
||||
| SCK | SCK GP18
|
||||
| MOSI | MOSI GP23
|
||||
| MISO | MISO GP19
|
||||
|
||||
|
||||
### External DAC:
|
||||
|
||||
@ -34,7 +17,7 @@ We connect the SD to the ESP32:
|
||||
| SCK | BCK (GPIO14)
|
||||
|
||||
|
||||
|
||||
Comments - The Playing is breaking up most likely because we receive the data not fast enough. Try to make an example with a lower sampes per second....
|
||||
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file url_raw-I2S_external_dac.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/url_raw-I2S_externel_dac/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "WiFi.h"
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
UrlStream music; // Music Stream
|
||||
I2S<int16_t> i2s; // I2S
|
||||
I2SStream i2s_stream(i2s);// I2S as Stream
|
||||
StreamCopy copier(i2s_stream, music, 1024); // copy music to i2s_stream
|
||||
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// connect to WIFI
|
||||
WiFi.begin("network", "pwd");
|
||||
while (WiFi.status() != WL_CONNECTED){
|
||||
Serial.print(".");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
// open music stream
|
||||
music.begin("https://pschatzmann.github.io/arduino-audio-tools/resources/audio-8000.raw");
|
||||
|
||||
// start I2S with external DAC
|
||||
Serial.println("\nstarting I2S...");
|
||||
I2SConfig cfg = i2s.defaultConfig(TX_MODE);
|
||||
cfg.sample_rate = 8000;
|
||||
i2s.begin(cfg);
|
||||
}
|
||||
|
||||
// Arduino loop - repeated processing: copy input stream to output stream
|
||||
void loop() {
|
||||
int len = copier.copy();
|
||||
if (len){
|
||||
Serial.print(".");
|
||||
} else {
|
||||
i2s.end();
|
||||
Serial.println("\nCopy ended");
|
||||
delay(10000);
|
||||
}
|
||||
}
|
24
sandbox/streams-url_raw-I2S_internal_dac/README.md
Normal file
24
sandbox/streams-url_raw-I2S_internal_dac/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Stream URL to I2S internal DAC
|
||||
|
||||
I am reading a raw audio file from the Intenet and write the data to the analog pins of the ESP32 using the I2S interface. The audio file must be available using 16 bit integers with 2 channels.
|
||||
|
||||
[Audacity](https://www.audacityteam.org/) might help you out here: export with the file name audio.raw as RAW signed 16 bit PCM and copy it to the SD card. In my example I was using the file [audio.raw](https://pschatzmann.github.io/arduino-audio-tools/resources/audio.raw).
|
||||
|
||||
|
||||
### Amplifier Pins:
|
||||
|
||||
To hear the sound I connected the ESP32 to an amplifier module: The analog output is available on GPIO25 & GPIO26. But you could also use some earphones...
|
||||
|
||||
|
||||
| Amp | ESP32
|
||||
|---------|---------------
|
||||
| + | 5V
|
||||
| - | GND
|
||||
| L | GPIO25
|
||||
| R | GPIO26
|
||||
| T | GND
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file url_raw-I2S_internal_dac.ino
|
||||
* @author Phil Schatzmann
|
||||
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/url_raw-I2S_internal_dac/README.md
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
UrlStream music; // Music Stream
|
||||
AnalogAudio dac;
|
||||
StreamCopy streamCopy(dac, music);
|
||||
ConverterToInternalDACFormat<int16_t> converter;
|
||||
|
||||
// Arduino Setup
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
// open music stream
|
||||
music.begin("https://pschatzmann.github.io/arduino-audio-tools/resources/audio-8000.raw");
|
||||
|
||||
// start I2S with internal DAC -> GPIO25 & GPIO26
|
||||
Serial.println("starting I2S...");
|
||||
I2SConfig cfg = i2s.defaultConfig(TX_MODE);
|
||||
cfg.sample_rate = 8000;
|
||||
i2s.begin(cfg);
|
||||
}
|
||||
|
||||
// Arduino loop - repeated processing: copy input stream to output stream
|
||||
void loop() {
|
||||
if (!streamCopy.copy(converter)){
|
||||
Serial.println("Copy ended");
|
||||
dac.end();
|
||||
music.end();
|
||||
stop();
|
||||
}
|
||||
}
|
@ -2,17 +2,37 @@
|
||||
|
||||
using namespace audio_tools;
|
||||
|
||||
void setup(){
|
||||
int24_t value;
|
||||
Serial.begin(115200);
|
||||
|
||||
void testMath() {
|
||||
int24_t value(1);
|
||||
|
||||
Serial.println("Expecting 2:")
|
||||
Serial.println(value+value);
|
||||
Serial.println(1+value);
|
||||
Serial.println(value+1);
|
||||
Serial.println("Expecting true")
|
||||
Serial.println(1==value);
|
||||
Serial.println(value==1);
|
||||
Serial.println(value>=1);
|
||||
Serial.println(value<=1);
|
||||
}
|
||||
|
||||
void testCompare() {
|
||||
int24_t value;
|
||||
for (int32_t j=8388607;j>-8388606;j++){
|
||||
value = j;
|
||||
Serial.print(j);
|
||||
Serial.print(", ");
|
||||
Serial.print((int32_t)value);
|
||||
Serial.println((int32_t)value == j ? ", OK" : ", ERROR");
|
||||
Serial.print(value);
|
||||
Serial.println(static_cast<int32_t<(value) == j ? ", OK" : ", ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
void setup(){
|
||||
Serial.begin(115200);
|
||||
|
||||
testMath();
|
||||
testCompare();
|
||||
|
||||
}
|
||||
|
||||
|
130
src/AudioA2DP.h
Normal file
130
src/AudioA2DP.h
Normal file
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @file AudioA2DP.h
|
||||
* @author Phil Schatzmann
|
||||
* @brief A2DP Support via Arduino Streams
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "AudioTools.h"
|
||||
|
||||
#ifdef USE_A2DP
|
||||
|
||||
#include "BluetoothA2DPSink.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
#include "AudioESP8266.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Covnerts the data from T src[][2] to a Channels array
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
class ChannelConverter {
|
||||
public:
|
||||
ChannelConverter( int16_t (*convert_ptr)(T from)){
|
||||
this->convert_ptr = convert_ptr;
|
||||
}
|
||||
|
||||
// The data is provided as int24_t tgt[][2] but returned as int24_t
|
||||
void convert(T src[][2], Channels* channels, size_t size) {
|
||||
for (int i=size; i>0; i--) {
|
||||
channels[i].channel1 = (*convert_ptr)(src[i][0]);
|
||||
channels[i].channel2 = (*convert_ptr)(src[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t (*convert_ptr)(T from);
|
||||
|
||||
};
|
||||
|
||||
AudioOutputWithCallback a2dp_stream_out(A2DP_BUFFER_SIZE, A2DP_BUFFER_COUNT);
|
||||
|
||||
// callback used by A2DP to provide the sound data
|
||||
int32_t a2dp_stream_source_sound_data(Channels* data, int32_t len) {
|
||||
// the data in the file must be in int16 with 2 channels
|
||||
size_t result_len_bytes = a2dp_stream_out.read(data, len);
|
||||
// result is in number of frames
|
||||
int32_t result_len = result_len_bytes / sizeof(Channels);
|
||||
ESP_LOGD("get_sound_data", "%d -> %d", result_len );
|
||||
return result_len;
|
||||
}
|
||||
|
||||
// callback used by A2DP to write the sound data
|
||||
void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len) {
|
||||
a2dp_stream_out.write(data, len);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Stream support for A2DP: When calling write we start a a2dp_source - when calling read we start a a2dp_sink
|
||||
* The data is in int16_t with 2 channels at 44100 hertz.
|
||||
*
|
||||
* Because we support only one instance the class is implemented as singleton!
|
||||
*/
|
||||
class A2DPStream : public BufferedStream {
|
||||
public:
|
||||
|
||||
/// Just defines the bluetooth name
|
||||
void begin(char* name) {
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
/// Provides the A2DPStream singleton instance
|
||||
static A2DPStream &instance() {
|
||||
static A2DPStream *ptr;
|
||||
if (ptr==nullptr){
|
||||
ptr = new A2DPStream();
|
||||
}
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// provides access to the
|
||||
BluetoothA2DPSource &source() {
|
||||
return a2dp_source;
|
||||
}
|
||||
|
||||
/// provides access to the BluetoothA2DPSink
|
||||
BluetoothA2DPSink &sink(){
|
||||
return a2dp_sink;
|
||||
}
|
||||
|
||||
protected:
|
||||
BluetoothA2DPSource a2dp_source;
|
||||
BluetoothA2DPSink a2dp_sink;
|
||||
bool is_setup = false;
|
||||
char* name = nullptr;
|
||||
|
||||
A2DPStream() : BufferedStream(A2DP_BUFFER_SIZE){
|
||||
}
|
||||
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) {
|
||||
if (name==nullptr) return 0;
|
||||
if (!is_setup){
|
||||
a2dp_source.start(name, a2dp_stream_source_sound_data);
|
||||
is_setup = true;
|
||||
}
|
||||
return a2dp_stream_out.write(data, len);
|
||||
}
|
||||
|
||||
virtual size_t readExt( uint8_t *data, size_t len) {
|
||||
if (name==nullptr) return 0;
|
||||
if (!is_setup){
|
||||
a2dp_sink.set_stream_reader(&a2dp_stream_sink_sound_data, false);
|
||||
a2dp_sink.start(name);
|
||||
is_setup = true;
|
||||
}
|
||||
return a2dp_stream_out.readBytes(data, len);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
65
src/AudioConfig.h
Normal file
65
src/AudioConfig.h
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @author Phil Schatzmann
|
||||
* @brief AutioTools Configuration
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @brief Optional Functionality - comment out if not wanted
|
||||
*/
|
||||
|
||||
#define USE_ESP8266_AUDIO
|
||||
#ifdef ESP32
|
||||
#define USE_A2DP
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Common Default Settings that can usually be changed in the API
|
||||
*/
|
||||
#ifndef LED_BUILTIN
|
||||
#define LED_BUILTIN 13 // pin number is specific to your esp32 board
|
||||
#endif
|
||||
#define DEFAULT_BUFFER_SIZE 1024
|
||||
#define I2S_DEFAULT_SAMPLE_RATE 44100
|
||||
#define I2S_DEFAULT_CHANNELS 2
|
||||
#define I2S_DEFAULT_BITS_PER_SAMPLE 16
|
||||
#define I2S_DEFAULT_PORT 0
|
||||
#define I2S_BUFFER_SIZE 1024
|
||||
#define I2S_BUFFER_COUNT 6
|
||||
#define A2DP_BUFFER_SIZE 512
|
||||
#define A2DP_BUFFER_COUNT 5
|
||||
#define DEFAUT_ADC_PIN 34
|
||||
#define I2S_TAG "I2S"
|
||||
|
||||
/**
|
||||
* @brief Platform specific Settings
|
||||
*
|
||||
*/
|
||||
#ifdef ESP32
|
||||
|
||||
#define USE_ESP32_LOGGER
|
||||
#define PIN_I2S_BCK 14
|
||||
#define PIN_I2S_WS 15
|
||||
#define PIN_I2S_DATA_IN 32
|
||||
#define PIN_I2S_DATA_OUT 22
|
||||
#define I2S_USE_APLL false
|
||||
// Default Setting: The mute pin can be switched off by setting it to -1. Or you could drive the LED by assigning LED_BUILTIN
|
||||
#define PIN_I2S_MUTE 23
|
||||
#define SOFT_MUTE_VALUE LOW
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
#define PIN_I2S_BCK -1
|
||||
#define PIN_I2S_WS -1
|
||||
#define PIN_I2S_DATA_IN -1
|
||||
#define PIN_I2S_DATA_OUT -1
|
||||
#define I2S_USE_APLL false
|
||||
#define PIN_I2S_MUTE 23
|
||||
#define SOFT_MUTE_VALUE LOW
|
||||
|
||||
#endif
|
||||
|
98
src/AudioESP8266.h
Normal file
98
src/AudioESP8266.h
Normal file
@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioTools.h"
|
||||
#ifdef USE_ESP8266_AUDIO
|
||||
#include "AudioOutput.h"
|
||||
#include "SoundData.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief ESP8266Audio AudioOutput class which stores the data in a temporary buffer.
|
||||
* The buffer can be consumed e.g. by a callback function by calling read();
|
||||
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
class AudioOutputWithCallback : public AudioOutput, public BufferedStream {
|
||||
public:
|
||||
// Default constructor
|
||||
AudioOutputWithCallback(int bufferSize, int bufferCount ):BufferedStream(bufferSize) {
|
||||
callback_buffer_ptr = new NBuffer<Channels>(bufferSize, bufferCount);
|
||||
}
|
||||
|
||||
virtual ~AudioOutputWithCallback() {
|
||||
delete callback_buffer_ptr;
|
||||
}
|
||||
|
||||
/// Activates the output
|
||||
virtual bool begin() {
|
||||
active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// puts the sample into a buffer
|
||||
virtual bool ConsumeSample(int16_t sample[2]) {
|
||||
Channels c;
|
||||
c.channel1 = sample[0];
|
||||
c.channel2 = sample[1];
|
||||
return callback_buffer_ptr->write(c);
|
||||
};
|
||||
|
||||
/// stops the processing
|
||||
virtual bool stop() {
|
||||
active = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
/// Provides the data from the internal buffer to the callback
|
||||
size_t read(Channels *src, size_t len){
|
||||
return active ? this->callback_buffer_ptr->readArray(src, len) : 0;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
NBuffer<Channels> *callback_buffer_ptr;
|
||||
bool active;
|
||||
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) {
|
||||
return callback_buffer_ptr->writeArray((Channels*)data, len/sizeof(Channels));
|
||||
}
|
||||
|
||||
virtual size_t readExt( uint8_t *data, size_t len) {
|
||||
return callback_buffer_ptr->readArray((Channels*)data, len/sizeof(Channels));;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// /**
|
||||
// * @brief ESP8266Audio Output to Stream
|
||||
// *
|
||||
// */
|
||||
// class SerialOutputStream : public AudioOutput {
|
||||
// public:
|
||||
// SerialOutputStream(const Stream &out){
|
||||
// this->out = &out;
|
||||
// }
|
||||
|
||||
// virtual bool begin() {
|
||||
// active = true;
|
||||
// }
|
||||
|
||||
// virtual bool ConsumeSample(int16_t sample[2]){
|
||||
// if (active) out->write((uint8_t*)sample, 2*sizeof(int16_t));
|
||||
// }
|
||||
|
||||
// virtual bool stop() {
|
||||
// active = false;
|
||||
// }
|
||||
|
||||
// protected:
|
||||
// Stream *out;
|
||||
// bool active;
|
||||
// };
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -7,13 +7,14 @@
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*/
|
||||
#include "AudioConfig.h"
|
||||
#include "AudioTools/AudioTypes.h"
|
||||
#include "AudioTools/Buffers.h"
|
||||
#include "AudioTools/Converter.h"
|
||||
#include "AudioTools/MusicalNotes.h"
|
||||
#include "AudioTools/SoundGenerator.h"
|
||||
#include "AudioTools/I2S.h"
|
||||
#include "AudioTools/ADC.h"
|
||||
#include "AudioTools/AudioI2S.h"
|
||||
#include "AudioTools/AnalogAudio.h"
|
||||
#include "AudioTools/AudioLogger.h"
|
||||
#include "AudioTools/Buffers.h"
|
||||
#include "AudioTools/Streams.h"
|
||||
|
@ -2,13 +2,16 @@
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "esp_a2dp_api.h"
|
||||
#pragma once
|
||||
#include "AudioConfig.h"
|
||||
#if(defined ESP32)
|
||||
#include "driver/i2s.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/adc.h"
|
||||
|
||||
#ifndef DEFAUT_ADC_PIN
|
||||
#define DEFAUT_ADC_PIN 34
|
||||
#include "esp_a2dp_api.h"
|
||||
//#include "freertos/queue.h"
|
||||
#endif
|
||||
#if(defined ESP8266)
|
||||
#include "i2s.h"
|
||||
#define I2S_NUM_0 0
|
||||
#endif
|
||||
|
||||
namespace audio_tools {
|
||||
@ -17,30 +20,46 @@ typedef int16_t arrayOf2int16_t[2];
|
||||
|
||||
const char* ADC_TAG = "ADC";
|
||||
/**
|
||||
* @brief ESP32 specific configuration for i2s input via adc. The default input pin is GPIO34.
|
||||
* @brief ESP32 specific configuration for i2s input via adc. The default input pin is GPIO34. We always use int16_t values on 2 channels
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*
|
||||
*/
|
||||
class ADCConfig {
|
||||
class AnalogConfig {
|
||||
public:
|
||||
// allow ADC to access the protected methods
|
||||
friend class ADC;
|
||||
friend class AnalogAudio;
|
||||
|
||||
// public config parameters
|
||||
RxTxMode mode;
|
||||
int sample_rate = 44100;
|
||||
int dma_buf_count = 10;
|
||||
int dma_buf_len = 512;
|
||||
int dma_buf_count = I2S_BUFFER_COUNT;
|
||||
int dma_buf_len = I2S_BUFFER_SIZE;
|
||||
bool use_apll = false;
|
||||
int mode_internal;
|
||||
|
||||
AnalogConfig() {
|
||||
this->mode = RX_MODE;
|
||||
setPin(DEFAUT_ADC_PIN);
|
||||
mode_internal = (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN);
|
||||
}
|
||||
|
||||
|
||||
/// Default constructor
|
||||
ADCConfig() {
|
||||
setPin(DEFAUT_ADC_PIN);
|
||||
AnalogConfig(RxTxMode mode) {
|
||||
this->mode = mode;
|
||||
if (mode == RX_MODE) {
|
||||
setPin(DEFAUT_ADC_PIN);
|
||||
mode_internal = (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN);
|
||||
} else {
|
||||
mode_internal = (I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN);
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy constructor
|
||||
ADCConfig(const ADCConfig &cfg) = default;
|
||||
AnalogConfig(const AnalogConfig &cfg) = default;
|
||||
|
||||
/// provides the current ADC pin
|
||||
int pin() {
|
||||
@ -98,32 +117,37 @@ class ADCConfig {
|
||||
|
||||
|
||||
/**
|
||||
* @brief A very fast Analog to Digital Converter which is using the ESP32 I2S interface
|
||||
* @brief A very fast ADC and DAC using the ESP32 I2S interface.
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
class ADC {
|
||||
class AnalogAudio {
|
||||
friend class AnalogAudioStream;
|
||||
|
||||
public:
|
||||
/// Default constructor
|
||||
ADC() {
|
||||
AnalogAudio() {
|
||||
}
|
||||
|
||||
/// Destructor
|
||||
~ADC() {
|
||||
stop();
|
||||
~AnalogAudio() {
|
||||
end();
|
||||
}
|
||||
|
||||
/// Provides the default configuration
|
||||
ADCConfig defaultConfig() {
|
||||
AnalogConfig defaultConfig(RxTxMode mode) {
|
||||
ESP_LOGD(ADC_TAG, "%s", __func__);
|
||||
ADCConfig config;
|
||||
AnalogConfig config(mode);
|
||||
return config;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(ADCConfig cfg) {
|
||||
void begin(AnalogConfig cfg) {
|
||||
ESP_LOGI(ADC_TAG, "%s", __func__);
|
||||
|
||||
adc_config = cfg;
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
|
||||
.mode = (i2s_mode_t) cfg.mode_internal,
|
||||
.sample_rate = cfg.sample_rate,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
@ -146,42 +170,66 @@ class ADC {
|
||||
ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_zero_dma_buffer");
|
||||
}
|
||||
|
||||
//init ADC pad
|
||||
if (i2s_set_adc_mode(cfg.unit, cfg.channel)!=ESP_OK) {
|
||||
ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_driver_install");
|
||||
}
|
||||
switch (cfg.mode) {
|
||||
case RX_MODE:
|
||||
//init ADC pad
|
||||
if (i2s_set_adc_mode(cfg.unit, cfg.channel)!=ESP_OK) {
|
||||
ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_driver_install");
|
||||
}
|
||||
|
||||
// enable the ADC
|
||||
if (i2s_adc_enable(i2s_num)!=ESP_OK) {
|
||||
ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_adc_enable");
|
||||
// enable the ADC
|
||||
if (i2s_adc_enable(i2s_num)!=ESP_OK) {
|
||||
ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_adc_enable");
|
||||
}
|
||||
break;
|
||||
case TX_MODE:
|
||||
i2s_set_pin(i2s_num, NULL);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(ADC_TAG, "%s - %s", __func__,"end");
|
||||
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void stop(){
|
||||
void end(){
|
||||
ESP_LOGD(ADC_TAG, "%s", __func__);
|
||||
i2s_driver_uninstall(i2s_num);
|
||||
}
|
||||
|
||||
/// Reads data from I2S
|
||||
size_t read(int16_t (*src)[2], size_t sizeFrames, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
size_t len = readBytes(src, sizeFrames * sizeof(int16_t)*2, ticks_to_wait); // 2 bytes * 2 channels
|
||||
size_t read(int16_t (*src)[2], size_t sizeFrames){
|
||||
size_t len = readBytes(src, sizeFrames * sizeof(int16_t)*2); // 2 bytes * 2 channels
|
||||
size_t result = len / (sizeof(int16_t) * 2);
|
||||
ESP_LOGD(ADC_TAG, "%s - len: %d -> %d", __func__,sizeFrames, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
AnalogConfig &config() {
|
||||
return adc_config;
|
||||
}
|
||||
|
||||
protected:
|
||||
const i2s_port_t i2s_num =(i2s_port_t) 0; // Analog input only supports 0!
|
||||
const i2s_port_t i2s_num = I2S_NUM_0; // Analog input only supports 0!
|
||||
AnalogConfig adc_config;
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
size_t result = 0;
|
||||
if (i2s_read(i2s_num, dest, size_bytes, &result, ticks_to_wait)!=ESP_OK){
|
||||
ESP_LOGE(ADC_TAGƒ, "%s", __func__);
|
||||
/// writes the data to the I2S interface
|
||||
size_t writeBytes(const void *src, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
if (i2s_write(i2s_num, src, size_bytes, &result, portMAX_DELAY)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
if (i2s_read(i2s_num, dest, size_bytes, &result, portMAX_DELAY)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
ESP_LOGD(ADC_TAG, "%s - len: %d -> %d", __func__, size_bytes, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
50
src/AudioTools/AudioI2S.h
Normal file
50
src/AudioTools/AudioI2S.h
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioConfig.h"
|
||||
#include "AudioTypes.h"
|
||||
#include "I2S_ESP32.h"
|
||||
#include "I2S_ESP8266.h"
|
||||
#include "I2S_SAMD.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief A Simple I2S interface class for the ESP32 which supports the reading and writing with a defined data type
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
template<typename T>
|
||||
class I2S : public I2SBase {
|
||||
|
||||
public:
|
||||
/// Default Constructor
|
||||
I2S() {
|
||||
}
|
||||
|
||||
/// Destructor
|
||||
~I2S() {
|
||||
end();
|
||||
}
|
||||
|
||||
void begin(I2SConfig cfg) {
|
||||
// define bits per sampe from data type
|
||||
cfg.bits_per_sample = sizeof(T) * 8;
|
||||
I2SBase::begin(cfg);
|
||||
}
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t write(T (*src)[2], size_t frameCount){
|
||||
return writeBytes(src, frameCount * sizeof(T) * 2); // 2 bytes * 2 channels
|
||||
}
|
||||
|
||||
/// Reads data from I2S
|
||||
size_t read(T (*src)[2], size_t frameCount){
|
||||
size_t len = readBytes(src, frameCount * sizeof(T) * 2); // 2 bytes * 2 channels
|
||||
size_t result = len / (sizeof(T) * 2);
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioConfig.h"
|
||||
#include "Stream.h"
|
||||
|
||||
#ifndef SOUND_LOG_LEVEL
|
||||
@ -50,59 +51,66 @@ class AudioLogger {
|
||||
}
|
||||
|
||||
/// logs an error
|
||||
void error(const char *str, const char* str1=nullptr, const char* str2=nullptr) const {
|
||||
log(Error, str, str1, str2);
|
||||
void error(const char *str) const {
|
||||
printf(Error,"%s\n", str);
|
||||
}
|
||||
|
||||
/// logs an info message
|
||||
void info(const char *str, const char* str1=nullptr, const char* str2=nullptr) const {
|
||||
log(Info, str, str1, str2);
|
||||
void info(const char *str) const {
|
||||
printf(Info,"%s\n", str);
|
||||
}
|
||||
|
||||
/// logs an warning
|
||||
void warning(const char *str, const char* str1=nullptr, const char* str2=nullptr) const {
|
||||
log(Warning, str, str1, str2);
|
||||
void warning(const char *str) const {
|
||||
printf(Warning,"%s\n", str);
|
||||
}
|
||||
|
||||
/// writes an debug message
|
||||
void debug(const char *str, const char* str1=nullptr, const char* str2=nullptr) const {
|
||||
log(Debug, str, str1, str2);
|
||||
void debug(const char *str) const {
|
||||
printf(Debug,"%s\n", str);
|
||||
}
|
||||
|
||||
/// printf support
|
||||
int printf(LogLevel current_level, const char* fmt, ...) const {
|
||||
char serial_printf_buffer[PRINTF_BUFFER_SIZE] = {0};
|
||||
int len = 0;
|
||||
va_list args;
|
||||
|
||||
#ifdef USE_ESP32_LOGGER
|
||||
va_start(args,fmt);
|
||||
len = vsnprintf(serial_printf_buffer, PRINTF_BUFFER_SIZE, fmt, args);
|
||||
va_end(args);
|
||||
Serial.println(serial_printf_buffer);
|
||||
// TODO why is the following not working
|
||||
switch(current_level){
|
||||
case Error:
|
||||
esp_log_write(ESP_LOG_ERROR, TAG, (const char*)serial_printf_buffer);
|
||||
break;
|
||||
case Info:
|
||||
esp_log_write(ESP_LOG_INFO, TAG, (const char*)serial_printf_buffer);
|
||||
break;
|
||||
case Warning:
|
||||
esp_log_write(ESP_LOG_WARN,TAG, (const char*)serial_printf_buffer);
|
||||
break;
|
||||
case Debug:
|
||||
esp_log_write(ESP_LOG_DEBUG, TAG, (const char*)serial_printf_buffer);
|
||||
break;
|
||||
default:
|
||||
esp_log_write(ESP_LOG_INFO, TAG, (const char*)serial_printf_buffer);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
if (this->active && log_stream_ptr!=nullptr && current_level >= log_level){
|
||||
char serial_printf_buffer[PRINTF_BUFFER_SIZE] = {0};
|
||||
va_list args;
|
||||
va_start(args,fmt);
|
||||
len = vsnprintf(serial_printf_buffer,PRINTF_BUFFER_SIZE, fmt, args);
|
||||
log_stream_ptr->print(serial_printf_buffer);
|
||||
va_end(args);
|
||||
}
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/// write an message to the log
|
||||
void log(LogLevel current_level, const char *str, const char* str1=nullptr, const char* str2=nullptr) const {
|
||||
if (this->active && log_stream_ptr!=nullptr){
|
||||
if (current_level >= log_level){
|
||||
log_stream_ptr->print((char*)str);
|
||||
if (str1!=nullptr){
|
||||
log_stream_ptr->print(" ");
|
||||
log_stream_ptr->print(str1);
|
||||
}
|
||||
if (str2!=nullptr){
|
||||
log_stream_ptr->print(" ");
|
||||
log_stream_ptr->print(str2);
|
||||
}
|
||||
log_stream_ptr->println();
|
||||
log_stream_ptr->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// provides the singleton instance
|
||||
static AudioLogger &instance(){
|
||||
static AudioLogger *ptr;
|
||||
@ -114,15 +122,14 @@ class AudioLogger {
|
||||
|
||||
|
||||
protected:
|
||||
Stream *log_stream_ptr;
|
||||
LogLevel log_level;
|
||||
bool active;
|
||||
Stream *log_stream_ptr = nullptr;
|
||||
const char* TAG = "AudioTools";
|
||||
LogLevel log_level = Error;
|
||||
bool active = false;
|
||||
|
||||
AudioLogger(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioConfig.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
#define INT24_MAX 0x7FFFFF
|
||||
@ -10,7 +12,7 @@ namespace audio_tools {
|
||||
* @copyright GPLv3
|
||||
*
|
||||
*/
|
||||
class int24_t {
|
||||
class int24_t : public Printable {
|
||||
|
||||
public:
|
||||
|
||||
@ -21,7 +23,10 @@ class int24_t {
|
||||
}
|
||||
|
||||
int24_t(void *ptr){
|
||||
int24_t(static_cast<uint8_t*>(ptr));
|
||||
uint8_t *ptrChar = static_cast<uint8_t*>(ptr);
|
||||
value[0]=ptrChar[0];
|
||||
value[1]=ptrChar[1];
|
||||
value[2]=ptrChar[2];
|
||||
}
|
||||
|
||||
int24_t(uint8_t *ptr){
|
||||
@ -30,19 +35,19 @@ class int24_t {
|
||||
value[2]=ptr[2];
|
||||
}
|
||||
|
||||
int24_t(int16_t in){
|
||||
int24_t(const int16_t &in) {
|
||||
value[2] = in > 0 ? 0 : 0xFF;
|
||||
value[1] = (in >> 8) & 0xFF;
|
||||
value[0] = in & 0xFF;
|
||||
}
|
||||
|
||||
int24_t(int32_t in){
|
||||
int24_t(const int32_t &in){
|
||||
value[2] = (in >> 16) & 0xFF;;
|
||||
value[1] = (in >> 8) & 0xFF;
|
||||
value[0] = in & 0xFF;
|
||||
}
|
||||
|
||||
operator int32_t() {
|
||||
operator int32_t() const {
|
||||
uint8_t tmp[4];
|
||||
tmp[0] = value[0];
|
||||
tmp[1] = value[1];
|
||||
@ -52,25 +57,31 @@ class int24_t {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
operator float() {
|
||||
return static_cast<int32_t>(*this);
|
||||
operator float() const {
|
||||
return static_cast<float>(*this);
|
||||
}
|
||||
|
||||
/// provides value between -32767 and 32767
|
||||
int16_t scale16() {
|
||||
int16_t scale16() const {
|
||||
return static_cast<float>(*this) * INT16_MAX / INT24_MAX ;
|
||||
}
|
||||
|
||||
/// provides value between -2,147,483,647 and 2,147,483,647
|
||||
int32_t scale32() {
|
||||
int32_t scale32() const {
|
||||
return static_cast<float>(*this) / static_cast<float>(INT24_MAX) * static_cast<float>(INT32_MAX) ;
|
||||
}
|
||||
|
||||
/// provides value between -1.0 and 1.0
|
||||
float scaleFloat() {
|
||||
float scaleFloat() const {
|
||||
return static_cast<float>(*this) / INT24_MAX ;
|
||||
}
|
||||
|
||||
virtual size_t printTo(Print& p) const {
|
||||
// just print as int32_t
|
||||
const int32_t v = *this;
|
||||
return p.print(v);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t value[3];
|
||||
};
|
||||
@ -94,7 +105,62 @@ class AudioBaseInfoDependent {
|
||||
public:
|
||||
virtual ~AudioBaseInfoDependent(){}
|
||||
virtual void setAudioBaseInfo(AudioBaseInfo info) {};
|
||||
virtual bool validate(AudioBaseInfo &info){
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
enum RxTxMode { TX_MODE, RX_MODE };
|
||||
|
||||
|
||||
enum I2SMode {
|
||||
I2S_PHILIPS_MODE,
|
||||
I2S_RIGHT_JUSTIFIED_MODE,
|
||||
I2S_LEFT_JUSTIFIED_MODE
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief configuration for all common i2s settings
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
class I2SConfig {
|
||||
public:
|
||||
|
||||
I2SConfig() {
|
||||
}
|
||||
|
||||
/// Constructor
|
||||
I2SConfig(RxTxMode mode) {
|
||||
this->rx_tx_mode = mode;
|
||||
pin_data = rx_tx_mode == TX_MODE ? PIN_I2S_DATA_OUT : PIN_I2S_DATA_IN;
|
||||
}
|
||||
|
||||
/// Default Copy Constructor
|
||||
I2SConfig(const I2SConfig &cfg) = default;
|
||||
|
||||
|
||||
/// public settings
|
||||
RxTxMode rx_tx_mode = TX_MODE;
|
||||
int port_no = 0; // processor dependent port
|
||||
int channels = I2S_DEFAULT_CHANNELS;
|
||||
int sample_rate = I2S_DEFAULT_SAMPLE_RATE;
|
||||
int bits_per_sample = I2S_DEFAULT_BITS_PER_SAMPLE;
|
||||
int pin_ws = PIN_I2S_WS;
|
||||
int pin_bck = PIN_I2S_BCK;
|
||||
int pin_data = PIN_I2S_DATA_OUT;
|
||||
I2SMode i2s_mode = I2S_PHILIPS_MODE;
|
||||
bool is_digital = true;
|
||||
|
||||
};
|
||||
|
||||
/// stops any further processing by spinning in an endless loop
|
||||
void stop() {
|
||||
while(true){
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,17 @@ public:
|
||||
return lenResult;
|
||||
}
|
||||
|
||||
int writeArray(T data[], int len){
|
||||
int result = 0;
|
||||
for (int j=0;j<len;j++){
|
||||
result = j;
|
||||
if (write(data[j])==0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// reads multiple values for array of 2 dimensional frames
|
||||
int readFrames(T data[][2], int len) {
|
||||
int result = min(len, available());
|
||||
@ -48,7 +59,6 @@ public:
|
||||
return lenResult;
|
||||
}
|
||||
|
||||
|
||||
// peeks the actual entry from the buffer
|
||||
virtual T peek() = 0;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "AudioTypes.h"
|
||||
#include "BluetoothA2DPSource.h"
|
||||
#include "Vector.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
@ -23,19 +23,20 @@ static int16_t convertFrom32To16(int32_t value) {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Abstract Base class for Filters
|
||||
* A filter is processing the data in the indicated array
|
||||
* @brief Abstract Base class for Converters
|
||||
* A converter is processing the data in the indicated array
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class BaseFilter {
|
||||
class BaseConverter {
|
||||
public:
|
||||
virtual void process(T (*src)[2], size_t size) = 0;
|
||||
virtual void convert(T (*src)[2], size_t size) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Multiplies the values with the indicated factor adds the offset and clips at maxValue. To mute use a factor of 0.0!
|
||||
* @author Phil Schatzmann
|
||||
@ -44,15 +45,15 @@ class BaseFilter {
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class FilterScaler : public BaseFilter<T> {
|
||||
class ConverterScaler : public BaseConverter<T> {
|
||||
public:
|
||||
FilterScaler(float factor, T offset, T maxValue){
|
||||
ConverterScaler(float factor, T offset, T maxValue){
|
||||
this->factor = factor;
|
||||
this->maxValue = maxValue;
|
||||
this->offset = offset;
|
||||
}
|
||||
|
||||
void process(T (*src)[2], size_t size) {
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
for (size_t j=0;j<size;j++){
|
||||
src[j][0] = (src[j][0] + offset) * factor;
|
||||
if (src[j][0]>maxValue){
|
||||
@ -74,6 +75,53 @@ class FilterScaler : public BaseFilter<T> {
|
||||
T offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Makes sure that the avg of the signal is set to 0
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class ConverterAutoCenter : public BaseConverter<T> {
|
||||
public:
|
||||
ConverterAutoCenter(){
|
||||
}
|
||||
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
setup(src, size);
|
||||
if (is_setup){
|
||||
for (size_t j=0; j<size; j++){
|
||||
src[j][0] = src[j][0] - offset;
|
||||
src[j][1] = src[j][1] - offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
T offset;
|
||||
float left;
|
||||
float right;
|
||||
bool is_setup = false;
|
||||
|
||||
void setup(T (*src)[2], size_t size){
|
||||
if (!is_setup) {
|
||||
for (size_t j=0;j<size;j++){
|
||||
left += src[j][0];
|
||||
right += src[j][1];
|
||||
}
|
||||
left = left / size;
|
||||
right = right / size;
|
||||
|
||||
if (left>0){
|
||||
offset = left;
|
||||
is_setup = true;
|
||||
} else if (right>0){
|
||||
offset = right;
|
||||
is_setup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Switches the left and right channel
|
||||
* @author Phil Schatzmann
|
||||
@ -82,11 +130,11 @@ class FilterScaler : public BaseFilter<T> {
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class FilterSwitchLeftAndRight : public BaseFilter<T> {
|
||||
class ConverterSwitchLeftAndRight : public BaseConverter<T> {
|
||||
public:
|
||||
FilterSwitchLeftAndRight(){
|
||||
ConverterSwitchLeftAndRight(){
|
||||
}
|
||||
void process(T (*src)[2], size_t size) {
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
for (size_t j=0;j<size;j++){
|
||||
src[j][1] = src[j][0];
|
||||
src[j][0] = src[j][1];
|
||||
@ -102,11 +150,11 @@ class FilterSwitchLeftAndRight : public BaseFilter<T> {
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class FilterFillLeftAndRight : public BaseFilter<T> {
|
||||
class ConverterFillLeftAndRight : public BaseConverter<T> {
|
||||
public:
|
||||
FilterFillLeftAndRight(){
|
||||
ConverterFillLeftAndRight(){
|
||||
}
|
||||
void process(T (*src)[2], size_t size) {
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
setup(src, size);
|
||||
if (left_empty && !right_empty){
|
||||
for (size_t j=0;j<size;j++){
|
||||
@ -156,12 +204,12 @@ class FilterFillLeftAndRight : public BaseFilter<T> {
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class FilterToInternalDACFormat : public BaseFilter<T> {
|
||||
class ConverterToInternalDACFormat : public BaseConverter<T> {
|
||||
public:
|
||||
FilterToInternalDACFormat(){
|
||||
ConverterToInternalDACFormat(){
|
||||
}
|
||||
|
||||
void process(T (*src)[2], size_t size) {
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
for (int i=0; i<size; i++) {
|
||||
src[i][0] = src[i][0] + 0x8000;
|
||||
src[i][1] = src[i][1] + 0x8000;
|
||||
@ -169,8 +217,33 @@ class FilterToInternalDACFormat : public BaseFilter<T> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Combines multiple converters
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class MultiConverter : public BaseConverter<T> {
|
||||
public:
|
||||
MultiConverter(){
|
||||
}
|
||||
|
||||
// adds a converter
|
||||
void add(BaseConverter<T> &converter){
|
||||
converters.push_back(converter);
|
||||
}
|
||||
|
||||
// The data is provided as int24_t tgt[][2] but returned as int24_t
|
||||
void convert(T (*src)[2], size_t size) {
|
||||
for(int i=0; i < converters.size(); i++){
|
||||
converters[i].convert(src);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<T> converters;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Converts e.g. 24bit data to the indicated bigger data type
|
||||
@ -180,9 +253,9 @@ class FilterToInternalDACFormat : public BaseFilter<T> {
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename FromType, typename ToType>
|
||||
class Converter {
|
||||
class CallbackConverter {
|
||||
public:
|
||||
Converter(ToType (*converter)(FromType v)){
|
||||
CallbackConverter(ToType (*converter)(FromType v)){
|
||||
this->convert_ptr = converter;
|
||||
}
|
||||
|
||||
@ -200,37 +273,5 @@ class Converter {
|
||||
};
|
||||
|
||||
|
||||
// static int16[2][] toArray(Channel *c){
|
||||
// return (static_cast<(int16[2])*>(c));
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief Covnerts the data from T src[][2] to a Channels array
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
class ChannelConverter {
|
||||
public:
|
||||
ChannelConverter( int16_t (*convert_ptr)(T from)){
|
||||
this->convert_ptr = convert_ptr;
|
||||
}
|
||||
|
||||
// The data is provided as int24_t tgt[][2] but returned as int24_t
|
||||
void convert(T src[][2], Channels* channels, size_t size) {
|
||||
for (int i=size; i>0; i--) {
|
||||
channels[i].channel1 = (*convert_ptr)(src[i][0]);
|
||||
channels[i].channel2 = (*convert_ptr)(src[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t (*convert_ptr)(T from);
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "AudioTypes.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
enum I2SMode { TX_MODE, RX_MODE };
|
||||
const char* I2S_TAG = "I2S";
|
||||
/**
|
||||
* @brief ESP32 specific configuration for all i2s settings
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
template<typename T>
|
||||
class I2SConfig {
|
||||
public:
|
||||
i2s_port_t port_no = I2S_NUM_0;
|
||||
i2s_config_t i2s;
|
||||
i2s_pin_config_t pin;
|
||||
int channels = 2;
|
||||
|
||||
I2SConfig() {
|
||||
i2s = defaultConfig(TX_MODE);
|
||||
pin = defaultPinConfig(TX_MODE);
|
||||
}
|
||||
|
||||
/// Default Constructor
|
||||
I2SConfig(I2SMode mode) {
|
||||
i2s = defaultConfig(mode);
|
||||
pin = defaultPinConfig(mode);
|
||||
}
|
||||
|
||||
/// Copy constructor
|
||||
I2SConfig(const I2SConfig<T> &cfg) {
|
||||
port_no = cfg.port_no;
|
||||
i2s = cfg.i2s;
|
||||
pin = cfg.pin;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
i2s_config_t defaultConfig(I2SMode mode) {
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
i2s_config_t config = {
|
||||
.mode = (i2s_mode_t) ((mode == TX_MODE) ? (I2S_MODE_MASTER | I2S_MODE_TX) : (I2S_MODE_MASTER | I2S_MODE_RX)) ,
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = (i2s_bits_per_sample_t) (sizeof(T) * 8),
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = static_cast<i2s_comm_format_t>(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // default interrupt priority
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 1024,
|
||||
.use_apll = false,
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
i2s_pin_config_t defaultPinConfig(I2SMode mode = TX_MODE) {
|
||||
ESP_LOGD(I2S_TAG, "%s - mode: %s", __func__, mode==TX_MODE ? "TX" : "RX");
|
||||
i2s_pin_config_t config = {
|
||||
.bck_io_num = 14,
|
||||
.ws_io_num = 15,
|
||||
.data_out_num = mode == TX_MODE ? 22 : I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = mode == RX_MODE ? 32 : I2S_PIN_NO_CHANGE
|
||||
};
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief A Simple I2S interface class.
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
template<typename T>
|
||||
class I2S : public AudioBaseInfoDependent {
|
||||
friend class I2SStream;
|
||||
|
||||
public:
|
||||
|
||||
/// Default Constructor
|
||||
I2S() {
|
||||
}
|
||||
|
||||
/// Destructor
|
||||
~I2S() {
|
||||
stop();
|
||||
}
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig<T> defaultConfig(I2SMode mode) {
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
I2SConfig<T> c(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(I2SConfig<T> cfg) {
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
this->cfg = cfg;
|
||||
this->i2s_num = cfg.port_no;
|
||||
|
||||
// We make sure that we can reconfigure
|
||||
if (is_started) {
|
||||
stop();
|
||||
ESP_LOGD(I2S_TAG, "%s", "I2S restarting");
|
||||
}
|
||||
|
||||
ESP_LOGD(I2S_TAG, "sample rate: %d", cfg.i2s.sample_rate);
|
||||
ESP_LOGD(I2S_TAG, "bits per sample: %d", cfg.i2s.bits_per_sample);
|
||||
ESP_LOGD(I2S_TAG, "number of channels: %d", cfg.channels);
|
||||
|
||||
// setup config
|
||||
if (i2s_driver_install(i2s_num, &cfg.i2s, 0, NULL)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s - %s", __func__, "i2s_driver_install");
|
||||
}
|
||||
|
||||
// setup pin config
|
||||
if (this->cfg.i2s.mode & I2S_MODE_DAC_BUILT_IN ) {
|
||||
ESP_LOGD(I2S_TAG, "Using built in DAC");
|
||||
//for internal DAC, this will enable both of the internal channels
|
||||
i2s_set_pin(i2s_num, NULL);
|
||||
} else {
|
||||
if (i2s_set_pin(i2s_num, &cfg.pin)!= ESP_OK){
|
||||
ESP_LOGD(I2S_TAG, "pin bck_io_num: %d", cfg.pin.bck_io_num);
|
||||
ESP_LOGD(I2S_TAG, "pin ws_io_num: %d", cfg.pin.ws_io_num);
|
||||
ESP_LOGD(I2S_TAG, "pin data_out_num: %d", cfg.pin.data_out_num);
|
||||
ESP_LOGD(I2S_TAG, "pin data_in_num: %d", cfg.pin.data_in_num);
|
||||
ESP_LOGE(I2S_TAG, "%s - %s", __func__, "i2s_set_pin");
|
||||
}
|
||||
}
|
||||
|
||||
// clear initial buffer
|
||||
i2s_zero_dma_buffer(i2s_num);
|
||||
|
||||
is_started = true;
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void stop(){
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
i2s_driver_uninstall(i2s_num);
|
||||
is_started = false;
|
||||
}
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t write(T (*src)[2], size_t sizeFrames, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
return writeBytes(src, sizeFrames * sizeof(T) * 2, ticks_to_wait); // 2 bytes * 2 channels
|
||||
}
|
||||
|
||||
/// Reads data from I2S
|
||||
size_t read(T (*src)[2], size_t sizeFrames, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
size_t len = readBytes(src, sizeFrames * sizeof(T) * 2, ticks_to_wait); // 2 bytes * 2 channels
|
||||
size_t result = len / (sizeof(T) * 2);
|
||||
ESP_LOGD(I2S_TAG, "%s - len: %d -> %d", __func__,sizeFrames, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// provides the actual configuration
|
||||
I2SConfig<T> config() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/// updates the sample rate dynamically
|
||||
virtual void setAudioBaseInfo(AudioBaseInfo info) {
|
||||
bool is_update = false;
|
||||
|
||||
if (cfg.i2s.sample_rate != info.sample_rate
|
||||
|| cfg.i2s.bits_per_sample != info.bits_per_sample) {
|
||||
cfg.i2s.sample_rate = info.sample_rate;
|
||||
cfg.i2s.bits_per_sample = static_cast<i2s_bits_per_sample_t>(info.bits_per_sample);
|
||||
is_update = true;
|
||||
}
|
||||
|
||||
if (cfg.channels != info.channels){
|
||||
if (info.channels==2){
|
||||
cfg.i2s.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
} else if (info.channels==1){
|
||||
cfg.i2s.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||
}
|
||||
cfg.channels = info.channels;
|
||||
is_update = true;
|
||||
}
|
||||
|
||||
if (is_update){
|
||||
// restart
|
||||
begin(config());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SConfig<T> cfg;
|
||||
i2s_port_t i2s_num;
|
||||
bool is_started = false;
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t writeBytes(const void *src, size_t size_bytes, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
size_t result = 0;
|
||||
if (i2s_write(i2s_num, src, size_bytes, &result, ticks_to_wait)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes, TickType_t ticks_to_wait=portMAX_DELAY){
|
||||
size_t result = 0;
|
||||
if (i2s_read(i2s_num, dest, size_bytes, &result, ticks_to_wait)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
148
src/AudioTools/I2S_ESP32.h
Normal file
148
src/AudioTools/I2S_ESP32.h
Normal file
@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_a2dp_api.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Basic I2S API - for the ESP32
|
||||
*
|
||||
*/
|
||||
class I2SBase {
|
||||
friend class I2SStream;
|
||||
|
||||
public:
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig defaultConfig(RxTxMode mode) {
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
I2SConfig c(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(I2SConfig cfg) {
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
this->cfg = cfg;
|
||||
this->i2s_num = (i2s_port_t) cfg.port_no;
|
||||
setChannels(cfg.channels);
|
||||
|
||||
i2s_config_t i2s_config_new = {
|
||||
.mode = (i2s_mode_t) ((cfg.rx_tx_mode == TX_MODE) ? (I2S_MODE_MASTER | I2S_MODE_TX) : (I2S_MODE_MASTER | I2S_MODE_RX)) ,
|
||||
.sample_rate = cfg.sample_rate,
|
||||
.bits_per_sample = (i2s_bits_per_sample_t) cfg.bits_per_sample,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = static_cast<i2s_comm_format_t>(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // default interrupt priority
|
||||
.dma_buf_count = I2S_BUFFER_COUNT,
|
||||
.dma_buf_len = I2S_BUFFER_SIZE,
|
||||
.use_apll = I2S_USE_APLL,
|
||||
};
|
||||
i2s_config = i2s_config_new;
|
||||
logConfig();
|
||||
|
||||
// We make sure that we can reconfigure
|
||||
if (is_started) {
|
||||
end();
|
||||
ESP_LOGD(I2S_TAG, "%s", "I2S restarting");
|
||||
}
|
||||
|
||||
|
||||
// setup config
|
||||
if (i2s_driver_install(i2s_num, &i2s_config, 0, NULL)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s - %s", __func__, "i2s_driver_install");
|
||||
}
|
||||
|
||||
// setup pin config
|
||||
if (this->cfg.is_digital ) {
|
||||
i2s_pin_config_t pin_config = {
|
||||
.bck_io_num = cfg.pin_bck,
|
||||
.ws_io_num = cfg.pin_ws,
|
||||
.data_out_num = cfg.rx_tx_mode == TX_MODE ? cfg.pin_data : I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = cfg.rx_tx_mode == RX_MODE ? cfg.pin_data : I2S_PIN_NO_CHANGE
|
||||
};
|
||||
logConfigPins(pin_config);
|
||||
|
||||
if (i2s_set_pin(i2s_num, &pin_config)!= ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s - %s", __func__, "i2s_set_pin");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(I2S_TAG, "Using built in DAC");
|
||||
//for internal DAC, this will enable both of the internal channels
|
||||
i2s_set_pin(i2s_num, NULL);
|
||||
}
|
||||
|
||||
// clear initial buffer
|
||||
i2s_zero_dma_buffer(i2s_num);
|
||||
|
||||
is_started = true;
|
||||
ESP_LOGD(I2S_TAG, "%s - %s", __func__, "started");
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void end(){
|
||||
ESP_LOGD(I2S_TAG, "%s", __func__);
|
||||
i2s_driver_uninstall(i2s_num);
|
||||
is_started = false;
|
||||
}
|
||||
|
||||
/// provides the actual configuration
|
||||
I2SConfig config() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SConfig cfg;
|
||||
i2s_port_t i2s_num;
|
||||
i2s_config_t i2s_config;
|
||||
bool is_started = false;
|
||||
|
||||
// update the cfg.i2s.channel_format based on the number of channels
|
||||
void setChannels(int channels){
|
||||
if (channels==2){
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
} else if (channels==1){
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||
}
|
||||
cfg.channels = channels;
|
||||
|
||||
}
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t writeBytes(const void *src, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
if (i2s_write(i2s_num, src, size_bytes, &result, portMAX_DELAY)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
if (i2s_read(i2s_num, dest, size_bytes, &result, portMAX_DELAY)!=ESP_OK){
|
||||
ESP_LOGE(I2S_TAG, "%s", __func__);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void logConfig() {
|
||||
ESP_LOGD(I2S_TAG, "mode: %s", cfg.rx_tx_mode == TX_MODE ? "TX":"RX");
|
||||
ESP_LOGD(I2S_TAG, "sample rate: %d", cfg.sample_rate);
|
||||
ESP_LOGD(I2S_TAG, "bits per sample: %d", cfg.bits_per_sample);
|
||||
ESP_LOGD(I2S_TAG, "number of channels: %d", cfg.channels);
|
||||
}
|
||||
|
||||
void logConfigPins(i2s_pin_config_t pin_config){
|
||||
ESP_LOGD(I2S_TAG, "pin bck_io_num: %d", cfg.pin_bck);
|
||||
ESP_LOGD(I2S_TAG, "pin ws_io_num: %d", cfg.pin_ws);
|
||||
ESP_LOGD(I2S_TAG, "pin data_num: %d", cfg.pin_data);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
78
src/AudioTools/I2S_ESP8266.h
Normal file
78
src/AudioTools/I2S_ESP8266.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "AudioTools/AudioLogger.h"
|
||||
#include "i2s.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Basic I2S API - for the ESP8266
|
||||
* Only 16 bits are supported !
|
||||
*
|
||||
*/
|
||||
class I2SBase {
|
||||
friend class I2SStream;
|
||||
public:
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig defaultConfig(RxTxMode mode) {
|
||||
I2SConfig c(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(I2SConfig cfg) {
|
||||
i2s_set_rate(cfg.sample_rate);
|
||||
cfg.bits_per_sample = 16;
|
||||
AudioLogger logger = AudioLogger::instance();
|
||||
if(!i2s_rxtx_begin(cfg.rx_tx_mode == RX_MODE, cfg.rx_tx_mode == TX_MODE)){
|
||||
logger.error("i2s_rxtx_begin failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void end(){
|
||||
i2s_end();
|
||||
}
|
||||
|
||||
/// provides the actual configuration
|
||||
I2SConfig config() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SConfig cfg;
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t writeBytes(const void *src, size_t size_bytes){
|
||||
size_t frame_size = cfg.channels * (cfg.bits_per_sample/8);
|
||||
uint16_t frame_count = size_bytes / frame_size;
|
||||
return i2s_write_buffer(( int16_t *)src, frame_count ) * frame_size;
|
||||
}
|
||||
|
||||
/// reads the data from the I2S interface
|
||||
size_t readBytes(void *dest, size_t size_bytes){
|
||||
size_t result_bytes = 0;
|
||||
uint16_t frame_size = cfg.channels * (cfg.bits_per_sample/8);
|
||||
size_t frames = size_bytes / frame_size;
|
||||
int16_t *ptr = (int16_t *)dest;
|
||||
|
||||
for (int j=0;j<frames;j++){
|
||||
int16_t *left = ptr;
|
||||
int16_t *right = ptr+1;
|
||||
bool ok = i2s_read_sample(left, right, false); // RX data returned in both 16-bit outputs.
|
||||
if(!ok){
|
||||
break;
|
||||
}
|
||||
ptr += 2;
|
||||
result_bytes += frame_size;
|
||||
}
|
||||
return result_bytes;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
57
src/AudioTools/I2S_SAMD.h
Normal file
57
src/AudioTools/I2S_SAMD.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __SAMD21G18A__
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Basic I2S API - for the SAMD
|
||||
*
|
||||
*/
|
||||
class I2SBase {
|
||||
friend class I2SStream;
|
||||
|
||||
public:
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig defaultConfig(RxTxMode mode) {
|
||||
I2SConfig c(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(I2SConfig cfg) {
|
||||
I2S.begin(cfg.i2s_mode, cfg.sample_rate, csg.bits_per_sample, true);
|
||||
if (cfg.mode = TX_MODE){
|
||||
I2S.enableTransmitter();
|
||||
} else {
|
||||
I2S.enableReceiver();
|
||||
}
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void end(){
|
||||
I2S.end();
|
||||
}
|
||||
|
||||
/// provides the actual configuration
|
||||
I2SConfig config() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
size_t writeBytes(const void *src, size_t size_bytes){
|
||||
return I2S.write((const uint8_t *)src, size_bytes);
|
||||
}
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes){
|
||||
return I2S.read(src, size_bytes);
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SConfig cfg;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
53
src/AudioTools/I2S_Template.h
Normal file
53
src/AudioTools/I2S_Template.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef SPECIFIC_PROCESSOR_ARCHITECTURE
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Basic I2S API - for the ...
|
||||
*
|
||||
*/
|
||||
class I2SBase {
|
||||
friend class I2SStream;
|
||||
|
||||
public:
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig defaultConfig(RxTxMode mode) {
|
||||
I2SConfig c(mode);
|
||||
return c;
|
||||
}
|
||||
|
||||
/// starts the DAC
|
||||
void begin(I2SConfig cfg) {
|
||||
|
||||
}
|
||||
|
||||
/// stops the I2C and unistalls the driver
|
||||
void end(){
|
||||
}
|
||||
|
||||
/// provides the actual configuration
|
||||
I2SConfig config() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SConfig cfg;
|
||||
|
||||
/// writes the data to the I2S interface
|
||||
size_t writeBytes(const void *src, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t readBytes(void *dest, size_t size_bytes){
|
||||
size_t result = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioLogger.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
@ -11,19 +13,78 @@ namespace audio_tools {
|
||||
*/
|
||||
template <class T>
|
||||
class SoundGenerator {
|
||||
virtual size_t readBytes(uint8_t *buffer, size_t bytes) = 0;
|
||||
|
||||
virtual size_t readSamples(T* data, size_t size)=0;
|
||||
|
||||
virtual size_t readSamples(T src[][2], size_t size) {
|
||||
T tmp[size];
|
||||
int len = readSamples(tmp, size);
|
||||
for (int j=0;j<len;j++) {
|
||||
T value = tmp[j];
|
||||
src[j][1] = src[j][0] = value;
|
||||
public:
|
||||
/// Provides the samples into simple array - which represents 1 channel
|
||||
virtual size_t readSamples(T* data, size_t sampleCount=512){
|
||||
for (int j=0;j<sampleCount;j++){
|
||||
data[j] = readSample();
|
||||
}
|
||||
return sampleCount;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/// Provides the samples into a 2 channel array
|
||||
virtual size_t readSamples(T src[][2], size_t frameCount) {
|
||||
T tmp[frameCount];
|
||||
int len = readSamples(tmp, frameCount);
|
||||
for (int j=0;j<len;j++) {
|
||||
T value = tmp[j];
|
||||
src[j][1] = src[j][0] = value;
|
||||
}
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
/// Provides a single sample
|
||||
virtual T readSample() = 0;
|
||||
|
||||
|
||||
/// Provides the data as byte array
|
||||
virtual size_t readBytes( uint8_t *buffer, size_t lengthBytes, uint8_t channels=1){
|
||||
size_t result = 0;
|
||||
int frame_size = sizeof(T) * channels;
|
||||
if (active){
|
||||
switch (channels){
|
||||
case 1:
|
||||
result = readSamples((T*) buffer, lengthBytes / frame_size) ;
|
||||
break;
|
||||
case 2:
|
||||
result = readSamples((T(*)[2]) buffer, lengthBytes / frame_size);
|
||||
break;
|
||||
default:
|
||||
logger().printf(AudioLogger::Error, "SoundGenerator::readBytes -> number of channels %d is not supported (use 1 or 2)\n", channels);
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger().debug("SoundGenerator::readBytes -> inactive");
|
||||
// when inactive did not generate anything
|
||||
result = 0;
|
||||
}
|
||||
logger().printf(AudioLogger::Debug, "SoundGenerator::readBytes (channels: %d) %d -> %d\n",channels, lengthBytes,result);
|
||||
return result * frame_size;
|
||||
}
|
||||
|
||||
void begin() {
|
||||
logger().info("SoundGenerator::begin");
|
||||
active = true;
|
||||
}
|
||||
|
||||
/// ends the processing
|
||||
void end(){
|
||||
logger().info("SoundGenerator::end");
|
||||
active = false;
|
||||
}
|
||||
|
||||
/// provides the logger
|
||||
AudioLogger &logger() {
|
||||
return AudioLogger::instance();
|
||||
}
|
||||
|
||||
bool isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool active = false;
|
||||
|
||||
};
|
||||
|
||||
@ -37,38 +98,50 @@ template <class T>
|
||||
class SineWaveGenerator : public SoundGenerator<T> {
|
||||
public:
|
||||
// the scale defines the max value which is generated
|
||||
SineWaveGenerator(double scale=1.0) {
|
||||
this->scale = scale;
|
||||
SineWaveGenerator(float amplitude = 32767.0, float phase = 0) {
|
||||
m_amplitude = amplitude;
|
||||
m_phase = phase;
|
||||
}
|
||||
|
||||
/// Starts the processing by defining the sample rate and frequency
|
||||
void begin(uint16_t sample_rate=44100, uint16_t frequency=0){
|
||||
this->frequency = frequency;
|
||||
this->sample_rate = sample_rate;
|
||||
SoundGenerator<T>::logger().info("SineWaveGenerator::begin");
|
||||
this->m_frequency = frequency;
|
||||
this->m_sample_rate = sample_rate;
|
||||
this->m_deltaTime = 1.0 / m_sample_rate;
|
||||
SoundGenerator<T>::active = true;
|
||||
logStatus();
|
||||
}
|
||||
|
||||
/// Defines the frequency - after the processing has been started
|
||||
void setFrequency(uint16_t frequency) {
|
||||
this->frequency = frequency;
|
||||
}
|
||||
|
||||
virtual size_t readBytes(uint8_t *buffer, size_t length){
|
||||
return readSamples((T*)buffer, length*sizeof(T)) * sizeof(T);
|
||||
/// Provides a single sample
|
||||
virtual T readSample() {
|
||||
float angle = double_Pi * m_frequency * m_time + m_phase;
|
||||
T result = m_amplitude * sin(angle);
|
||||
m_time += m_deltaTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual size_t readSamples(T* data, size_t len=512){
|
||||
for (int j=0;j<len;j++){
|
||||
data[j] = readSample();;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t sample_step;
|
||||
uint16_t frequency;
|
||||
double scale;
|
||||
uint64_t sample_rate;
|
||||
uint16_t m_sample_rate;
|
||||
|
||||
T readSample() {
|
||||
return sin(2.0 * PI * sample_step++ * frequency / sample_rate) * (double)scale;
|
||||
float m_frequency = 0;
|
||||
float m_time = 0.0;
|
||||
float m_amplitude = 1.0;
|
||||
float m_deltaTime = 1.0 / m_sample_rate;
|
||||
float m_phase = 0.0;
|
||||
float double_Pi = PI * 2.0;
|
||||
|
||||
void logStatus() {
|
||||
AudioLogger &log = SoundGenerator<T>::logger();
|
||||
log.printf(AudioLogger::Info, "frequency: %f", this->m_frequency );
|
||||
log.printf(AudioLogger::Info, "sample rate: %u", this->m_sample_rate );
|
||||
log.printf(AudioLogger::Info, "amplitude: %f", this->m_amplitude );
|
||||
log.printf(AudioLogger::Info, "active: %s", SoundGenerator<T>::active ? "true" : "false" );
|
||||
}
|
||||
|
||||
};
|
||||
@ -88,27 +161,14 @@ class NoiseGenerator : public SoundGenerator<T> {
|
||||
this->scale = scale;
|
||||
}
|
||||
|
||||
void begin(){
|
||||
}
|
||||
|
||||
virtual size_t readBytes(uint8_t *buffer, size_t length){
|
||||
return readSamples((T*)buffer, length*sizeof(T)) * sizeof(T);
|
||||
}
|
||||
|
||||
virtual size_t readSamples(T* data, size_t len=512){
|
||||
for (int j=0;j<len;j++){
|
||||
data[j] = readSample();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
protected:
|
||||
double scale;
|
||||
|
||||
/// Provides a single sample
|
||||
T readSample() {
|
||||
return ((rand() % (static_cast<T>(2 * scale)) - scale)); // generate number between -scale / scale
|
||||
}
|
||||
|
||||
protected:
|
||||
double scale;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
#pragma once
|
||||
#include "Arduino.h"
|
||||
#include "AudioConfig.h"
|
||||
#include "AudioTypes.h"
|
||||
#include "Buffers.h"
|
||||
#include "AudioI2S.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <esp_http_client.h>
|
||||
#include "i2s.h"
|
||||
#endif
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
const AudioLogger &log = AudioLogger::instance();
|
||||
|
||||
/**
|
||||
* @brief A simple Stream implementation which is backed by allocated memory
|
||||
@ -42,21 +49,43 @@ class MemoryStream : public Stream {
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual size_t write(const uint8_t *buffer, size_t size){
|
||||
size_t result = 0;
|
||||
for (int j=0;j<size;j++){
|
||||
if(!write(buffer[j])){
|
||||
break;
|
||||
}
|
||||
result = j;
|
||||
}
|
||||
}
|
||||
|
||||
virtual int available() {
|
||||
return write_pos - read_pos;
|
||||
}
|
||||
|
||||
virtual int read() {
|
||||
int result = peek();
|
||||
if (result>0){
|
||||
if (result>=0){
|
||||
read_pos++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t readBytes(char *buffer, size_t length){
|
||||
size_t count = 0;
|
||||
while (count < length) {
|
||||
int c = read();
|
||||
if (c < 0) break;
|
||||
*buffer++ = (char)c;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
virtual int peek() {
|
||||
int result = -1;
|
||||
if (available()>0 && read_pos < write_pos){
|
||||
if (available()>0){
|
||||
result = buffer[read_pos];
|
||||
}
|
||||
return result;
|
||||
@ -74,16 +103,85 @@ class MemoryStream : public Stream {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
int write_pos;
|
||||
int read_pos;
|
||||
int buffer_size;
|
||||
uint8_t *buffer;
|
||||
bool owns_buffer;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Source for reading generated tones. Please note
|
||||
* - that the output is for one channel only!
|
||||
* - we do not support reading of individual characters!
|
||||
* - we do not support any write operations
|
||||
* @param generator
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
class GeneratedSoundStream : public Stream {
|
||||
public:
|
||||
GeneratedSoundStream(SoundGenerator<T> &generator, uint8_t channels=2){
|
||||
this->generator_ptr = &generator;
|
||||
this->channels = channels;
|
||||
}
|
||||
|
||||
/// unsupported operations
|
||||
virtual size_t write(uint8_t) {
|
||||
return not_supported();
|
||||
};
|
||||
/// unsupported operations
|
||||
virtual int availableForWrite() {
|
||||
return not_supported();
|
||||
}
|
||||
|
||||
/// unsupported operations
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) {
|
||||
return not_supported();
|
||||
}
|
||||
|
||||
/// unsupported operations
|
||||
virtual int read() {
|
||||
return -1;
|
||||
}
|
||||
/// unsupported operations
|
||||
virtual int peek() {
|
||||
return -1;
|
||||
}
|
||||
/// This is unbounded so we just return the buffer size
|
||||
virtual int available() {
|
||||
return DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/// privide the data as byte stream
|
||||
size_t readBytes( char *buffer, size_t length) {
|
||||
return generator_ptr->readBytes((uint8_t*)buffer, length, channels);
|
||||
}
|
||||
|
||||
/// start the processing
|
||||
void begin() {
|
||||
generator_ptr->begin();
|
||||
}
|
||||
|
||||
/// stop the processing
|
||||
void stop() {
|
||||
generator_ptr->stop();
|
||||
}
|
||||
|
||||
void flush(){
|
||||
}
|
||||
|
||||
protected:
|
||||
SoundGenerator<T> *generator_ptr;
|
||||
uint8_t channels;
|
||||
|
||||
int not_supported() {
|
||||
log.error("GeneratedSoundStream-unsupported operation!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@ -93,7 +191,7 @@ class MemoryStream : public Stream {
|
||||
*/
|
||||
class StreamCopy {
|
||||
public:
|
||||
StreamCopy(Stream &to, Stream &from, int buffer_size){
|
||||
StreamCopy(Print &to, Stream &from, int buffer_size=DEFAULT_BUFFER_SIZE){
|
||||
this->from = &from;
|
||||
this->to = &to;
|
||||
this->buffer_size = buffer_size;
|
||||
@ -104,16 +202,31 @@ class StreamCopy {
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
/// copies the available bytes from the input stream to the ouptut stream
|
||||
/// copies a buffer length of data - repeat this call until everthing is copied
|
||||
size_t copy() {
|
||||
size_t total_bytes = available();
|
||||
size_t result = total_bytes;
|
||||
while (total_bytes>0){
|
||||
size_t bytes_to_read = min(total_bytes,static_cast<size_t>(buffer_size) );
|
||||
from->readBytes(buffer, bytes_to_read);
|
||||
to->write(buffer, bytes_to_read);
|
||||
total_bytes -= bytes_to_read;
|
||||
}
|
||||
log.printf(AudioLogger::Info, "StreamCopy::copy");
|
||||
size_t result = available();
|
||||
if (result>0){
|
||||
size_t bytes_to_read = min(result,static_cast<size_t>(buffer_size) );
|
||||
result = from->readBytes(buffer, bytes_to_read);
|
||||
to->write(buffer, result);
|
||||
}
|
||||
log.printf(AudioLogger::Info, "StreamCopy::copy %d bytes\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// copies a buffer length of data and applies the converter
|
||||
template<typename T>
|
||||
size_t copy(BaseConverter<T> &converter) {
|
||||
size_t result = available();
|
||||
BaseConverter<T> *coverter_ptr = &converter;
|
||||
if (result>0){
|
||||
size_t bytes_to_read = min(result, static_cast<size_t>(buffer_size) );
|
||||
result = from->readBytes(buffer, bytes_to_read);
|
||||
// convert to pointer to array of 2
|
||||
coverter_ptr->convert((T(*)[2])buffer, result / (sizeof(T)*2) );
|
||||
to->write(buffer, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -123,15 +236,298 @@ class StreamCopy {
|
||||
|
||||
protected:
|
||||
Stream *from;
|
||||
Stream *to;
|
||||
Print *to;
|
||||
uint8_t *buffer;
|
||||
int buffer_size;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Typed Stream Copy which supports the conversion from channel to 2 channels. We make sure that we
|
||||
* allways copy full samples
|
||||
* @tparam T
|
||||
*/
|
||||
template <class T>
|
||||
class StreamCopyT {
|
||||
public:
|
||||
StreamCopyT(Print &to, Stream &from, int buffer_size=DEFAULT_BUFFER_SIZE){
|
||||
this->from = &from;
|
||||
this->to = &to;
|
||||
this->buffer_size = buffer_size;
|
||||
buffer = new uint8_t[buffer_size];
|
||||
}
|
||||
|
||||
~StreamCopyT(){
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
// copies the data from one channel from the source to 2 channels on the destination
|
||||
size_t copy(){
|
||||
size_t result = available();
|
||||
if (result>0){
|
||||
size_t bytes_to_read = min(result, static_cast<size_t>(buffer_size));
|
||||
size_t samples = bytes_to_read / sizeof(T);
|
||||
size_t bytes = samples * sizeof(T);
|
||||
from->readBytes(buffer, bytes);
|
||||
result = to->write(buffer, bytes);
|
||||
}
|
||||
log.printf(AudioLogger::Info, "StreamCopyT::copy %d bytes\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// copies the data from one channel from the source to 2 channels on the destination
|
||||
size_t copy2(){
|
||||
size_t result = available();
|
||||
size_t bytes_read;
|
||||
log.printf(AudioLogger::Info, "StreamCopyT::copy2 %u\n", result);
|
||||
if (result>0){
|
||||
size_t bytes_to_read = min(result, static_cast<size_t>(buffer_size / 2));
|
||||
size_t frames = bytes_to_read / sizeof(T);
|
||||
T temp_data[frames];
|
||||
bytes_read = from->readBytes((uint8_t*)temp_data, frames * sizeof(T));
|
||||
|
||||
T* bufferT = (T*) buffer;
|
||||
for (int j=0;j<frames;j++){
|
||||
*bufferT = temp_data[j];
|
||||
bufferT++;
|
||||
*bufferT = temp_data[j];
|
||||
bufferT++;
|
||||
}
|
||||
result = to->write(buffer, frames * sizeof(T)*2);
|
||||
}
|
||||
log.printf(AudioLogger::Info, "StreamCopyT::copy2 %u -> %u bytes / available %u\n", bytes_read, result, available());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// available bytes in the data source
|
||||
int available() {
|
||||
return from->available();
|
||||
}
|
||||
|
||||
protected:
|
||||
Stream *from;
|
||||
Print *to;
|
||||
uint8_t *buffer;
|
||||
int buffer_size;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The Arduino Stream supports operations on single characters. This is usually not the best way to push audio information, but we
|
||||
* will support it anyway - by using a buffer. On reads: if the buffer is empty it gets refilled - for writes
|
||||
* if it is full it gets flushed.
|
||||
*
|
||||
*/
|
||||
class BufferedStream : public Stream {
|
||||
public:
|
||||
BufferedStream(size_t buffer_size){
|
||||
buffer = new SingleBuffer<uint8_t>(buffer_size);
|
||||
}
|
||||
|
||||
~BufferedStream() {
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
/// writes a byte to the buffer
|
||||
virtual size_t write(uint8_t c) {
|
||||
if (buffer->isFull()){
|
||||
flush();
|
||||
}
|
||||
return buffer->write(c);
|
||||
}
|
||||
|
||||
/// Use this method: write an array
|
||||
virtual size_t write(const uint8_t* data, size_t len) {
|
||||
flush();
|
||||
return writeExt(data, len);
|
||||
}
|
||||
|
||||
/// empties the buffer
|
||||
virtual void flush() {
|
||||
// just dump the memory of the buffer and clear it
|
||||
if (buffer->available()>0){
|
||||
writeExt(buffer->address(), buffer->available());
|
||||
buffer->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// reads a byte - to be avoided
|
||||
virtual int read() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->read();
|
||||
}
|
||||
|
||||
/// peeks a byte - to be avoided
|
||||
virtual int peek() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->peek();
|
||||
};
|
||||
|
||||
/// Use this method !!
|
||||
size_t readBytes( uint8_t *data, size_t length) {
|
||||
if (buffer->isEmpty()){
|
||||
return readExt(data, length);
|
||||
} else {
|
||||
return buffer->readArray(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
// refills the buffer with data from i2s
|
||||
void refill() {
|
||||
size_t result = readExt(buffer->address(), buffer->size());
|
||||
buffer->setAvailable(result);
|
||||
}
|
||||
|
||||
/// Returns the available bytes in the buffer: to be avoided
|
||||
virtual int available() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->available();
|
||||
}
|
||||
|
||||
protected:
|
||||
SingleBuffer<uint8_t> *buffer;
|
||||
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) = 0;
|
||||
virtual size_t readExt( uint8_t *data, size_t length) = 0;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Stream Wrapper which can be used to print the values as readable ASCII to the screen to be analyzed in the Serial Plotter
|
||||
* The frames are separated by a new line. The channels in one frame are separated by a ,
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T>
|
||||
class CsvStream : public BufferedStream, public AudioBaseInfoDependent {
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
CsvStream(Print &out, int channels, int buffer_size=DEFAULT_BUFFER_SIZE, bool active=true) : BufferedStream(buffer_size){
|
||||
this->channels = channels;
|
||||
this->out_ptr = &out;
|
||||
this->active = active;
|
||||
}
|
||||
|
||||
/// Sets the CsvStream as active
|
||||
void begin(){
|
||||
active = true;
|
||||
}
|
||||
|
||||
/// Sets the CsvStream as inactive
|
||||
void end() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
T *data_ptr;
|
||||
Print *out_ptr;
|
||||
int channels;
|
||||
bool active;
|
||||
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) {
|
||||
if (!active) return 0;
|
||||
size_t lenChannels = len / (sizeof(T)*channels);
|
||||
data_ptr = (T*)data;
|
||||
for (int j=0;j<lenChannels;j++){
|
||||
for (int ch=0;ch<channels;ch++){
|
||||
out_ptr->print(*data_ptr);
|
||||
data_ptr++;
|
||||
if (ch<channels-1) Serial.print(", ");
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
virtual size_t readExt( uint8_t *data, size_t length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief We support the Stream interface for the I2S access. In addition we allow a separate mute pin which might also be used
|
||||
* to drive a LED...
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
|
||||
class I2SStream : public BufferedStream, public AudioBaseInfoDependent {
|
||||
|
||||
public:
|
||||
I2SStream(int mute_pin=PIN_I2S_MUTE) : BufferedStream(DEFAULT_BUFFER_SIZE){
|
||||
this->mute_pin = mute_pin;
|
||||
if (mute_pin>0) {
|
||||
pinMode(mute_pin, OUTPUT);
|
||||
mute(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the default configuration
|
||||
I2SConfig defaultConfig(RxTxMode mode) {
|
||||
return i2s.defaultConfig(mode);
|
||||
}
|
||||
|
||||
void begin(I2SConfig cfg) {
|
||||
i2s.begin(cfg);
|
||||
// unmute
|
||||
mute(false);
|
||||
}
|
||||
|
||||
void end() {
|
||||
mute(true);
|
||||
i2s.end();
|
||||
}
|
||||
|
||||
/// updates the sample rate dynamically
|
||||
virtual void setAudioBaseInfo(AudioBaseInfo info) {
|
||||
bool is_update = false;
|
||||
I2SConfig cfg = i2s.config();
|
||||
if (cfg.sample_rate != info.sample_rate
|
||||
|| cfg.channels != info.channels
|
||||
|| cfg.bits_per_sample != info.bits_per_sample) {
|
||||
cfg.sample_rate = info.sample_rate;
|
||||
cfg.bits_per_sample = info.bits_per_sample;
|
||||
cfg.channels = info.channels;
|
||||
|
||||
i2s.end();
|
||||
i2s.begin(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
I2SBase i2s;
|
||||
int mute_pin;
|
||||
|
||||
/// set mute pin on or off
|
||||
void mute(bool is_mute){
|
||||
if (mute_pin>0) {
|
||||
digitalWrite(mute_pin, is_mute ? SOFT_MUTE_VALUE : !SOFT_MUTE_VALUE );
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) {
|
||||
return i2s.writeBytes(data, len);
|
||||
}
|
||||
|
||||
virtual size_t readExt( uint8_t *data, size_t length) {
|
||||
return i2s.readBytes(data, length);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
/**
|
||||
* @brief Represents the content of a URL as Stream. We use the ESP32 ESP HTTP Client API
|
||||
* @author Phil Schatzmann
|
||||
@ -140,21 +536,20 @@ class StreamCopy {
|
||||
*/
|
||||
class UrlStream : public Stream {
|
||||
public:
|
||||
UrlStream(int readBufferSize=512){
|
||||
UrlStream(int readBufferSize=DEFAULT_BUFFER_SIZE){
|
||||
read_buffer = new uint8_t[readBufferSize];
|
||||
}
|
||||
|
||||
~UrlStream(){
|
||||
delete[] read_buffer;
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
end();
|
||||
}
|
||||
|
||||
int begin(const char* url) {
|
||||
int result = -1;
|
||||
config.url = url;
|
||||
config.method = HTTP_METHOD_GET;
|
||||
Logger.info("UrlStream.begin ",url);
|
||||
Logger.printf(AudioLogger::Info, "UrlStream.begin %s\n",url);
|
||||
|
||||
// cleanup last begin if necessary
|
||||
if (client==nullptr){
|
||||
@ -189,16 +584,19 @@ class UrlStream : public Stream {
|
||||
}
|
||||
|
||||
virtual int available() {
|
||||
return size - read_pos;
|
||||
return size - total_read;
|
||||
}
|
||||
|
||||
virtual size_t readBytes(uint8_t *buffer, size_t length){
|
||||
return esp_http_client_read(client, (char*)buffer, length);
|
||||
size_t read = esp_http_client_read(client, (char*)buffer, length);
|
||||
total_read+=read;
|
||||
return read;
|
||||
}
|
||||
|
||||
virtual int read() {
|
||||
fillBuffer();
|
||||
return isEOS() ? -1 : read_buffer[read_pos++];
|
||||
total_read++;
|
||||
return isEOS() ? -1 :read_buffer[read_pos++];
|
||||
}
|
||||
|
||||
virtual int peek() {
|
||||
@ -213,11 +611,17 @@ class UrlStream : public Stream {
|
||||
Logger.error("UrlStream write - not supported");
|
||||
}
|
||||
|
||||
void end(){
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
esp_http_client_handle_t client;
|
||||
esp_http_client_config_t config;
|
||||
long size;
|
||||
long total_read;
|
||||
// buffered read
|
||||
uint8_t *read_buffer;
|
||||
uint16_t read_buffer_size;
|
||||
@ -225,9 +629,9 @@ class UrlStream : public Stream {
|
||||
uint16_t read_size;
|
||||
const AudioLogger &Logger = AudioLogger::instance();
|
||||
|
||||
|
||||
inline void fillBuffer() {
|
||||
if (isEOS()){
|
||||
// if we consumed all bytes we refill the buffer
|
||||
read_size = readBytes(read_buffer,read_buffer_size);
|
||||
read_pos = 0;
|
||||
}
|
||||
@ -241,95 +645,51 @@ class UrlStream : public Stream {
|
||||
|
||||
|
||||
/**
|
||||
* @brief We support the Stream interface on I2S
|
||||
* @brief We support the Stream interface for the ADC class
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
|
||||
class I2SStream : public Stream, public AudioBaseInfoDependent {
|
||||
class AnalogAudioStream : public BufferedStream, public AudioBaseInfoDependent {
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
I2SStream(I2S<T> &i2s){
|
||||
this->i2s = (I2S<uint8_t>*) &i2s;
|
||||
size_t buffer_size = this->i2s->config().i2s.dma_buf_len;
|
||||
buffer = new SingleBuffer<uint8_t>(buffer_size);
|
||||
}
|
||||
public:
|
||||
AnalogAudioStream() : BufferedStream(DEFAULT_BUFFER_SIZE){
|
||||
}
|
||||
|
||||
~I2SStream() {
|
||||
delete [] buffer;
|
||||
}
|
||||
/// Provides the default configuration
|
||||
AnalogConfig defaultConfig(RxTxMode mode) {
|
||||
return adc.defaultConfig(mode);
|
||||
}
|
||||
|
||||
/// writes a byte to the buffer
|
||||
virtual size_t write(uint8_t c) {
|
||||
if (buffer->isFull()){
|
||||
flush();
|
||||
}
|
||||
return buffer->write(c);
|
||||
}
|
||||
void begin(AnalogConfig cfg) {
|
||||
adc.begin(cfg);
|
||||
// unmute
|
||||
mute(false);
|
||||
}
|
||||
|
||||
/// Use this method: write an array
|
||||
virtual size_t write(const uint8_t* data, size_t len) {
|
||||
flush();
|
||||
return i2s->writeBytes(data, len);
|
||||
}
|
||||
void end() {
|
||||
mute(true);
|
||||
adc.end();
|
||||
}
|
||||
|
||||
/// empties the buffer
|
||||
virtual void flush() {
|
||||
// just dump the memory of the buffer and clear it
|
||||
if (buffer->available()>0){
|
||||
i2s->writeBytes(buffer->address(), buffer->available());
|
||||
buffer->reset();
|
||||
}
|
||||
}
|
||||
protected:
|
||||
AnalogAudio adc;
|
||||
int mute_pin;
|
||||
|
||||
/// reads a byte - to be avoided
|
||||
virtual int read() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->read();
|
||||
}
|
||||
/// set mute pin on or off
|
||||
void mute(bool is_mute){
|
||||
if (mute_pin>0) {
|
||||
digitalWrite(mute_pin, is_mute ? SOFT_MUTE_VALUE : !SOFT_MUTE_VALUE );
|
||||
}
|
||||
}
|
||||
|
||||
/// peeks a byte - to be avoided
|
||||
virtual int peek() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->peek();
|
||||
};
|
||||
|
||||
/// Use this method !!
|
||||
size_t readBytes( uint8_t *data, size_t length) {
|
||||
if (buffer->isEmpty()){
|
||||
return i2s->readBytes(data, length);
|
||||
} else {
|
||||
return buffer->readArray(data, length);
|
||||
}
|
||||
}
|
||||
virtual size_t writeExt(const uint8_t* data, size_t len) {
|
||||
return adc.writeBytes(data, len);
|
||||
}
|
||||
|
||||
/// Returns the available bytes in the buffer: to be avoided
|
||||
virtual int available() {
|
||||
if (buffer->isEmpty()){
|
||||
refill();
|
||||
}
|
||||
return buffer->available();
|
||||
}
|
||||
|
||||
/// updates the sample rate dynamically
|
||||
virtual void setAudioBaseInfo(AudioBaseInfo info) {
|
||||
i2s->setAudioBaseInfo(info);
|
||||
}
|
||||
|
||||
protected:
|
||||
I2S<uint8_t> *i2s;
|
||||
SingleBuffer<uint8_t> *buffer;
|
||||
|
||||
// refills the buffer with data from i2s
|
||||
void refill() {
|
||||
size_t result = i2s->readBytes(buffer->address(), buffer->size());
|
||||
buffer->setAvailable(result);
|
||||
}
|
||||
virtual size_t readExt( uint8_t *data, size_t length) {
|
||||
return adc.readBytes(data, length);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
290
src/AudioTools/Vector.h
Normal file
290
src/AudioTools/Vector.h
Normal file
@ -0,0 +1,290 @@
|
||||
#pragma once
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief Vector implementation which provides the most important methods as defined by std::vector. This is neither part of
|
||||
* Pico nor of the Arduino framwork but nevertheless it is quite handy to have and most of the times quite better then dealing
|
||||
* with raw c arrays.
|
||||
*
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
**/
|
||||
|
||||
template <class T>
|
||||
class Vector {
|
||||
public:
|
||||
/**
|
||||
* @brief Iterator for the Vector class
|
||||
*
|
||||
* by Phil Schatzmann
|
||||
**/
|
||||
|
||||
class iterator {
|
||||
protected:
|
||||
T *ptr;
|
||||
size_t pos_;
|
||||
public:
|
||||
inline iterator(){
|
||||
}
|
||||
inline iterator(T* parPtr, size_t pos){
|
||||
this->ptr = parPtr;
|
||||
this->pos_ = pos;
|
||||
}
|
||||
// copy constructor
|
||||
inline iterator(const iterator ©From){
|
||||
ptr = copyFrom.ptr;
|
||||
pos_ = copyFrom.pos_;
|
||||
}
|
||||
inline iterator operator++(int n) {
|
||||
ptr++;
|
||||
pos_++;
|
||||
return *this;
|
||||
}
|
||||
inline iterator operator++() {
|
||||
ptr++;
|
||||
pos_++;
|
||||
return *this;
|
||||
}
|
||||
inline iterator operator--(int n) {
|
||||
ptr--;
|
||||
pos_--;
|
||||
return *this;
|
||||
}
|
||||
inline iterator operator--() {
|
||||
ptr--;
|
||||
pos_--;
|
||||
return *this;
|
||||
}
|
||||
inline iterator operator+(int offset) {
|
||||
pos_ += offset;
|
||||
return iterator(ptr+offset, offset);
|
||||
}
|
||||
inline bool operator==(iterator it) {
|
||||
return ptr == it.getPtr();
|
||||
}
|
||||
inline bool operator<(iterator it) {
|
||||
return ptr < it.getPtr();
|
||||
}
|
||||
inline bool operator<=(iterator it) {
|
||||
return ptr <= it.getPtr();
|
||||
}
|
||||
inline bool operator>(iterator it) {
|
||||
return ptr > it.getPtr();
|
||||
}
|
||||
inline bool operator>=(iterator it) {
|
||||
return ptr >= it.getPtr();
|
||||
}
|
||||
inline bool operator!=(iterator it) {
|
||||
return ptr != it.getPtr();
|
||||
}
|
||||
inline T &operator*() {
|
||||
return *ptr;
|
||||
}
|
||||
inline T *getPtr() {
|
||||
return ptr;
|
||||
}
|
||||
inline size_t pos() {
|
||||
return pos_;
|
||||
}
|
||||
inline size_t operator-(iterator it) {
|
||||
return (ptr - it.getPtr());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// default constructor
|
||||
inline Vector(int len = 20) {
|
||||
resize_internal(len, false);
|
||||
}
|
||||
|
||||
// allocate size and initialize array
|
||||
inline Vector(int size, T value) {
|
||||
resize(size);
|
||||
for (int j=0;j< size;j++){
|
||||
data[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// copy constructor
|
||||
inline Vector( Vector<T> ©From) {
|
||||
resize_internal(copyFrom.size(), false);
|
||||
for (int j=0;j<copyFrom.size();j++){
|
||||
data[j] = copyFrom[j];
|
||||
}
|
||||
this->len = copyFrom.size();
|
||||
}
|
||||
|
||||
// legacy constructor with pointer range
|
||||
inline Vector(T *from, T *to) {
|
||||
this->len = to - from;
|
||||
resize_internal(this->len, false);
|
||||
for (size_t j=0;j<this->len;j++){
|
||||
data[j] = from[j];
|
||||
}
|
||||
}
|
||||
|
||||
inline ~Vector() {
|
||||
clear();
|
||||
shrink_to_fit();
|
||||
delete [] this->data;
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
len = 0;
|
||||
}
|
||||
|
||||
inline int size() {
|
||||
return len;
|
||||
}
|
||||
|
||||
inline bool empty() {
|
||||
return size()==0;
|
||||
}
|
||||
|
||||
inline void push_back(T value){
|
||||
resize_internal(len+1, true);
|
||||
data[len] = value;
|
||||
len++;
|
||||
}
|
||||
|
||||
inline void pop_back(){
|
||||
if (len>0) {
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
inline void assign(iterator v1, iterator v2) {
|
||||
size_t newLen = v2 - v1;
|
||||
resize_internal(newLen, false);
|
||||
this->len = newLen;
|
||||
int pos = 0;
|
||||
for (auto ptr = v1; ptr != v2; ptr++) {
|
||||
data[pos++] = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline void assign(size_t number, T value) {
|
||||
resize_internal(number, false);
|
||||
this->len = number;
|
||||
for (int j=0;j<number;j++){
|
||||
data[j]=value;
|
||||
}
|
||||
}
|
||||
|
||||
inline void swap(Vector<T> &in){
|
||||
// save data
|
||||
T *dataCpy = data;
|
||||
int bufferLenCpy = bufferLen;
|
||||
int lenCpy = len;
|
||||
// swap this
|
||||
data = in.data;
|
||||
len = in.len;
|
||||
bufferLen = in.bufferLen;
|
||||
// swp in
|
||||
in.data = dataCpy;
|
||||
in.len = lenCpy;
|
||||
in.bufferLen = bufferLenCpy;
|
||||
}
|
||||
|
||||
inline T &operator[](int index) {
|
||||
return data[index];
|
||||
}
|
||||
|
||||
inline Vector<T> &operator=(Vector<T> ©From) {
|
||||
resize_internal(copyFrom.size(), false);
|
||||
for (int j=0;j<copyFrom.size();j++){
|
||||
data[j] = copyFrom[j];
|
||||
}
|
||||
this->len = copyFrom.size();
|
||||
}
|
||||
|
||||
inline T &operator[] (const int index) const {
|
||||
return data[index];
|
||||
}
|
||||
|
||||
inline bool resize(int newSize, T value){
|
||||
if (resize(newSize)){
|
||||
for (int j=0;j<newSize;j++){
|
||||
data[j]=value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void shrink_to_fit() {
|
||||
resize_internal(this->len, true, true);
|
||||
}
|
||||
|
||||
int capacity(){
|
||||
return this->bufferLen;
|
||||
}
|
||||
|
||||
inline bool resize(int newSize){
|
||||
int oldSize = this->len;
|
||||
resize_internal(newSize, true);
|
||||
this->len = newSize;
|
||||
return this->len!=oldSize;
|
||||
}
|
||||
|
||||
inline iterator begin(){
|
||||
return iterator(data, 0);
|
||||
}
|
||||
|
||||
inline T& back(){
|
||||
return *iterator(data+(len-1), len-1);
|
||||
}
|
||||
|
||||
inline iterator end(){
|
||||
return iterator(data+len, len);
|
||||
}
|
||||
|
||||
// removes a single element
|
||||
inline void erase(iterator it) {
|
||||
int pos = it.pos();
|
||||
if (pos<len){
|
||||
int lenToEnd = len - pos - 1;
|
||||
// call destructor on data to be erased
|
||||
data[pos].~T();
|
||||
// shift values by 1 position
|
||||
memmove((void*) &data[pos],(void*)(&data[pos+1]),lenToEnd*sizeof(T));
|
||||
// make sure that we have a valid object at the end
|
||||
data[len-1] = T();
|
||||
len--;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int bufferLen;
|
||||
int len = 0;
|
||||
T *data = nullptr;
|
||||
|
||||
inline void resize_internal(int newSize, bool copy, bool shrink=false) {
|
||||
bool withNewSize = false;
|
||||
if (newSize>bufferLen || this->data==nullptr ||shrink){
|
||||
withNewSize = true;
|
||||
T* oldData = data;
|
||||
int oldBufferLen = this->bufferLen;
|
||||
this->data = new T[newSize+1];
|
||||
this->bufferLen = newSize;
|
||||
if (oldData != nullptr) {
|
||||
if(copy && this->len > 0){
|
||||
memcpy((void*)data,(void*) oldData, this->len*sizeof(T));
|
||||
}
|
||||
if (shrink){
|
||||
cleanup(oldData, newSize, oldBufferLen);
|
||||
}
|
||||
delete [] oldData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup(T*data, int from, int to){
|
||||
for (int j=from;j<to;j++){
|
||||
data[j].~T();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -226,7 +226,7 @@ class WAVDecoder {
|
||||
public:
|
||||
WAVDecoder(Print &out_stream, AudioBaseInfoDependent &bi = AudioBaseInfoDependentNone){
|
||||
this->out = &out_stream;
|
||||
this->audioBaseInfoSupport = &bi;
|
||||
this->audioBaseInfoSupport = bi;
|
||||
}
|
||||
|
||||
void begin() {
|
||||
@ -260,17 +260,19 @@ class WAVDecoder {
|
||||
isValid = false;
|
||||
} else {
|
||||
// update sampling rate if the target supports it
|
||||
if (audioBaseInfoSupport!=nullptr){
|
||||
AudioBaseInfo bi;
|
||||
bi.sample_rate = header.audioInfo().sample_rate;
|
||||
bi.channels = header.audioInfo().channels;
|
||||
bi.bits_per_sample = header.audioInfo().bits_per_sample;
|
||||
AudioBaseInfo bi;
|
||||
bi.sample_rate = header.audioInfo().sample_rate;
|
||||
bi.channels = header.audioInfo().channels;
|
||||
bi.bits_per_sample = header.audioInfo().bits_per_sample;
|
||||
// we provide some functionality so that we could check if the destination supports the requested format
|
||||
isValid = audioBaseInfoSupport.validate(bi);
|
||||
WAVLogger.printf(AudioLogger::Error,"isValid: ", isValid ? "true":"false");
|
||||
if (isValid){
|
||||
audioBaseInfoSupport->setAudioBaseInfo(bi);
|
||||
// write prm data from first record
|
||||
WAVLogger.printf(AudioLogger::Info,"WAVDecoder writing first sound data");
|
||||
out->write(sound_ptr, len);
|
||||
}
|
||||
|
||||
// write prm data from first record
|
||||
WAVLogger.printf(AudioLogger::Info,"WAVDecoder writing first sound data");
|
||||
out->write(sound_ptr, len);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -281,7 +283,7 @@ class WAVDecoder {
|
||||
protected:
|
||||
WAVHeader header;
|
||||
Print *out;
|
||||
AudioBaseInfoDependent *audioBaseInfoSupport;
|
||||
const AudioBaseInfoDependent &audioBaseInfoSupport;
|
||||
bool isFirst = true;
|
||||
bool isValid = true;
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioOutput.h"
|
||||
#include "SoundData.h"
|
||||
#include "AudioTools.h"
|
||||
|
||||
namespace audio_tools {
|
||||
|
||||
/**
|
||||
* @brief ESP8266Audio AudioOutput class which stores the data in a temporary buffer.
|
||||
* The buffer can be consumed e.g. by a callback function by calling read();
|
||||
* @author Phil Schatzmann
|
||||
* @copyright GPLv3
|
||||
*/
|
||||
class AudioOutputWithCallback : public AudioOutput {
|
||||
public:
|
||||
// Default constructor
|
||||
AudioOutputWithCallback(int bufferSize, int bufferCount ){
|
||||
buffer_ptr = new NBuffer<Channels>(bufferSize, bufferCount);
|
||||
}
|
||||
|
||||
virtual ~AudioOutputWithCallback() {
|
||||
delete buffer_ptr;
|
||||
}
|
||||
|
||||
/// Activates the output
|
||||
virtual bool begin() {
|
||||
active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// puts the sample into a buffer
|
||||
virtual bool ConsumeSample(int16_t sample[2]) {
|
||||
Channels c;
|
||||
c.channel1 = sample[0];
|
||||
c.channel2 = sample[1];
|
||||
return buffer_ptr->write(c);
|
||||
};
|
||||
|
||||
/// stops the processing
|
||||
virtual bool stop() {
|
||||
active = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
/// Provides the data from the internal buffer to the callback
|
||||
size_t read(Channels *src, size_t len){
|
||||
return active ? this->buffer_ptr->readArray(src, len) : 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
NBuffer<Channels> *buffer_ptr;
|
||||
bool active;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ESP8266Audio Output to Stream
|
||||
*
|
||||
*/
|
||||
class SerialOutputStream(): public AudioOutput {
|
||||
public:
|
||||
SerialOutputStream(Stream &out){
|
||||
this->out = out;
|
||||
}
|
||||
|
||||
virtual bool begin() {
|
||||
active = start;
|
||||
}
|
||||
|
||||
virtual bool ConsumeSample(int16_t sample[2]){
|
||||
if (active) out.write((uint8_t*)sample, const_exptr(2*sizeof(int16_t)));
|
||||
}
|
||||
|
||||
virtual bool stop() {
|
||||
active = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
const Stream &out;
|
||||
bool active;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user