Compare commits

...

7 Commits

Author SHA1 Message Date
Frank Möhle
09faf4c981 Merge branch 'main' into copilot/add-smooth-colour-palettes 2026-04-13 23:36:38 +02:00
Frank
55e88685b6 fix a few more AI-generated bugs
WTF, dude
2026-04-10 01:52:07 +02:00
Frank
bfb2b88559 fix some bugs, mark some leftover errors
* move  paletteBandAvg out of esp32-only section (fixes 8266 build error)
* style: move default case _below_ other cases
* add missing "generated by AI" labels
* mark some leftover bugs
2026-04-10 00:51:31 +02:00
Frank
9adbba6018 Merge branch 'main' into copilot/add-smooth-colour-palettes 2026-04-10 00:19:27 +02:00
copilot-swe-agent[bot]
746f8c1147 Improve documentation of new audio palettes per reviewer feedback
Agent-Logs-Url: https://github.com/wled/WLED/sessions/2dff17f8-2992-4060-8a37-5c72dd1fbf51

Co-authored-by: softhack007 <91616163+softhack007@users.noreply.github.com>
2026-04-09 17:12:35 +00:00
copilot-swe-agent[bot]
5390cfcf43 Add two new smoothed audio-reactive palettes (Track Character + Spectral Balance)
Agent-Logs-Url: https://github.com/wled/WLED/sessions/a11e5210-96ae-4713-abdd-ba42b0e33bc5

Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
2026-04-01 08:01:58 +00:00
copilot-swe-agent[bot]
779a82faaf Initial plan 2026-04-01 07:31:41 +00:00

View File

