From cf3901149bcd801b94ff7faaf79dec4bfb5d4466 Mon Sep 17 00:00:00 2001 From: Ewoud Date: Fri, 8 Mar 2024 13:10:34 +0100 Subject: [PATCH] Add RainEffect LedsLed: add blur1D LedEffects: reorder effects and add RainEffect (1D version) --- src/App/LedEffects.h | 1302 ++++++++++++++++++++++------------------ src/App/LedFixture.cpp | 14 +- src/App/LedLeds.h | 19 + 3 files changed, 746 insertions(+), 589 deletions(-) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index 855fea59..5f50eb75 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -138,33 +138,6 @@ class SinelonEffect: public Effect { } }; //Sinelon -//https://www.perfectcircuit.com/signal/difference-between-waveforms -class RunningEffect: public Effect { -public: - const char * name() {return "Running";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’ซ";} - - void loop(Leds &leds) { - // a colored dot sweeping back and forth, with fading trails - leds.fadeToBlackBy(mdl->getValue("fade").as()); //physical leds - int pos = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.nrOfLeds-1 ); //instead of call%leds.nrOfLeds - // int pos2 = map(beat16( mdl->getValue("BPM").as(), 1000), 0, UINT16_MAX, 0, leds.nrOfLeds-1 ); //one second later - leds[pos] = CHSV( gHue, 255, 192); //make sure the right physical leds get their value - // leds[leds.nrOfLeds -1 - pos2] = CHSV( gHue, 255, 192); //make sure the right physical leds get their value - } - void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "BPM", 60, 0, 255, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun - case f_UIFun: - ui->setComment(var, "in BPM!"); - return true; - default: return false; - }}); - //tbd: check if memory is freed! - ui->initSlider(parentVar, "fade", 128); - } -}; - class ConfettiEffect: public Effect { public: const char * name() {return "Confetti";} @@ -217,143 +190,569 @@ class JuggleEffect: public Effect { } }; -class RipplesEffect: public Effect { +//https://www.perfectcircuit.com/signal/difference-between-waveforms +class RunningEffect: public Effect { public: - const char * name() {return "Ripples";} - unsigned8 dim() {return _3D;} + const char * name() {return "Running";} + unsigned8 dim() {return _1D;} const char * tags() {return "๐Ÿ’ซ";} void loop(Leds &leds) { - stackUnsigned8 interval = mdl->getValue("interval"); - stackUnsigned8 speed = mdl->getValue("speed"); + // a colored dot sweeping back and forth, with fading trails + leds.fadeToBlackBy(mdl->getValue("fade").as()); //physical leds + int pos = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.nrOfLeds-1 ); //instead of call%leds.nrOfLeds + // int pos2 = map(beat16( mdl->getValue("BPM").as(), 1000), 0, UINT16_MAX, 0, leds.nrOfLeds-1 ); //one second later + leds[pos] = CHSV( gHue, 255, 192); //make sure the right physical leds get their value + // leds[leds.nrOfLeds -1 - pos2] = CHSV( gHue, 255, 192); //make sure the right physical leds get their value + } + void controls(JsonObject parentVar) { + ui->initSlider(parentVar, "BPM", 60, 0, 255, false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { //varFun + case f_UIFun: + ui->setComment(var, "in BPM!"); + return true; + default: return false; + }}); + //tbd: check if memory is freed! + ui->initSlider(parentVar, "fade", 128); + } +}; - float ripple_interval = 1.3f * (interval/128.0f); +class RingEffect:public Effect { + protected: - leds.fill_solid(CRGB::Black); + void setRing(Leds &leds, int ring, CRGB colour) { //so britisch ;-) + leds[ring] = colour; + } - Coord3D pos = {0,0,0}; - for (pos.z=0; pos.zdistance(3.5f, 3.5f, 0.0f, (float)pos.x, (float)pos.z, 0.0f) / 9.899495f * leds.size.x; - stackUnsigned32 time_interval = now/(100 - speed)/((256.0f-128.0f)/20.0f); - pos.x = floor(leds.size.x/2.0f + sinf(d/ripple_interval + time_interval) * leds.size.x/2.0f); //between 0 and leds.size.x +}; - leds[pos] = CHSV( gHue + random8(64), 200, 255);// ColorFromPalette(pal,call, bri, LINEARBLEND); - } +class RingRandomFlow:public RingEffect { +public: + const char * name() {return "RingRandomFlow";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’ซ";} + + void loop(Leds &leds) { + stackUnsigned8 *hue = leds.sharedData.bind(hue, leds.nrOfLeds); //array + + hue[0] = random(0, 255); + for (int r = 0; r < leds.nrOfLeds; r++) { + setRing(leds, r, CHSV(hue[r], 255, 255)); } - } - void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "speed", 50, 0, 99); - ui->initSlider(parentVar, "interval", 128); + for (int r = (leds.nrOfLeds - 1); r >= 1; r--) { + hue[r] = hue[(r - 1)]; // set this ruing based on the inner + } + // FastLED.delay(SPEED); } }; -class SphereMoveEffect: public Effect { +//BouncingBalls inspired by WLED +#define maxNumBalls 16 +//each needs 12 bytes +typedef struct Ball { + unsigned long lastBounceTime; + float impactVelocity; + float height; +} ball; + +class BouncingBalls: public Effect { public: - const char * name() {return "SphereMove";} - unsigned8 dim() {return _3D;} - const char * tags() {return "๐Ÿ’ซ";} + const char * name() {return "Bouncing Balls";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’ก";} void loop(Leds &leds) { - stackUnsigned8 speed = mdl->getValue("speed"); + stackUnsigned8 grav = mdl->getValue("gravity"); + stackUnsigned8 numBalls = mdl->getValue("balls"); + CRGBPalette16 pal = getPalette(); + + Ball *balls = leds.sharedData.bind(balls, maxNumBalls); //array leds.fill_solid(CRGB::Black); - stackUnsigned32 time_interval = now/(100 - speed)/((256.0f-128.0f)/20.0f); + // non-chosen color is a random color + const float gravity = -9.81f; // standard value of gravity + // const bool hasCol2 = SEGCOLOR(2); + const unsigned long time = now; - Coord3D origin; - origin.x = 3.5f+sinf(time_interval)*2.5f; - origin.y = 3.5f+cosf(time_interval)*2.5f; - origin.z = 3.5f+cosf(time_interval)*2.0f; + //not necessary as sharedData is cleared at setup(Leds &leds) + // if (call == 0) { + // for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; + // } - float diameter = 2.0f+sinf(time_interval/3.0f); + for (size_t i = 0; i < numBalls; i++) { + float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-grav)/64 +1); + float timeSec = timeSinceLastBounce/1000.0f; + balls[i].height = (0.5f * gravity * timeSec + balls[i].impactVelocity) * timeSec; // avoid use pow(x, 2) - its extremely slow ! - // CRGBPalette256 pal; - Coord3D pos; - for (pos.x=0; pos.xdistance(pos.x, pos.y, pos.z, origin.x, origin.y, origin.z); + if (balls[i].height <= 0.0f) { + balls[i].height = 0.0f; + //damping for better effect using multiple balls + float dampening = 0.9f - float(i)/float(numBalls * numBalls); // avoid use pow(x, 2) - its extremely slow ! + balls[i].impactVelocity = dampening * balls[i].impactVelocity; + balls[i].lastBounceTime = time; - if (d>diameter && d 1.0f) { + continue; // do not draw OOB ball + } + + // stackUnsigned32 color = SEGCOLOR(0); + // if (SEGMENT.palette) { + // color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); + // } + // else if (hasCol2) { + // color = SEGCOLOR(i % NUM_COLORS); + // } + + int pos = roundf(balls[i].height * (leds.nrOfLeds - 1)); + + CRGB color = ColorFromPalette(pal, i*(256/max(numBalls, (stackUnsigned8)8)), 255); + + leds[pos] = color; + // if (leds.nrOfLeds<32) leds.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index + // else leds.setPixelColor(balls[i].height + (stripNr+1)*10.0f, color); + } //balls } + void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "speed", 50, 0, 99); + addPalette(parentVar, 4); + ui->initSlider(parentVar, "gravity", 128); + ui->initSlider(parentVar, "balls", 8, 1, 16); } -}; // SphereMove3DEffect +}; // BouncingBalls + +void mode_fireworks(Leds &leds, stackUnsigned16 *aux0, stackUnsigned16 *aux1, uint8_t speed, uint8_t intensity, CRGBPalette16 pal, bool useAudio = false) { + // fade_out(0); + leds.fadeToBlackBy(10); + // if (SEGENV.call == 0) { + // *aux0 = UINT16_MAX; + // *aux1 = UINT16_MAX; + // } + bool valid1 = (*aux0 < leds.nrOfLeds); + bool valid2 = (*aux1 < leds.nrOfLeds); + CRGB sv1 = 0, sv2 = 0; + if (valid1) sv1 = leds.getPixelColor(*aux0); + if (valid2) sv2 = leds.getPixelColor(*aux1); + + // WLEDSR + uint8_t blurAmount = 255 - speed; // make parameter explicit + uint8_t my_intensity = 129 - (intensity >> 1); + bool addPixels = true; // false -> inhibit new pixels in silence + int soundColor = -1; // -1 = random color; 0..255 = use as palette index + + // if (useAudio) { + // if (FFT_MajorPeak < 100) { blurAmount = 254;} // big blobs + // else { + // if (FFT_MajorPeak > 3200) { blurAmount = 1;} // small blobs + // else { // blur + color depends on major frequency + // float musicIndex = logf(FFT_MajorPeak); // log scaling of peak freq + // blurAmount = mapff(musicIndex, 4.60, 8.08, 253, 1);// map to blur range (low freq = more blur) + // blurAmount = constrain(blurAmount, 1, 253); // remove possible "overshot" results + // soundColor = mapff(musicIndex, 4.6, 8.08, 0, 255); // pick color from frequency + // } } + // if (sampleAgc <= 1.0) { // silence -> no new pixels, just blur + // valid1 = valid2 = false; // do not copy last pixels + // addPixels = false; + // blurAmount = 128; + // } + // my_intensity = 129 - (SEGMENT.speed >> 1); // dirty hack: use "speed" slider value intensity (no idea how to _disable_ the first slider, but show the second one) + // if (samplePeak == 1) my_intensity -= my_intensity / 4; // inclease intensity at peaks + // if (samplePeak > 1) my_intensity = my_intensity / 2; // double intensity at main peaks + // } + // // WLEDSR end + + leds.blur1d(blurAmount); + if (valid1) leds.setPixelColor(*aux0, sv1); + if (valid2) leds.setPixelColor(*aux1, sv2); + + if (addPixels) { // WLEDSR + for(uint16_t i=0; igetValue("BPM"); - stackUnsigned8 intensity = mdl->getValue("intensity"); CRGBPalette16 pal = getPalette(); + uint8_t speed = mdl->getValue("speed"); + uint8_t intensity = mdl->getValue("intensity"); - for (int i = 8; i > 0; i--) { - Coord3D pos = {0,0,0}; - pos.x = beatsin8(bpm/8 + i, 0, leds.size.x - 1); - pos.y = beatsin8(intensity/8 - i, 0, leds.size.y - 1); - CRGB color = ColorFromPalette(pal, beatsin8(12, 0, 255), 255); - leds[pos] = color; + stackUnsigned16 *aux0 = leds.sharedData.bind(aux0); + stackUnsigned16 *aux1 = leds.sharedData.bind(aux1); + stackUnsigned8 *step = leds.sharedData.bind(step); + + // if(SEGENV.call == 0) { + // SEGMENT.fill(BLACK); + // } + *step += 1000 / 40;// FRAMETIME; + if (*step > (5U + (50U*(255U - speed))/leds.nrOfLeds)) { //SPEED_FORMULA_L) { + *step = 1; + // if (strip.isMatrix) { + // //uint32_t ctemp[leds.size.x]; + // //for (int i = 0; i= leds.size.x*leds.size.y) *aux0 = 0; // ignore + if (*aux1 >= leds.size.x*leds.size.y) *aux1 = 0; } - leds.blur2d(mdl->getValue("blur")); + mode_fireworks(leds, aux0, aux1, speed, intensity, pal); } - void controls(JsonObject parentVar) { addPalette(parentVar, 4); - ui->initSlider(parentVar, "BPM", 60); - ui->initSlider(parentVar, "intensity", 128); - ui->initSlider(parentVar, "blur", 128); + ui->initSlider(parentVar, "speed", 128, 1, 255); + ui->initSlider(parentVar, "intensity", 128,1,255); } -}; // Frizzles2D +}; // ExampleEffect -class Lines: public Effect { +class AudioRings:public RingEffect { public: - const char * name() {return "Lines";} - unsigned8 dim() {return _2D;} - const char * tags() {return "๐Ÿ’ซ";} + const char * name() {return "AudioRings";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’ซโ™ซ";} void loop(Leds &leds) { - leds.fadeToBlackBy(100); - - Coord3D pos = {0,0,0}; - if (mdl->getValue("Vertical").as()) { - pos.x = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.size.x-1 ); //instead of call%width + CRGBPalette16 pal = getPalette(); + for (int i = 0; i < 7; i++) { // 7 rings - for (pos.y = 0; pos.y < leds.size.y; pos.y++) { - leds[pos] = CHSV( gHue, 255, 192); + byte val; + if(mdl->getValue("inWards").as()) { + val = wledAudioMod->fftResults[(i*2)]; } - } else { - pos.y = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.size.y-1 ); //instead of call%height - for (pos.x = 0; pos.x < leds.size.x; pos.x++) { - leds[pos] = CHSV( gHue, 255, 192); + else { + int b = 14 -(i*2); + val = wledAudioMod->fftResults[b]; } + + // Visualize leds to the beat + CRGB color = ColorFromPalette(pal, val, val); +// CRGB color = ColorFromPalette(currentPalette, val, 255, currentBlending); +// color.nscale8_video(val); + setRing(leds, i, color); +// setRingFromFtt((i * 2), i); } + + setRingFromFtt(leds, pal, 2, 7); // set outer ring to bass + setRingFromFtt(leds, pal, 0, 8); // set outer ring to bass + + } + void setRingFromFtt(Leds &leds, CRGBPalette16 pal, int index, int ring) { + byte val = wledAudioMod->fftResults[index]; + // Visualize leds to the beat + CRGB color = ColorFromPalette(pal, val, 255); + color.nscale8_video(val); + setRing(leds, ring, color); } void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "BPM", 60); - ui->initCheckBox(parentVar, "Vertical", true); + addPalette(parentVar, 4); + ui->initCheckBox(parentVar, "inWards", true); } -}; // Lines2D +}; -unsigned8 gamma8(unsigned8 b) { //we do nothing with gamma for now - return b; -} +class FreqMatrix:public Effect { +public: + const char * name() {return "FreqMatrix";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’กโ™ช";} + + void setup(Leds &leds) { + leds.fadeToBlackBy(16); + } + + void loop(Leds &leds) { + + stackUnsigned8 *aux0 = leds.sharedData.bind(aux0); + + stackUnsigned8 speed = mdl->getValue("speed"); + stackUnsigned8 fx = mdl->getValue("Sound effect"); + stackUnsigned8 lowBin = mdl->getValue("Low bin"); + stackUnsigned8 highBin = mdl->getValue("High bin"); + stackUnsigned8 sensitivity10 = mdl->getValue("Sensivity"); + + stackUnsigned8 secondHand = (speed < 255) ? (micros()/(256-speed)/500 % 16) : 0; + if((speed > 254) || (*aux0 != secondHand)) { // WLEDMM allow run run at full speed + *aux0 = secondHand; -//DistortionWaves2D inspired by WLED, ldirko and blazoncek, https://editor.soulmatelights.com/gallery/1089-distorsion-waves + // Pixel brightness (value) based on volume * sensitivity * intensity + // uint_fast8_t sensitivity10 = map(sensitivity, 0, 31, 10, 100); // reduced resolution slider // WLEDMM sensitivity * 10, to avoid losing precision + int pixVal = wledAudioMod->sync.volumeSmth * (float)fx * (float)sensitivity10 / 2560.0f; // WLEDMM 2560 due to sensitivity * 10 + if (pixVal > 255) pixVal = 255; // make a brightness from the last avg + + CRGB color = CRGB::Black; + + if (wledAudioMod->sync.FFT_MajorPeak > MAX_FREQUENCY) wledAudioMod->sync.FFT_MajorPeak = 1; + // MajorPeak holds the freq. value which is most abundant in the last sample. + // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz + // we will treat everything with less than 65Hz as 0 + + if ((wledAudioMod->sync.FFT_MajorPeak > 80.0f) && (wledAudioMod->sync.volumeSmth > 0.25f)) { // WLEDMM + // Pixel color (hue) based on major frequency + int upperLimit = 80 + 42 * highBin; + int lowerLimit = 80 + 3 * lowBin; + //stackUnsigned8 i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // (original formula) may under/overflow - so we enforce unsigned8 + int freqMapped = lowerLimit!=upperLimit ? map(wledAudioMod->sync.FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : wledAudioMod->sync.FFT_MajorPeak; // WLEDMM preserve overflows + stackUnsigned8 i = abs(freqMapped) & 0xFF; // WLEDMM we embrace overflow ;-) by "modulo 256" + + color = CHSV(i, 240, (unsigned8)pixVal); // implicit conversion to RGB supplied by FastLED + } + + // shift the pixels one pixel up + leds.setPixelColor(0, color); + for (int i = leds.nrOfLeds - 1; i > 0; i--) leds.setPixelColor(i, leds.getPixelColor(i-1)); + } + } + + void controls(JsonObject parentVar) { + ui->initSlider(parentVar, "speed", 255); + ui->initSlider(parentVar, "Sound effect", 128); + ui->initSlider(parentVar, "Low bin", 18); + ui->initSlider(parentVar, "High bin", 48); + ui->initSlider(parentVar, "Sensivity", 30, 10, 100); + } +}; + +#define maxNumPopcorn 21 // max 21 on 16 segment ESP8266 +#define NUM_COLORS 3 /* number of colors per segment */ + +//each needs 19 bytes +//Spark type is used for popcorn, 1D fireworks, and drip +typedef struct Spark { + float pos, posX; + float vel, velX; + unsigned16 col; + unsigned8 colIndex; +} spark; + + +class PopCorn: public Effect { +public: + const char * name() {return "PopCorn";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’กโ™ช";} + + void loop(Leds &leds) { + CRGBPalette16 pal = getPalette(); + stackUnsigned8 speed = mdl->getValue("speed"); + stackUnsigned8 intensity = mdl->getValue("intensity"); + bool useaudio = mdl->getValue("useaudio"); + + Spark *popcorn = leds.sharedData.bind(popcorn, maxNumPopcorn); //array + + float gravity = -0.0001 - (speed/200000.0); // m/s/s + gravity *= leds.nrOfLeds; + + stackUnsigned8 numPopcorn = intensity*maxNumPopcorn/255; + if (numPopcorn == 0) numPopcorn = 1; + + for (int i = 0; i < numPopcorn; i++) { + if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position + popcorn[i].pos += popcorn[i].vel; + popcorn[i].vel += gravity; + } else { // if kernel is inactive, randomly pop it + bool doPopCorn = false; // WLEDMM allows to inhibit new pops + // WLEDMM begin + if (useaudio) { + if ( (wledAudioMod->sync.volumeSmth > 1.0f) // no pops in silence + // &&((wledAudioMod->sync.samplePeak > 0) || (wledAudioMod->sync.volumeRaw > 128)) // try to pop at onsets (our peek detector still sucks) + &&(random8() < 4) ) // stay somewhat random + doPopCorn = true; + } else { + if (random8() < 2) doPopCorn = true; // default POP!!! + } + // WLEDMM end + + if (doPopCorn) { // POP!!! + popcorn[i].pos = 0.01f; + + stackUnsigned16 peakHeight = 128 + random8(128); //0-255 + peakHeight = (peakHeight * (leds.nrOfLeds -1)) >> 8; + popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); + + // if (SEGMENT.palette) + // { + popcorn[i].colIndex = random8(); + // } else { + // byte col = random8(0, NUM_COLORS); + // if (!SEGCOLOR(2) || !SEGCOLOR(col)) col = 0; + // popcorn[i].colIndex = col; + // } + } + } + if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped) + // stackUnsigned32 col = SEGMENT.color_wheel(popcorn[i].colIndex); + // if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); + stackUnsigned16 ledIndex = popcorn[i].pos; + CRGB col = ColorFromPalette(pal, popcorn[i].colIndex*(256/maxNumPopcorn), 255); + if (ledIndex < leds.nrOfLeds) leds.setPixelColor(ledIndex, col); + } + } + + } + void controls(JsonObject parentVar) { + addPalette(parentVar, 4); + ui->initSlider(parentVar, "speed", 128); + ui->initSlider(parentVar, "intensity", 128); + ui->initCheckBox(parentVar, "useaudio"); + ui->initSlider(parentVar, "nrOfPopCorn", 10, 1, 21); + } +}; //PopCorn + +class DJLight:public Effect { +public: + + const char * name() {return "DJLight";} + unsigned8 dim() {return _1D;} + const char * tags() {return "๐Ÿ’กโ™ซ";} + + void setup(Leds &leds) { + leds.fill_solid(CRGB::Black, true); //no blend + } + + void loop(Leds &leds) { + + const int mid = leds.nrOfLeds / 2; + + unsigned8 *aux0 = leds.sharedData.bind(aux0); + + uint8_t *fftResult = wledAudioMod->fftResults; + float volumeSmth = wledAudioMod->volumeSmth; + + unsigned8 speed = mdl->getValue("speed"); + bool candyFactory = mdl->getValue("candyFactory").as(); + unsigned8 fade = mdl->getValue("fade"); + + + unsigned8 secondHand = (speed < 255) ? (micros()/(256-speed)/500 % 16) : 0; + if((speed > 254) || (*aux0 != secondHand)) { // WLEDMM allow run run at full speed + *aux0 = secondHand; + + CRGB color = CRGB(0,0,0); + // color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // formula from 0.13.x (10Khz): R = 3880-5120, G=240-340, B=60-100 + if (!candyFactory) { + color = CRGB(fftResult[12]/2, fftResult[3]/2, fftResult[1]/2); // formula for 0.14.x (22Khz): R = 3015-3704, G=216-301, B=86-129 + } else { + // candy factory: an attempt to get more colors + color = CRGB(fftResult[11]/2 + fftResult[12]/4 + fftResult[14]/4, // red : 2412-3704 + 4479-7106 + fftResult[4]/2 + fftResult[3]/4, // green: 216-430 + fftResult[0]/4 + fftResult[1]/4 + fftResult[2]/4); // blue: 46-216 + if ((color.getLuma() < 96) && (volumeSmth >= 1.5f)) { // enhance "almost dark" pixels with yellow, based on not-yet-used channels + unsigned yello_g = (fftResult[5] + fftResult[6] + fftResult[7]) / 3; + unsigned yello_r = (fftResult[7] + fftResult[8] + fftResult[9] + fftResult[10]) / 4; + color.green += (uint8_t) yello_g / 2; + color.red += (uint8_t) yello_r / 2; + } + } + + if (volumeSmth < 1.0f) color = CRGB(0,0,0); // silence = black + + // make colors less "pastel", by turning up color saturation in HSV space + if (color.getLuma() > 32) { // don't change "dark" pixels + CHSV hsvColor = rgb2hsv_approximate(color); + hsvColor.v = min(max(hsvColor.v, (uint8_t)48), (uint8_t)204); // 48 < brightness < 204 + if (candyFactory) + hsvColor.s = max(hsvColor.s, (uint8_t)204); // candy factory mode: strongly turn up color saturation (> 192) + else + hsvColor.s = max(hsvColor.s, (uint8_t)108); // normal mode: turn up color saturation to avoid pastels + color = hsvColor; + } + //if (color.getLuma() > 12) color.maximizeBrightness(); // for testing + + //leds.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // 0.13.x fade -> 180hz-260hz + uint8_t fadeVal = map(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz + if (candyFactory) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out + leds.setPixelColor(mid, color.fadeToBlackBy(fadeVal)); + + for (int i = leds.nrOfLeds - 1; i > mid; i--) leds.setPixelColor(i, leds.getPixelColor(i-1)); // move to the left + for (int i = 0; i < mid; i++) leds.setPixelColor(i, leds.getPixelColor(i+1)); // move to the right + + leds.fadeToBlackBy(fade); + + } + } + + void controls(JsonObject parentVar) { + ui->initSlider(parentVar, "speed", 255); + ui->initCheckBox(parentVar, "candyFactory", true); + ui->initSlider(parentVar, "fade", 4, 0, 10); + } +}; //DJLight + + + + +//2D Effects +//========== + +class Lines: public Effect { +public: + const char * name() {return "Lines";} + unsigned8 dim() {return _2D;} + const char * tags() {return "๐Ÿ’ซ";} + + void loop(Leds &leds) { + leds.fadeToBlackBy(100); + + Coord3D pos = {0,0,0}; + if (mdl->getValue("Vertical").as()) { + pos.x = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.size.x-1 ); //instead of call%width + + for (pos.y = 0; pos.y < leds.size.y; pos.y++) { + leds[pos] = CHSV( gHue, 255, 192); + } + } else { + pos.y = map(beat16( mdl->getValue("BPM").as()), 0, UINT16_MAX, 0, leds.size.y-1 ); //instead of call%height + for (pos.x = 0; pos.x < leds.size.x; pos.x++) { + leds[pos] = CHSV( gHue, 255, 192); + } + } + } + + void controls(JsonObject parentVar) { + ui->initSlider(parentVar, "BPM", 60); + ui->initCheckBox(parentVar, "Vertical", true); + } +}; // Lines + +//DistortionWaves inspired by WLED, ldirko and blazoncek, https://editor.soulmatelights.com/gallery/1089-distorsion-waves +unsigned8 gamma8(unsigned8 b) { //we do nothing with gamma for now + return b; +} class DistortionWaves: public Effect { public: const char * name() {return "DistortionWaves";} @@ -409,7 +808,7 @@ class DistortionWaves: public Effect { } }; // DistortionWaves2D -//Octopus2D inspired by WLED, Stepko and Sutaburosu and blazoncek +//Octopus inspired by WLED, Stepko and Sutaburosu and blazoncek //Idea from https://www.youtube.com/watch?v=HsA-6KIbgto&ab_channel=GreatScott%21 (https://editor.soulmatelights.com/gallery/671-octopus) class Octopus: public Effect { public: @@ -467,7 +866,7 @@ class Octopus: public Effect { for (pos.y = 0; pos.y < leds.size.y; pos.y++) { byte angle = rMap[leds.XY(pos.x,pos.y)].angle; byte radius = rMap[leds.XY(pos.x,pos.y)].radius; - //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); + //CRGB c = CHSV(*step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + *step) + radius - *step * 2 + angle * (SEGMENT.custom3/3+1))); uint16_t intensity = sin8(sin8((angle * 4 - radius) / 4 + *step/2) + radius - *step + angle * legs); intensity = map(intensity*intensity, 0, UINT16_MAX, 0, 255); // add a bit of non-linearity for cleaner display CRGB color = ColorFromPalette(pal, *step / 2 - radius, intensity); @@ -499,9 +898,9 @@ class Octopus: public Effect { ui->initSlider(parentVar, "Offset Y", 128); ui->initSlider(parentVar, "Legs", 4, 1, 8); } -}; // Octopus2D +}; // Octopus -//Lissajous2D inspired by WLED, Andrew Tuline +//Lissajous inspired by WLED, Andrew Tuline class Lissajous: public Effect { public: const char * name() {return "Lissajous";} @@ -527,9 +926,9 @@ class Lissajous: public Effect { for (int i=0; i < maxLoops; i++) { locn.x = float(sin8(phase/2 + (i* freqX)/64)) / 255.0f; // WLEDMM align speed with original effect locn.y = float(cos8(phase/2 + i*2)) / 255.0f; - //SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing + //leds.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing unsigned palIndex = (256*locn.y) + phase/2 + (i* freqX)/64; - // SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation + // leds.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(palIndex, false, PALETTE_SOLID_WRAP, 0)); // draw pixel with anti-aliasing - color follows rotation leds[locn] = ColorFromPalette(pal, palIndex); } } else @@ -539,7 +938,7 @@ class Lissajous: public Effect { locn.y = cos8(phase/2 + i*2); locn.x = (leds.size.x < 2) ? 1 : (map(2*locn.x, 0,511, 0,2*(leds.size.x-1)) +1) /2; // softhack007: "*2 +1" for proper rounding locn.y = (leds.size.y < 2) ? 1 : (map(2*locn.y, 0,511, 0,2*(leds.size.y-1)) +1) /2; // "leds.size.y > 2" is needed to avoid div/0 in map() - // SEGMENT.setPixelColorXY((unsigned8)xlocn, (unsigned8)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); + // leds.setPixelColorXY((unsigned8)xlocn, (unsigned8)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); leds[locn] = ColorFromPalette(pal, now/100+i); } } @@ -550,117 +949,39 @@ class Lissajous: public Effect { ui->initSlider(parentVar, "speed", 128); ui->initCheckBox(parentVar, "Smooth", false); } -}; // Lissajous2D +}; // Lissajous - -#define maxNumBalls 16 - -//BouncingBalls1D inspired by WLED -//each needs 12 bytes -typedef struct Ball { - unsigned long lastBounceTime; - float impactVelocity; - float height; -} ball; - -class BouncingBalls: public Effect { +//Frizzles inspired by WLED, Stepko, Andrew Tuline, https://editor.soulmatelights.com/gallery/640-color-frizzles +class Frizzles: public Effect { public: - const char * name() {return "Bouncing Balls";} - unsigned8 dim() {return _1D;} + const char * name() {return "Frizzles";} + unsigned8 dim() {return _2D;} const char * tags() {return "๐Ÿ’ก";} void loop(Leds &leds) { - stackUnsigned8 grav = mdl->getValue("gravity"); - stackUnsigned8 numBalls = mdl->getValue("balls"); - CRGBPalette16 pal = getPalette(); - - Ball *balls = leds.sharedData.bind(balls, maxNumBalls); //array - - leds.fill_solid(CRGB::Black); - - // non-chosen color is a random color - const float gravity = -9.81f; // standard value of gravity - // const bool hasCol2 = SEGCOLOR(2); - const unsigned long time = now; - - //not necessary as sharedData is cleared at setup(Leds &leds) - // if (call == 0) { - // for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; - // } - - for (size_t i = 0; i < numBalls; i++) { - float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-grav)/64 +1); - float timeSec = timeSinceLastBounce/1000.0f; - balls[i].height = (0.5f * gravity * timeSec + balls[i].impactVelocity) * timeSec; // avoid use pow(x, 2) - its extremely slow ! - - if (balls[i].height <= 0.0f) { - balls[i].height = 0.0f; - //damping for better effect using multiple balls - float dampening = 0.9f - float(i)/float(numBalls * numBalls); // avoid use pow(x, 2) - its extremely slow ! - balls[i].impactVelocity = dampening * balls[i].impactVelocity; - balls[i].lastBounceTime = time; - - if (balls[i].impactVelocity < 0.015f) { - float impactVelocityStart = sqrtf(-2.0f * gravity) * random8(5,11)/10.0f; // randomize impact velocity - balls[i].impactVelocity = impactVelocityStart; - } - } else if (balls[i].height > 1.0f) { - continue; // do not draw OOB ball - } - - // stackUnsigned32 color = SEGCOLOR(0); - // if (SEGMENT.palette) { - // color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); - // } - // else if (hasCol2) { - // color = SEGCOLOR(i % NUM_COLORS); - // } - - int pos = roundf(balls[i].height * (leds.nrOfLeds - 1)); + leds.fadeToBlackBy(16); - CRGB color = ColorFromPalette(pal, i*(256/max(numBalls, (stackUnsigned8)8)), 255); + stackUnsigned8 bpm = mdl->getValue("BPM"); + stackUnsigned8 intensity = mdl->getValue("intensity"); + CRGBPalette16 pal = getPalette(); + for (int i = 8; i > 0; i--) { + Coord3D pos = {0,0,0}; + pos.x = beatsin8(bpm/8 + i, 0, leds.size.x - 1); + pos.y = beatsin8(intensity/8 - i, 0, leds.size.y - 1); + CRGB color = ColorFromPalette(pal, beatsin8(12, 0, 255), 255); leds[pos] = color; - // if (SEGLEN<32) SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index - // else SEGMENT.setPixelColor(balls[i].height + (stripNr+1)*10.0f, color); - } //balls - } - - void controls(JsonObject parentVar) { - addPalette(parentVar, 4); - ui->initSlider(parentVar, "gravity", 128); - ui->initSlider(parentVar, "balls", 8, 1, 16); - } -}; // BouncingBalls2D - -class RingEffect:public Effect { - protected: - - void setRing(Leds &leds, int ring, CRGB colour) { //so britisch ;-) - leds[ring] = colour; } - -}; - -class RingRandomFlow:public RingEffect { -public: - const char * name() {return "RingRandomFlow";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’ซ";} - - void loop(Leds &leds) { - stackUnsigned8 *hue = leds.sharedData.bind(hue, leds.nrOfLeds); //array - - hue[0] = random(0, 255); - for (int r = 0; r < leds.nrOfLeds; r++) { - setRing(leds, r, CHSV(hue[r], 255, 255)); - } - for (int r = (leds.nrOfLeds - 1); r >= 1; r--) { - hue[r] = hue[(r - 1)]; // set this ruing based on the inner - } - // FastLED.delay(SPEED); + leds.blur2d(mdl->getValue("blur")); + } + + void controls(JsonObject parentVar) { + addPalette(parentVar, 4); + ui->initSlider(parentVar, "BPM", 60); + ui->initSlider(parentVar, "intensity", 128); + ui->initSlider(parentVar, "blur", 128); } -}; +}; // Frizzles class ScrollingText: public Effect { public: @@ -700,138 +1021,6 @@ class ScrollingText: public Effect { }; -class Waverly: public Effect { -public: - const char * name() {return "Waverly";} - unsigned8 dim() {return _2D;} - const char * tags() {return "๐Ÿ’กโ™ช";} - - void loop(Leds &leds) { - CRGBPalette16 pal = getPalette(); - stackUnsigned8 amplification = mdl->getValue("Amplification"); - stackUnsigned8 sensitivity = mdl->getValue("Sensitivity"); - bool noClouds = mdl->getValue("No Clouds"); - // bool soundPressure = mdl->getValue("Sound Pressure"); - // bool agcDebug = mdl->getValue("AGC debug"); - - leds.fadeToBlackBy(amplification); - // if (agcDebug && soundPressure) soundPressure = false; // only one of the two at any time - // if ((soundPressure) && (wledAudioMod->sync.volumeSmth > 0.5f)) wledAudioMod->sync.volumeSmth = wledAudioMod->sync.soundPressure; // show sound pressure instead of volume - // if (agcDebug) wledAudioMod->sync.volumeSmth = 255.0 - wledAudioMod->sync.agcSensitivity; // show AGC level instead of volume - - long t = now / 2; - Coord3D pos; - for (pos.x = 0; pos.x < leds.size.x; pos.x++) { - stackUnsigned16 thisVal = wledAudioMod->sync.volumeSmth*sensitivity/64 * inoise8(pos.x * 45 , t , t)/64; // WLEDMM back to SR code - stackUnsigned16 thisMax = min(map(thisVal, 0, 512, 0, leds.size.y), (long)leds.size.x); - - for (pos.y = 0; pos.y < thisMax; pos.y++) { - CRGB color = ColorFromPalette(pal, map(pos.y, 0, thisMax, 250, 0), 255, LINEARBLEND); - if (!noClouds) - leds.addPixelColor(pos, color); - leds.addPixelColor(leds.XY((leds.size.x - 1) - pos.x, (leds.size.y - 1) - pos.y), color); - } - } - leds.blur2d(16); - - } - void controls(JsonObject parentVar) { - addPalette(parentVar, 4); - ui->initSlider(parentVar, "Amplification", 128); - ui->initSlider(parentVar, "Sensitivity", 128); - ui->initCheckBox(parentVar, "No Clouds"); - // ui->initCheckBox(parentVar, "Sound Pressure"); - // ui->initCheckBox(parentVar, "AGC debug"); - } -}; - -#define maxNumPopcorn 21 // max 21 on 16 segment ESP8266 -#define NUM_COLORS 3 /* number of colors per segment */ - -//each needs 19 bytes -//Spark type is used for popcorn, 1D fireworks, and drip -typedef struct Spark { - float pos, posX; - float vel, velX; - unsigned16 col; - unsigned8 colIndex; -} spark; - - -class PopCorn: public Effect { -public: - const char * name() {return "PopCorn";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’กโ™ช";} - - void loop(Leds &leds) { - CRGBPalette16 pal = getPalette(); - stackUnsigned8 speed = mdl->getValue("speed"); - stackUnsigned8 intensity = mdl->getValue("intensity"); - bool useaudio = mdl->getValue("useaudio"); - - Spark *popcorn = leds.sharedData.bind(popcorn, maxNumPopcorn); //array - - float gravity = -0.0001 - (speed/200000.0); // m/s/s - gravity *= leds.nrOfLeds; - - stackUnsigned8 numPopcorn = intensity*maxNumPopcorn/255; - if (numPopcorn == 0) numPopcorn = 1; - - for (int i = 0; i < numPopcorn; i++) { - if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position - popcorn[i].pos += popcorn[i].vel; - popcorn[i].vel += gravity; - } else { // if kernel is inactive, randomly pop it - bool doPopCorn = false; // WLEDMM allows to inhibit new pops - // WLEDMM begin - if (useaudio) { - if ( (wledAudioMod->sync.volumeSmth > 1.0f) // no pops in silence - // &&((wledAudioMod->sync.samplePeak > 0) || (wledAudioMod->sync.volumeRaw > 128)) // try to pop at onsets (our peek detector still sucks) - &&(random8() < 4) ) // stay somewhat random - doPopCorn = true; - } else { - if (random8() < 2) doPopCorn = true; // default POP!!! - } - // WLEDMM end - - if (doPopCorn) { // POP!!! - popcorn[i].pos = 0.01f; - - stackUnsigned16 peakHeight = 128 + random8(128); //0-255 - peakHeight = (peakHeight * (leds.nrOfLeds -1)) >> 8; - popcorn[i].vel = sqrtf(-2.0f * gravity * peakHeight); - - // if (SEGMENT.palette) - // { - popcorn[i].colIndex = random8(); - // } else { - // byte col = random8(0, NUM_COLORS); - // if (!SEGCOLOR(2) || !SEGCOLOR(col)) col = 0; - // popcorn[i].colIndex = col; - // } - } - } - if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped) - // stackUnsigned32 col = SEGMENT.color_wheel(popcorn[i].colIndex); - // if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); - stackUnsigned16 ledIndex = popcorn[i].pos; - CRGB col = ColorFromPalette(pal, popcorn[i].colIndex*(256/maxNumPopcorn), 255); - if (ledIndex < leds.nrOfLeds) leds.setPixelColor(ledIndex, col); - } - } - - } - void controls(JsonObject parentVar) { - addPalette(parentVar, 4); - ui->initSlider(parentVar, "speed", 128); - ui->initSlider(parentVar, "intensity", 128); - ui->initCheckBox(parentVar, "useaudio"); - ui->initSlider(parentVar, "nrOfPopCorn", 10, 1, 21); - } -}; //PopCorn1D - - #ifdef STARMOD_USERMOD_WLEDAUDIO class GEQEffect:public Effect { @@ -903,243 +1092,100 @@ class GEQEffect:public Effect { 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(pos.x, 0, leds.size.x-1, 0, 255); //WLEDMM - } - lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration - stackUnsigned16 barHeight = map(bandHeight, 0, 255, 0, leds.size.y); // Now we map bandHeight to barHeight. do not subtract -1 from leds.size.y here - // WLEDMM end - - if (barHeight > leds.size.y) barHeight = leds.size.y; // WLEDMM map() can "overshoot" due to rounding errors - if (barHeight > previousBarHeight[pos.x]) previousBarHeight[pos.x] = barHeight; //drive the peak up - - CRGB ledColor = CRGB::Black; - - for (pos.y=0; pos.y < barHeight; pos.y++) { - if (colorBars) //color_vertical / color bars toggle - colorIndex = map(pos.y, 0, leds.size.y-1, 0, 255); - - ledColor = ColorFromPalette(pal, (unsigned8)colorIndex); - - leds.setPixelColor(leds.XY(pos.x, leds.size.y - 1 - pos.y), ledColor); - } - - if ((ripple > 0) && (previousBarHeight[pos.x] > 0) && (previousBarHeight[pos.x] < leds.size.y)) // WLEDMM avoid "overshooting" into other segments - leds.setPixelColor(leds.XY(pos.x, leds.size.y - previousBarHeight[pos.x]), CHSV( gHue, 255, 192)); // take gHue color for the time being - - if (rippleTime && previousBarHeight[pos.x]>0) previousBarHeight[pos.x]--; //delay/ripple effect - - } - } - - void controls(JsonObject parentVar) { - addPalette(parentVar, 4); - ui->initSlider(parentVar, "fadeOut", 255); - ui->initSlider(parentVar, "ripple", 128); - ui->initCheckBox(parentVar, "colorBars", false); - ui->initCheckBox(parentVar, "smoothBars", true); - - // Nice an effect can register it's own DMX channel, but not a fan of repeating the range and type of the param - - // #ifdef STARMOD_USERMOD_E131 - - // if (e131mod->isEnabled) { - // e131mod->patchChannel(3, "fadeOut", 255); // TODO: add constant for name - // e131mod->patchChannel(4, "ripple", 255); - // for (JsonObject childVar: mdl->findVar("e131Tbl")["n"].as()) { - // ui->callVarFun(childVar, UINT8_MAX, f_UIFun); - // } - // } - - // #endif - } -}; - -class AudioRings:public RingEffect { -public: - const char * name() {return "AudioRings";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’ซโ™ซ";} - - void loop(Leds &leds) { - CRGBPalette16 pal = getPalette(); - for (int i = 0; i < 7; i++) { // 7 rings - - byte val; - if(mdl->getValue("inWards").as()) { - val = wledAudioMod->fftResults[(i*2)]; - } - else { - int b = 14 -(i*2); - val = wledAudioMod->fftResults[b]; - } - - // Visualize leds to the beat - CRGB color = ColorFromPalette(pal, val, val); -// CRGB color = ColorFromPalette(currentPalette, val, 255, currentBlending); -// color.nscale8_video(val); - setRing(leds, i, color); -// setRingFromFtt((i * 2), i); - } - - setRingFromFtt(leds, pal, 2, 7); // set outer ring to bass - setRingFromFtt(leds, pal, 0, 8); // set outer ring to bass - - } - void setRingFromFtt(Leds &leds, CRGBPalette16 pal, int index, int ring) { - byte val = wledAudioMod->fftResults[index]; - // Visualize leds to the beat - CRGB color = ColorFromPalette(pal, val, 255); - color.nscale8_video(val); - setRing(leds, ring, color); - } - - void controls(JsonObject parentVar) { - addPalette(parentVar, 4); - ui->initCheckBox(parentVar, "inWards", true); - } -}; - -class FreqMatrix:public Effect { -public: - const char * name() {return "FreqMatrix";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’กโ™ช";} - - void setup(Leds &leds) { - leds.fadeToBlackBy(16); - } - - void loop(Leds &leds) { - - stackUnsigned8 *aux0 = leds.sharedData.bind(aux0); - - stackUnsigned8 speed = mdl->getValue("speed"); - stackUnsigned8 fx = mdl->getValue("Sound effect"); - stackUnsigned8 lowBin = mdl->getValue("Low bin"); - stackUnsigned8 highBin = mdl->getValue("High bin"); - stackUnsigned8 sensitivity10 = mdl->getValue("Sensivity"); - - stackUnsigned8 secondHand = (speed < 255) ? (micros()/(256-speed)/500 % 16) : 0; - if((speed > 254) || (*aux0 != secondHand)) { // WLEDMM allow run run at full speed - *aux0 = secondHand; + } + lastBandHeight = bandHeight; // remember BandHeight (left side) for next iteration + stackUnsigned16 barHeight = map(bandHeight, 0, 255, 0, leds.size.y); // Now we map bandHeight to barHeight. do not subtract -1 from leds.size.y here + // WLEDMM end - // Pixel brightness (value) based on volume * sensitivity * intensity - // uint_fast8_t sensitivity10 = map(sensitivity, 0, 31, 10, 100); // reduced resolution slider // WLEDMM sensitivity * 10, to avoid losing precision - int pixVal = wledAudioMod->sync.volumeSmth * (float)fx * (float)sensitivity10 / 2560.0f; // WLEDMM 2560 due to sensitivity * 10 - if (pixVal > 255) pixVal = 255; // make a brightness from the last avg + if (barHeight > leds.size.y) barHeight = leds.size.y; // WLEDMM map() can "overshoot" due to rounding errors + if (barHeight > previousBarHeight[pos.x]) previousBarHeight[pos.x] = barHeight; //drive the peak up - CRGB color = CRGB::Black; + CRGB ledColor = CRGB::Black; - if (wledAudioMod->sync.FFT_MajorPeak > MAX_FREQUENCY) wledAudioMod->sync.FFT_MajorPeak = 1; - // MajorPeak holds the freq. value which is most abundant in the last sample. - // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz - // we will treat everything with less than 65Hz as 0 + for (pos.y=0; pos.y < barHeight; pos.y++) { + if (colorBars) //color_vertical / color bars toggle + colorIndex = map(pos.y, 0, leds.size.y-1, 0, 255); - if ((wledAudioMod->sync.FFT_MajorPeak > 80.0f) && (wledAudioMod->sync.volumeSmth > 0.25f)) { // WLEDMM - // Pixel color (hue) based on major frequency - int upperLimit = 80 + 42 * highBin; - int lowerLimit = 80 + 3 * lowBin; - //stackUnsigned8 i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // (original formula) may under/overflow - so we enforce unsigned8 - int freqMapped = lowerLimit!=upperLimit ? map(wledAudioMod->sync.FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : wledAudioMod->sync.FFT_MajorPeak; // WLEDMM preserve overflows - stackUnsigned8 i = abs(freqMapped) & 0xFF; // WLEDMM we embrace overflow ;-) by "modulo 256" + ledColor = ColorFromPalette(pal, (unsigned8)colorIndex); - color = CHSV(i, 240, (unsigned8)pixVal); // implicit conversion to RGB supplied by FastLED + leds.setPixelColor(leds.XY(pos.x, leds.size.y - 1 - pos.y), ledColor); } - // shift the pixels one pixel up - leds.setPixelColor(0, color); - for (int i = leds.nrOfLeds - 1; i > 0; i--) leds.setPixelColor(i, leds.getPixelColor(i-1)); + if ((ripple > 0) && (previousBarHeight[pos.x] > 0) && (previousBarHeight[pos.x] < leds.size.y)) // WLEDMM avoid "overshooting" into other segments + leds.setPixelColor(leds.XY(pos.x, leds.size.y - previousBarHeight[pos.x]), CHSV( gHue, 255, 192)); // take gHue color for the time being + + if (rippleTime && previousBarHeight[pos.x]>0) previousBarHeight[pos.x]--; //delay/ripple effect + } } void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "speed", 255); - ui->initSlider(parentVar, "Sound effect", 128); - ui->initSlider(parentVar, "Low bin", 18); - ui->initSlider(parentVar, "High bin", 48); - ui->initSlider(parentVar, "Sensivity", 30, 10, 100); - } -}; + addPalette(parentVar, 4); + ui->initSlider(parentVar, "fadeOut", 255); + ui->initSlider(parentVar, "ripple", 128); + ui->initCheckBox(parentVar, "colorBars", false); + ui->initCheckBox(parentVar, "smoothBars", true); + // Nice an effect can register it's own DMX channel, but not a fan of repeating the range and type of the param -class DJLight:public Effect { -public: + // #ifdef STARMOD_USERMOD_E131 - const char * name() {return "DJLight";} - unsigned8 dim() {return _1D;} - const char * tags() {return "๐Ÿ’กโ™ซ";} + // if (e131mod->isEnabled) { + // e131mod->patchChannel(3, "fadeOut", 255); // TODO: add constant for name + // e131mod->patchChannel(4, "ripple", 255); + // for (JsonObject childVar: mdl->findVar("e131Tbl")["n"].as()) { + // ui->callVarFun(childVar, UINT8_MAX, f_UIFun); + // } + // } - void setup(Leds &leds) { - leds.fill_solid(CRGB::Black, true); //no blend + // #endif } +}; //GEQ - void loop(Leds &leds) { - - const int mid = leds.nrOfLeds / 2; - - unsigned8 *aux0 = leds.sharedData.bind(aux0); - - uint8_t *fftResult = wledAudioMod->fftResults; - float volumeSmth = wledAudioMod->volumeSmth; - - unsigned8 speed = mdl->getValue("speed"); - bool candyFactory = mdl->getValue("candyFactory").as(); - unsigned8 fade = mdl->getValue("fade"); - +class Waverly: public Effect { +public: + const char * name() {return "Waverly";} + unsigned8 dim() {return _2D;} + const char * tags() {return "๐Ÿ’กโ™ช";} - unsigned8 secondHand = (speed < 255) ? (micros()/(256-speed)/500 % 16) : 0; - if((speed > 254) || (*aux0 != secondHand)) { // WLEDMM allow run run at full speed - *aux0 = secondHand; + void loop(Leds &leds) { + CRGBPalette16 pal = getPalette(); + stackUnsigned8 amplification = mdl->getValue("Amplification"); + stackUnsigned8 sensitivity = mdl->getValue("Sensitivity"); + bool noClouds = mdl->getValue("No Clouds"); + // bool soundPressure = mdl->getValue("Sound Pressure"); + // bool agcDebug = mdl->getValue("AGC debug"); - CRGB color = CRGB(0,0,0); - // color = CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2); // formula from 0.13.x (10Khz): R = 3880-5120, G=240-340, B=60-100 - if (!candyFactory) { - color = CRGB(fftResult[12]/2, fftResult[3]/2, fftResult[1]/2); // formula for 0.14.x (22Khz): R = 3015-3704, G=216-301, B=86-129 - } else { - // candy factory: an attempt to get more colors - color = CRGB(fftResult[11]/2 + fftResult[12]/4 + fftResult[14]/4, // red : 2412-3704 + 4479-7106 - fftResult[4]/2 + fftResult[3]/4, // green: 216-430 - fftResult[0]/4 + fftResult[1]/4 + fftResult[2]/4); // blue: 46-216 - if ((color.getLuma() < 96) && (volumeSmth >= 1.5f)) { // enhance "almost dark" pixels with yellow, based on not-yet-used channels - unsigned yello_g = (fftResult[5] + fftResult[6] + fftResult[7]) / 3; - unsigned yello_r = (fftResult[7] + fftResult[8] + fftResult[9] + fftResult[10]) / 4; - color.green += (uint8_t) yello_g / 2; - color.red += (uint8_t) yello_r / 2; - } - } + leds.fadeToBlackBy(amplification); + // if (agcDebug && soundPressure) soundPressure = false; // only one of the two at any time + // if ((soundPressure) && (wledAudioMod->sync.volumeSmth > 0.5f)) wledAudioMod->sync.volumeSmth = wledAudioMod->sync.soundPressure; // show sound pressure instead of volume + // if (agcDebug) wledAudioMod->sync.volumeSmth = 255.0 - wledAudioMod->sync.agcSensitivity; // show AGC level instead of volume - if (volumeSmth < 1.0f) color = CRGB(0,0,0); // silence = black + long t = now / 2; + Coord3D pos; + for (pos.x = 0; pos.x < leds.size.x; pos.x++) { + stackUnsigned16 thisVal = wledAudioMod->sync.volumeSmth*sensitivity/64 * inoise8(pos.x * 45 , t , t)/64; // WLEDMM back to SR code + stackUnsigned16 thisMax = min(map(thisVal, 0, 512, 0, leds.size.y), (long)leds.size.x); - // make colors less "pastel", by turning up color saturation in HSV space - if (color.getLuma() > 32) { // don't change "dark" pixels - CHSV hsvColor = rgb2hsv_approximate(color); - hsvColor.v = min(max(hsvColor.v, (uint8_t)48), (uint8_t)204); // 48 < brightness < 204 - if (candyFactory) - hsvColor.s = max(hsvColor.s, (uint8_t)204); // candy factory mode: strongly turn up color saturation (> 192) - else - hsvColor.s = max(hsvColor.s, (uint8_t)108); // normal mode: turn up color saturation to avoid pastels - color = hsvColor; + for (pos.y = 0; pos.y < thisMax; pos.y++) { + CRGB color = ColorFromPalette(pal, map(pos.y, 0, thisMax, 250, 0), 255, LINEARBLEND); + if (!noClouds) + leds.addPixelColor(pos, color); + leds.addPixelColor(leds.XY((leds.size.x - 1) - pos.x, (leds.size.y - 1) - pos.y), color); } - //if (color.getLuma() > 12) color.maximizeBrightness(); // for testing - - //SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // 0.13.x fade -> 180hz-260hz - uint8_t fadeVal = map(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz - if (candyFactory) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out - leds.setPixelColor(mid, color.fadeToBlackBy(fadeVal)); - - for (int i = leds.nrOfLeds - 1; i > mid; i--) leds.setPixelColor(i, leds.getPixelColor(i-1)); // move to the left - for (int i = 0; i < mid; i++) leds.setPixelColor(i, leds.getPixelColor(i+1)); // move to the right - - leds.fadeToBlackBy(fade); - } - } + leds.blur2d(16); + } void controls(JsonObject parentVar) { - ui->initSlider(parentVar, "speed", 255); - ui->initCheckBox(parentVar, "candyFactory", true); - ui->initSlider(parentVar, "fade", 4, 0, 10); + addPalette(parentVar, 4); + ui->initSlider(parentVar, "Amplification", 128); + ui->initSlider(parentVar, "Sensitivity", 128); + ui->initCheckBox(parentVar, "No Clouds"); + // ui->initCheckBox(parentVar, "Sound Pressure"); + // ui->initCheckBox(parentVar, "AGC debug"); } -}; +}; //Waverly class FunkyPlank:public Effect { public: @@ -1188,45 +1234,135 @@ class FunkyPlank:public Effect { ui->initSlider(parentVar, "speed", 255); ui->initSlider(parentVar, "bands", NUM_GEQ_CHANNELS, 1, NUM_GEQ_CHANNELS); } -}; +}; //FunkyPlank #endif // End Audio Effects + +//3D Effects +//========== + +class RipplesEffect: public Effect { +public: + const char * name() {return "Ripples";} + unsigned8 dim() {return _3D;} + const char * tags() {return "๐Ÿ’ซ";} + + void loop(Leds &leds) { + stackUnsigned8 interval = mdl->getValue("interval"); + stackUnsigned8 speed = mdl->getValue("speed"); + + float ripple_interval = 1.3f * (interval/128.0f); + + leds.fill_solid(CRGB::Black); + + Coord3D pos = {0,0,0}; + for (pos.z=0; pos.zdistance(3.5f, 3.5f, 0.0f, (float)pos.x, (float)pos.z, 0.0f) / 9.899495f * leds.size.x; + stackUnsigned32 time_interval = now/(100 - speed)/((256.0f-128.0f)/20.0f); + pos.x = floor(leds.size.x/2.0f + sinf(d/ripple_interval + time_interval) * leds.size.x/2.0f); //between 0 and leds.size.x + + leds[pos] = CHSV( gHue + random8(64), 200, 255);// ColorFromPalette(pal,call, bri, LINEARBLEND); + } + } + } + void controls(JsonObject parentVar) { + ui->initSlider(parentVar, "speed", 50, 0, 99); + ui->initSlider(parentVar, "interval", 128); + } +}; + +class SphereMoveEffect: public Effect { +public: + const char * name() {return "SphereMove";} + unsigned8 dim() {return _3D;} + const char * tags() {return "๐Ÿ’ซ";} + + void loop(Leds &leds) { + stackUnsigned8 speed = mdl->getValue("speed"); + + leds.fill_solid(CRGB::Black); + + stackUnsigned32 time_interval = now/(100 - speed)/((256.0f-128.0f)/20.0f); + + Coord3D origin; + origin.x = 3.5f+sinf(time_interval)*2.5f; + origin.y = 3.5f+cosf(time_interval)*2.5f; + origin.z = 3.5f+cosf(time_interval)*2.0f; + + float diameter = 2.0f+sinf(time_interval/3.0f); + + // CRGBPalette256 pal; + Coord3D pos; + for (pos.x=0; pos.xdistance(pos.x, pos.y, pos.z, origin.x, origin.y, origin.z); + + if (d>diameter && dinitSlider(parentVar, "speed", 50, 0, 99); + } +}; // SphereMove3DEffect + class Effects { public: std::vector effects; Effects() { //create effects before fx.chFun is called + + //1D Basis effects.push_back(new SolidEffect); + // 1D FastLed effects.push_back(new RainbowEffect); effects.push_back(new RainbowWithGlitterEffect); effects.push_back(new SinelonEffect); - effects.push_back(new RunningEffect); effects.push_back(new ConfettiEffect); effects.push_back(new BPMEffect); effects.push_back(new JuggleEffect); - effects.push_back(new RipplesEffect); - effects.push_back(new SphereMoveEffect); - effects.push_back(new Frizzles); + //1D StarMod + effects.push_back(new RunningEffect); + effects.push_back(new RingRandomFlow); + // 1D WLED + effects.push_back(new BouncingBalls); + effects.push_back(new RainEffect); + + #ifdef STARMOD_USERMOD_WLEDAUDIO + //1D StarMod + effects.push_back(new AudioRings); + //1D WLED + effects.push_back(new FreqMatrix); + effects.push_back(new PopCorn); + effects.push_back(new DJLight); + #endif + + //2D StarMod effects.push_back(new Lines); + //2D WLED effects.push_back(new DistortionWaves); effects.push_back(new Octopus); effects.push_back(new Lissajous); - effects.push_back(new BouncingBalls); - effects.push_back(new RingRandomFlow); + effects.push_back(new Frizzles); effects.push_back(new ScrollingText); - effects.push_back(new Waverly); - effects.push_back(new PopCorn); #ifdef STARMOD_USERMOD_WLEDAUDIO + //2D WLED effects.push_back(new GEQEffect); - effects.push_back(new AudioRings); - effects.push_back(new FreqMatrix); - effects.push_back(new DJLight); + effects.push_back(new Waverly); effects.push_back(new FunkyPlank); #endif - } + //3D + effects.push_back(new RipplesEffect); + effects.push_back(new SphereMoveEffect); +} void setup() { //check of no local variables (should be only 4 bytes): tbd: can we loop over effects (sizeof(effect does not work)) @@ -1240,14 +1376,14 @@ class Effects { // USER_PRINTF("Size of %s is %d\n", "ConfettiEffect", sizeof(ConfettiEffect)); // USER_PRINTF("Size of %s is %d\n", "BPMEffect", sizeof(BPMEffect)); // USER_PRINTF("Size of %s is %d\n", "JuggleEffect", sizeof(JuggleEffect)); - // USER_PRINTF("Size of %s is %d\n", "Ripples3DEffect", sizeof(Ripples3DEffect)); - // USER_PRINTF("Size of %s is %d\n", "SphereMove3DEffect", sizeof(SphereMove3DEffect)); - // USER_PRINTF("Size of %s is %d\n", "Frizzles2D", sizeof(Frizzles2D)); - // USER_PRINTF("Size of %s is %d\n", "Lines2D", sizeof(Lines2D)); - // USER_PRINTF("Size of %s is %d\n", "DistortionWaves2D", sizeof(DistortionWaves2D)); - // USER_PRINTF("Size of %s is %d\n", "Octopus2D", sizeof(Octopus2D)); - // USER_PRINTF("Size of %s is %d\n", "Lissajous2D", sizeof(Lissajous2D)); - // USER_PRINTF("Size of %s is %d\n", "BouncingBalls1D", sizeof(BouncingBalls1D)); + // USER_PRINTF("Size of %s is %d\n", "RipplesEffect", sizeof(RipplesEffect)); + // USER_PRINTF("Size of %s is %d\n", "SphereMoveEffect", sizeof(SphereMoveEffect)); + // USER_PRINTF("Size of %s is %d\n", "Frizzles", sizeof(Frizzles)); + // USER_PRINTF("Size of %s is %d\n", "Lines", sizeof(Lines)); + // USER_PRINTF("Size of %s is %d\n", "DistortionWaves", sizeof(DistortionWaves)); + // USER_PRINTF("Size of %s is %d\n", "Octopus", sizeof(Octopus)); + // USER_PRINTF("Size of %s is %d\n", "Lissajous", sizeof(Lissajous)); + // USER_PRINTF("Size of %s is %d\n", "BouncingBalls", sizeof(BouncingBalls)); // USER_PRINTF("Size of %s is %d\n", "RingRandomFlow", sizeof(RingRandomFlow)); // #ifdef STARMOD_USERMOD_WLEDAUDIO // USER_PRINTF("Size of %s is %d\n", "GEQEffect", sizeof(GEQEffect)); diff --git a/src/App/LedFixture.cpp b/src/App/LedFixture.cpp index 70ff53c1..89394421 100644 --- a/src/App/LedFixture.cpp +++ b/src/App/LedFixture.cpp @@ -20,7 +20,7 @@ void Fixture::projectAndMap() { char fileName[32] = ""; - if (files->seqNrToName(fileName, fixtureNr)) { + if (files->seqNrToName(fileName, fixtureNr)) { // get the fixture.json StarModJson starModJson(fileName); //open fileName for deserialize // reset leds @@ -362,8 +362,9 @@ void Fixture::projectAndMap() { if (starModJson.deserialize(false)) { //this will call above function parameter for each led + //after processing each led stackUnsigned8 rowNr = 0; - // for (std::vector::iterator leds=ledsList.begin(); leds!=ledsList.end() && leds->doMap; ++leds) { + for (Leds *leds: ledsList) { if (leds->doMap) { USER_PRINTF("Leds pre [%d] f:%d p:%d s:%d\n", rowNr, leds->fx, leds->projectionNr, ledsList.size()); @@ -389,22 +390,23 @@ void Fixture::projectAndMap() { leds->nrOfLeds = leds->mappingTable.size(); + //debug info + summary values stackUnsigned16 indexV = 0; for (auto map:leds->mappingTable) { if (map.indexes && map.indexes->size()) { // if (nrOfMappings < 10 || map.indexes->size() - indexV < 10) //first 10 and last 10 - if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) + // if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) USER_PRINTF("ledV %d mapping: #ledsP (%d):", indexV, nrOfMappings, map.indexes->size()); for (forUnsigned16 indexP:*map.indexes) { // if (nrOfPixels < 10 || map.indexes->size() - indexV < 10) - if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) + // if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) USER_PRINTF(" %d", indexP); nrOfPixels++; } // if (nrOfPixels < 10 || map.indexes->size() - indexV < 10) - if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) + // if (nrOfMappings%(leds->nrOfLeds/10+1) == 0) USER_PRINTF("\n"); } nrOfMappings++; @@ -418,7 +420,7 @@ void Fixture::projectAndMap() { // mdl->setValueV("fxSize", rowNr, "%d x %d x %d = %d", leds->size.x, leds->size.y, leds->size.z, leds->nrOfLeds); char buf[32]; - print->fFormat(buf, sizeof(buf)-1,"%d x %d x %d = %d", leds->size.x, leds->size.y, leds->size.z, leds->nrOfLeds); + print->fFormat(buf, sizeof(buf)-1,"%d x %d x %d -> %d", leds->size.x, leds->size.y, leds->size.z, leds->nrOfLeds); mdl->setValue("fxSize", JsonString(buf, JsonString::Copied), rowNr); // web->sendResponseObject(); diff --git a/src/App/LedLeds.h b/src/App/LedLeds.h index f4227934..ab87a71d 100644 --- a/src/App/LedLeds.h +++ b/src/App/LedLeds.h @@ -251,6 +251,25 @@ class Leds { void fadeToBlackBy(unsigned8 fadeBy = 255); void fill_solid(const struct CRGB& color, bool noBlend = false); void fill_rainbow(unsigned8 initialhue, unsigned8 deltahue); + + + void blur1d(fract8 blur_amount) + { + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for( uint16_t i = 0; i < nrOfLeds; ++i) { + CRGB cur = getPixelColor(i); + CRGB part = cur; + part.nscale8( seep); + cur.nscale8( keep); + cur += carryover; + if( i) addPixelColor(i-1, part); + setPixelColor(i, cur); + carryover = part; + } + } + void blur2d(fract8 blur_amount) { blurRows(size.x, size.y, blur_amount);