diff --git a/platformio.ini b/platformio.ini index 7f6bbd63..3df87564 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,6 +34,12 @@ lib_deps = https://github.com/dawidchyrzynski/arduino-home-assistant.git#2.0.0 https://github.com/knolleary/pubsubclient.git#v2.8 +[usermod_wledaudio] +build_flags = + -D USERMOD_WLEDAUDIO +lib_deps = + https://github.com/netmindz/WLED-sync#v0.14.0.b16 + [env:esp32dev] platform = espressif32 board = esp32dev @@ -45,11 +51,13 @@ build_flags = ${appmod_leds.build_flags} ${usermod_e131.build_flags} ; ${usermod_ha.build_flags} + ${usermod_wledaudio.build_flags} lib_deps = ${starmod.lib_deps} ${appmod_leds.lib_deps} ${usermod_e131.lib_deps} ; ${usermod_ha.lib_deps} + ${usermod_wledaudio.lib_deps} ; RAM: [== ] 15.6% (used 51124 bytes from 327680 bytes) ; Flash: [======= ] 68.1% (used 892033 bytes from 1310720 bytes) diff --git a/src/App/AppEffects.h b/src/App/AppEffects.h index 2fb28f6b..d11eadbc 100644 --- a/src/App/AppEffects.h +++ b/src/App/AppEffects.h @@ -7,9 +7,17 @@ @Copyright (c) 2023 Github StarMod Commit Authors @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 */ +#ifdef USERMOD_WLEDAUDIO + #include "User/UserModWLEDAudio.h" +#endif +#ifdef USERMOD_E131 + #include "../User/UserModE131.h" +#endif + static uint8_t gHue = 0; // rotating "base color" used by many of the patterns static unsigned long call = 0; +static unsigned long step = 0; //should not contain variables/bytes to keep mem as small as possible!! class Effect { @@ -444,4 +452,194 @@ class BouncingBalls1D: public Effect { } }; // DistortionWaves2D +class RingEffect:public Effect { + protected: + CRGBPalette16 palette = PartyColors_p; + bool INWARD; // TODO: param + uint8_t hue[9]; // TODO: needs to match LedsV::nrOfLedsV + + void setRing(int ring, CRGB colour) { + ledsV[ring] = colour; + } + +}; + +class RingRandomFlow:public RingEffect { +public: + const char * name() { + return "RingRandomFlow 1D"; + } + void setup() {} + void loop() { + hue[0] = random(0, 255); + for (int r = 0; r < LedsV::nrOfLedsV; r++) { + setRing(r, CHSV(hue[r], 255, 255)); + } + for (int r = (LedsV::nrOfLedsV - 1); r >= 1; r--) { + hue[r] = hue[(r - 1)]; // set this ruing based on the inner + } + // FastLED.delay(SPEED); + } +}; + + +#ifdef USERMOD_WLEDAUDIO + +class GEQEffect:public Effect { +public: + byte previousBarHeight[1024]; + + const char * name() { + return "GEQ 2D"; + } + + void setup() { + fadeToBlackBy( ledsP, LedsV::nrOfLedsP, 16); + for (int i=0; ifftResults; + #ifdef SR_DEBUG + uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; + #endif + + uint8_t speed = mdl->getValue("speed"); + uint8_t intensity = mdl->getValue("intensity"); + bool colorBars = mdl->getValue("colorBars"); + bool smoothBars = mdl->getValue("smoothBars"); + + bool rippleTime = false; + if (millis() - step >= (256U - intensity)) { + step = millis(); + rippleTime = true; + } + + int fadeoutDelay = (256 - speed) / 64; + if ((fadeoutDelay <= 1 ) || ((call % fadeoutDelay) == 0)) fadeToBlackBy( ledsP, LedsV::nrOfLedsP, speed); + + uint16_t lastBandHeight = 0; // WLEDMM: for smoothing out bars + + //WLEDMM: evenly ditribut bands + float bandwidth = (float)cols / NUM_BANDS; + float remaining = bandwidth; + uint8_t band = 0; + for (int x=0; x < cols; x++) { + //WLEDMM if not enough remaining + if (remaining < 1) {band++; remaining+= bandwidth;} //increase remaining but keep the current remaining + remaining--; //consume remaining + + // Serial.printf("x %d b %d n %d w %f %f\n", x, band, NUM_BANDS, bandwidth, remaining); + uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. + // frBand = constrain(frBand, 0, 15); //WLEDMM can never be out of bounds (I think...) + uint16_t colorIndex = frBand * 17; //WLEDMM 0.255 + uint16_t bandHeight = fftResult[frBand]; // WLEDMM we use the original ffResult, to preserve accuracy + + // WLEDMM begin - smooth out bars + if ((x > 0) && (x < (cols-1)) && (smoothBars)) { + // get height of next (right side) bar + uint8_t nextband = (remaining < 1)? band +1: band; + nextband = constrain(nextband, 0, 15); // just to be sure + frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. + uint16_t nextBandHeight = fftResult[frBand]; + // smooth Band height + bandHeight = (7*bandHeight + 3*lastBandHeight + 3*nextBandHeight) / 12; // yeees, its 12 not 13 (10% amplification) + bandHeight = constrain(bandHeight, 0, 255); // remove potential over/underflows + colorIndex = map(x, 0, cols-1, 0, 255); //WLEDMM + } + lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration + uint16_t barHeight = map(bandHeight, 0, 255, 0, rows); // Now we map bandHeight to barHeight. do not subtract -1 from rows here + // WLEDMM end + + if (barHeight > rows) barHeight = rows; // WLEDMM map() can "overshoot" due to rounding errors + if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up + + CRGB ledColor = CRGB::Black; + for (int y=0; y < barHeight; y++) { + if (colorBars) //color_vertical / color bars toggle + colorIndex = map(y, 0, rows-1, 0, 255); + + CRGBPalette16 palette = PartyColors_p; + ledColor = ColorFromPalette(palette, (uint8_t)colorIndex); + + ledsV.setPixelColor(x + LedsV::widthV * (rows-1 - y), ledColor); + } + + if ((intensity < 255) && (previousBarHeight[x] > 0) && (previousBarHeight[x] < rows)) // WLEDMM avoid "overshooting" into other segments + ledsV.setPixelColor(x + LedsV::widthV * (rows - previousBarHeight[x]), ledColor); + + if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect + + } + } + + bool controls(JsonObject parentVar) { + ui->initNumber(parentVar, "speed", 255, false); + ui->initNumber(parentVar, "intensity", 255, false); + ui->initCheckBox(parentVar, "colorBars", false, false); // + ui->initCheckBox(parentVar, "smoothBars", false, false); + + // Nice an effect can register it's own DMX channel, but not a fan of repeating the range and type of the param + + e131mod->patchChannel(3, "speed", 255); // TODO: add constant for name + e131mod->patchChannel(4, "intensity", 255); + + return true; + } +}; + +class AudioRings:public RingEffect { + private: + uint8_t *fftResult = wledAudioMod->fftResults; + + public: + const char * name() { + return "AudioRings 1D"; + } + void setup() {} + + void setRingFromFtt(int index, int ring) { + uint8_t val = fftResult[index]; + // Visualize leds to the beat + CRGB color = ColorFromPalette(palette, val, 255); + color.nscale8_video(val); + setRing(ring, color); + } + + + void loop() { + for (int i = 0; i < 7; i++) { // 7 rings + + uint8_t val; + if(INWARD) { + val = fftResult[(i*2)]; + } + else { + int b = 14 -(i*2); + val = fftResult[b]; + } + + // Visualize leds to the beat + CRGB color = ColorFromPalette(palette, val, val); + // CRGB color = ColorFromPalette(currentPalette, val, 255, currentBlending); + // color.nscale8_video(val); + setRing(i, color); + // setRingFromFtt((i * 2), i); + } + + setRingFromFtt(2, 7); // set outer ring to bass + setRingFromFtt(0, 8); // set outer ring to bass + + } +}; + + +#endif // End Audio Effects + static std::vector effects; \ No newline at end of file diff --git a/src/App/AppModLeds.h b/src/App/AppModLeds.h index af1556f5..02199424 100644 --- a/src/App/AppModLeds.h +++ b/src/App/AppModLeds.h @@ -195,13 +195,18 @@ class AppModLeds:public Module { effects.push_back(new Lines2D); effects.push_back(new DistortionWaves2D); effects.push_back(new BouncingBalls1D); + effects.push_back(new RingRandomFlow); +#ifdef USERMOD_WLEDAUDIO + effects.push_back(new GEQEffect); + effects.push_back(new AudioRings); +#endif #ifdef USERMOD_E131 - e131mod->addWatch(1, "bri", 256); - e131mod->addWatch(2, "fx", effects.size()); + e131mod->patchChannel(1, "bri", 255); //should be 256?? + e131mod->patchChannel(2, "fx", effects.size()); // //add these temporary to test remote changing of this values do not crash the system - // e131mod->addWatch(3, "projection", Projections::count); - // e131mod->addWatch(4, "ledFix", 5); //assuming 5!!! + // e131mod->patchChannel(3, "projection", Projections::count); + // e131mod->patchChannel(4, "ledFix", 5); //assuming 5!!! #endif print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed"); diff --git a/src/User/UserModE131.h b/src/User/UserModE131.h index 8d8085c5..776317b4 100644 --- a/src/User/UserModE131.h +++ b/src/User/UserModE131.h @@ -109,11 +109,22 @@ class UserModE131:public Module { } } - void addWatch(uint8_t channel, const char * id, uint16_t max) { + void patchChannel(uint8_t channel, const char * id, uint8_t max = 255) { varsToWatch[channel].id = id; + varsToWatch[channel].savedValue = 0; // Always reset when (re)patching so variable gets set to DMX value even if unchanged varsToWatch[channel].max = max; } + // uint8_t getValue(const char * id) { + // for (int i=0; i < maxChannels; i++) { + // if(varsToWatch[i].id == id) { + // return varsToWatch[i].savedValue; + // } + // } + // print->print("ERROR: failed to find param %s\n", id); + // return 0; + // } + private: ESPAsyncE131 e131; boolean e131Created = false; diff --git a/src/User/UserModWLEDAudio.h b/src/User/UserModWLEDAudio.h new file mode 100644 index 00000000..df428976 --- /dev/null +++ b/src/User/UserModWLEDAudio.h @@ -0,0 +1,65 @@ +/* + @title StarMod + @file UserModWLEDAudio.h + @date 20230810 + @repo https://github.com/ewowi/StarMod + @Authors https://github.com/ewowi/StarMod/commits/main + @Copyright (c) 2023 Github StarMod Commit Authors + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 +*/ + +#pragma once + +#include // https://github.com/netmindz/WLED-sync + +class UserModWLEDAudio:public Module { + +public: + + uint8_t fftResults[NUM_GEQ_CHANNELS]= {0}; + + UserModWLEDAudio() :Module("WLED Audio Sync Receiver") { + print->print("%s %s\n", __PRETTY_FUNCTION__, name); + + isEnabled = false; //default off + + print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed"); + }; + + //setup filesystem + void setup() { + Module::setup(); + print->print("%s %s\n", __PRETTY_FUNCTION__, name); + + print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed"); + } + + void onOffChanged() { + if (SysModModules::isConnected && isEnabled) { + print->print("%s %s\n", __PRETTY_FUNCTION__, name); + sync.begin(); + } else { + // sync.end();??? + } + } + + void loop(){ + // Module::loop(); + if (sync.read()) { + if(debug) print->print("WLED-Sync: "); + for (int b = 0; b < NUM_GEQ_CHANNELS; b++) { + uint8_t val = sync.fftResult[b]; + fftResults[b] = val; + if(debug) print->print("%u ", val); + } + if(debug) print->print("\n"); + } + } + + private: + WLEDSync sync; + boolean debug = false; + +}; + +static UserModWLEDAudio *wledAudioMod; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d2546440..8badbcb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,6 +39,9 @@ #ifdef USERMOD_HA #include "User/UserModHA.h" #endif +#ifdef USERMOD_WLEDAUDIO + #include "User/UserModWLEDAudio.h" +#endif //setup all modules void setup() { @@ -69,6 +72,9 @@ void setup() { #ifdef USERMOD_HA hamod = new UserModHA(); #endif + #ifdef USERMOD_WLEDAUDIO + wledAudioMod = new UserModWLEDAudio(); + #endif //prefered default order in the UI. //Reorder with care! If changed make sure mdlEnabled.chFun executes var.createNestedArray("value"); and saveModel! @@ -100,6 +106,9 @@ void setup() { #endif mdls->add(mdl); mdls->add(ui); + #ifdef USERMOD_WLEDAUDIO + mdls->add(wledAudioMod); + #endif //do not add mdls itself as it does setup and loop for itself!!! (it is the orchestrator) mdls->setup();