diff --git a/README.md b/README.md index 3e47b1f..d42dbae 100644 --- a/README.md +++ b/README.md @@ -1 +1,72 @@ -# WLED-AudioReactive-Usermod \ No newline at end of file +# 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). \ No newline at end of file diff --git a/audio_reactive.h b/audio_reactive.h index 2bf3e6f..081d097 100644 --- a/audio_reactive.h +++ b/audio_reactive.h @@ -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; } diff --git a/audio_source.h b/audio_source.h index fd20d4a..c171a45 100644 --- a/audio_source.h +++ b/audio_source.h @@ -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 */ @@ -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 @@ -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 { @@ -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); @@ -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) { @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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; } @@ -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;