@@ -71,7 +71,7 @@
#define PLOT_PRINTF(x...)
#endif
#define MAX_PALETTES 3
#define MAX_PALETTES 5
static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks.
static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value)
@@ -98,6 +98,9 @@ static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same t
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
static float paletteBandAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Slowly smoothed band averages used only by audio palettes 3 & 4 (EMA, alpha=0.05 → ~400ms time constant at 20ms cycle)
static constexpr float PALETTE_SMOOTHING = 0.05f; // EMA smoothing factor for paletteBandAvg: 0.05 gives ~400ms time constant; increase for faster response, decrease for slower
// TODO: probably best not used by receive nodes
//static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255
@@ -1683,6 +1686,7 @@ class AudioReactive : public Usermod {
memset(fftCalc, 0, sizeof(fftCalc));
memset(fftAvg, 0, sizeof(fftAvg));
memset(fftResult, 0, sizeof(fftResult));
memset(paletteBandAvg, 0, sizeof(paletteBandAvg));
for(int i=(init?0:1); i<NUM_GEQ_CHANNELS; i+=2) fftResult[i] = 16; // make a tiny pattern
inputLevel = 128; // reset level slider to default
autoResetPeak();
@@ -1723,6 +1727,7 @@ class AudioReactive : public Usermod {
// reset sound data
volumeRaw = 0; volumeSmth = 0;
for(int i=(init?0:1); i<NUM_GEQ_CHANNELS; i+=2) fftResult[i] = 16; // make a tiny pattern
memset(paletteBandAvg, 0, sizeof(paletteBandAvg));
autoResetPeak();
if (init) {
if (udpSyncConnected) { // close UDP sync connection (if open)
@@ -2223,6 +2228,69 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) {
hsv = CHSV(fftResult[b], 255, map(fftResult[b], 0, 255, 30, 255)); // pick hue
value = hsv; // convert to R,G,B
break;
// AI: below section was generated by an AI
case 3: {
// "Track Character" palette (palette index 3)
// Uses the spectral centroid of paletteBandAvg[] to derive a single hue that
// reflects the tonal balance of the music over the past ~400ms: // softhack007 this statement is wrong
// low centroid (bass-heavy drop) → warm red/orange (hue ≈ 0)
// mid centroid (vocals/melody) → green/cyan (hue ≈ 80-120)
// high centroid (cymbals/bright synth) → blue/purple (hue ≈ 200)
// x (0-255) spreads palette positions ±30 hue units around that base hue.
static const float bandFreq[NUM_GEQ_CHANNELS] = { // approximate centre frequency (Hz) of each GEQ channel
65, 107, 172, 258, 365, 495, 689, 969,
1270, 1658, 2153, 2713, 3359, 4091, 5792, 8182
};
float wSum = 0, tEnergy = 0;
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
wSum += paletteBandAvg[i] * bandFreq[i]; // frequency-weighted energy
tEnergy += paletteBandAvg[i]; // total energy
}
// centroid = energy-weighted average frequency; default to 500 Hz when signal is silent
float centroid = (tEnergy > 1.0f) ? (wSum / tEnergy) : 500.0f;
// Map centroid to hue on a log scale (human pitch perception is logarithmic).
// log2(60 Hz) ≈ 5.9, log2(8000 Hz) ≈ 13.0 → hue range 0..200 (red → blue-purple)
float logC = log2f(constrain(centroid, 60.0f, 8000.0f)); // softhack007 ToDO: use logf() instead of log2f()
uint8_t baseHue = (uint8_t)mapf(logC, 5.9f, 13.0f, 0.0f, 200.0f); // mapf() cannot produce negative results due to previous constrain() --> safe to directly cast to uint8_t
int8_t hueSpread = map(x, 0, 255, -30, 30); // spread palette positions ±30 hue units // softhack007 ToDO: use CHSV32 with 16bit HUE
uint8_t saturation = (uint8_t)constrain((int)(tEnergy / 6.0f) + 180, 180, 255); // louder = more saturated // softhack007 WTF dude?
hsv = CHSV(baseHue + hueSpread, saturation, (uint8_t)constrain(x, 30, 255));
value = hsv;
break;
}
// AI: end
// AI: below section was generated by an AI
case 4: {
// "Spectral Balance" palette (palette index 4)
// Divides the spectrum into three broad bands and uses their energy ratio to derive hue:
// bass dominant (channels 0-3, ~43-301 Hz) → warm hue ≈ 20 (red/orange)
// mid dominant (channels 4-9, ~301-1895 Hz) → green hue ≈ 110 (green/cyan)
// high dominant (channels 10-15, ~1895-9259 Hz)→ cool hue ≈ 190 (blue/violet)
// x (0-255) spreads palette positions ±25 hue units around that weighted hue,
// giving a smooth colour band rather than a single flat colour.
float bassEnergy = 0, midEnergy = 0, highEnergy = 0;
for (int i = 0; i < 4; i++) bassEnergy += paletteBandAvg[i]; // sub-bass + bass
for (int i = 4; i < 10; i++) midEnergy += paletteBandAvg[i]; // midrange
for (int i = 10; i < 16; i++) highEnergy += paletteBandAvg[i]; // high-mid + high
float total = bassEnergy + midEnergy + highEnergy;
if (total < 1.0f) total = 1.0f; // avoid division by zero when silent
float bassRatio = bassEnergy / total; // fraction of energy in bass band
float midRatio = midEnergy / total;
float highRatio = highEnergy / total;
// Weighted hue: pure bass→20, pure mid→110, pure high→190
int weightedHue = roundf(bassRatio * 20.0f + midRatio * 110.0f + highRatio * 190.0f);
uint8_t hue = min(255, max(0, weightedHue)); // clamp to [0...255]
// Saturation: dominated spectrum (one band clearly wins) → high sat; balanced → lower sat
float maxRatio = max(bassRatio, max(midRatio, highRatio));
uint8_t sat = (uint8_t)constrain((int)(maxRatio * 255.0f * 1.5f), 180, 255); // softhack007 OMG, WTF?
int8_t hueOffset = map(x, 0, 255, -25, 25); // spread palette positions ±25 hue units // softhack007 ToDO: use CHSV32 with 16bit HUE
// brightness: minimum 30, boosted by overall loudness and palette position
uint8_t val = (uint8_t)constrain((int)(total / 8.0f) + (int)map(x, 0, 255, 30, 255), 30, 255);
hsv = CHSV(hue + hueOffset, sat, val);
value = hsv;
break;
}
// AI: end
default:
if (x == 1) {
value = CRGB(fftResult[10]/2, fftResult[4]/2, fftResult[0]/2);
@@ -2240,6 +2308,17 @@ void AudioReactive::fillAudioPalettes() {
if (!palettes) return;
size_t lastCustPalette = customPalettes.size();
if (int(lastCustPalette) >= palettes) lastCustPalette -= palettes;
// AI: below section was generated by an AI
// Update slowly-smoothed band averages used by palettes 3 & 4.
// Alpha=PALETTE_SMOOTHING gives ~400ms time constant at a 20ms update cycle,
// so palette colours reflect the overall tonal character of the music rather than
// reacting to individual beats (which would appear "twitchy").
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
paletteBandAvg[i] += PALETTE_SMOOTHING * ((float)fftResult[i] - paletteBandAvg[i]); // BUG: this IIR filter assumes 20ms activation rate (which is totally wrong)
}
// AI: end
for (int pal=0; pal<palettes; pal++) {
uint8_t tcp[16]; // Needs to be 4 times however many colors are being used.
// 3 colors = 12, 4 colors = 16, etc.