Skip to content
Merged
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
1 change: 1 addition & 0 deletions interface/src/lib/components/UpdateIndicator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
// check if the asset is of type *.bin
if (
results.assets[i].name.includes('.bin') &&
!results.assets[i].name.includes('webflash') && // 🌙
results.assets[i].name.includes(page.data.features.firmware_built_target)
) {
update = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
// check if the asset is of type *.bin
if (
release.assets[i].name.includes('.bin') &&
!release.assets[i].name.includes('webflash') && // 🌙
release.assets[i].name.includes(page.data.features.firmware_built_target)
) {
url = release.assets[i].browser_download_url;
Expand Down
25,227 changes: 12,614 additions & 12,613 deletions lib/framework/WWWData.h

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ build_flags =
-D BUILD_TARGET=\"$PIOENV\"
-D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
-D APP_VERSION=\"0.8.1\" ; semver compatible version string
-D APP_DATE=\"20260221\" ; 🌙
-D APP_DATE=\"20260224\" ; 🌙

-D PLATFORM_VERSION=\"pioarduino-55.03.37\" ; 🌙 make sure it matches with above plaftform

Expand Down Expand Up @@ -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=\"20260224\"
lib_deps =
https://github.com/FastLED/FastLED#9d0b0eb9b5e59e4093982e0c2bdcfdff72ca80cb ; master 20260221
https://github.com/FastLED/FastLED#56d49b14796fa14e4fcc3f394256ae0bbe509b65 ; master 20260224
https://github.com/ewowi/WLED-sync#25f280b5e8e47e49a95282d0b78a5ce5301af4fe ; sourceIP + fftUdp.clear() if arduino >=3 (20251104)

; 💫 currently only enabled on s3 as esp32dev runs over 100%
Expand Down
28 changes: 27 additions & 1 deletion src/MoonBase/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class Module : public StatefulService<ModuleState> {

// run in sveltekit task
virtual void loop() {}
virtual void loop20ms() { // any Module that overrides loop20ms() must continue to call Module::loop20ms()
virtual void loop20ms() { // any Module that overrides loop20ms() must continue to call Module::loop20ms()
if (requestUIUpdate) {
requestUIUpdate = false; // reset the flag
EXT_LOGD(ML_TAG, "requestUIUpdate %s", _moduleName);
Expand Down Expand Up @@ -156,6 +156,32 @@ class Module : public StatefulService<ModuleState> {
virtual void onUpdate(const UpdatedItem& updatedItem, const String& originId) {};
virtual void onReOrderSwap(uint8_t stateIndex, uint8_t newIndex) {};

bool updatePin(uint8_t& pin, const uint8_t pinUsage, bool checkOut = false) {
uint8_t oldPin = pin;
pin = UINT8_MAX; // Assume deleted until found

read(
[&](ModuleState& state) {
for (JsonObject pinObject : state.data["pins"].as<JsonArray>()) {
uint8_t gpio = pinObject["GPIO"];
if (GPIO_IS_VALID_GPIO(gpio) && gpio < GPIO_PIN_COUNT && (!checkOut || GPIO_IS_VALID_OUTPUT_GPIO(gpio))) {
if (pinObject["usage"] == pinUsage && pin != gpio) {
pin = gpio;
break;
}
} else
EXT_LOGW(MB_TAG, "Pin %d (u:%d) not valid (o:%d)", gpio, pinUsage, checkOut);
}
},
_moduleName);
if (pin != oldPin) {
return true;
} else {
pin = oldPin; // set the original value
return false;
}
}

protected:
EventSocket* _socket;

Expand Down
4 changes: 2 additions & 2 deletions src/MoonBase/Modules/ModuleIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ModuleIO : public Module {

rows = control["n"].to<JsonArray>();
{
control = addControl(rows, "GPIO", "number", 0, SOC_GPIO_PIN_COUNT - 1, true); // ro
control = addControl(rows, "GPIO", "number", 0, GPIO_PIN_COUNT - 1, true); // ro

control = addControl(rows, "usage", "select");
control["default"] = 0;
Expand Down Expand Up @@ -272,7 +272,7 @@ class ModuleIO : public Module {
PinAssigner pinAssigner(pins);

// reset all pins
for (int gpio_num = 0; gpio_num < SOC_GPIO_PIN_COUNT; gpio_num++) {
for (int gpio_num = 0; gpio_num < GPIO_PIN_COUNT; gpio_num++) {
JsonObject pin = pins.add<JsonObject>();
pin["GPIO"] = gpio_num;
pin["usage"] = 0;
Expand Down
17 changes: 14 additions & 3 deletions src/MoonBase/Nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class LiveScriptNode : public Node {
// layout
void onLayout() override; // call map in LiveScript

~LiveScriptNode();
~LiveScriptNode() override;

// LiveScript functions
void compileAndRun();
Expand Down Expand Up @@ -346,6 +346,16 @@ static struct SharedData {
size_t clientListSize;

Coord3D gravity;

// FastLED Audio
bool vocalsActive = false;
float vocalConfidence = 0.0f;
float bassLevel = 0.0f;
float midLevel = 0.0f;
float trebleLevel = 0.0f;
bool beat = false;
uint8_t percussionType = UINT8_MAX;

} sharedData;

/**
Expand All @@ -359,10 +369,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_FastLED.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"
Expand Down
9 changes: 6 additions & 3 deletions src/MoonLight/Modules/ModuleDrivers.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,12 @@ class ModuleDrivers : public NodeManager {
layerP.requestMapVirtual = true;
},
_moduleName);
} // readPins
} // readPins

void begin() override {
defaultNodeName = ""; // getNameAndTags<PanelLayout>();
nodes = &layerP.nodes;
NodeManager::begin();

}

void addNodes(const JsonObject& control) override {
Expand All @@ -97,10 +96,12 @@ class ModuleDrivers : public NodeManager {
addControlValue(control, getNameAndTags<SpiralLayout>());
addControlValue(control, getNameAndTags<SingleRowLayout>());
addControlValue(control, getNameAndTags<SingleColumnLayout>());
addControlValue(control, getNameAndTags<TubesLayout>());

// Drivers, Most used first
addControlValue(control, getNameAndTags<ParallelLEDDriver>());
addControlValue(control, getNameAndTags<FastLEDDriver>());
addControlValue(control, getNameAndTags<FastLEDAudioDriver>());
addControlValue(control, getNameAndTags<ArtNetInDriver>());
addControlValue(control, getNameAndTags<ArtNetOutDriver>());
addControlValue(control, getNameAndTags<AudioSyncDriver>());
Expand Down Expand Up @@ -135,10 +136,12 @@ class ModuleDrivers : public NodeManager {
if (!node) node = checkAndAlloc<TorontoBarGourdsLayout>(name);
if (!node) node = checkAndAlloc<SingleRowLayout>(name);
if (!node) node = checkAndAlloc<SingleColumnLayout>(name);
if (!node) node = checkAndAlloc<TubesLayout>(name);

// Drivers most used first
if (!node) node = checkAndAlloc<ParallelLEDDriver>(name);
if (!node) node = checkAndAlloc<FastLEDDriver>(name);
if (!node) node = checkAndAlloc<FastLEDAudioDriver>(name);
if (!node) node = checkAndAlloc<ArtNetInDriver>(name);
if (!node) node = checkAndAlloc<ArtNetOutDriver>(name);
if (!node) node = checkAndAlloc<AudioSyncDriver>(name);
Expand Down Expand Up @@ -171,7 +174,7 @@ class ModuleDrivers : public NodeManager {
node->moduleIO = _moduleIO; // to get pin allocations
node->moduleNodes = (Module*)this; // to request UI update
node->setup(); // run the setup of the effect
node->onSizeChanged(Coord3D()); // to init memory allocations
node->onSizeChanged(Coord3D()); // to init memory allocations
// layers[0]->nodes.reserve(index+1);

// from here it runs concurrently in the drivers task
Expand Down
2 changes: 2 additions & 0 deletions src/MoonLight/Modules/ModuleEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class ModuleEffects : public NodeManager {

// FastLED effects
addControlValue(control, getNameAndTags<RainbowEffect>());
addControlValue(control, getNameAndTags<FLAudioEffect>());

// Moving head effects, alphabetically
addControlValue(control, getNameAndTags<AmbientMoveEffect>());
Expand Down Expand Up @@ -291,6 +292,7 @@ class ModuleEffects : public NodeManager {

// FastLED
if (!node) node = checkAndAlloc<RainbowEffect>(name);
if (!node) node = checkAndAlloc<FLAudioEffect>(name);

// Moving head effects, alphabetically

Expand Down
191 changes: 191 additions & 0 deletions src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
@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"

// 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<fl::IAudioInput> audioInput;

public:
static const char* name() { return "FastLED Audio"; }
static uint8_t dim() { return _NoD; }
static const char* tags() { return "☸️"; }

fl::AudioProcessor audioProcessor;

bool signalConditioning = true;
bool autoGain = false;
bool noiseFloorTracking = false;
uint8_t channel = fl::Left;

void setup() override {
addControl(signalConditioning, "signalConditioning", "checkbox");
addControl(autoGain, "autoGain", "checkbox");
addControl(noiseFloorTracking, "noiseFloorTracking", "checkbox");
addControl(channel, "channel", "select");
addControlValue("Left");
addControlValue("Right");
addControlValue("Both");

ioUpdateHandler = moduleIO->addUpdateHandler([this](const String& originId) { readPins(); });
readPins(); // Node added at runtime so initial IO update not received so run explicitly

audioProcessor.onBeat([]() {
sharedData.beat = true;
// EXT_LOGD(ML_TAG, "onBeat");
});

audioProcessor.onVocalStart([]() {
sharedData.vocalsActive = true;
// EXT_LOGD(ML_TAG, "onVocalStart");
});

audioProcessor.onVocalEnd([]() {
sharedData.vocalsActive = false;
// EXT_LOGD(ML_TAG, "onVocalEnd");
});

audioProcessor.onVocalConfidence([](float confidence) {
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, "onBass: %f", level);
}
});

audioProcessor.onMid([](float level) {
if (level > 0.01f) {
sharedData.midLevel = level;
// EXT_LOGD(ML_TAG, "onMid: %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);
sharedData.percussionType = (uint8_t)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
stopService();
startService();
}
}

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;
}

bool changed = moduleIO->updatePin(pinI2SWS, pin_I2S_WS);
changed = moduleIO->updatePin(pinI2SSD, pin_I2S_SD) || changed;
changed = moduleIO->updatePin(pinI2SSCK, pin_I2S_SCK) || changed;

if (changed) {
EXT_LOGI(ML_TAG, "(re)creating audioInput %d %d %d", pinI2SWS, pinI2SSD, pinI2SSCK);
stopService();
if (pinI2SWS != UINT8_MAX && pinI2SSD != UINT8_MAX && pinI2SSCK != UINT8_MAX) startService();
}
}

void loop() override {
if (!audioInput) return;

sharedData.beat = false;
sharedData.percussionType = UINT8_MAX;

while (fl::AudioSample sample = audioInput->read()) {
audioProcessor.update(sample);
}
}

void startService() {
// 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 stopService() {
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 {
stopService();
moduleIO->removeUpdateHandler(ioUpdateHandler);
}

private:
update_handler_id_t ioUpdateHandler;
};

#endif
Loading