diff --git a/docs/develop/development.md b/docs/develop/development.md
index e33e7066b..c7f522259 100644
--- a/docs/develop/development.md
+++ b/docs/develop/development.md
@@ -89,12 +89,12 @@ Before merging:
For the most urgent items (blockers or high-risk changes), include a ready-to-paste
prompt that a Claude Code agent can execute immediately before merge.
-2. **End-user docs prompt** — a ready-to-paste prompt for a Claude Code agent to update
+2. **End-user docs prompt** — a ready-to-paste prompt for AI agents to update
`/docs`. Rules: only describe usage implications (what changed for the user);
no internals, no code, no architecture; check existing pages before adding —
update in place rather than duplicating; keep additions compact and user-friendly.
-3. **Developer docs prompt** — a ready-to-paste prompt for a Claude Code agent to update
+3. **Developer docs prompt** — a ready-to-paste prompt for AI agents to update
`/docs/develop`. Rules: target contributors, not end users; be concise — if the
detail is already in the code or commit messages, do not repeat it; focus on
decisions, patterns, and guidance that are not obvious from reading the source.
diff --git a/docs/develop/network.md b/docs/develop/network.md
index aeab466d8..5f95432fa 100644
--- a/docs/develop/network.md
+++ b/docs/develop/network.md
@@ -31,3 +31,24 @@ if (_connecting) {
#### 30-second timeout
If the IDF event loop drops both `STA_CONNECTED` and `STA_DISCONNECTED` (rare but observed on congested stacks), `_connecting` would stay `true` permanently and block all future reconnections. The 30-second timeout in the guard above is the escape hatch: after 30 s with no event, `_connecting` is reset with a `LOGW` and the next `manageSTA()` call retries normally.
+
+## ModuleDevices — UDP Port Segregation
+
+**Problem:** WLED devices listen on port 65506 for metadata/discovery. When MoonLight sent control commands (brightness, palette, preset) to the same port, WLED devices received and misinterpreted them, causing interference.
+
+**Solution:** Port segregation via two separate UDP sockets in `ModuleDevices` ([`src/MoonBase/Modules/ModuleDevices.h`](https://github.com/MoonModules/MoonLight/blob/main/src/MoonBase/Modules/ModuleDevices.h)):
+
+| Port | Socket | Direction | Purpose | WLED visibility |
+|------|--------|-----------|---------|-----------------|
+| **65506** | `deviceUDP` | Bidirectional | Device discovery & metadata broadcasts; shared with WLED | ✅ Sees WLED packets, WLED sees device info |
+| **65507** | `deviceControlUDP` | Outbound unicast | MoonLight-only control commands (from UI → device) | ❌ WLED cannot see |
+
+**Implementation:**
+
+- `loop10s()` initializes both sockets; control socket binding failures are logged.
+- `onUpdate()` sends targeted device control changes via `deviceControlUDP.beginPacket(..., 65507)` instead of `deviceUDP`.
+- `receiveUDP()` polls both sockets via a shared `processUDPMessage()` helper:
+ - **Port 65506 (discovery):** Use existing group/broadcast logic; also process device info from WLED.
+ - **Port 65507 (control):** Enforce `isControlCommand + hostname match` — only apply if message is addressed to this device.
+
+**Trade-off:** Adds a second socket (small memory cost, ~300 bytes stack per packet). Benefit: Clean isolation; no cross-vendor interference; WLED remains compatible.
diff --git a/docs/develop/nodes.md b/docs/develop/nodes.md
index a3420d136..39548939e 100644
--- a/docs/develop/nodes.md
+++ b/docs/develop/nodes.md
@@ -63,6 +63,39 @@ A node implements the following (overloaded) functions:
Node construction is routed through `LayerManager`: `ModuleEffects::addNode()` calls `layerMgr.getSelectedLayer()` and `layerP.ensureLayer(index)` to target the currently active layer — never hard-code `layers[0]` in a new module. Modules that host layers must also override `onNodeRemoved()` and `onBeforeStateLoad()` to delegate to `layerMgr.onNodeRemoved()` and `layerMgr.prepareForPresetLoad()` respectively; `ModuleEffects` is the reference implementation for both.
+## Shared Audio Data
+
+Audio effects access microphone or network audio data via shared global fields. Both **FastLED Audio** (`D_FastLEDAudio.h`) and **WLED Audio** (`D_WLEDAudio.h`) drivers populate the same fields — effects should not depend on which driver is active.
+
+### Audio fields and normalization
+
+| Field | Type | Range | Source | Notes |
+|-------|------|-------|--------|-------|
+| `bands[16]` | `uint8_t[]` | 0–255 | FastLED FFT or WLED FFT_Magnitude | 16-band graphic EQ; 0–3 (bass) to 12–15 (treble) |
+| `volume` | `float` | 0–255 | FastLED sampleAvg or WLED volumeSmth | Smoothed volume; direct use in effects |
+| `volumeRaw` | `uint8_t` | 0–255 | FastLED sampleRaw or WLED volumeRaw | Unsmoothed sample; for peak detection |
+| `majorPeak` | `float` | ~64–8183 Hz | FastLED getEqDominantFreqHz() or WLED FFT_MajorPeak | Dominant frequency; compare to bass/treble thresholds |
+| `magnitude` | `float` | 0–unbounded | FastLED or WLED FFT_Magnitude | Peak frequency strength; **no guaranteed upper bound** |
+
+### Using magnitude safely
+
+`magnitude` has no clamped upper bound from FastLED's FFT. Effects should clamp or normalize before using it as an intensity scalar or bar height:
+
+```cpp
+// Bad: magnitude can exceed 255 and overflow
+uint8_t barHeight = magnitude;
+
+// Good: clamp to 0–255 range
+uint8_t barHeight = (magnitude > 255) ? 255 : (uint8_t)magnitude;
+
+// Or: normalize to 0.0–1.0 before scaling
+float normalized = magnitude / 256.0f; // arbitrary normalisation
+```
+
+### Field synchronization
+
+Both drivers call the same `updateSharedAudioData()` function from their `loop()`. If neither driver is active, all fields remain at their previous value (do not auto-reset). Effects depending on audio should check driver presence via the Drivers module state before applying audio logic.
+
## Drivers
### Initless drivers
diff --git a/docs/moonbase/devices.md b/docs/moonbase/devices.md
index 1026acf04..71f9161c0 100644
--- a/docs/moonbase/devices.md
+++ b/docs/moonbase/devices.md
@@ -31,6 +31,7 @@ Part 1:
* If one of the controls is changed for a device in the devices overview, it sends a message to that device updating to update its controls
* Every module receives these messages and updates them in the devices overview
+**Network isolation:** MoonLight uses separate UDP ports for device discovery (65506) and control (65507). This keeps MoonLight control commands isolated from WLED devices on the same network, preventing interference while still allowing device discovery.
Part 2:
diff --git a/docs/moonlight/drivers.md b/docs/moonlight/drivers.md
index a1de8cbde..3c1a7c069 100644
--- a/docs/moonlight/drivers.md
+++ b/docs/moonlight/drivers.md
@@ -46,6 +46,10 @@ Custom layouts can also be created as **Live Scripts** — `.sc` files with an `
| IR Driver |
|
| Receive IR commands and [Lights Control](lightscontrol.md) |
| IMU Driver |
|
| Receive inertial data from an IMU / I2C peripheral, see [IO](../moonbase/inputoutput.md#i2c-peripherals)
Used in [particles effect](effects.md#moonlight-effects) |
+### Audio Driver Data
+
+Both **FastLED Audio** and **WLED Audio** drivers provide audio data to effects. In addition to volume and frequency bands (FFT), effects can now access **magnitude** — the strength of the dominant frequency peak. This enables more sophisticated audio-reactive visualizations. See [Effects](effects.md) for details on using audio data in effects.
+
### Light Preset
* **Max Power**: moved to [IO Module](../moonbase/inputoutput.md) board presets.
diff --git a/platformio.ini b/platformio.ini
index c34199acf..8ae76dda4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -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.9.1\" ; semver compatible version string
- -D APP_DATE=\"20260406\" ; 🌙
+ -D APP_DATE=\"20260411\" ; 🌙
-D PLATFORM_VERSION=\"pioarduino-55.03.37\" ; 🌙 make sure it matches with above plaftform
@@ -224,9 +224,10 @@ build_flags =
-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=\"20260406\"
+ -D FASTLED_BUILD=\"20260411\"
lib_deps =
- https://github.com/FastLED/FastLED#335a142c54124cf7e87e17db13c4a8e70bcc7dcd ; 20260406 band[0] fixed
+ https://github.com/FastLED/FastLED#c83f7632c14933dcf70012c25135fae42ce477ce ; 20260411
+ ; https://github.com/FastLED/FastLED#335a142c54124cf7e87e17db13c4a8e70bcc7dcd ; 20260406 band[0] fixed
; https://github.com/FastLED/FastLED#f72b5b6a0725368f2bccd0e58c8c911e74c439a1 ; 20260405 'FL_LOGE' was not declared in this scope; did you mean 'FL_E'?
; https://github.com/FastLED/FastLED#eca4af576737353e703866fa54d4c897cebb9208 ; 20260404 third party Yves link errors
; https://github.com/FastLED/FastLED#682179f8c6c4697c36c11c9c764f9a326ad758ce ; 20260331 ✅
@@ -269,22 +270,22 @@ lib_deps =
build_flags =
-D FT_LIVESCRIPT=1
lib_deps =
- ; https://github.com/hpwit/ESPLiveScript.git#39e9409 ; 1.3.2 / v4.2 15-02-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#3b1d1b6 ; v4.3 07-03-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#62ba8ac ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#afc1d6a ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#7286d31 ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#4074cb5 ; okay
- ; https://github.com/hpwit/ESPLiveScript.git#29ced05 ;
- ; https://github.com/hpwit/ESPLiveScript.git#7b97f74 ; okay
- ; https://github.com/hpwit/ESPLiveScript.git#7b52eca ; okay
- ; https://github.com/hpwit/ESPLiveScript.git#cccfc0b ; notokay
- ; https://github.com/hpwit/ESPLiveScript.git#4a0cb82 ; vjson 09-06-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#6f86b6e ; vjson 09-06-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/hpwit/ESPLiveScript.git#9f002b4ec450cd47f5d417bfaddd616dd764d1f3 ; vjson 16-06-2025 ; Comment if FT_LIVESCRIPT=0
- ; https://github.com/ewowi/ESPLiveScript.git#e02c2dccdd5044c9aa3edb874d26759afb0e1cb9 ; 02-11-2025 fix warnings
- ; https://github.com/ewowi/ESPLiveScript.git#bd364c3bf08af2fb3eb4c142c86268403b6ce7e6 ; 20260328 stack size argument and tasks in psram
https://github.com/ewowi/ESPLiveScript.git#48715099a9c82cc914efe576b70c7a7364f258ce ; 20260402 fix setRGB(random16(NUM_LEDS), CRGB(0, 0, 255));
+ ; https://github.com/ewowi/ESPLiveScript.git#bd364c3bf08af2fb3eb4c142c86268403b6ce7e6 ; 20260328 stack size argument and tasks in psram
+ ; https://github.com/ewowi/ESPLiveScript.git#e02c2dccdd5044c9aa3edb874d26759afb0e1cb9 ; 02-11-2025 fix warnings
+ ; https://github.com/hpwit/ESPLiveScript.git#9f002b4ec450cd47f5d417bfaddd616dd764d1f3 ; vjson 16-06-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#6f86b6e ; vjson 09-06-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#4a0cb82 ; vjson 09-06-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#cccfc0b ; notokay
+ ; https://github.com/hpwit/ESPLiveScript.git#7b52eca ; okay
+ ; https://github.com/hpwit/ESPLiveScript.git#7b97f74 ; okay
+ ; https://github.com/hpwit/ESPLiveScript.git#29ced05 ;
+ ; https://github.com/hpwit/ESPLiveScript.git#4074cb5 ; okay
+ ; https://github.com/hpwit/ESPLiveScript.git#7286d31 ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#afc1d6a ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#62ba8ac ; vjson 24-05-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#3b1d1b6 ; v4.3 07-03-2025 ; Comment if FT_LIVESCRIPT=0
+ ; https://github.com/hpwit/ESPLiveScript.git#39e9409 ; 1.3.2 / v4.2 15-02-2025 ; Comment if FT_LIVESCRIPT=0
;For all boards
[ESP32_LEDSDRIVER]
@@ -298,44 +299,51 @@ lib_deps =
[HP_ALL_DRIVERS]
build_flags =
-D HP_ALL_DRIVERS
- -D HP_ALL_BUILD=\"20260319\"
+ -D HP_ALL_BUILD=\"20260410\"
lib_deps =
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#5d5508ca38a15497392950d4249cd0d910c3505d
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#d8cdb31f8b0d52c0562eb5b0ce4723e26f0dc62f
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#6bf1400b2fece454bccc53a07bb84258c266b198 ; testint2, 20250804 10:14PM not working yet
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#a7d3dafd005d059a8fb680927567cdd37914ed01 ; dev 20250804 12:05PM cache region crash
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#4a6b4392206daaf56424f99627e4367b9d87af13 ; 20250804 3:53PM cache region crash
-
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#1616a33295b3009033bf47d30d0c88619997220a ; dev 20250801 4:10PM (before test int), working 54.03.21
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#716ecd218215804cbe14753480ef657ad8e96a74 ; dev 20250809 11:52: correct gamma bug
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#3dc6cea7b7e5c605f4303ff3c9ba79654bee4d85
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#9beeedb2d4ef45206b55731e07066e4775434c0f ; testint3, 20250828 2:50
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#17aaa565d3c8623de64191d83bf0747749aa3977 ; dev 20250920: DMA buffer variable and psram
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#f0d55a42f98b9b24f95c83b59c9f55ef83c8034e ; dev 20250921: DMA buffer variable and psram
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#eeb429d79b5b24facb5e8164e8588bdf526c6107 ; dev 20251007: prepare for ESP32-P4
- ; https://github.com/ewowi/I2SClocklessLedDriver.git#b8d9d55e47c6f8dacaa106dc3b122b7e412efea9 ; dev 20251016: uint8_t pins
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#e25ddeeb4c2f8c702f0a98ab3d47627dad05905a ; dev 20251017: no waπrnings
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#6af7c8ce35021afb251d44e119a7a3d74fd087e4 ; dev 20251019: .h/cpp + setShowDelay
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#90967a0ec5803c80b18041d8f9eb4f917f60182c ; dev 20251020: setGlobalNumStrips
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#8f0ed49eaf82730479c9e775df811fd8097dddd8 ; dev 20251021: updateDriver + deleteDriver + uint16_t lenghts
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#1c519f4e195118491933d9f0f0b514ead8f6b6aa
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#58fd98b3fb9d963c551fe65c3a90efa1d50a28b2 ; dev 20251025: initled and updateLeds with custom channels
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#d42695a1da3d3db3b0f99f5731248c696de57180 ; dev 20251025: strange behavior with initled with custom channels
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#3b030e760b8a933bf3bae4c66dce90bb6271a3b5 ; dev 20251026: no COLOR_ORDER_X (set explicitly)
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#5c234c1431f012750b77fee48966d87c5afa496f ; dev 20251120: sk6812 tweaks...
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#3dedc755effb10bd7aaadef068d110c8431ab60e ; dev 20251130: revert part of the sk6812 tweaks
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#131988ed9f997bc3059dc2af26e3e15a3013247f ; dev 20251231: use p_w for rgbw (https://github.com/hpwit/I2SClocklessLedDriver/pull/50)
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#4363655d3921fc4f6a40f13dd33d368e93d59ca3 ; dev 20260102: Add deleteDriver checks on DMABuffersTampon
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#0f15e79789846591dc2e68ff3d18fb21bac28b49 ; dev 20260118: Add extractWhiteFromRGB, total_leds 32 bits (allow > 65K LEDs)
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#052a84576eb6c136539b7547d50724674ed22ee8 ; dev 20260120: ESP32-D0: Remove ESP_INTR_FLAG_IRAM from esp_intr_alloc
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#4548eee0ada619679216793452af730c06107a1f ; dev 20260307: setGamma arguments in rgb order, sem wait log to 500ms, small fixes
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#a411023b3f7bfad727e5b2835719af39e56f3de1 ; dev 20260318: add RGBCCT
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#01f67930a1fee95a2b33e79ec108f3fbd28f3825 ; dev 20260318: add RGBCCT + mem mgmt
- ; https://github.com/hpwit/I2SClocklessLedDriver.git#0996b637b1770e8f8efc15d01c55f512e7d36033 ; dev 20260319: add RGBCCT + mem mgmt + fixes
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#a2901a51f9303c96db93c4834513d7f36991129c ; main phase 9 ✅ +++++
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#9c9c5acd3136db6cd7255a3038c79559e9f91cb1 ; main phase 9 + fixes + more fixes and more
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#35bd75389dc3042ad7fcd0e2ccccdd9853f1562f ; main phase 9 + fixes + more fixes
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#7402d8a434afdacdb139d7ecb1a7530066ba70ac ; main phase 9 + fixes
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#b4b2f57ae17f628982c3ad5e27aa4d7abfbc05da ; main phase 9
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#02086972ab5cf3e406fbb71661576690bc1a79b4 ; main phase 8
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#b07a4c47ff85fe888e565b080b7cf856aeca607c ; main phase 7
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#5b3699cdcb0e20304196b6ef366e180805358af8 ; main phase 6 ❌❌ [ 5458][W][I2SClocklessLedDriver.h:1343] showPixelsImpl(): [TAG] sem wait too long
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#375382ae95fd66ac227b58d517e917028cfe20f0 ; main phase 5 ✅✅
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#1ec96fffb58730708f93a94f5d237c99a7b98003 ; main phase 2 and 3 ✅
+ https://github.com/hpwit/I2SClocklessLedDriver.git#a736f5ecb1d71056521f8054295a8326908550d4 ; main 20260406: release 1.4
; https://github.com/hpwit/I2SClocklessLedDriver.git#54db938cce6943f149791fb0558fecd12682959a ; dev 20260406: linting applied
- https://github.com/hpwit/I2SClocklessLedDriver.git#a736f5ecb1d71056521f8054295a8326908550d4 ; dev 20260406: release 1.4
- ; https://github.com/hpwit/I2SClocklessLedDriver.git@1.4
- ; hpwit/I2SClocklessLedDriver@1.4
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#0996b637b1770e8f8efc15d01c55f512e7d36033 ; dev 20260319: add RGBCCT + mem mgmt + fixes
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#01f67930a1fee95a2b33e79ec108f3fbd28f3825 ; dev 20260318: add RGBCCT + mem mgmt
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#a411023b3f7bfad727e5b2835719af39e56f3de1 ; dev 20260318: add RGBCCT
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#4548eee0ada619679216793452af730c06107a1f ; dev 20260307: setGamma arguments in rgb order, sem wait log to 500ms, small fixes
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#052a84576eb6c136539b7547d50724674ed22ee8 ; dev 20260120: ESP32-D0: Remove ESP_INTR_FLAG_IRAM from esp_intr_alloc
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#0f15e79789846591dc2e68ff3d18fb21bac28b49 ; dev 20260118: Add extractWhiteFromRGB, total_leds 32 bits (allow > 65K LEDs)
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#4363655d3921fc4f6a40f13dd33d368e93d59ca3 ; dev 20260102: Add deleteDriver checks on DMABuffersTampon
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#131988ed9f997bc3059dc2af26e3e15a3013247f ; dev 20251231: use p_w for rgbw (https://github.com/hpwit/I2SClocklessLedDriver/pull/50)
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#3dedc755effb10bd7aaadef068d110c8431ab60e ; dev 20251130: revert part of the sk6812 tweaks
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#5c234c1431f012750b77fee48966d87c5afa496f ; dev 20251120: sk6812 tweaks...
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#3b030e760b8a933bf3bae4c66dce90bb6271a3b5 ; dev 20251026: no COLOR_ORDER_X (set explicitly)
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#d42695a1da3d3db3b0f99f5731248c696de57180 ; dev 20251025: strange behavior with initled with custom channels
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#58fd98b3fb9d963c551fe65c3a90efa1d50a28b2 ; dev 20251025: initled and updateLeds with custom channels
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#1c519f4e195118491933d9f0f0b514ead8f6b6aa
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#8f0ed49eaf82730479c9e775df811fd8097dddd8 ; dev 20251021: updateDriver + deleteDriver + uint16_t lenghts
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#90967a0ec5803c80b18041d8f9eb4f917f60182c ; dev 20251020: setGlobalNumStrips
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#6af7c8ce35021afb251d44e119a7a3d74fd087e4 ; dev 20251019: .h/cpp + setShowDelay
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#e25ddeeb4c2f8c702f0a98ab3d47627dad05905a ; dev 20251017: no waπrnings
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#b8d9d55e47c6f8dacaa106dc3b122b7e412efea9 ; dev 20251016: uint8_t pins
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#eeb429d79b5b24facb5e8164e8588bdf526c6107 ; dev 20251007: prepare for ESP32-P4
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#f0d55a42f98b9b24f95c83b59c9f55ef83c8034e ; dev 20250921: DMA buffer variable and psram
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#17aaa565d3c8623de64191d83bf0747749aa3977 ; dev 20250920: DMA buffer variable and psram
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#9beeedb2d4ef45206b55731e07066e4775434c0f ; testint3, 20250828 2:50
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#3dc6cea7b7e5c605f4303ff3c9ba79654bee4d85
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#716ecd218215804cbe14753480ef657ad8e96a74 ; dev 20250809 11:52: correct gamma bug
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#1616a33295b3009033bf47d30d0c88619997220a ; dev 20250801 4:10PM (before test int), working 54.03.21
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#4a6b4392206daaf56424f99627e4367b9d87af13 ; 20250804 3:53PM cache region crash
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#a7d3dafd005d059a8fb680927567cdd37914ed01 ; dev 20250804 12:05PM cache region crash
+ ; https://github.com/hpwit/I2SClocklessLedDriver.git#6bf1400b2fece454bccc53a07bb84258c266b198 ; testint2, 20250804 10:14PM not working yet
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#d8cdb31f8b0d52c0562eb5b0ce4723e26f0dc62f
+ ; https://github.com/ewowi/I2SClocklessLedDriver.git#5d5508ca38a15497392950d4249cd0d910c3505d
[HP_VIRTUAL_DRIVER]
build_flags =
diff --git a/src/MoonBase/Modules/ModuleDevices.h b/src/MoonBase/Modules/ModuleDevices.h
index ad13c0344..4508be17c 100644
--- a/src/MoonBase/Modules/ModuleDevices.h
+++ b/src/MoonBase/Modules/ModuleDevices.h
@@ -34,8 +34,10 @@ struct UDPMessage {
class ModuleDevices : public Module {
public:
- NetworkUDP deviceUDP;
- uint16_t deviceUDPPort = 65506;
+ NetworkUDP deviceUDP; // Listen for discovery messages from other devices (port 65506)
+ NetworkUDP deviceControlUDP; // Send control messages to other devices (port 65507)
+ uint16_t deviceUDPPort = 65506; // Discovery: receive WLED device info and MoonLight broadcasts
+ uint16_t deviceControlUDPPort = 65507; // Control: send control commands (isolated from WLED discovery)
bool deviceUDPConnected = false;
Module* _moduleControl;
@@ -105,11 +107,11 @@ class ModuleDevices : public Module {
EXT_LOGD(MB_TAG, "Applied UDP control from originator: bri=%d pal=%d preset=%d", message.brightness, message.palette, message.preset);
} else {
- // if a device is updated in the UI, send that update to that device
- if (deviceUDP.beginPacket(targetIP, deviceUDPPort)) {
- deviceUDP.write(reinterpret_cast(&message), sizeof(message));
- deviceUDP.endPacket();
- EXT_LOGD(MB_TAG, "UDP from %s update sent to ...%d / %s on=%d bri=%d pal=%d preset=%d", updatedItem.originId->c_str(), targetIP[3], message.name.c_str(), message.lightsOn, message.brightness, message.palette, message.preset);
+ // if a device is updated in the UI, send that update to that device via control port
+ if (deviceControlUDP.beginPacket(targetIP, deviceControlUDPPort)) {
+ deviceControlUDP.write(reinterpret_cast(&message), sizeof(message));
+ deviceControlUDP.endPacket();
+ EXT_LOGD(MB_TAG, "UDP control from %s update sent to ...%d / %s on=%d bri=%d pal=%d preset=%d", updatedItem.originId->c_str(), targetIP[3], message.name.c_str(), message.lightsOn, message.brightness, message.palette, message.preset);
// need to add the targetip?
}
}
@@ -131,7 +133,11 @@ class ModuleDevices : public Module {
if (!deviceUDPConnected) {
deviceUDPConnected = deviceUDP.begin(deviceUDPPort);
- EXT_LOGD(MB_TAG, "deviceUDPConnected %d i:%d p:%d", deviceUDPConnected, deviceUDP.remoteIP()[3], deviceUDPPort);
+ bool controlConnected = deviceControlUDP.begin(deviceControlUDPPort); // Initialize control UDP on separate port
+ EXT_LOGD(MB_TAG, "deviceUDPConnected %d i:%d p:%d, control on p:%d (%d)", deviceUDPConnected, deviceUDP.remoteIP()[3], deviceUDPPort, deviceControlUDPPort, controlConnected);
+ if (!controlConnected) {
+ EXT_LOGW(MB_TAG, "Failed to bind control UDP socket on port %d", deviceControlUDPPort);
+ }
}
if (!deviceUDPConnected) return;
@@ -234,40 +240,57 @@ class ModuleDevices : public Module {
// }
}
- void receiveUDP() {
- while (size_t packetSize = deviceUDP.parsePacket()) {
- if (packetSize < 38 || packetSize > sizeof(UDPMessage)) { // UDP message is smaller then 256 for the foreseable future
- EXT_LOGW(MB_TAG, "Invalid UDP packet size: %d (expected %d-%d)", packetSize, 38, sizeof(UDPMessage));
- deviceUDP.clear(); // Discard invalid packet (flush (deprecated) is now clear)
- continue;
- }
+ void processUDPMessage(NetworkUDP& udp, const UDPMessage& message, bool isControlPort) {
+ if (isControlPort) {
+ // Unicast control: only process if addressed to this device
+ if (!message.isControlCommand || esp32sveltekit.getSystemHostname() != message.name.c_str()) return;
- char buffer[sizeof(UDPMessage)];
- UDPMessage message{};
- deviceUDP.read(buffer, packetSize);
- memcpy(&message, buffer, packetSize);
+ JsonDocument doc;
+ JsonObject newState = doc.to();
+ messageToControlState(message, newState);
- // if a controlmessage is received (from another device), and this device is part of its group update this device.
- // this can be both a broadcast or a unicast (then partofgroup will also be true)
- if (packetSize == sizeof(UDPMessage) && message.isControlCommand && partOfGroup(esp32sveltekit.getSystemHostname(), message.name.c_str())) {
+ EXT_LOGD(MB_TAG, "UDP unicast control from %s : bri=%d pal=%d preset=%d", udp.remoteIP().toString().c_str(), message.brightness, message.palette, message.preset);
+ _moduleControl->update(newState, ModuleState::update, _moduleName);
+ } else {
+ // Broadcast discovery: handle group control or device update
+ if (message.isControlCommand && partOfGroup(esp32sveltekit.getSystemHostname(), message.name.c_str())) {
JsonDocument doc;
JsonObject newState = doc.to();
-
messageToControlState(message, newState);
EXT_LOGD(MB_TAG, "UDP control message from group via %s : bri=%d pal=%d preset=%d", message.name.c_str(), message.brightness, message.palette, message.preset);
if (esp32sveltekit.getSystemHostname() == message.name.c_str()) {
- _moduleControl->update(newState, ModuleState::update, _moduleName); // addUpdateHandler will send a message with control status
- } else
- _moduleControl->update(newState, ModuleState::update, "group"); // addUpdateHandler will send a message without control status (to avoid infinite loops)
- } else
+ _moduleControl->update(newState, ModuleState::update, _moduleName);
+ } else {
+ _moduleControl->update(newState, ModuleState::update, "group");
+ }
+ } else {
+ updateDevices(message, udp.remoteIP());
+ }
+ }
+ }
+
+ void receiveUDP() {
+ // Parse and process UDP packets on both ports
+ auto processUdpSocket = [this](NetworkUDP& udp, bool isControlPort) {
+ while (size_t packetSize = udp.parsePacket()) {
+ if (packetSize < 38 || packetSize > sizeof(UDPMessage)) {
+ EXT_LOGW(MB_TAG, "Invalid UDP packet size: %d (expected %d-%d)", packetSize, 38, sizeof(UDPMessage));
+ udp.clear();
+ continue;
+ }
- // EXT_LOGD(MB_TAG, "UDP message %s : bri=%d pal=%d preset=%d control:%d", message.name.c_str(), message.brightness, message.palette, message.preset, message.isControlCommand);
+ char buffer[sizeof(UDPMessage)];
+ UDPMessage message{};
+ udp.read(buffer, packetSize);
+ memcpy(&message, buffer, packetSize);
+ processUDPMessage(udp, message, isControlPort);
+ }
+ };
- // also update if control command as it can be another device
- updateDevices(message, deviceUDP.remoteIP());
- }
+ processUdpSocket(deviceUDP, false); // Discovery port (65506)
+ processUdpSocket(deviceControlUDP, true); // Control port (65507)
}
// from addUpdateHandler (!group) and loop10s (false)
diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h
index 2d5375db4..1cdf9061c 100644
--- a/src/MoonBase/Nodes.h
+++ b/src/MoonBase/Nodes.h
@@ -178,6 +178,45 @@ struct SharedData {
float volume; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
int16_t volumeRaw;
float majorPeak; // FFT: strongest (peak) frequency
+ float magnitude; // FFT: strongest (peak) frequency
+
+ // ┌───────────────┬─────────────────────┬────────────────────────────────────────────┬──────────────┐
+ // │ Variable │ Range │ Use Case │ Priority │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ volumeSmth │ 0.0–255.0 │ Overall volume/intensity responsiveness │ 🔴 Essential │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ sampleAvg │ 0.0–255.0 │ Smoothed volume (simpler, no AGC) │ 🟡 Secondary │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ sampleAgc │ 0.0–255.0 │ AGC-adjusted volume (responds to gain) │ 🟡 Secondary │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ FFT_MajorPeak │ 1.0–11025.0 Hz │ Dominant frequency (bass/treble detection) │ 🟡 Secondary │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ FFT_Magnitude │ 0.0–4096.0 │ Strength of peak frequency │ 🟡 Secondary │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ fftResult[16] │ 0–255 (per channel) │ 16-band graphic EQ │ 🟡 Secondary │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ samplePeak │ true/false │ Beat/peak detection │ 🟢 Optional │
+ // ├───────────────┼─────────────────────┼────────────────────────────────────────────┼──────────────┤
+ // │ soundAgc │ 0–3 │ AGC mode (for debugging) │ 🟢 Optional │
+ // └───────────────┴─────────────────────┴────────────────────────────────────────────┴──────────────┘
+
+ // Recommended Approach:
+
+ // 1. For intensity-based effects (brightness scaling):
+ // - Use volumeSmth — automatically handles AGC mode internally
+ // - Falls back gracefully if audio not available
+ // 2. For frequency-reactive effects (spectrum analyzers, bass boosters):
+ // - Primary: FFT_MajorPeak + FFT_Magnitude for single-band response
+ // - Advanced: fftResult[16] for 16-band EQ visualization
+ // 3. For beat detection:
+ // - Use samplePeak flag with debounce logic (resets automatically)
+ // - Only if effect has beat-sync features
+
+ // Value Normalization Tips:
+
+ // - Normalize volumeSmth by dividing by 255 to get 0.0–1.0 float for effect calculations
+ // - FFT_MajorPeak is already in Hz — compare directly to frequency thresholds (e.g., < 200 Hz for bass, > 2000 Hz for treble)
+ // - fftResult[16] channels: 0–3 (bass), 4–8 (mid), 9–15 (treble) — useful for visual effect zones
// used in scrollingtext
uint16_t fps;
diff --git a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h
index bf17c6190..a0179f0a2 100644
--- a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h
+++ b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h
@@ -14,9 +14,9 @@
#if FT_MOONLIGHT
#include "fl/audio/audio.h"
- #include "fl/audio/input.h"
#include "fl/audio/audio_processor.h"
#include "fl/audio/detector/equalizer.h"
+ #include "fl/audio/input.h"
// #include "fl/time_alpha.h"
// https://github.com/FastLED/FastLED/blob/master/src/fl/audio/README.md
@@ -37,12 +37,12 @@ class FastLEDAudioDriver : public Node {
fl::audio::Processor audioProcessor;
- bool signalConditioning = false; // if true nothng is displayed ...
+ bool signalConditioning = false; // if true nothng is displayed ...
// bool autoGain = false;
bool noiseFloorTracking = false;
uint8_t channel = (uint8_t)fl::audio::AudioChannel::Left;
uint8_t gain = 128;
- bool drainBuffer = false; // if false 60 fps. otherwise 40 fps
+ bool drainBuffer = false; // if false 60 fps. otherwise 40 fps
Char<32> status = "No pins";
void setup() override {
@@ -212,9 +212,13 @@ class FastLEDAudioDriver : public Node {
for (int i = 0; i < 16; ++i) {
sharedData.bands[i] = static_cast(audioProcessor.getEqBin(i) * 255);
}
- const float norm = (audioProcessor.getEqVolumeNormFactor() > 0.000001f) ? audioProcessor.getEqVolumeNormFactor() : 1.0f;
- sharedData.volume = audioProcessor.getEqVolume() / norm;
- sharedData.volumeRaw = static_cast(sharedData.volume * 32767.0f);
+ // Volume system overhaul — now 0.0–1.0 normalized: https://github.com/FastLED/FastLED/issues/2193#issuecomment-4192711473
+ // const float norm = (audioProcessor.getEqVolumeNormFactor() > 0.000001f) ? audioProcessor.getEqVolumeNormFactor() : 1.0f;
+ sharedData.volume = audioProcessor.getEqVolume() * 255.0;// normalised volume ( * 255 * 2560.0f; // WLED correction!)
+ sharedData.volumeRaw = (int16_t)sharedData.volume;
+ // sharedData.volumeRaw = audioProcessor.getEqVolumeDb() * 255;
+ sharedData.majorPeak = audioProcessor.getEqDominantFreqHz();
+ sharedData.magnitude = audioProcessor.getEqDominantMagnitude(); // * 4096.0; // 4096 is WLED max
sharedData.fl_bassLevel = audioProcessor.getEqBass();
sharedData.fl_midLevel = audioProcessor.getEqMid();
diff --git a/src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h b/src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h
index 714114def..6ab17fbc3 100644
--- a/src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h
+++ b/src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h
@@ -121,7 +121,7 @@ class ParallelLEDDriver : public DriverNode {
uint8_t savedBrightness = ledsDriver.brightness; //(initLed sets it to 255 and thats not what we want)
EXT_LOGD(ML_TAG, "init Parallel LED Driver %d %d %d %d %d", layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite);
- ledsDriver.initled(layerP.lights.channelsD, pins, layerP.ledsPerPin, nrOfPins, layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite2, true); // 🌙 offsetWhite2 for RGBCCT warm white
+ ledsDriver.initled(layerP.lights.channelsD, pins, layerP.ledsPerPin, nrOfPins, layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite2, true); // 🌙 offsetWhite2 for RGBCCT warm white, extractWhiteFromRGB=true
ledsDriver.setBrightness(savedBrightness); //(initLed sets it to 255 and thats not what we want)
@@ -134,7 +134,8 @@ class ParallelLEDDriver : public DriverNode {
// don't call initled again as that will crash because if channelsPerLight (nb_components) change, the dma buffers are not big enough
EXT_LOGD(ML_TAG, "update Parallel LED Driver %d %d %d %d %d", layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite);
- ledsDriver.updateDriver(pins, layerP.ledsPerPin, nrOfPins, dmaBuffer, layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite2); // 🌙 offsetWhite2 for RGBCCT warm white
+ // ledsDriver.updateDriver(pins, layerP.ledsPerPin, nrOfPins, dmaBuffer, layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite2, true); // 🌙 offsetWhite2 for RGBCCT warm white and extract rgbw
+ ledsDriver.updateDriver(pins, layerP.ledsPerPin, nrOfPins, dmaBuffer, layerP.lights.header.channelsPerLight, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetRed, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetGreen, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetBlue, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite, layerP.lights.header.offsetRGBW + layerP.lights.header.offsetWhite2); // 🌙 offsetWhite2 for RGBCCT warm white and extract rgbw
}
#else // P4: Parlio Troy Driver
diff --git a/src/MoonLight/Nodes/Drivers/D_WLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_WLEDAudio.h
index 14fe783b7..f8fffaa18 100644
--- a/src/MoonLight/Nodes/Drivers/D_WLEDAudio.h
+++ b/src/MoonLight/Nodes/Drivers/D_WLEDAudio.h
@@ -50,6 +50,7 @@ class WLEDAudioDriver : public Node {
sharedData.volume = 0;
sharedData.volumeRaw = 0;
sharedData.majorPeak = 0;
+ sharedData.magnitude = 0;
init = false;
EXT_LOGI(ML_TAG, "WLED Audio Sync: stopped");
}
@@ -76,6 +77,7 @@ class WLEDAudioDriver : public Node {
sharedData.volume = sync.volumeSmth;
sharedData.volumeRaw = sync.volumeRaw;
sharedData.majorPeak = sync.FFT_MajorPeak;
+ sharedData.magnitude = sync.FFT_Magnitude;
moduleControl->read(
[&](const ModuleState& state) {
diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h
index e63412e40..452dcda2c 100644
--- a/src/MoonLight/Nodes/Effects/E_FastLED.h
+++ b/src/MoonLight/Nodes/Effects/E_FastLED.h
@@ -85,7 +85,13 @@ class FLAudioEffect : public Node {
layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.fl_trebleLevel, CRGB::Green);
columnNr++;
- layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.volume, CRGB::Yellow);
+ layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.volume / 255.0, CRGB::Yellow);
+ columnNr++;
+ layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.volumeRaw / 255.0, CRGB::Yellow);
+ columnNr++;
+ layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.majorPeak / 11025.0, CRGB::Yellow);
+ columnNr++;
+ layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.magnitude, CRGB::Yellow);
columnNr++;
// Normalize BPM to 0-1 range (assuming typical range 60-200 BPM)
float normalizedBpm = constrain(sharedData.fl_bpm, 60.0f, 200.0f);