Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,72 @@
# WLED-AudioReactive-Usermod
# Audioreactive usermod

Enables controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).

Does audio processing and provides data structure that specially written effects can use.

**does not** provide effects or draw anything to an LED strip/matrix.

## Additional Documentation
This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and a lot of documentation and information can be found in the [SR-WLED wiki](https://github.com/atuline/WLED/wiki):
* [getting started with audio](https://github.com/atuline/WLED/wiki/First-Time-Setup#sound)
* [Sound settings](https://github.com/atuline/WLED/wiki/Sound-Settings) - similar to options on the usemod settings page in WLED.
* [Digital Audio](https://github.com/atuline/WLED/wiki/Digital-Microphone-Hookup)
* [Analog Audio](https://github.com/atuline/WLED/wiki/Analog-Audio-Input-Options)
* [UDP Sound sync](https://github.com/atuline/WLED/wiki/UDP-Sound-Sync)


## Supported MCUs
This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support.

It will compile successfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.

Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3.

Currently ESP8266 is not supported, due to low speed and small RAM of this chip.
There are however plans to create a lightweight audioreactive for the 8266, with reduced features.
## Installation

### using latest _arduinoFFT_ library

* `build_flags` = `-D USERMOD_AUDIOREACTIVE`
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`

## Configuration

All parameters are runtime configurable. Some may require a hard reset after changing them (I2S microphone or selected GPIOs).

If you want to define default GPIOs during compile time, use the following (default values in parentheses):

- `-D SR_DMTYPE=x` : defines digital microphone type: 0=analog, 1=generic I2S (default), 2=ES7243 I2S, 3=SPH0645 I2S, 4=generic I2S with master clock, 5=PDM I2S
- `-D AUDIOPIN=x` : GPIO for analog microphone/AUX-in (36)
- `-D I2S_SDPIN=x` : GPIO for SD pin on digital microphone (32)
- `-D I2S_WSPIN=x` : GPIO for WS pin on digital microphone (15)
- `-D I2S_CKPIN=x` : GPIO for SCK pin on digital microphone (14)
- `-D MCLK_PIN=x` : GPIO for master clock pin on digital Line-In boards (-1)
- `-D ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1)
- `-D ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1)

Other options:

- `-D UM_AUDIOREACTIVE_ENABLE` : makes usermod default enabled (not the same as include into build option!)
- `-D UM_AUDIOREACTIVE_DYNAMICS_LIMITER_OFF` : disables rise/fall limiter default

**NOTE** I2S is used for analog audio sampling. Hence, the analog *buttons* (i.e. potentiometers) are disabled when running this usermod with an analog microphone.

### Advanced Compile-Time Options
You can use the following additional flags in your `build_flags`
* `-D SR_SQUELCH=x` : Default "squelch" setting (10)
* `-D SR_GAIN=x` : Default "gain" setting (60)
* `-D SR_AGC=x` : (Only ESP32) Default "AGC (Automatic Gain Control)" setting (0): 0=off, 1=normal, 2=vivid, 3=lazy
* `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this).
* `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM resources (not recommended unless you absolutely need this).
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continuously sample analog ADC microphone. Only effective on ESP32. WARNING this *will* cause conflicts(lock-up) with any analogRead() call.
* `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE)
* `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB.
* `-D WLED_USE_PINMANAGER_V14`: Use WLED V14 PinManager API for backward compatibility. Only needed when compiling against WLED V14 or earlier. V16+ is the default.

## Release notes

* 2022-06 Ported from [soundreactive WLED](https://github.com/atuline/WLED) - by @blazoncek (AKA Blaz Kristan) and the [SR-WLED team](https://github.com/atuline/WLED/wiki#sound-reactive-wled-fork-team).
* 2022-11 Updated to align with "[MoonModules/WLED](https://amg.wled.me)" audioreactive usermod - by @softhack007 (AKA Frank Möhle).
4 changes: 4 additions & 0 deletions audio_reactive.h
Original file line number Diff line number Diff line change
Expand Up @@ -2556,7 +2556,11 @@ class AudioReactive : public Usermod {
// better would be for AudioSource to implement getType()
if (enabled
&& dmType == 0 && audioPin>=0
#ifndef WLED_USE_PINMANAGER_V14
&& (buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED)
#else
&& (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)
#endif
) {
return true;
}
Expand Down
83 changes: 60 additions & 23 deletions audio_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ constexpr i2s_port_t AR_I2S_PORT = I2S_NUM_0; // I2S port to use (do not c
#endif
#endif

// PinManager API Compatibility
// By default, this usermod uses WLED V16+ PinManager API (PinManager:: static class methods)
// Define WLED_USE_PINMANAGER_V14 to use the older V14 API (pinManager instance methods)
// The method signatures are the same, but V14 uses lowercase 'pinManager' instance
// while V16+ uses uppercase 'PinManager::' static class methods
// Example: -D WLED_USE_PINMANAGER_V14
// #define WLED_USE_PINMANAGER_V14 // Uncomment for V14 compatibility mode

/* ToDo: remove. ES7243 is controlled via compiler defines
Until this configuration is moved to the webinterface
*/
Expand Down Expand Up @@ -188,6 +196,35 @@ class AudioSource {
I2S_datatype newSampleBuffer[I2S_SAMPLES_MAX+4] = { 0 }; // global buffer for i2s_read
};


// Helper functions for PinManager API compatibility
#ifdef WLED_USE_PINMANAGER_V14
// V14 API compatibility - uses pinManager instance methods (lowercase)
inline bool ar_allocatePin(int8_t pin, bool output, PinOwner owner) {
return pinManager.allocatePin(pin, output, owner);
}
inline void ar_deallocatePin(int8_t pin, PinOwner owner) {
pinManager.deallocatePin(pin, owner);
}
inline bool ar_joinWire(int8_t sda, int8_t scl) {
return pinManager.joinWire(sda, scl);
}
#else
// V16+ API (default) - uses PinManager static class methods (uppercase)
inline bool ar_allocatePin(int8_t pin, bool output, PinOwner owner) {
return PinManager::allocatePin(pin, output, owner);
}
inline void ar_deallocatePin(int8_t pin, PinOwner owner) {
PinManager::deallocatePin(pin, owner);
}
inline bool ar_joinWire(int8_t sda, int8_t scl) {
USER_PRINTLN("AR: WARNING PinManager::joinWire() unsupported - returning false.");
return false;
// return PinManager::joinWire(sda, scl); // TODO: implement PinManager::joinWire()
}
#endif


/* Basic I2S microphone source
All functions are marked virtual, so derived classes can replace them
WARNING: i2sMaster = false is experimental, and most likely will not work
Expand Down Expand Up @@ -241,17 +278,17 @@ class I2SSource : public AudioSource {
virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) {
DEBUGSR_PRINTLN("I2SSource:: initialize().");
if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) {
if (!PinManager::allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) ||
!PinManager::allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pins: ws=%d, sd=%d\n", i2swsPin, i2ssdPin);
if (!ar_allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) ||
!ar_allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pins: ws=%d, sd=%d\n", i2swsPin, i2ssdPin);
return;
}
}

// i2ssckPin needs special treatment, since it might be unused on PDM mics
if (i2sckPin != I2S_PIN_NO_CHANGE) {
if (!PinManager::allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) {
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin);
if (!ar_allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) {
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin);
return;
}
} else {
Expand Down Expand Up @@ -318,8 +355,8 @@ class I2SSource : public AudioSource {
// Reserve the master clock pin if provided
_mclkPin = mclkPin;
if (mclkPin != I2S_PIN_NO_CHANGE) {
if(!PinManager::allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) {
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin);
if(!ar_allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) {
ERRORSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin);
return;
} else
_routeMclk(mclkPin);
Expand Down Expand Up @@ -380,11 +417,11 @@ class I2SSource : public AudioSource {
DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err);
return;
}
if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive);
if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive);
if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive);
if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) ar_deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive);
if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) ar_deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive);
if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) ar_deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive);
// Release the master clock pin
if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive);
if (_mclkPin != I2S_PIN_NO_CHANGE) ar_deallocatePin(_mclkPin, PinOwner::UM_Audioreactive);
}

virtual void getSamples(float *buffer, uint16_t num_samples) {
Expand Down Expand Up @@ -505,8 +542,8 @@ class ES7243 : public I2SSource {
return;
}
#ifdef WLEDMM
if (!PinManager::joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
if (!ar_joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
return;
}
#endif
Expand Down Expand Up @@ -637,8 +674,8 @@ class ES8388Source : public I2SSource {
return;
}
#ifdef WLEDMM
if (!PinManager::joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
if (!ar_joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
return;
}
#endif
Expand Down Expand Up @@ -737,8 +774,8 @@ class ES8311Source : public I2SSource {
return;
}
#ifdef WLEDMM
if (!PinManager::joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
if (!ar_joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
return;
}
#endif
Expand Down Expand Up @@ -835,8 +872,8 @@ class WM8978Source : public I2SSource {
return;
}
#ifdef WLEDMM
if (!PinManager::joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
if (!ar_joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
return;
}
#endif
Expand Down Expand Up @@ -939,8 +976,8 @@ class AC101Source : public I2SSource {
return;
}
#ifdef WLEDMM
if (!PinManager::joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
if (!ar_joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
return;
}
#endif
Expand Down Expand Up @@ -998,7 +1035,7 @@ class I2SAdcSource : public I2SSource {
void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
DEBUGSR_PRINTLN("I2SAdcSource:: initialize().");
_myADCchannel = 0x0F;
if(!PinManager::allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) {
if(!ar_allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) {
ERRORSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin);
return;
}
Expand Down Expand Up @@ -1126,7 +1163,7 @@ class I2SAdcSource : public I2SSource {
}

void deinitialize() {
PinManager::deallocatePin(_audioPin, PinOwner::UM_Audioreactive);
ar_deallocatePin(_audioPin, PinOwner::UM_Audioreactive);
_initialized = false;
_myADCchannel = 0x0F;

Expand Down