diff --git a/README.md b/README.md index 2f1656999..ad7cc1b91 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Some basic __header-only C++ classes__ that can be used for __Audio Processing__ - 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 +- a simple PWM class (to write audio data with the help of PWM) +- Additional Stream implementations: MemoryStream, URLStream, I2SStream, A2DPStream, PrintStream, - Converters - Musical Notes (with frequencies of notes) - SineWaveGenerator (to generate a sine tone) and [Mozzi](https://sensorium.github.io/Mozzi/) for more complex scenario @@ -30,6 +31,7 @@ As “Audio Sinks” we will have e.g: - external DAC – [I2SStream](https://pschatzmann.github.io/arduino-audio-tools/html/classaudio__tools_1_1_i2_s_stream.html) - an Amplifier – [AnalogAudioStream](https://pschatzmann.github.io/arduino-audio-tools/html/classaudio__tools_1_1_analog_audio_stream.html) +- Earphones – [PWMAudioStream](https://pschatzmann.github.io/arduino-audio-tools/html/classaudio__tools_1_1_pwm_audio_stream.html) - Bluetooth Speakers – [A2DPStream](https://pschatzmann.github.io/arduino-audio-tools/html/classaudio__tools_1_1_a2_d_p_stream.html) - Serial to display the data as CSV – [CsvStream](https://pschatzmann.github.io/arduino-audio-tools/html/classaudio__tools_1_1_csv_stream.html) - Any other Arduino Classes implementing Streams: SD, Ethernet etc @@ -69,6 +71,12 @@ void loop(){ ``` A complete list of the supported Audio Stream classes and scenarios can be found in the [Scenarios Document](Scenarios.md) +### Sound Output + +- __I2SStream__: The best quality can be achieved with the help of I2S and an external DAC. I2S is supporting 2 channels only. +- __AnalogAudioStream__: Some processors are providing an analog output, this is usually an easy and good approach: The number of pins (and herewith output channels) however is usually very limited. +- __PWMAudioStream__: The last possibility is to simulate an analog output with the help of PWM by using a frequency which is beyond the audible range of 20 KHz. This method is supported by all processor and usually supports a bigger number of output pins. In terms of audio quality this is usually the worst option. + ## Examples diff --git a/Scenarios.md b/Scenarios.md index 4582cd7ac..5afbd76dc 100644 --- a/Scenarios.md +++ b/Scenarios.md @@ -5,12 +5,12 @@ 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 | | | | | + | +| Processor | I2S | ADC/DAC | A2DP | URLStream | PWM | Other | +|----------------|-----------|----------|--------|-----------|-------|--------| +| ESP32 | + | + | + | + | * | + | +| ESP8266 | * | * | | | | + | +| SAMD21 | * | | | | | + | +| Raspberry Pico | | | | | * | + | + supported @@ -24,6 +24,7 @@ Here are the related Stream classes with their supported operations that can be | Class | Read | Write | Comments | |-------------------------|------|-------|--------------------| | I2SStream | + | + | i2s | +| PWMAduioStream | | + | pwm | | AnalogAudioStream | + | + | adc, dac | | MemoryStream | + | + | memory | | URLStream | + | | url | diff --git a/src/AudioConfig.h b/src/AudioConfig.h index 33c7e81d7..2e7e21074 100644 --- a/src/AudioConfig.h +++ b/src/AudioConfig.h @@ -40,6 +40,8 @@ #define A2DP_BUFFER_SIZE 4096 #define A2DP_BUFFER_COUNT 8 #define DEFAUT_ADC_PIN 34 +#define PWM_BUFFER_SIZE 1024 +#define PWM_BUFFERS 4 /** * ------------------------------------------------------------------------- diff --git a/src/AudioPWM/PWMforESP32.h b/src/AudioPWM/PWMforESP32.h index 7952de239..8b39ccca1 100644 --- a/src/AudioPWM/PWMforESP32.h +++ b/src/AudioPWM/PWMforESP32.h @@ -5,15 +5,14 @@ #include "AudioTools/AudioLogger.h" #include "AudioTools/Vector.h" #include "Stream.h" - -#define PWM_BUFFER_LENGTH 512 +#include /* pow */ namespace audio_tools { -enum PWMResolution {Res8,Res9,Res10,Res11}; -void defaultAudioOutputCallback(); +// forward declaration class AudioPWM; -static AudioPWM *accessAudioPWM; +void defaultPWMAudioOutputCallback(); +static AudioPWM *accessAudioPWM = nullptr; /** * @brief Configuration for PWM output @@ -25,6 +24,7 @@ static AudioPWM *accessAudioPWM; * 11 | 2048 | 39.0625 * * The default resolution is 8. The value must be between 8 and 11 and also drives the PWM frequency. + * * @author Phil Schatzmann * @copyright GPLv3 @@ -36,7 +36,7 @@ struct PWMConfig { int start_pin = 3; int buffer_size = 1024 * 8; int bits_per_sample = 16; - int resolution = 8; // must be between 8 and 11 + int resolution = 8; // must be between 8 and 11 -> drives pwm frequency } default_config; /** @@ -50,17 +50,17 @@ struct PINInfo { }; /** - * @brief Audio output to PWM pins for the ESP32. + * @brief Audio output to PWM pins for the ESP32. The ESP32 supports up to 16 channels. * @author Phil Schatzmann * @copyright GPLv3 */ -class AudioPWM : public Stream { +class PWMAudioStream : public Stream { friend void defaultAudioOutputCallback(); public: - AudioPWM(){ + PWMAudioStream(){ accessAudioPWM = this; } @@ -73,15 +73,30 @@ class AudioPWM : public Stream { } // starts the processing - virtual void begin(PWMConfig config){ + bool begin(PWMConfig config){ LOGD(__FUNCTION__); this->audio_config = config; LOGI("sample_rate: %d", audio_config.sample_rate); LOGI("channels: %d", audio_config.channels); + LOGI("bits_per_sample: %d", audio_config.bits_per_sample); LOGI("start_pin: %d", audio_config.start_pin); + LOGI("resolution: %d", audio_config.resolution); + + // controller has max 16 independent channels + if (audio_config.channels>=16){ + LOGE("Only max 16 channels are supported"); + return false; + } + + // check selected resolution + if (audio_config.resolution<8 || audio_config.resolution>11){ + LOGE("The resolution must be between 8 and 11!"); + return false; + } setupPWM(); setupTimer(); + return true; } // Ends the output @@ -91,7 +106,7 @@ class AudioPWM : public Stream { for (int j=0;j1){ buffer.write(value); - has_data = true; + setWriteStarted(); } } // blocking write for an array: we expect a singed value and convert it into a unsigned virtual size_t write(const uint8_t *wrt_buffer, size_t size){ + LOGI("write: %lu bytes", size) while(availableForWrite() %d", size, result); + } + setWriteStarted(); return result; } @@ -147,15 +168,24 @@ class AudioPWM : public Stream { protected: PWMConfig audio_config; Vector pins; - NBuffer buffer = NBuffer(DEFAULT_BUFFER_SIZE,4); - int buffer_idx = 0; - bool data_write_started = false; - hw_timer_t * timer = NULL; + NBuffer buffer = NBuffer(PWM_BUFFER_SIZE, PWM_BUFFERS); + hw_timer_t * timer = nullptr; portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; - bool has_data = false; + bool data_write_started = false; + /// when we get the first write -> we activate the timer to start with the output of data + void setWriteStarted(){ + if (!data_write_started){ + LOGI("timerAlarmEnable"); + data_write_started = true; + timerAlarmEnable(timer); + } + } + /// Setup LED PWM void setupPWM(){ + LOGD(__FUNCTION__); + pins.resize(audio_config.channels); for (int j=0;j= required){ - for (int j=0;j= required){ + for (int j=0;j "); + //Serial.println(map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue())); return map(value, -maxValue(16), maxValue(16), 0, maxUnsignedValue()); } case 24: { int24_t value; - buffer.readArray((uint8_t*)&value,3); + if (buffer.readArray((uint8_t*)&value,3)!=3){ + LOGE("Could not read full data"); + } return map((int32_t)value, -maxValue(24), maxValue(24), 0, maxUnsignedValue()); } case 32: { int32_t value; - buffer.readArray((uint8_t*)&value,4); + if (buffer.readArray((uint8_t*)&value,4)!=4){ + LOGE("Could not read full data"); + } return map(value, -maxValue(32), maxValue(32), 0, maxUnsignedValue()); } } @@ -236,7 +286,8 @@ class AudioPWM : public Stream { }; -void IRAM_ATTR defaultAudioOutputCallback() { +/// timer callback: write the next frame to the pins +void IRAM_ATTR defaultPWMAudioOutputCallback() { if (accessAudioPWM!=nullptr){ portENTER_CRITICAL_ISR(&(accessAudioPWM->timerMux)); accessAudioPWM->playNextFrame(); diff --git a/src/AudioPWM/PWMforPico.h b/src/AudioPWM/PWMforPico.h index 9bea7ec5b..6a08c9f03 100644 --- a/src/AudioPWM/PWMforPico.h +++ b/src/AudioPWM/PWMforPico.h @@ -51,12 +51,12 @@ struct PWMConfig { */ template -class AudioPWM : public Stream { +class PWMAudioStream : public Stream { friend bool defaultAudioOutputCallback(repeating_timer* ptr); public: - AudioPWM(){ + PWMAudioStream(){ T amplitude_in = getDefaultAmplitude(); audio_config.amplitude_in = amplitude_in; default_config.amplitude_in = amplitude_in; @@ -157,7 +157,7 @@ class AudioPWM : public Stream { protected: PWMConfig audio_config; Vector pins; - NBuffer buffer = NBuffer(DEFAULT_BUFFER_SIZE,4); + NBuffer buffer = NBuffer(PWM_BUFFER_SIZE, PWM_BUFFERS); repeating_timer_t timer; uint64_t underflow_count = 0; bool data_write_started = false;