Port of https://github.com/Vasil-Pahomov/ArWs2812 from Arduino to ESP8266 Implementing garland of WS2812 Co-authored-by: Dmitry Blinov <dblinov@blackberry.com>mcspr-patch-1
@ -1,3 +1,5 @@ | |||||
*.gz.h -diff -merge | *.gz.h -diff -merge | ||||
*.gz.h linguist-generated=true | *.gz.h linguist-generated=true | ||||
*.ini text eol=lf | *.ini text eol=lf | ||||
*.h text eol=lf | |||||
*.cpp text eol=lf |
@ -0,0 +1,484 @@ | |||||
/* | |||||
GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
Tested on 60 led strip. | |||||
!!! For more leds can cause WDT rebot. Need to be carefully tested for more than 60 leds !!! | |||||
The most time consuming operation is actually showing leds by Adafruit Neopixel. It take about 1870 mcs. | |||||
More long strip can take more time to show. | |||||
Currently animation calculation, brightness calculation/transition and showing makes in one loop cycle. | |||||
Debug output shows timings. Overal timing should be not more that 3000 ms. | |||||
For longer strips have sense to divide entire strip (pixels) on parts about 100 pixels and show one part | |||||
at a cycle. | |||||
*/ | |||||
#include "garland.h" | |||||
#if GARLAND_SUPPORT | |||||
#include <Adafruit_NeoPixel.h> | |||||
#include <vector> | |||||
#include "garland/color.h" | |||||
#include "garland/palette.h" | |||||
#include "garland/scene.h" | |||||
#include "ws.h" | |||||
const char* NAME_GARLAND_ENABLED = "garlandEnabled"; | |||||
const char* NAME_GARLAND_BRIGHTNESS = "garlandBrightness"; | |||||
const char* NAME_GARLAND_SPEED = "garlandSpeed"; | |||||
const char* NAME_GARLAND_SWITCH = "garland_switch"; | |||||
const char* NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness"; | |||||
const char* NAME_GARLAND_SET_SPEED = "garland_set_speed"; | |||||
const char* NAME_GARLAND_SET_DEFAULT = "garland_set_default"; | |||||
#define EFFECT_UPDATE_INTERVAL_MIN 5000 // 5 sec | |||||
#define EFFECT_UPDATE_INTERVAL_MAX 10000 // 10 sec | |||||
bool _garland_enabled = true; | |||||
unsigned long _last_update = 0; | |||||
unsigned long _interval_effect_update; | |||||
// Palette should | |||||
Palette pals[] = { | |||||
// palettes below are taken from http://www.color-hex.com/color-palettes/ (and modified) | |||||
// RGB: Red,Green,Blue sequence | |||||
Palette("RGB", {0xFF0000, 0x00FF00, 0x0000FF}), | |||||
// Rainbow: Rainbow colors | |||||
Palette("Rainbow", {0xFF0000, 0xAB5500, 0xABAB00, 0x00FF00, 0x00AB55, 0x0000FF, 0x5500AB, 0xAB0055}), | |||||
// RainbowStripe: Rainbow colors with alternating stripes of black | |||||
Palette("Stripe", {0xFF0000, 0x000000, 0xAB5500, 0x000000, 0xABAB00, 0x000000, 0x00FF00, 0x000000, | |||||
0x00AB55, 0x000000, 0x0000FF, 0x000000, 0x5500AB, 0x000000, 0xAB0055, 0x000000}), | |||||
// Party: Blue purple ping red orange yellow (and back). Basically, everything but the greens. | |||||
// This palette is good for lighting at a club or party. | |||||
Palette("Party", {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, 0xE81700, 0xB84700, 0xAB7700, 0xABAB00, | |||||
0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}), | |||||
// Heat: Approximate "black body radiation" palette, akin to the FastLED 'HeatColor' function. | |||||
// Recommend that you use values 0-240 rather than the usual 0-255, as the last 15 colors will be | |||||
// 'wrapping around' from the hot end to the cold end, which looks wrong. | |||||
Palette("Heat", {0x700070, 0xFF0000, 0xFFFF00, 0xFFFFCC}), | |||||
// Fire: | |||||
Palette("Fire", {0x000000, 0x220000, 0x880000, 0xFF0000, 0xFF6600, 0xFFCC00}), | |||||
// Blue: | |||||
Palette("Blue", {0xffffff, 0x0000ff, 0x00ffff}), | |||||
// Sun: Slice Of The Sun | |||||
Palette("Sun", {0xfff95b, 0xffe048, 0xffc635, 0xffad22, 0xff930f}), | |||||
// Lime: yellow green mix | |||||
Palette("Lime", {0x51f000, 0x6fff00, 0x96ff00, 0xc9ff00, 0xf0ff00}), | |||||
// Pastel: Pastel Fruity Mixture | |||||
Palette("Pastel", {0x75aa68, 0x5960ae, 0xe4be6c, 0xca5959, 0x8366ac}), | |||||
// Green: Vibrant greens | |||||
Palette("Green", {0x89ff01, 0x42c501, 0x349404, 0x0f6902, 0x004208})}; | |||||
constexpr size_t palsSize() { return sizeof(pals)/sizeof(pals[0]); } | |||||
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(GARLAND_LEDS, GARLAND_D_PIN, NEO_GRB + NEO_KHZ800); | |||||
Scene scene(&pixels); | |||||
Anim* anims[] = {new AnimStart(), new AnimPixieDust(), new AnimSparkr(), new AnimRun(), new AnimStars(), | |||||
new AnimSpread(), new AnimRandCyc(), new AnimFly(), new AnimComets(), new AnimAssemble()}; | |||||
constexpr size_t animsSize() { return sizeof(anims)/sizeof(anims[0]); } | |||||
//------------------------------------------------------------------------------ | |||||
void garlandDisable() { | |||||
pixels.clear(); | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
void garlandEnabled(bool enabled) { | |||||
_garland_enabled = enabled; | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
bool garlandEnabled() { | |||||
return _garland_enabled; | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
// Setup | |||||
//------------------------------------------------------------------------------ | |||||
void _garlandConfigure() { | |||||
_garland_enabled = getSetting(NAME_GARLAND_ENABLED, true); | |||||
DEBUG_MSG_P(PSTR("[GARLAND] _garland_enabled = %d\n"), _garland_enabled); | |||||
byte brightness = getSetting(NAME_GARLAND_BRIGHTNESS, 255); | |||||
scene.setBrightness(brightness); | |||||
DEBUG_MSG_P(PSTR("[GARLAND] brightness = %d\n"), brightness); | |||||
float speed = getSetting(NAME_GARLAND_SPEED, 50); | |||||
scene.setSpeed(speed); | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
void _garlandReload() { | |||||
_garlandConfigure(); | |||||
} | |||||
#if WEB_SUPPORT | |||||
//------------------------------------------------------------------------------ | |||||
void _garlandWebSocketOnConnected(JsonObject& root) { | |||||
root[NAME_GARLAND_ENABLED] = garlandEnabled(); | |||||
root[NAME_GARLAND_BRIGHTNESS] = scene.getBrightness(); | |||||
root[NAME_GARLAND_SPEED] = scene.getSpeed(); | |||||
root["garlandVisible"] = 1; | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) { | |||||
if (strncmp(key, NAME_GARLAND_ENABLED, strlen(NAME_GARLAND_ENABLED)) == 0) return true; | |||||
if (strncmp(key, NAME_GARLAND_BRIGHTNESS, strlen(NAME_GARLAND_BRIGHTNESS)) == 0) return true; | |||||
if (strncmp(key, NAME_GARLAND_SPEED, strlen(NAME_GARLAND_SPEED)) == 0) return true; | |||||
return false; | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
void _garlandWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) { | |||||
if (strcmp(action, NAME_GARLAND_SWITCH) == 0) { | |||||
if (data.containsKey("status") && data.is<int>("status")) { | |||||
_garland_enabled = (1 == data["status"].as<int>()); | |||||
setSetting(NAME_GARLAND_ENABLED, _garland_enabled); | |||||
if (!_garland_enabled) { | |||||
schedule_function([](){ | |||||
pixels.clear(); | |||||
pixels.show(); | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
if (strcmp(action, NAME_GARLAND_SET_BRIGHTNESS) == 0) { | |||||
if (data.containsKey("brightness")) { | |||||
byte new_brightness = data.get<byte>("brightness"); | |||||
DEBUG_MSG_P(PSTR("[GARLAND] new brightness = %d\n"), new_brightness); | |||||
setSetting(NAME_GARLAND_BRIGHTNESS, new_brightness); | |||||
scene.setBrightness(new_brightness); | |||||
} | |||||
} | |||||
if (strcmp(action, NAME_GARLAND_SET_SPEED) == 0) { | |||||
if (data.containsKey("speed")) { | |||||
byte new_speed = data.get<byte>("speed"); | |||||
DEBUG_MSG_P(PSTR("[GARLAND] new speed = %d\n"), new_speed); | |||||
setSetting(NAME_GARLAND_SPEED, new_speed); | |||||
scene.setSpeed(new_speed); | |||||
} | |||||
} | |||||
if (strcmp(action, NAME_GARLAND_SET_DEFAULT) == 0) { | |||||
scene.setDefault(); | |||||
byte brightness = scene.getBrightness(); | |||||
setSetting(NAME_GARLAND_BRIGHTNESS, brightness); | |||||
byte speed = scene.getSpeed(); | |||||
setSetting(NAME_GARLAND_SPEED, speed); | |||||
char buffer[128]; | |||||
snprintf_P(buffer, sizeof(buffer), PSTR("{\"garlandBrightness\": %d, \"garlandSpeed\": %d}"), brightness, speed); | |||||
wsSend(buffer); | |||||
} | |||||
} | |||||
#endif | |||||
//------------------------------------------------------------------------------ | |||||
// Loop | |||||
//------------------------------------------------------------------------------ | |||||
void garlandLoop(void) { | |||||
if (!garlandEnabled()) | |||||
return; | |||||
scene.run(); | |||||
unsigned long animation_time = millis() - _last_update; | |||||
if (animation_time > _interval_effect_update && scene.finishedAnimCycle()) { | |||||
_last_update = millis(); | |||||
_interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); | |||||
static int animInd = 0; | |||||
int prevAnimInd = animInd; | |||||
while (prevAnimInd == animInd) animInd = secureRandom(1, animsSize()); | |||||
static int paletteInd = 0; | |||||
int prevPalInd = paletteInd; | |||||
while (prevPalInd == paletteInd) paletteInd = secureRandom(palsSize()); | |||||
int numShows = scene.getNumShows(); | |||||
int frameRate = animation_time > 0 ? numShows * 1000 / animation_time : 0; | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s timings: calc: %4d pixl: %3d show: %4d frate: %d\n"), | |||||
anims[prevAnimInd]->name(), pals[prevPalInd].name(), | |||||
scene.getAvgCalcTime(), scene.getAvgPixlTime(), scene.getAvgShowTime(), frameRate); | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Anim: %-10s Pal: %-8s Inter: %d\n"), | |||||
anims[animInd]->name(), pals[paletteInd].name(), _interval_effect_update); | |||||
scene.setAnim(anims[animInd]); | |||||
scene.setPalette(&pals[paletteInd]); | |||||
scene.setup(); | |||||
} | |||||
} | |||||
//------------------------------------------------------------------------------ | |||||
void garlandSetup() { | |||||
_garlandConfigure(); | |||||
// Websockets | |||||
#if WEB_SUPPORT | |||||
wsRegister() | |||||
.onConnected(_garlandWebSocketOnConnected) | |||||
.onKeyCheck(_garlandWebSocketOnKeyCheck) | |||||
.onAction(_garlandWebSocketOnAction); | |||||
#endif | |||||
espurnaRegisterLoop(garlandLoop); | |||||
espurnaRegisterReload(_garlandReload); | |||||
pixels.begin(); | |||||
scene.setAnim(anims[0]); | |||||
scene.setPalette(&pals[0]); | |||||
scene.setup(); | |||||
_interval_effect_update = secureRandom(EFFECT_UPDATE_INTERVAL_MIN, EFFECT_UPDATE_INTERVAL_MAX); | |||||
} | |||||
/*####################################################################### | |||||
_____ | |||||
/ ____| | |||||
| (___ ___ ___ _ __ ___ | |||||
\___ \ / __| / _ \ | '_ \ / _ \ | |||||
____) | | (__ | __/ | | | | | __/ | |||||
|_____/ \___| \___| |_| |_| \___| | |||||
#######################################################################*/ | |||||
#define GARLAND_SCENE_TRANSITION_MS 1000 // transition time between animations, ms | |||||
#define GARLAND_SCENE_SPEED_MAX 70 | |||||
#define GARLAND_SCENE_SPEED_FACTOR 10 | |||||
#define GARLAND_SCENE_DEFAULT_SPEED 50 | |||||
#define GARLAND_SCENE_DEFAULT_BRIGHTNESS 255 | |||||
Scene::Scene(Adafruit_NeoPixel* pixels) | |||||
: _pixels(pixels), | |||||
_numLeds(pixels->numPixels()), | |||||
_leds1(_numLeds), | |||||
_leds2(_numLeds), | |||||
_ledstmp(_numLeds), | |||||
_seq(_numLeds) { | |||||
} | |||||
void Scene::setPalette(Palette* palette) { | |||||
_palette = palette; | |||||
if (setUpOnPalChange) { | |||||
setupImpl(); | |||||
} | |||||
} | |||||
void Scene::setBrightness(byte brightness) { | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Scene::setBrightness = %d\n"), brightness); | |||||
this->brightness = brightness; | |||||
} | |||||
byte Scene::getBrightness() { | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Scene::getBrightness = %d\n"), brightness); | |||||
return brightness; | |||||
} | |||||
// Speed is reverse to cycleFactor and 10x | |||||
void Scene::setSpeed(byte speed) { | |||||
this->speed = speed; | |||||
cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR; | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Scene::setSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000)); | |||||
} | |||||
byte Scene::getSpeed() { | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Scene::getSpeed %d cycleFactor = %d\n"), speed, (int)(cycleFactor * 1000)); | |||||
return speed; | |||||
} | |||||
void Scene::setDefault() { | |||||
speed = GARLAND_SCENE_DEFAULT_SPEED; | |||||
cycleFactor = (float)(GARLAND_SCENE_SPEED_MAX - speed) / GARLAND_SCENE_SPEED_FACTOR; | |||||
brightness = GARLAND_SCENE_DEFAULT_BRIGHTNESS; | |||||
DEBUG_MSG_P(PSTR("[GARLAND] Scene::setDefault speed = %d cycleFactor = %d brightness = %d\n"), speed, (int)(cycleFactor * 1000), brightness); | |||||
} | |||||
void Scene::run() { | |||||
unsigned long iteration_start_time = micros(); | |||||
if (state == Calculate || cyclesRemain < 1) { | |||||
// Calculate number of cycles for this animation iteration | |||||
float cycleSum = cycleFactor * (_anim ? _anim->getCycleFactor() : 1.0) + cycleTail; | |||||
cyclesRemain = cycleSum; | |||||
if (cyclesRemain < 1) { | |||||
cyclesRemain = 1; | |||||
cycleSum = 0; | |||||
cycleTail = 0; | |||||
} else { | |||||
cycleTail = cycleSum - cyclesRemain; | |||||
} | |||||
if (_anim) { | |||||
_anim->Run(); | |||||
} | |||||
sum_calc_time += (micros() - iteration_start_time); | |||||
iteration_start_time = micros(); | |||||
++calc_num; | |||||
state = Transition; | |||||
} | |||||
if (state == Transition && cyclesRemain < 3) { | |||||
// transition coef, if within 0..1 - transition is active | |||||
// changes from 1 to 0 during transition, so we interpolate from current | |||||
// color to previous | |||||
float transc = (float)((long)transms - (long)millis()) / GARLAND_SCENE_TRANSITION_MS; | |||||
Color* leds_prev = (_leds == &_leds1[0]) ? &_leds2[0] : &_leds1[0]; | |||||
if (transc > 0) { | |||||
for (int i = 0; i < _numLeds; i++) { | |||||
// transition is in progress | |||||
Color c = _leds[i].interpolate(leds_prev[i], transc); | |||||
byte r = (int)(bri_lvl[c.r]) * brightness / 256; | |||||
byte g = (int)(bri_lvl[c.g]) * brightness / 256; | |||||
byte b = (int)(bri_lvl[c.b]) * brightness / 256; | |||||
_pixels->setPixelColor(i, _pixels->Color(r, g, b)); | |||||
} | |||||
} else { | |||||
for (int i = 0; i < _numLeds; i++) { | |||||
// regular operation | |||||
byte r = (int)(bri_lvl[_leds[i].r]) * brightness / 256; | |||||
byte g = (int)(bri_lvl[_leds[i].g]) * brightness / 256; | |||||
byte b = (int)(bri_lvl[_leds[i].b]) * brightness / 256; | |||||
_pixels->setPixelColor(i, _pixels->Color(r, g, b)); | |||||
} | |||||
} | |||||
sum_pixl_time += (micros() - iteration_start_time); | |||||
iteration_start_time = micros(); | |||||
++pixl_num; | |||||
state = Show; | |||||
} | |||||
if (state == Show && cyclesRemain < 2) { | |||||
_pixels->show(); | |||||
sum_show_time += (micros() - iteration_start_time); | |||||
++show_num; | |||||
state = Calculate; | |||||
++numShows; | |||||
} | |||||
--cyclesRemain; | |||||
} | |||||
void Scene::setupImpl() { | |||||
transms = millis() + GARLAND_SCENE_TRANSITION_MS; | |||||
// switch operation buffers (for transition to operate) | |||||
if (_leds == &_leds1[0]) { | |||||
_leds = &_leds2[0]; | |||||
} else { | |||||
_leds = &_leds1[0]; | |||||
} | |||||
if (_anim) { | |||||
_anim->Setup(_palette, _numLeds, _leds, &_ledstmp[0], &_seq[0]); | |||||
} | |||||
} | |||||
void Scene::setup() { | |||||
sum_calc_time = 0; | |||||
sum_pixl_time = 0; | |||||
sum_show_time = 0; | |||||
calc_num = 0; | |||||
pixl_num = 0; | |||||
show_num = 0; | |||||
numShows = 0; | |||||
if (!setUpOnPalChange) { | |||||
setupImpl(); | |||||
} | |||||
} | |||||
unsigned long Scene::getAvgCalcTime() { return sum_calc_time / calc_num; } | |||||
unsigned long Scene::getAvgPixlTime() { return sum_pixl_time / pixl_num; } | |||||
unsigned long Scene::getAvgShowTime() { return sum_show_time / show_num; } | |||||
/*####################################################################### | |||||
_ _ _ | |||||
/\ (_) | | (_) | |||||
/ \ _ __ _ _ __ ___ __ _ | |_ _ ___ _ __ | |||||
/ /\ \ | '_ \ | | | '_ ` _ \ / _` | | __| | | / _ \ | '_ \ | |||||
/ ____ \ | | | | | | | | | | | | | (_| | | |_ | | | (_) | | | | | | |||||
/_/ \_\ |_| |_| |_| |_| |_| |_| \__,_| \__| |_| \___/ |_| |_| | |||||
#######################################################################*/ | |||||
Anim::Anim(const char* name) : _name(name) {} | |||||
void Anim::Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* ledstmp, byte* seq) { | |||||
this->palette = palette; | |||||
this->numLeds = numLeds; | |||||
this->leds = leds; | |||||
this->ledstmp = ledstmp; | |||||
this->seq = seq; | |||||
SetupImpl(); | |||||
} | |||||
void Anim::initSeq() { | |||||
for (int i = 0; i < numLeds; i++) | |||||
seq[i] = i; | |||||
} | |||||
void Anim::shuffleSeq() { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
byte ind = (unsigned int)(rngb() * numLeds / 256); | |||||
if (ind != i) { | |||||
std::swap(seq[ind], seq[i]); | |||||
} | |||||
} | |||||
} | |||||
void Anim::glowSetUp() { | |||||
braPhaseSpd = secureRandom(4, 13); | |||||
if (braPhaseSpd > 8) { | |||||
braPhaseSpd = braPhaseSpd - 17; | |||||
} | |||||
braFreq = secureRandom(20, 60); | |||||
} | |||||
void Anim::glowForEachLed(int i) { | |||||
int8 bra = braPhase + i * braFreq; | |||||
bra = BRA_OFFSET + (abs(bra) >> BRA_AMP_SHIFT); | |||||
leds[i] = leds[i].brightness(bra); | |||||
} | |||||
void Anim::glowRun() { braPhase += braPhaseSpd; } | |||||
bool operator== (const Color &c1, const Color &c2) | |||||
{ | |||||
return (c1.r == c2.r && c1.g == c2.g && c1.b == c2.b); | |||||
} | |||||
unsigned int rng() { | |||||
static unsigned int y = 0; | |||||
y += micros(); // seeded with changing number | |||||
y ^= y << 2; | |||||
y ^= y >> 7; | |||||
y ^= y << 7; | |||||
return (y); | |||||
} | |||||
// Ranom numbers generator in byte range (256) much faster than secureRandom. | |||||
// For usage in time-critical places. | |||||
byte rngb() { return (byte)rng(); } | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,16 @@ | |||||
/* | |||||
GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
*/ | |||||
#pragma once | |||||
#include "espurna.h" | |||||
#if GARLAND_SUPPORT | |||||
void garlandSetup(); | |||||
#endif |
@ -0,0 +1,76 @@ | |||||
/* | |||||
Part of the GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
*/ | |||||
#pragma once | |||||
#include "color.h" | |||||
#define BRA_AMP_SHIFT 1 // brigthness animation amplitude shift. true BrA amplitude is calculated | |||||
// as (0..127) value shifted right by this amount | |||||
#define BRA_OFFSET 127 //(222-64) // brigthness animation amplitude offset | |||||
class Palette; | |||||
class Anim { | |||||
public: | |||||
Anim(const char* name); | |||||
const char* name() { return _name; } | |||||
void Setup(Palette* palette, uint16_t numLeds, Color* leds, Color* _ledstmp, byte* seq); | |||||
virtual bool finishedycle() const { return true; }; | |||||
virtual void Run() = 0; | |||||
virtual void setCycleFactor(float new_cycle_factor) { cycleFactor = new_cycle_factor; } | |||||
virtual float getCycleFactor() { return cycleFactor; } | |||||
protected: | |||||
uint16_t numLeds = 0; | |||||
Palette* palette = nullptr; | |||||
Color* leds = nullptr; | |||||
Color* ledstmp = nullptr; | |||||
byte* seq = nullptr; | |||||
int phase; | |||||
int pos; | |||||
int inc; | |||||
//brigthness animation (BrA) current initial phase | |||||
byte braPhase; | |||||
//braPhase change speed | |||||
byte braPhaseSpd = 5; | |||||
//BrA frequency (spatial) | |||||
byte braFreq = 150; | |||||
Color curColor = Color(0); | |||||
Color prevColor = Color(0); | |||||
const Color sparkleColor = Color(0xFFFFFF); | |||||
// Reversed analog of speed. To control speed for particular animation (fine tuning for one). | |||||
// Lower - faster. Set cycleFactor < 1 speed up animation, while cycleFactor > 1 slow it down. | |||||
float cycleFactor = 1.0; | |||||
virtual void SetupImpl() = 0; | |||||
// helper functions for animations | |||||
void initSeq(); | |||||
void shuffleSeq(); | |||||
//glow animation setup | |||||
void glowSetUp(); | |||||
//glow animation - must be called for each LED after it's BASIC color is set | |||||
//note this overwrites the LED color, so the glow assumes that color will be stored elsewhere (not in leds[]) | |||||
//or computed each time regardless previous leds[] value | |||||
void glowForEachLed(int i); | |||||
//glow animation - must be called at the end of each animaton run | |||||
void glowRun(); | |||||
private: | |||||
const char* _name; | |||||
}; | |||||
unsigned int rng(); | |||||
byte rngb(); |
@ -0,0 +1,54 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include <list> | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimAssemble : public Anim { | |||||
public: | |||||
AnimAssemble() : Anim("Assemble") { | |||||
cycleFactor = 2; | |||||
} | |||||
void SetupImpl() override { | |||||
inc = 1 + (rngb() >> 5); | |||||
if (secureRandom(10) > 5) { | |||||
inc = -inc; | |||||
} | |||||
int p = 0; | |||||
for (int i = 0; i < numLeds; i++) { | |||||
leds[i] = 0; | |||||
Color c = palette->getCachedPalColor((byte)p); | |||||
ledstmp[i] = c; | |||||
p = p + inc; | |||||
if (p >= 256) { | |||||
p = p - 256; | |||||
} else if (p < 0) { | |||||
p = p + 256; | |||||
} | |||||
} | |||||
initSeq(); | |||||
shuffleSeq(); | |||||
pos = 0; | |||||
} | |||||
void Run() override { | |||||
if (pos < numLeds) { | |||||
byte cur_point = seq[pos]; | |||||
leds[cur_point] = ledstmp[cur_point]; | |||||
++pos; | |||||
} else { | |||||
int del_pos = pos - numLeds; | |||||
byte cur_point = seq[del_pos]; | |||||
leds[cur_point] = 0; | |||||
if (++pos >= numLeds * 2) | |||||
pos = 0; | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,66 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include <list> | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimComets : public Anim { | |||||
public: | |||||
AnimComets() : Anim("Comets") { | |||||
} | |||||
void SetupImpl() override { | |||||
comets.clear(); | |||||
for (int i = 0; i < 4; ++i) | |||||
comets.emplace_back(palette, numLeds); | |||||
} | |||||
void Run() override { | |||||
for (int i = 0; i < numLeds; i++) leds[i] = 0; | |||||
for (auto& c : comets) { | |||||
int tail = c.head + c.len * -c.dir; | |||||
// Check if Comet out of range and generate it again | |||||
if ((c.head < 0 && tail < 0) || (c.head >= numLeds && tail >= numLeds)) { | |||||
Comet new_comet(palette, numLeds); | |||||
std::swap(c, new_comet); | |||||
} | |||||
for (int l = 0; l < c.len; ++l) { | |||||
int p = c.head + l * -c.dir; | |||||
if (p >= 0 && p < numLeds) { | |||||
leds[p] = c.points[l]; | |||||
} | |||||
} | |||||
c.head = c.head + c.speed * c.dir; | |||||
} | |||||
} | |||||
private: | |||||
struct Comet { | |||||
float head; | |||||
int len = secureRandom(10, 20); | |||||
float speed = ((float)secureRandom(4, 10)) / 10; | |||||
Color color; | |||||
int dir = 1; | |||||
std::vector<Color> points; | |||||
Comet(Palette* pal, uint16_t numLeds) : head(secureRandom(0, numLeds / 2)), color(pal->getRndInterpColor()), points(len) { | |||||
// DEBUG_MSG_P(PSTR("[GARLAND] Comet created head = %d len = %d speed = %g cr = %d cg = %d cb = %d\n"), head, len, speed, color.r, color.g, color.b); | |||||
if (secureRandom(10) > 5) { | |||||
head = numLeds - head; | |||||
dir = -1; | |||||
} | |||||
for (int i = 0; i < len; ++i) { | |||||
points[i] = Color((byte)(color.r * (len - i) / len), (byte)(color.g * (len - i) / len), (byte)(color.b * (len - i) / len)); | |||||
} | |||||
} | |||||
}; | |||||
std::list<Comet> comets; | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,48 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimFly : public Anim { | |||||
public: | |||||
AnimFly() : Anim("Fly") { | |||||
} | |||||
void SetupImpl() override { | |||||
//length of particle tail | |||||
pos = secureRandom(2, 15); | |||||
//probability of the tail | |||||
inc = secureRandom(5, 15); | |||||
if (secureRandom(10) > 5) { | |||||
inc = -inc; | |||||
} | |||||
phase = 0; | |||||
} | |||||
void Run() override { | |||||
byte launchpos; | |||||
if (inc > 0) { | |||||
launchpos = numLeds - 1; | |||||
for (int i = 1; i < numLeds; i++) { | |||||
leds[i - 1] = leds[i]; | |||||
} | |||||
} else { | |||||
launchpos = 0; | |||||
for (int i = numLeds - 2; i >= 0; i--) { | |||||
leds[i + 1] = leds[i]; | |||||
} | |||||
} | |||||
if (secureRandom(abs(inc)) == 0) { | |||||
curColor = palette->getRndInterpColor(); | |||||
phase = pos; | |||||
} | |||||
leds[launchpos] = Color((int)curColor.r * phase / pos, (int)curColor.g * phase / pos, (int)curColor.b * phase / pos); | |||||
if (phase > 0) phase--; | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,68 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
#define DUST_LENGTH 20 | |||||
//------------------------------------------------------------------------------ | |||||
class AnimPixieDust : public Anim { | |||||
public: | |||||
AnimPixieDust() : Anim("PixieDust") { | |||||
} | |||||
void SetupImpl() override { | |||||
phase = 0; | |||||
curColor = palette->getRndInterpColor(); | |||||
prevColor = palette->getRndInterpColor(); | |||||
inc = secureRandom(2) * 2 - 1; | |||||
if (inc > 0) { | |||||
phase = -DUST_LENGTH / 2; | |||||
} else { | |||||
phase = numLeds + DUST_LENGTH / 2; | |||||
} | |||||
glowSetUp(); | |||||
} | |||||
void Run() override { | |||||
if (inc > 0) { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
leds[i] = (i > phase) ? prevColor : curColor; | |||||
glowForEachLed(i); | |||||
} | |||||
phase++; | |||||
if (phase >= 4 * numLeds) { | |||||
phase = -DUST_LENGTH / 2; | |||||
prevColor = curColor; | |||||
curColor = palette->getRndInterpColor(); | |||||
} | |||||
} else { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
leds[i] = (i < phase) ? prevColor : curColor; | |||||
glowForEachLed(i); | |||||
} | |||||
phase--; | |||||
if (phase <= -3 * numLeds) { | |||||
phase = numLeds + DUST_LENGTH / 2; | |||||
prevColor = curColor; | |||||
curColor = palette->getContrastColor(prevColor); | |||||
} | |||||
} | |||||
glowRun(); | |||||
for (int k = phase - DUST_LENGTH / 2; k < (phase + DUST_LENGTH / 2); k++) { | |||||
if (k >= 0 && k < numLeds) { | |||||
int mix = abs(k - phase) * 255 / DUST_LENGTH + ((int)rngb() - 125); | |||||
if (mix < 0) { | |||||
mix = 0; | |||||
} else if (mix > 255) { | |||||
mix = 255; | |||||
} | |||||
leds[k] = sparkleColor.interpolate(leds[k], (float)mix / 255); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,25 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../palette.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimRandCyc : public Anim { | |||||
public: | |||||
AnimRandCyc() : Anim("RandCyc") { | |||||
} | |||||
void SetupImpl() override { | |||||
for (int i = 0; i < numLeds; i++) | |||||
seq[i] = rngb(); | |||||
} | |||||
void Run() override { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
leds[i] = palette->getCachedPalColor(seq[i]); | |||||
seq[i] += rngb() >> 6; | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,38 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimRun : public Anim { | |||||
public: | |||||
AnimRun() : Anim("Run") { | |||||
} | |||||
void SetupImpl() override { | |||||
pos = 0; | |||||
inc = 1 + (rngb() >> 5); | |||||
if (secureRandom(10) > 5) { | |||||
inc = -inc; | |||||
} | |||||
} | |||||
void Run() override { | |||||
int p = pos; | |||||
for (int i = 0; i < numLeds; i++) { | |||||
Color c = palette->getCachedPalColor((byte)p); | |||||
leds[i] = c; | |||||
p = p + inc; | |||||
if (p >= 256) { | |||||
p = p - 256; | |||||
} else if (p < 0) { | |||||
p = p + 256; | |||||
} | |||||
} | |||||
pos = pos + 1; | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,51 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
#define SPARK_PROB 3 //probability of spark when in idle plase | |||||
//------------------------------------------------------------------------------ | |||||
class AnimSparkr : public Anim { | |||||
public: | |||||
AnimSparkr() : Anim("Sparkr") { | |||||
} | |||||
void SetupImpl() override { | |||||
glowSetUp(); | |||||
phase = 0; | |||||
curColor = palette->getRndInterpColor(); | |||||
prevColor = palette->getRndInterpColor(); | |||||
initSeq(); | |||||
shuffleSeq(); | |||||
} | |||||
void Run() override { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
byte pos = seq[i]; | |||||
leds[pos] = (i > phase) ? prevColor | |||||
: (i == phase) ? sparkleColor | |||||
: curColor; | |||||
glowForEachLed(i); | |||||
} | |||||
glowRun(); | |||||
if (phase > numLeds) { | |||||
if (secureRandom(SPARK_PROB) == 0) { | |||||
int i = (int)rngb() * numLeds / 256; | |||||
leds[i] = sparkleColor; | |||||
} | |||||
} | |||||
phase++; | |||||
if (phase > 2 * numLeds) { | |||||
phase = 0; | |||||
prevColor = curColor; | |||||
curColor = palette->getContrastColor(prevColor); | |||||
shuffleSeq(); | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,73 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
#include "debug.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimSpread : public Anim { | |||||
const byte minWidth = 10; | |||||
const byte maxWidth = 15; | |||||
const byte numTries = 5; | |||||
const byte fade_ind = 255 / minWidth; | |||||
int GeneratePos() { | |||||
int pos = -1; | |||||
for (int i = 0; i < numTries; ++i) { | |||||
pos = secureRandom(0, numLeds); | |||||
for (int j = pos - maxWidth; j < pos + maxWidth; ++j) { | |||||
if (j >= 0 && j < numLeds && seq[j] > 0) { | |||||
pos = -1; | |||||
break; | |||||
} | |||||
} | |||||
if (pos >= 0) break; | |||||
} | |||||
return pos; | |||||
} | |||||
public: | |||||
AnimSpread() : Anim("Spread") { | |||||
cycleFactor = 2; | |||||
} | |||||
void SetupImpl() override { | |||||
inc = secureRandom(2, 4); | |||||
// DEBUG_MSG_P(PSTR("[GARLAND] AnimSpread inc = %d\n"), inc); | |||||
for (int i = 0; i < numLeds; i++) | |||||
seq[i] = 0; | |||||
} | |||||
void Run() override { | |||||
for (int i = 0; i < numLeds; i++) | |||||
leds[i] = 0; | |||||
for (int i = 0; i < numLeds; i++) { | |||||
if (seq[i] > 0) { | |||||
byte width = maxWidth - seq[i]; | |||||
for (int j = i - width; j <= (i + width); j++) { | |||||
Color c = ledstmp[i]; | |||||
if (j >= 0 && j < numLeds) { | |||||
leds[j].r += c.r; | |||||
leds[j].g += c.g; | |||||
leds[j].b += c.b; | |||||
} | |||||
} | |||||
ledstmp[i].fade(fade_ind); | |||||
seq[i]--; | |||||
} | |||||
} | |||||
if (secureRandom(inc) == 0) { | |||||
int pos = GeneratePos(); | |||||
if (pos == -1) | |||||
return; | |||||
ledstmp[pos] = palette->getRndInterpColor(); | |||||
seq[pos] = secureRandom(minWidth, maxWidth); | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,53 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
#include "../color.h" | |||||
#include "../palette.h" | |||||
//seq keeps phases: 0..127 increasing, 128..255 decreasing, ends at 255 (steady off) | |||||
//ledstmp keeps color of stars | |||||
//------------------------------------------------------------------------------ | |||||
class AnimStars : public Anim { | |||||
public: | |||||
AnimStars() : Anim("Stars") { | |||||
} | |||||
void SetupImpl() override { | |||||
//inc is (average) interval between appearance of new stars | |||||
inc = secureRandom(2, 5); | |||||
//reset all phases | |||||
for (int i = 0; i < numLeds; i++) | |||||
seq[i] = 255; | |||||
} | |||||
void Run() override { | |||||
for (byte i = 0; i < numLeds; i++) { | |||||
byte phi = seq[i]; | |||||
if (phi < 254) { | |||||
Color col = ledstmp[i]; | |||||
if (phi <= 127) { | |||||
leds[i] = col.brightness(phi << 1); | |||||
} else { | |||||
leds[i] = col.brightness((255 - phi) << 1); | |||||
} | |||||
seq[i] += 2; | |||||
} else { | |||||
leds[i].r = 0; | |||||
leds[i].g = 0; | |||||
leds[i].b = 0; | |||||
} | |||||
} | |||||
if (secureRandom(inc) == 0) { | |||||
byte pos = secureRandom(numLeds); | |||||
if (seq[pos] > 250) { | |||||
seq[pos] = 0; | |||||
ledstmp[pos] = palette->getRndInterpColor(); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,41 @@ | |||||
#if GARLAND_SUPPORT | |||||
#include "../anim.h" | |||||
//------------------------------------------------------------------------------ | |||||
class AnimStart : public Anim { | |||||
public: | |||||
AnimStart() : Anim("Start") { | |||||
} | |||||
void SetupImpl() override { | |||||
phase = 0; | |||||
} | |||||
void Run() override { | |||||
if (phase < numLeds) { | |||||
leds[phase].r = 255; | |||||
leds[phase].g = 255; | |||||
leds[phase].b = 255; | |||||
for (int i = 0; i < numLeds; i++) { | |||||
leds[i].fade(50); | |||||
} | |||||
} else if (phase >= numLeds) { | |||||
for (int i = 0; i < numLeds; i++) { | |||||
short r = numLeds + 255 - phase + rngb(); | |||||
r = min(r, (short)255); | |||||
leds[i].r = (byte)max(r, (short)0); | |||||
short g = numLeds + 255 - phase + rngb(); | |||||
g = min(g, (short)255); | |||||
leds[i].g = (byte)max(g, (short)0); | |||||
short b = numLeds + 255 - phase + rngb(); | |||||
b = min(b, (short)255); | |||||
leds[i].b = (byte)max(b, (short)0); | |||||
} | |||||
phase++; | |||||
} | |||||
phase++; | |||||
} | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,87 @@ | |||||
/* | |||||
Part of the GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
*/ | |||||
#pragma once | |||||
#if GARLAND_SUPPORT | |||||
#include <Arduino.h> | |||||
struct Color | |||||
{ | |||||
byte r; | |||||
byte g; | |||||
byte b; | |||||
inline Color() : r(0), g(0), b(0) {} | |||||
// allow construction from R, G, B | |||||
inline Color(uint8_t ir, uint8_t ig, uint8_t ib) | |||||
: r(ir), g(ig), b(ib) { | |||||
} | |||||
// allow construction from 32-bit (really 24-bit) bit 0xRRGGBB color code | |||||
inline Color(uint32_t colorcode) | |||||
: r((colorcode >> 16) & 0xFF), g((colorcode >> 8) & 0xFF), b((colorcode >> 0) & 0xFF) { | |||||
} | |||||
//interpolates between this color and provided. | |||||
//x is from 0 to 1, 0 gives this color, 1 gives provided color, values between give interpolation | |||||
Color interpolate(Color color, float x) const { | |||||
int r0 = x * (color.r - r) + r; | |||||
int g0 = x * (color.g - g) + g; | |||||
int b0 = x * (color.b - b) + b; | |||||
return Color(r0, g0, b0); | |||||
} | |||||
//creates color with decreased brightness | |||||
Color brightness(byte k) const { | |||||
int r0 = r * (int)k / 255; | |||||
int g0 = g * (int)k / 255; | |||||
int b0 = b * (int)k / 255; | |||||
return Color(r0, g0, b0); | |||||
} | |||||
//fades (decreases all RGB channels brightness) this color by k | |||||
void fade(byte k) { | |||||
if (r>=k) { r=r-k; } else { r=0; } | |||||
if (g>=k) { g=g-k; } else { g=0; } | |||||
if (b>=k) { b=b-k; } else { b=0; } | |||||
} | |||||
//fades color separately for each channel | |||||
void fade3(byte dr, byte dg, byte db) { | |||||
if (r>=dr) { r=r-dr; } else { r=0; } | |||||
if (g>=dg) { g=g-dg; } else { g=0; } | |||||
if (b>=db) { b=b-db; } else { b=0; } | |||||
} | |||||
//checks whether this color is visually close to given one | |||||
bool isCloseTo(Color c) const { | |||||
int diff = abs(r - c.r) + abs(g - c.g) + abs(b - c.b); | |||||
return diff <= 220; //220 is magic number. Low values give "true" on closer colors, while higher can cause infinite loop while trying to find different color | |||||
} | |||||
//return value, that can be used to define how close one color to another | |||||
int howCloseTo(Color c) const { | |||||
return abs(r - c.r) + abs(g - c.g) + abs(b - c.b); | |||||
} | |||||
bool empty() const { | |||||
return r == 0 && g == 0 && b == 0; | |||||
} | |||||
void println() const { | |||||
Serial.print(("r="));Serial.print(r);Serial.print((" ")); | |||||
Serial.print(("g="));Serial.print(g);Serial.print( (" ")); | |||||
Serial.print(("b="));Serial.println(b); | |||||
} | |||||
friend bool operator== (const Color &c1, const Color &c2); | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,95 @@ | |||||
/* | |||||
Part of the GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
*/ | |||||
#pragma once | |||||
#if GARLAND_SUPPORT | |||||
#include <vector> | |||||
#include "color.h" | |||||
class Palette { | |||||
public: | |||||
Palette(const char* name, std::vector<Color>&& colors) : _name(name), _numColors(colors.size()), _colors(std::move(colors)), _cache(256) { | |||||
} | |||||
const char* name() const { return _name; } | |||||
/** | |||||
* Get the interpolated color from the palette. | |||||
* The argument is a floating number between 0 and 1 | |||||
* Used to smoothly traverse through palette. | |||||
*/ | |||||
Color getPalColor(float i) const { | |||||
int i0 = (int)(i * _numColors) % (_numColors); | |||||
int i1 = (int)(i * _numColors + 1) % (_numColors); | |||||
// decimal part is used to interpolate between the two colors | |||||
float t0 = i * _numColors - trunc(i * _numColors); | |||||
return _colors[i0].interpolate(_colors[i1], t0); | |||||
} | |||||
Color getCachedPalColor(byte i) { | |||||
if (!_cache[i].empty()) | |||||
return _cache[i]; | |||||
Color col = getPalColor((float)i / 256); | |||||
if (col.empty()) | |||||
col = 1; | |||||
_cache[i] = col; | |||||
return col; | |||||
} | |||||
/** | |||||
* Get the interpolated color between two random neighbour colors. | |||||
*/ | |||||
Color getRndNeighborInterpColor() const { | |||||
int i0 = secureRandom(_numColors); | |||||
int i1 = (i0 + 1) % (_numColors); | |||||
float t0 = (float)(secureRandom(256)) / 256; | |||||
return _colors[i0].interpolate(_colors[i1], t0); | |||||
} | |||||
/** | |||||
* Get the interpolated color between two random colors. | |||||
*/ | |||||
Color getRndInterpColor() const { | |||||
int i0 = secureRandom(_numColors); | |||||
int i1 = secureRandom(_numColors); | |||||
float t0 = (float)(secureRandom(256)) / 256; | |||||
return _colors[i0].interpolate(_colors[i1], t0); | |||||
} | |||||
Color getContrastColor(const Color& prevColor) const { | |||||
int tries = 0; | |||||
int bestDiff = 0; | |||||
Color bestColor; | |||||
// 220 is magic number. Low values give "true" on closer colors, while higher can cause infinite loop while trying to find different color | |||||
// let's try to find contras enough color but no more than 10 tries | |||||
while (bestDiff <= 220 && tries++ < 10) { | |||||
Color newColor = getRndInterpColor(); | |||||
int diff = prevColor.howCloseTo(newColor); | |||||
if (bestDiff < diff) { | |||||
bestDiff = diff; | |||||
bestColor = newColor; | |||||
} | |||||
} | |||||
return bestColor; | |||||
} | |||||
private: | |||||
const char* _name; | |||||
const int _numColors; | |||||
std::vector<Color> _colors; | |||||
std::vector<Color> _cache; | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1,111 @@ | |||||
/* | |||||
Part of the GARLAND MODULE | |||||
Copyright (C) 2020 by Dmitry Blinov <dblinov76 at gmail dot com> | |||||
Inspired by https://github.com/Vasil-Pahomov/ArWs2812 (currently https://github.com/Vasil-Pahomov/Liana) | |||||
*/ | |||||
#pragma once | |||||
#if GARLAND_SUPPORT | |||||
#include <array> | |||||
#include <vector> | |||||
#include "anim.h" | |||||
#include "animations/anim_assemble.h" | |||||
#include "animations/anim_comets.h" | |||||
#include "animations/anim_fly.h" | |||||
#include "animations/anim_pixiedust.h" | |||||
#include "animations/anim_randcyc.h" | |||||
#include "animations/anim_run.h" | |||||
#include "animations/anim_sparkr.h" | |||||
#include "animations/anim_spread.h" | |||||
#include "animations/anim_stars.h" | |||||
#include "animations/anim_start.h" | |||||
class Adafruit_NeoPixel; | |||||
class Palette; | |||||
class Scene { | |||||
public: | |||||
Scene(Adafruit_NeoPixel* pixels); | |||||
void setPalette(Palette* palette); | |||||
void setBrightness(byte brightness); | |||||
byte getBrightness(); | |||||
void setSpeed(byte speed); | |||||
byte getSpeed(); | |||||
void setDefault(); | |||||
void setAnim(Anim* anim) { _anim = anim; } | |||||
void run(); | |||||
void setup(); | |||||
bool finishedAnimCycle() { return _anim ? _anim->finishedycle() : true; } | |||||
unsigned long getAvgCalcTime(); | |||||
unsigned long getAvgPixlTime(); | |||||
unsigned long getAvgShowTime(); | |||||
int getNumShows() { return numShows; } | |||||
private: | |||||
Adafruit_NeoPixel* _pixels = nullptr; | |||||
uint16_t _numLeds; | |||||
//Color arrays - two for making transition | |||||
std::vector<Color> _leds1; | |||||
std::vector<Color> _leds2; | |||||
// array of Colorfor anim to currently work with | |||||
Color* _leds = nullptr; | |||||
Anim* _anim = nullptr; | |||||
//auxiliary colors array for mutual usage of anims | |||||
std::vector<Color> _ledstmp; | |||||
std::vector<byte> _seq; | |||||
Palette* _palette = nullptr; | |||||
// millis to transition end | |||||
unsigned long transms; | |||||
byte brightness = 0; | |||||
// Reverse to speed. If more convenient to calculate in this way. | |||||
// 1 < cycleFactor < 4 | |||||
byte speed = 50; | |||||
float cycleFactor = 2.0; | |||||
float cycleTail = 0; | |||||
int cyclesRemain = 0; | |||||
enum State { | |||||
Calculate, | |||||
Transition, | |||||
Show | |||||
} state = Calculate; | |||||
int numShows = 0; | |||||
//whether to call SetUp on palette change | |||||
//(some animations require full transition with fade, otherwise the colors would change in a step, some not) | |||||
bool setUpOnPalChange = true; | |||||
unsigned long sum_calc_time = 0; | |||||
unsigned long sum_pixl_time = 0; | |||||
unsigned long sum_show_time = 0; | |||||
unsigned int calc_num = 0; | |||||
unsigned int show_num = 0; | |||||
unsigned int pixl_num = 0; | |||||
std::array<byte, 256> bri_lvl = {{0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, | |||||
4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 10, | |||||
10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 17, 17, | |||||
17, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, | |||||
29, 30, 30, 31, 31, 32, 32, 33, 34, 34, 35, 35, 36, 37, 37, 38, 39, 39, 40, 41, 42, 42, 43, 44, 45, 45, 46, | |||||
47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, | |||||
74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 89, 90, 91, 93, 94, 96, 98, 99, 101, 102, 104, 106, 108, 109, | |||||
111, 113, 115, 117, 119, 121, 122, 124, 126, 129, 131, 133, 135, 137, 139, 142, 144, 146, 149, 151, 153, 156, | |||||
158, 161, 163, 166, 169, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 208, 211, 214, 218, 221, | |||||
225, 228, 232, 236, 239, 243, 247, 251, 255}}; | |||||
void setupImpl(); | |||||
}; | |||||
#endif // GARLAND_SUPPORT |
@ -0,0 +1 @@ | |||||
#define GARLAND_SUPPORT 1 |