Skip to content

Commit 06326b0

Browse files
committed
Add audioreactive color palettes (Ratio, Hue, Spectrum)
Port the getCRGBForBand palette generation logic from the upstream WLED usermods/audioreactive fork, adding an opt-in 'add-palettes' setting that registers three audio-reactive color palettes with the core palette system and refreshes them each loop.
1 parent 171c0bb commit 06326b0

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

‎audio_reactive.h‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ static bool audioSyncBroadcast = false; // if true, use subnet
145145
static bool udpSyncConnected = false; // UDP connection status -> true if connected to UDP sync
146146

147147
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
148+
#define MAX_PALETTES 3 // number of audioreactive palettes provided by this usermod
148149

149150
// audioreactive variables
150151
#ifdef ARDUINO_ARCH_ESP32
@@ -1205,6 +1206,8 @@ class AudioReactive : public Usermod {
12051206
bool enabled = false;
12061207
#endif
12071208
bool initDone = false;
1209+
bool addPalettes = false;
1210+
int8_t palettes = 0;
12081211

12091212
// variables for UDP sound sync
12101213
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
@@ -1254,8 +1257,16 @@ class AudioReactive : public Usermod {
12541257
static const char _digitalmic[];
12551258
static const char UDP_SYNC_HEADER[];
12561259
static const char UDP_SYNC_HEADER_v1[];
1260+
static const char _addPalettes[];
1261+
static const char _palName0[];
1262+
static const char _palName1[];
1263+
static const char _palName2[];
12571264

12581265
// private methods
1266+
void removeAudioPalettes(void);
1267+
void createAudioPalettes(void);
1268+
CRGB getCRGBForBand(int x, int pal);
1269+
void fillAudioPalettes(void);
12591270

12601271
////////////////////
12611272
// Debug support //
@@ -2150,6 +2161,7 @@ class AudioReactive : public Usermod {
21502161
receivedFormat = 0;
21512162
delay(100);
21522163
if (enabled) connectUDPSoundSync();
2164+
if (enabled && addPalettes) createAudioPalettes();
21532165
initDone = true;
21542166
DEBUGSR_PRINT(F("AR: init done, enabled = "));
21552167
DEBUGSR_PRINTLN(enabled ? F("true.") : F("false."));
@@ -2456,6 +2468,8 @@ class AudioReactive : public Usermod {
24562468
lastTime = millis();
24572469
}
24582470
#endif
2471+
2472+
fillAudioPalettes();
24592473
}
24602474

24612475
#if defined(_MoonModules_WLED_) && defined(WLEDMM_FASTPATH)
@@ -2800,6 +2814,11 @@ class AudioReactive : public Usermod {
28002814
if (usermod[FPSTR(_enabled)].is<bool>()) {
28012815
enabled = usermod[FPSTR(_enabled)].as<bool>();
28022816
if (prevEnabled != enabled) onUpdateBegin(!enabled);
2817+
if (addPalettes) {
2818+
// add/remove custom/audioreactive palettes
2819+
if (prevEnabled && !enabled) removeAudioPalettes();
2820+
if (!prevEnabled && enabled) createAudioPalettes();
2821+
}
28032822
}
28042823
#ifdef ARDUINO_ARCH_ESP32
28052824
if (usermod[FPSTR(_inputLvl)].is<int>()) {
@@ -2809,6 +2828,13 @@ class AudioReactive : public Usermod {
28092828
}
28102829
}
28112830

2831+
void onStateChange(uint8_t callMode) {
2832+
if (initDone && enabled && addPalettes && palettes==0) {
2833+
// if palettes were removed during JSON call re-add them
2834+
createAudioPalettes();
2835+
}
2836+
}
2837+
28122838

28132839
/*
28142840
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
@@ -2848,6 +2874,7 @@ class AudioReactive : public Usermod {
28482874
void addToConfig(JsonObject& root) override {
28492875
JsonObject top = root.createNestedObject(FPSTR(_name));
28502876
top[FPSTR(_enabled)] = enabled;
2877+
top[FPSTR(_addPalettes)] = addPalettes;
28512878
#ifdef ARDUINO_ARCH_ESP32
28522879
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
28532880
JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
@@ -2920,6 +2947,7 @@ class AudioReactive : public Usermod {
29202947
bool readFromConfig(JsonObject& root) override {
29212948
JsonObject top = root[FPSTR(_name)];
29222949
bool configComplete = !top.isNull();
2950+
bool oldAddPalettes = addPalettes;
29232951

29242952
#ifdef ARDUINO_ARCH_ESP32
29252953
// remember previous values
@@ -2932,6 +2960,7 @@ class AudioReactive : public Usermod {
29322960
#endif
29332961

29342962
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
2963+
configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes);
29352964
#ifdef ARDUINO_ARCH_ESP32
29362965
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
29372966
configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin);
@@ -2985,6 +3014,14 @@ class AudioReactive : public Usermod {
29853014
configComplete &= getJsonValue(top["sync"][F("check_sequence")], audioSyncSequence);
29863015
configComplete &= getJsonValue(top["sync"][F("broadcast")], audioSyncBroadcast);
29873016

3017+
#ifdef ARDUINO_ARCH_ESP32
3018+
if (initDone) {
3019+
// add/remove custom/audioreactive palettes
3020+
if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes();
3021+
if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes();
3022+
}
3023+
#endif
3024+
29883025
// WLEDMM notify user when a reboot is necessary
29893026
#ifdef ARDUINO_ARCH_ESP32
29903027
if (initDone) {
@@ -3289,6 +3326,90 @@ class AudioReactive : public Usermod {
32893326
}
32903327
};
32913328

3329+
void AudioReactive::removeAudioPalettes(void) {
3330+
DEBUG_PRINTLN(F("Removing audio palettes."));
3331+
palettes -= (int8_t)removeUsermodPalettes(_name);
3332+
if (palettes < 0) palettes = 0; // safeguard
3333+
}
3334+
3335+
void AudioReactive::createAudioPalettes(void) {
3336+
if (palettes) return;
3337+
DEBUG_PRINTLN(F("Adding audio palettes."));
3338+
static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2};
3339+
for (int i=0; i<MAX_PALETTES; i++) {
3340+
if (usermodPalettes.size() < WLED_MAX_USERMOD_PALETTES) {
3341+
usermodPalettes.push_back({CRGBPalette16(CRGB(BLACK)), _name, (uint8_t)i, palNames[i]}); // start black, filled each loop by fillAudioPalettes()
3342+
palettes++;
3343+
DEBUG_PRINTLN(palettes);
3344+
} else break;
3345+
}
3346+
}
3347+
3348+
// credit @netmindz ar palette, adapted for usermod @blazoncek
3349+
CRGB AudioReactive::getCRGBForBand(int x, int pal) {
3350+
CRGB value;
3351+
CHSV hsv;
3352+
int b;
3353+
switch (pal) {
3354+
case 2:
3355+
b = map(x, 0, 255, 0, NUM_GEQ_CHANNELS/2); // convert palette position to lower half of freq band
3356+
hsv = CHSV(fftResult[b], 255, x);
3357+
value = hsv; // convert to R,G,B
3358+
break;
3359+
case 1:
3360+
b = map(x, 1, 255, 0, 10); // convert palette position to lower half of freq band
3361+
hsv = CHSV(fftResult[b], 255, map(fftResult[b], 0, 255, 30, 255)); // pick hue
3362+
value = hsv; // convert to R,G,B
3363+
break;
3364+
default:
3365+
if (x == 1) {
3366+
value = CRGB(fftResult[10]/2, fftResult[4]/2, fftResult[0]/2);
3367+
} else if(x == 255) {
3368+
value = CRGB(fftResult[10]/2, fftResult[0]/2, fftResult[4]/2);
3369+
} else {
3370+
value = CRGB(fftResult[0]/2, fftResult[4]/2, fftResult[10]/2);
3371+
}
3372+
break;
3373+
}
3374+
return value;
3375+
}
3376+
3377+
void AudioReactive::fillAudioPalettes() {
3378+
if (!palettes) return;
3379+
// Scan by name pointer identity to find the palettes we added, palIndex = 0/1/2... selects the getCRGBForBand variant, matching how the entries were created.
3380+
for (auto &ump : usermodPalettes) {
3381+
if (ump.name != _name) continue;
3382+
const int pal = ump.palIndex;
3383+
uint8_t tcp[16]; // Needs to be 4 times however many colors are being used.
3384+
// 3 colors = 12, 4 colors = 16, etc.
3385+
3386+
tcp[0] = 0; // anchor of first color - must be zero
3387+
tcp[1] = 0;
3388+
tcp[2] = 0;
3389+
tcp[3] = 0;
3390+
3391+
CRGB rgb = getCRGBForBand(1, pal);
3392+
tcp[4] = 1; // anchor of first color
3393+
tcp[5] = rgb.r;
3394+
tcp[6] = rgb.g;
3395+
tcp[7] = rgb.b;
3396+
3397+
rgb = getCRGBForBand(128, pal);
3398+
tcp[8] = 128;
3399+
tcp[9] = rgb.r;
3400+
tcp[10] = rgb.g;
3401+
tcp[11] = rgb.b;
3402+
3403+
rgb = getCRGBForBand(255, pal);
3404+
tcp[12] = 255; // anchor of last color - must be 255
3405+
tcp[13] = rgb.r;
3406+
tcp[14] = rgb.g;
3407+
tcp[15] = rgb.b;
3408+
3409+
ump.palette.loadDynamicGradientPalette(tcp);
3410+
}
3411+
}
3412+
32923413
// strings to reduce flash memory usage (used more than twice)
32933414
const char AudioReactive::_name[] PROGMEM = "AudioReactive";
32943415
const char AudioReactive::_enabled[] PROGMEM = "enabled";
@@ -3299,3 +3420,7 @@ const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
32993420
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
33003421
const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure
33013422
const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature
3423+
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
3424+
const char AudioReactive::_palName0[] PROGMEM = "Ratio";
3425+
const char AudioReactive::_palName1[] PROGMEM = "Hue";
3426+
const char AudioReactive::_palName2[] PROGMEM = "Spectrum";

0 commit comments

Comments
 (0)