From 1497e93d3067e7a78f1444cb5aff9d5598d85049 Mon Sep 17 00:00:00 2001 From: ewowi Date: Sun, 22 Feb 2026 12:57:23 +0100 Subject: [PATCH 1/3] Add FastLED audio Backend ======= - Add D_FastLEDAudio.h Add FastLED audio variables to SharedData - WIP - Add Tubes Layout - Add FastLEDAudioDriver - Add FLAudioEffect --- platformio.ini | 6 +- src/MoonBase/Nodes.h | 14 ++- src/MoonLight/Modules/ModuleDrivers.h | 4 + src/MoonLight/Modules/ModuleEffects.h | 2 + src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h | 97 +++++++++++++++++++ .../{D_FastLED.h => D_FastLEDDriver.h} | 2 +- src/MoonLight/Nodes/Effects/E_FastLED.h | 34 +++++++ src/MoonLight/Nodes/Effects/E_WLED.h | 10 +- src/MoonLight/Nodes/Layouts/L_MoonLight.h | 32 ++++++ src/main.cpp | 7 +- 10 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h rename src/MoonLight/Nodes/Drivers/{D_FastLED.h => D_FastLEDDriver.h} (99%) diff --git a/platformio.ini b/platformio.ini index 2f363325..09df2e96 100644 --- a/platformio.ini +++ b/platformio.ini @@ -201,12 +201,12 @@ build_flags = -D FT_MOONLIGHT=1 -D FT_MONITOR=1 -D EFFECTS_STACK_SIZE=3072 ; psramFound() ? 4 * 1024 : 3 * 1024 - -D DRIVERS_STACK_SIZE=4096 ; psramFound() ? 4 * 1024 : 3 * 1024, 4096 is sufficient for now + -D DRIVERS_STACK_SIZE=6144 ; psramFound() ? 4 * 1024 : 3 * 1024, 4096 is sufficient for now. Update: due to FastLED audio I had to increase to 6144 (might want to move audio to a separate task) ; -D FASTLED_TESTING ; causes duplicate definition of initSpiHardware(); - workaround: removed implementation in spi_hw_manager_esp32.cpp.hpp - -D FASTLED_BUILD=\"20260221\" + -D FASTLED_BUILD=\"20260222\" lib_deps = - https://github.com/FastLED/FastLED#9d0b0eb9b5e59e4093982e0c2bdcfdff72ca80cb ; master 20260221 + https://github.com/FastLED/FastLED#27c99130c83ab666c4c6dbf389425fec5f27db05 ; master 20260222 https://github.com/ewowi/WLED-sync#25f280b5e8e47e49a95282d0b78a5ce5301af4fe ; sourceIP + fftUdp.clear() if arduino >=3 (20251104) ; 💫 currently only enabled on s3 as esp32dev runs over 100% diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index f13272bd..a01a35f0 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -268,7 +268,7 @@ class LiveScriptNode : public Node { // layout void onLayout() override; // call map in LiveScript - ~LiveScriptNode(); + ~LiveScriptNode() override; // LiveScript functions void compileAndRun(); @@ -346,6 +346,15 @@ static struct SharedData { size_t clientListSize; Coord3D gravity; + + //FastLED Audio + uint8_t beatBrightness = 0; // Decaying brightness for beat pulse + bool vocalsActive = false; + float vocalConfidence = 0; + float bassLevel = 0; + float trebleLevel = 0; + bool beat = false; + } sharedData; /** @@ -359,7 +368,8 @@ static struct SharedData { #include "MoonLight/Nodes/Drivers/D_ArtnetIn.h" #include "MoonLight/Nodes/Drivers/D_ArtnetOut.h" #include "MoonLight/Nodes/Drivers/D_AudioSync.h" - #include "MoonLight/Nodes/Drivers/D_FastLED.h" + #include "MoonLight/Nodes/Drivers/D_FastLEDDriver.h" + #include "MoonLight/Nodes/Drivers/D_FastLEDAudio.h" #include "MoonLight/Nodes/Drivers/D_Hub75.h" #include "MoonLight/Nodes/Drivers/D_Infrared.h" #include "MoonLight/Nodes/Drivers/D_IMU.h" diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 234533d3..d4ae53d1 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -97,10 +97,12 @@ class ModuleDrivers : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // Drivers, Most used first addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -135,10 +137,12 @@ class ModuleDrivers : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // Drivers most used first if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index e21cbba9..dddb666b 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -170,6 +170,7 @@ class ModuleEffects : public NodeManager { // FastLED effects addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // Moving head effects, alphabetically addControlValue(control, getNameAndTags()); @@ -291,6 +292,7 @@ class ModuleEffects : public NodeManager { // FastLED if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // Moving head effects, alphabetically diff --git a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h new file mode 100644 index 00000000..3100d6b6 --- /dev/null +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h @@ -0,0 +1,97 @@ +/** + @title MoonLight + @file D_FastLEDAudio.h + @repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs + @Authors https://github.com/MoonModules/MoonLight/commits/main + @Doc https://moonmodules.org/MoonLight/moonlight/overview/ + @Copyright © 2026 Github MoonLight Commit Authors + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license For non GPL-v3 usage, commercial licenses must be purchased. Contact us for more information. +**/ + +#pragma once + +#if FT_MOONLIGHT + + #include "fl/audio.h" + #include "fl/audio/audio_processor.h" + #include "fl/audio_input.h" + #include "fl/time_alpha.h" + + #define I2S_CLK_PIN 6 // Serial Clock (SCK) (BLUE) + #define I2S_WS_PIN 4 // Word Select (WS) (GREEN) + #define I2S_SD_PIN 5 // Serial Data (SD) (YELLOW) + #define I2S_CHANNEL fl::Left + +static fl::AudioConfigI2S i2sConfig(I2S_WS_PIN, I2S_SD_PIN, I2S_CLK_PIN, 0, I2S_CHANNEL, 44100, 16, fl::Philips); +static fl::AudioConfig config(i2sConfig); +static fl::shared_ptr audioInput; + +class FastLEDAudioDriver : public Node { + public: + static const char* name() { return "FastLED Audio"; } + static uint8_t dim() { return _NoD; } + static const char* tags() { return "☸️"; } + + fl::AudioProcessor audioProcessor; + + // Beat detection state + float currentBPM = 0.0f; + uint32_t lastBeatTime = 0; + uint32_t beatCount = 0; + uint32_t onsetCount = 0; + + void setup() override { + Node::setup(); // !! + + fl::string errorMsg; + audioInput = fl::IAudioInput::create(config, &errorMsg); + audioInput->start(); + + audioProcessor.onBeat([&]() { + beatCount++; + lastBeatTime = fl::millis(); + sharedData.beat = true; + EXT_LOGD(ML_TAG, "BEAT #: %d", beatCount); + }); + + audioProcessor.onVocalStart([&]() { sharedData.vocalsActive = true; }); + + audioProcessor.onVocalEnd([&]() { sharedData.vocalsActive = false; }); + + audioProcessor.onVocalConfidence([](float confidence) { + static uint32_t lastPrint = 0; + if (fl::millis() - lastPrint > 200) { + sharedData.vocalConfidence = confidence; + EXT_LOGD(ML_TAG, "Vocal confidence: %f", confidence); + lastPrint = fl::millis(); + } + }); + + audioProcessor.onBass([](float level) { + if (level > 0.01f) { + sharedData.bassLevel = level; + EXT_LOGD(ML_TAG, "Bass: %f", level); + } + }); + + audioProcessor.onTreble([](float level) { + if (level > 0.01f) { + sharedData.trebleLevel = level; + EXT_LOGD(ML_TAG, "Treble: %f", level); + } + }); + } + + void loop20ms() override { + while (fl::AudioSample sample = audioInput->read()) { + audioProcessor.update(sample); + } + + sharedData.beat = false; + } + + ~FastLEDAudioDriver() override {} +}; + +#endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Drivers/D_FastLED.h b/src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h similarity index 99% rename from src/MoonLight/Nodes/Drivers/D_FastLED.h rename to src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h index c1066209..c8e9f3a6 100644 --- a/src/MoonLight/Nodes/Drivers/D_FastLED.h +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h @@ -1,6 +1,6 @@ /** @title MoonLight - @file FastLED.h + @file D_FastLEDDriver.h @repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs @Authors https://github.com/MoonModules/MoonLight/commits/main @Doc https://moonmodules.org/MoonLight/moonlight/overview/ diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h index a48cf96e..cfb31305 100644 --- a/src/MoonLight/Nodes/Effects/E_FastLED.h +++ b/src/MoonLight/Nodes/Effects/E_FastLED.h @@ -37,4 +37,38 @@ class RainbowEffect : public Node { } }; +class FLAudioEffect : public Node { + public: + static const char* name() { return "FLAudio"; } + static uint8_t dim() { return _2D; } + static const char* tags() { return "⚡️🎵"; } + + void setup() {} + + uint16_t hue = 0; + uint8_t beatBrightness = 0; + + void loop() override { + if (sharedData.vocalsActive) { + layer->fill_rainbow((hue += 8 * 32) >> 8, 7); // hue back to uint8_t + } else if (beatBrightness > 0) { + if (sharedData.beat) beatBrightness = 255; + CHSV color = CHSV(hue, 255, beatBrightness); + layer->fill_solid(color); + + // Decay the brightness + if (beatBrightness > 10) { + beatBrightness = beatBrightness * 0.85f; // Exponential decay + hue += 32; // Shift color on each beat + + } else { + beatBrightness = 0; + } + } else { // random pixels + layer->fadeToBlackBy(70); + layer->setRGB(random16(layer->nrOfLights), ColorFromPalette(layerP.palette, random8())); + } + } +}; + #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index 5bd37b19..ce8175a3 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -3560,14 +3560,18 @@ class WaterfallEffect : public Node { uint8_t pixCol = (log10f(myMajorPeak) - 2.26f) * 150; if (myMajorPeak < 182.0f) pixCol = 0; - for (int i = 0; i < layer->nrOfLights - 1; i++) layer->setRGB(i, layer->getRGB(i + 1)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) + for (int y = 0; y < layer->size.y - 1; y++) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y + 1, z))); bool peak = sharedData.volume > 128.0f; if (peak) { - layer->setRGB(layer->nrOfLights - 1, CHSV(92, 92, 92)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, layer->size.y - 1, z), CHSV(92, 92, 92)); } else { CRGB color = ColorFromPalette(layerP.palette, pixCol + intensity, 127 + myMagnitude / 2.0); - layer->setRGB(layer->nrOfLights - 1, color); // blend(layerP.color2, color, (int)myMagnitude)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, layer->size.y - 1, z), color); // blend(layerP.color2, color, (int)myMagnitude)); } } } diff --git a/src/MoonLight/Nodes/Layouts/L_MoonLight.h b/src/MoonLight/Nodes/Layouts/L_MoonLight.h index 0f9bef5f..2e89e452 100644 --- a/src/MoonLight/Nodes/Layouts/L_MoonLight.h +++ b/src/MoonLight/Nodes/Layouts/L_MoonLight.h @@ -9,6 +9,8 @@ @license For non GPL-v3 usage, commercial licenses must be purchased. Contact us for more information. **/ +#pragma once + #if FT_MOONLIGHT class HumanSizedCubeLayout : public Node { @@ -437,6 +439,36 @@ class SingleColumnLayout : public Node { } }; +class TubesLayout : public Node { + public: + static const char* name() { return "Tubes"; } + static uint8_t dim() { return _2D; } + static const char* tags() { return "🚥"; } + + uint8_t nrOfTubes = 4; + uint8_t ledsPerTube = 54; + uint8_t tubeDistance = 10; + bool reversed_order = false; + + void setup() override { + addControl(nrOfTubes, "nrOfTubes", "slider"); + addControl(ledsPerTube, "ledsPerTube", "slider"); + addControl(tubeDistance, "tubeDistance", "slider"); + addControl(reversed_order, "reversed order", "checkbox"); + } + + bool hasOnLayout() const override { return true; } + void onLayout() override { + for (int tube = 0; tube < nrOfTubes; tube++) { + SingleColumnLayout tubeLayout; + tubeLayout.height = ledsPerTube; + tubeLayout.reversed_order = reversed_order; + tubeLayout.xposition = tube * tubeDistance; + tubeLayout.onLayout(); + } + } +}; + class RingLayout : public Node { public: static const char* name() { return "Ring"; } diff --git a/src/main.cpp b/src/main.cpp index 5615c019..2e195bd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -258,9 +258,10 @@ void setup() { if (Serial) Serial.printf("Serial init wait %d\n", i * 300); } - if (Serial) Serial.flush(); - - Serial.setDebugOutput(true); + if (Serial) { + Serial.flush(); + // Serial.setDebugOutput(true); //causes all EXT_LOG to dissappear + } Serial.printf("C++ Standard: %ld\n", __cplusplus); // 202002L From d68030f500471f475d08863c574fd03f1f74b037 Mon Sep 17 00:00:00 2001 From: ewowi Date: Sun, 22 Feb 2026 14:48:21 +0100 Subject: [PATCH 2/3] FastLED audio tweaks --- src/MoonBase/Nodes.h | 1 - src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h | 50 +++++++++++--------- src/MoonLight/Nodes/Effects/E_FastLED.h | 46 ++++++++++-------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index a01a35f0..0f396bf1 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -348,7 +348,6 @@ static struct SharedData { Coord3D gravity; //FastLED Audio - uint8_t beatBrightness = 0; // Decaying brightness for beat pulse bool vocalsActive = false; float vocalConfidence = 0; float bassLevel = 0; diff --git a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h index 3100d6b6..ffbb5db0 100644 --- a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h @@ -35,63 +35,67 @@ class FastLEDAudioDriver : public Node { fl::AudioProcessor audioProcessor; - // Beat detection state - float currentBPM = 0.0f; - uint32_t lastBeatTime = 0; - uint32_t beatCount = 0; - uint32_t onsetCount = 0; - void setup() override { Node::setup(); // !! fl::string errorMsg; audioInput = fl::IAudioInput::create(config, &errorMsg); + if (!audioInput) { + EXT_LOGE(ML_TAG, "Failed to create audio input: %s", errorMsg.c_str()); + return; + } audioInput->start(); - audioProcessor.onBeat([&]() { - beatCount++; - lastBeatTime = fl::millis(); + audioProcessor.onBeat([]() { sharedData.beat = true; - EXT_LOGD(ML_TAG, "BEAT #: %d", beatCount); + EXT_LOGD(ML_TAG, "onBeat"); }); - audioProcessor.onVocalStart([&]() { sharedData.vocalsActive = true; }); + audioProcessor.onVocalStart([]() { + sharedData.vocalsActive = true; + // EXT_LOGD(ML_TAG, "onVocalStart"); + }); - audioProcessor.onVocalEnd([&]() { sharedData.vocalsActive = false; }); + audioProcessor.onVocalEnd([]() { + sharedData.vocalsActive = false; + // EXT_LOGD(ML_TAG, "onVocalEnd"); + }); audioProcessor.onVocalConfidence([](float confidence) { - static uint32_t lastPrint = 0; - if (fl::millis() - lastPrint > 200) { - sharedData.vocalConfidence = confidence; - EXT_LOGD(ML_TAG, "Vocal confidence: %f", confidence); - lastPrint = fl::millis(); - } + sharedData.vocalConfidence = sharedData.vocalsActive ? confidence : 0.0; + // EXT_LOGD(ML_TAG, "onVocalConfidence %d", confidence); }); audioProcessor.onBass([](float level) { if (level > 0.01f) { sharedData.bassLevel = level; - EXT_LOGD(ML_TAG, "Bass: %f", level); + // EXT_LOGD(ML_TAG, "onBass: %f", level); } }); audioProcessor.onTreble([](float level) { if (level > 0.01f) { sharedData.trebleLevel = level; - EXT_LOGD(ML_TAG, "Treble: %f", level); + // EXT_LOGD(ML_TAG, "onTreble: %f", level); } }); } void loop20ms() override { + if (!audioInput) return; + + sharedData.beat = false; + while (fl::AudioSample sample = audioInput->read()) { audioProcessor.update(sample); } - - sharedData.beat = false; } - ~FastLEDAudioDriver() override {} + ~FastLEDAudioDriver() override { + if (audioInput) { + audioInput->stop(); + } + } }; #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h index cfb31305..2b22144c 100644 --- a/src/MoonLight/Nodes/Effects/E_FastLED.h +++ b/src/MoonLight/Nodes/Effects/E_FastLED.h @@ -46,28 +46,34 @@ class FLAudioEffect : public Node { void setup() {} uint16_t hue = 0; - uint8_t beatBrightness = 0; + uint8_t beatLevel = 0; + float maxBass = 0; + float maxTreble = 0; + float maxVocal = 0; void loop() override { - if (sharedData.vocalsActive) { - layer->fill_rainbow((hue += 8 * 32) >> 8, 7); // hue back to uint8_t - } else if (beatBrightness > 0) { - if (sharedData.beat) beatBrightness = 255; - CHSV color = CHSV(hue, 255, beatBrightness); - layer->fill_solid(color); - - // Decay the brightness - if (beatBrightness > 10) { - beatBrightness = beatBrightness * 0.85f; // Exponential decay - hue += 32; // Shift color on each beat - - } else { - beatBrightness = 0; - } - } else { // random pixels - layer->fadeToBlackBy(70); - layer->setRGB(random16(layer->nrOfLights), ColorFromPalette(layerP.palette, random8())); - } + layer->fadeToBlackBy(70); + + // maxLevels + if (sharedData.bassLevel > maxBass) maxBass = sharedData.bassLevel; + if (sharedData.trebleLevel > maxTreble) maxTreble = sharedData.trebleLevel; + if (sharedData.vocalConfidence > maxVocal) maxVocal = sharedData.vocalConfidence; + + if (sharedData.beat) beatLevel = 255; + + // EXT_LOGD(ML_TAG, "%f-%d %f-%d %d-%d %f-%d", sharedData.bassLevel, bassLevel, sharedData.trebleLevel, trebleLevel, sharedData.beat, beatLevel, sharedData.vocalsActive ? sharedData.vocalConfidence : 0, vocalsLevel); + + layer->drawLine(0, layer->size.y - 1, 0, layer->size.y - 1 - layer->size.y * sharedData.bassLevel / maxBass, CRGB::Blue); + layer->drawLine(1, layer->size.y - 1, 1, layer->size.y - 1 - layer->size.y * sharedData.trebleLevel / maxTreble, CRGB::Orange); + layer->drawLine(2, layer->size.y - 1, 2, layer->size.y - 1 - layer->size.y * sharedData.vocalConfidence / maxVocal, CRGB::Green); + layer->drawLine(3, layer->size.y - 1, 3, layer->size.y - 1 - layer->size.y * beatLevel / 255, CRGB::Red); + + //correct if lower output + if (maxBass > 0) maxBass -= 0.01; + if (maxTreble > 0) maxTreble -= 0.01; + if (maxVocal > 0) maxVocal -= 0.01; + + if (beatLevel) beatLevel--; } }; From abca48eb2269e47090f2f796fedee3c860b68661 Mon Sep 17 00:00:00 2001 From: ewowi Date: Sun, 22 Feb 2026 22:04:07 +0100 Subject: [PATCH 3/3] FastLED Audio: use IO Pins, add controls FastLED Audio driver - Move static variables inside class - add controls (gain etc) - add onUpdate to set controls - add readPins to get I2S pins from IO module - add initAudio and stopAudio - destructor FLAudio effect - add Mid --- src/MoonBase/Nodes.h | 13 +- src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h | 143 ++++++++++++++++--- src/MoonLight/Nodes/Effects/E_FastLED.h | 20 ++- 3 files changed, 145 insertions(+), 31 deletions(-) diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index 0f396bf1..d9207691 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -347,11 +347,12 @@ static struct SharedData { Coord3D gravity; - //FastLED Audio + // FastLED Audio bool vocalsActive = false; - float vocalConfidence = 0; - float bassLevel = 0; - float trebleLevel = 0; + float vocalConfidence = 0.0f; + float bassLevel = 0.0f; + float midLevel = 0.0f; + float trebleLevel = 0.0f; bool beat = false; } sharedData; @@ -367,11 +368,11 @@ static struct SharedData { #include "MoonLight/Nodes/Drivers/D_ArtnetIn.h" #include "MoonLight/Nodes/Drivers/D_ArtnetOut.h" #include "MoonLight/Nodes/Drivers/D_AudioSync.h" - #include "MoonLight/Nodes/Drivers/D_FastLEDDriver.h" #include "MoonLight/Nodes/Drivers/D_FastLEDAudio.h" + #include "MoonLight/Nodes/Drivers/D_FastLEDDriver.h" #include "MoonLight/Nodes/Drivers/D_Hub75.h" - #include "MoonLight/Nodes/Drivers/D_Infrared.h" #include "MoonLight/Nodes/Drivers/D_IMU.h" + #include "MoonLight/Nodes/Drivers/D_Infrared.h" #include "MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h" #include "MoonLight/Nodes/Drivers/D__Sandbox.h" #include "MoonLight/Nodes/Effects/E_FastLED.h" diff --git a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h index ffbb5db0..5a38e5e6 100644 --- a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h @@ -18,16 +18,15 @@ #include "fl/audio_input.h" #include "fl/time_alpha.h" - #define I2S_CLK_PIN 6 // Serial Clock (SCK) (BLUE) - #define I2S_WS_PIN 4 // Word Select (WS) (GREEN) - #define I2S_SD_PIN 5 // Serial Data (SD) (YELLOW) - #define I2S_CHANNEL fl::Left - -static fl::AudioConfigI2S i2sConfig(I2S_WS_PIN, I2S_SD_PIN, I2S_CLK_PIN, 0, I2S_CHANNEL, 44100, 16, fl::Philips); -static fl::AudioConfig config(i2sConfig); -static fl::shared_ptr audioInput; +// https://github.com/FastLED/FastLED/blob/master/src/fl/audio/README.md class FastLEDAudioDriver : public Node { + private: + // Member variables for audio configuration + fl::AudioConfigI2S* i2sConfig = nullptr; + fl::AudioConfig* config = nullptr; + fl::shared_ptr audioInput; + public: static const char* name() { return "FastLED Audio"; } static uint8_t dim() { return _NoD; } @@ -35,20 +34,26 @@ class FastLEDAudioDriver : public Node { fl::AudioProcessor audioProcessor; + bool signalConditioning = true; + bool autoGain = false; + bool noiseFloorTracking = false; + uint8_t channel = fl::Left; + void setup() override { - Node::setup(); // !! + addControl(signalConditioning, "signalConditioning", "checkbox"); + addControl(autoGain, "autoGain", "checkbox"); + addControl(noiseFloorTracking, "noiseFloorTracking", "checkbox"); + addControl(channel, "channel", "select"); + addControlValue("Left"); + addControlValue("Right"); + addControlValue("Both"); - fl::string errorMsg; - audioInput = fl::IAudioInput::create(config, &errorMsg); - if (!audioInput) { - EXT_LOGE(ML_TAG, "Failed to create audio input: %s", errorMsg.c_str()); - return; - } - audioInput->start(); + moduleIO->addUpdateHandler([this](const String& originId) { readPins(); }, false); + readPins(); // Node added at runtime so initial IO update not received so run explicitly audioProcessor.onBeat([]() { sharedData.beat = true; - EXT_LOGD(ML_TAG, "onBeat"); + // EXT_LOGD(ML_TAG, "onBeat"); }); audioProcessor.onVocalStart([]() { @@ -73,12 +78,85 @@ class FastLEDAudioDriver : public Node { } }); + audioProcessor.onMid([](float level) { + if (level > 0.01f) { + sharedData.midLevel = level; + // EXT_LOGD(ML_TAG, "onBass: %f", level); + } + }); + audioProcessor.onTreble([](float level) { if (level > 0.01f) { sharedData.trebleLevel = level; // EXT_LOGD(ML_TAG, "onTreble: %f", level); } }); + audioProcessor.onPercussion([](fl::PercussionType type) { + EXT_LOGD(ML_TAG, "onPercussion: %d", type); + }); + } + + void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { + if (control["name"] == "signalConditioning") { + audioProcessor.setSignalConditioningEnabled(signalConditioning); + } + if (control["name"] == "autoGain") { + audioProcessor.setAutoGainEnabled(autoGain); + } + if (control["name"] == "noiseFloorTracking") { + audioProcessor.setNoiseFloorTrackingEnabled(noiseFloorTracking); + } + if (control["name"] == "channel" && oldValue != "") { // not on boot as readPins will do it then + // recreate with the new channel + stopAudio(); + startAudio(); + } + } + + uint8_t pinI2SSD = UINT8_MAX; + uint8_t pinI2SWS = UINT8_MAX; + uint8_t pinI2SSCK = UINT8_MAX; + + void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + + moduleIO->read( + [&](ModuleState& state) { + bool i2sPinsChanged = false; + for (JsonObject pinObject : state.data["pins"].as()) { + uint8_t usage = pinObject["usage"]; + switch (usage) { + case pin_I2S_SD: + if (pinI2SSD != pinObject["GPIO"]) { + pinI2SSD = pinObject["GPIO"]; + i2sPinsChanged = true; + } + break; + case pin_I2S_WS: + if (pinI2SWS != pinObject["GPIO"]) { + pinI2SWS = pinObject["GPIO"]; + i2sPinsChanged = true; + } + break; + case pin_I2S_SCK: + if (pinI2SSCK != pinObject["GPIO"]) { + pinI2SSCK = pinObject["GPIO"]; + i2sPinsChanged = true; + } + break; + } + } + if (i2sPinsChanged && pinI2SSD != UINT8_MAX && pinI2SWS != UINT8_MAX && pinI2SSCK != UINT8_MAX) { + EXT_LOGI(ML_TAG, "(re)creating audioInput)"); + + stopAudio(); + startAudio(); + } + }, + name()); } void loop20ms() override { @@ -91,11 +169,40 @@ class FastLEDAudioDriver : public Node { } } - ~FastLEDAudioDriver() override { + void startAudio() { + // Create configuration objects + i2sConfig = new fl::AudioConfigI2S(pinI2SWS, pinI2SSD, pinI2SSCK, 0, channel == 1 ? fl::Right : channel == 2 ? fl::Both : fl::Left, 44100, 16, fl::Philips); + + config = new fl::AudioConfig(*i2sConfig); + + fl::string errorMsg; + audioInput = fl::IAudioInput::create(*config, &errorMsg); + if (!audioInput) { + EXT_LOGE(ML_TAG, "Failed to create audio input: %s", errorMsg.c_str()); + return; + } + audioInput->start(); + } + + void stopAudio() { if (audioInput) { audioInput->stop(); + audioInput.reset(); // Explicitly release shared_ptr, even makes it a nullptr... + } + + // Clean up raw pointers + if (config) { + delete config; + config = nullptr; + } + + if (i2sConfig) { + delete i2sConfig; + i2sConfig = nullptr; } } + + ~FastLEDAudioDriver() override { stopAudio(); } }; #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h index 2b22144c..455de4ba 100644 --- a/src/MoonLight/Nodes/Effects/E_FastLED.h +++ b/src/MoonLight/Nodes/Effects/E_FastLED.h @@ -48,14 +48,16 @@ class FLAudioEffect : public Node { uint16_t hue = 0; uint8_t beatLevel = 0; float maxBass = 0; + float maxMid = 0; float maxTreble = 0; float maxVocal = 0; void loop() override { layer->fadeToBlackBy(70); - // maxLevels + // calculate max levels if (sharedData.bassLevel > maxBass) maxBass = sharedData.bassLevel; + if (sharedData.midLevel > maxMid) maxMid = sharedData.midLevel; if (sharedData.trebleLevel > maxTreble) maxTreble = sharedData.trebleLevel; if (sharedData.vocalConfidence > maxVocal) maxVocal = sharedData.vocalConfidence; @@ -63,17 +65,21 @@ class FLAudioEffect : public Node { // EXT_LOGD(ML_TAG, "%f-%d %f-%d %d-%d %f-%d", sharedData.bassLevel, bassLevel, sharedData.trebleLevel, trebleLevel, sharedData.beat, beatLevel, sharedData.vocalsActive ? sharedData.vocalConfidence : 0, vocalsLevel); - layer->drawLine(0, layer->size.y - 1, 0, layer->size.y - 1 - layer->size.y * sharedData.bassLevel / maxBass, CRGB::Blue); - layer->drawLine(1, layer->size.y - 1, 1, layer->size.y - 1 - layer->size.y * sharedData.trebleLevel / maxTreble, CRGB::Orange); - layer->drawLine(2, layer->size.y - 1, 2, layer->size.y - 1 - layer->size.y * sharedData.vocalConfidence / maxVocal, CRGB::Green); - layer->drawLine(3, layer->size.y - 1, 3, layer->size.y - 1 - layer->size.y * beatLevel / 255, CRGB::Red); + uint8_t columnNr = 0; + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * sharedData.bassLevel / maxBass, CRGB::Red); + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * sharedData.midLevel / maxMid, CRGB::Orange); + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * sharedData.trebleLevel / maxTreble, CRGB::Green); + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * sharedData.vocalConfidence / maxVocal, CRGB::Blue); + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * beatLevel / 255, CRGB::Purple); - //correct if lower output + // correct for lower output if (maxBass > 0) maxBass -= 0.01; + if (maxMid > 0) maxMid -= 0.01; if (maxTreble > 0) maxTreble -= 0.01; if (maxVocal > 0) maxVocal -= 0.01; - if (beatLevel) beatLevel--; + // beat delay + if (beatLevel) beatLevel -= MIN(255/layer->size.y, beatLevel); } };