2019-05-22 04:52:41 +00:00
|
|
|
class Effect {
|
2019-05-23 19:18:15 +00:00
|
|
|
protected:
|
|
|
|
Window window = {0, 0, LED_WIDTH, LED_HEIGHT}; // Use a full screen window per default.
|
2019-05-22 04:52:41 +00:00
|
|
|
public:
|
2019-05-23 19:18:15 +00:00
|
|
|
virtual void loop() = 0;
|
|
|
|
boolean supports_window = false;
|
2019-05-23 20:14:15 +00:00
|
|
|
virtual boolean can_be_shown_with_clock() { return false; };
|
|
|
|
virtual boolean clock_as_mask() { return false; };
|
2019-05-23 19:18:15 +00:00
|
|
|
void setWindow(Window win) {
|
|
|
|
window = win;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
struct EffectEntry {
|
|
|
|
char* name;
|
|
|
|
Effect* effect;
|
2019-05-22 04:52:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class Bell : public Effect {
|
|
|
|
private:
|
2019-05-23 19:18:15 +00:00
|
|
|
|
2019-05-22 04:52:41 +00:00
|
|
|
CRGB color_on = CRGB(0xFFFF00);
|
2019-05-23 19:18:15 +00:00
|
|
|
CRGB color_off = CRGB(0x000000);
|
2019-05-22 04:52:41 +00:00
|
|
|
boolean invert = false;
|
|
|
|
|
|
|
|
public:
|
|
|
|
void loop() {
|
2019-05-23 19:18:15 +00:00
|
|
|
Serial.println("This is Bell.loop()");
|
|
|
|
for (int y = 0; y < 16; y++) {
|
|
|
|
for (int x = 0; x < 2; x++) {
|
|
|
|
for (int z = 0; z < 8; z++) {
|
|
|
|
leds[XYsafe(x * 8 + z, y)] = sprite_bell[y * 2 + x] >> (7 - z) & 1 ^ invert ? color_on : color_off;
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EVERY_N_MILLISECONDS(300) {
|
|
|
|
invert = !invert;
|
|
|
|
}
|
2019-05-23 19:18:15 +00:00
|
|
|
}
|
2019-05-22 04:52:41 +00:00
|
|
|
};
|
|
|
|
|
2019-05-23 19:18:15 +00:00
|
|
|
class BigClock : public Effect {
|
2019-05-22 04:52:41 +00:00
|
|
|
private:
|
|
|
|
CRGB color_h = CRGB(0xFF0000);
|
|
|
|
CRGB color_m = CRGB(0x00FF00);
|
|
|
|
CRGB color_colon = CRGB(0xFFFF00);
|
|
|
|
|
|
|
|
void drawNumber(uint8_t number, int x, int y, CRGB color) {
|
|
|
|
char buffer[7];
|
|
|
|
sprintf(buffer, "%02d", number);
|
|
|
|
drawText(buffer, x, y, color);
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawText(char *text, int x, int y, CRGB color) {
|
|
|
|
for (int i = 0; i < strlen(text); i++) {
|
|
|
|
drawSprite(font_char(numbers4x7, text[i]), x + i * 4, y, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char* font_char(unsigned char* font, char c) {
|
|
|
|
return &font[(c - 48) * 4];
|
|
|
|
}
|
|
|
|
|
|
|
|
void drawSprite(unsigned char* sprite, int xOffset, int yOffset, CRGB color) {
|
|
|
|
for ( byte y = 0; y < 7; y++) {
|
|
|
|
for ( byte x = 0; x < 4; x++) {
|
|
|
|
bool on = (sprite[x] >> y & 1) * 255;
|
|
|
|
if (on) {
|
|
|
|
leds[ XYsafe(x + xOffset, y + yOffset) ] = color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public:
|
2019-05-23 19:18:15 +00:00
|
|
|
BigClock() {}
|
2019-05-22 04:52:41 +00:00
|
|
|
void loop() {
|
|
|
|
clear();
|
|
|
|
drawNumber(ntpClient.getHours(), 0, 0, color_h);
|
|
|
|
drawNumber(ntpClient.getMinutes(), 8, 0, color_m);
|
|
|
|
/*if (ntpClient.getSeconds() & 1) {
|
|
|
|
leds[XYsafe(13, 2)] = color_colon;
|
|
|
|
leds[XYsafe(13, 5)] = color_colon;
|
|
|
|
}*/
|
|
|
|
drawNumber(ntpClient.getSeconds(), 8, 8, color_colon);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-23 19:18:15 +00:00
|
|
|
class Clock : public Effect {
|
2019-05-22 04:52:41 +00:00
|
|
|
private:
|
2019-05-24 03:13:07 +00:00
|
|
|
Window window = {0, LED_HEIGHT - 6, LED_WIDTH, 6};
|
2019-05-22 04:52:41 +00:00
|
|
|
|
|
|
|
public:
|
2019-05-23 19:18:15 +00:00
|
|
|
Clock() {}
|
2019-05-23 20:14:15 +00:00
|
|
|
void loop() { loop(false, CRGB(0xFFFFFF), CRGB(0x000000)); }
|
|
|
|
void loop(boolean invert, CRGB fg_color, CRGB bg_color) {
|
|
|
|
if (!invert) {
|
|
|
|
clear(window, bg_color);
|
|
|
|
} else {
|
|
|
|
// Manually clear the needed parts
|
2019-05-24 03:13:07 +00:00
|
|
|
for(int i=0; i<window.w; i++) setPixel(window, i, 0, bg_color);
|
2019-05-23 20:14:15 +00:00
|
|
|
for(int i=0; i<5; i++) {
|
|
|
|
setPixel(window, 3, i, bg_color);
|
|
|
|
setPixel(window, 7, i, bg_color);
|
|
|
|
setPixel(window, 8, i, bg_color);
|
|
|
|
setPixel(window, 12, i, bg_color);
|
|
|
|
}
|
|
|
|
}
|
2019-05-22 04:52:41 +00:00
|
|
|
int h = ntpClient.getHours();
|
2019-05-24 03:13:07 +00:00
|
|
|
drawDigit(window, numbers3x5, 3, 5, 0, 1, h / 10, invert ? bg_color : fg_color, invert);
|
|
|
|
drawDigit(window, numbers3x5, 3, 5, 4, 1, h % 10, invert ? bg_color : fg_color, invert);
|
2019-05-22 04:52:41 +00:00
|
|
|
int m = ntpClient.getMinutes();
|
2019-05-24 03:13:07 +00:00
|
|
|
drawDigit(window, numbers3x5, 3, 5, 9, 1, m / 10, invert ? bg_color : fg_color, invert);
|
|
|
|
drawDigit(window, numbers3x5, 3, 5, 13, 1, m % 10, invert ? bg_color : fg_color, invert);
|
2019-05-22 04:52:41 +00:00
|
|
|
if (ntpClient.getSeconds() & 1) {
|
2019-05-24 03:13:07 +00:00
|
|
|
setPixel(window, 7, 2, invert ? bg_color : fg_color);
|
|
|
|
setPixel(window, 7, 4, invert ? bg_color : fg_color);
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class Sinematrix3 : public Effect {
|
|
|
|
private:
|
|
|
|
double pangle = 0;
|
|
|
|
double angle = 0;
|
|
|
|
double sx = 0;
|
|
|
|
double sy = 0;
|
|
|
|
double tx = 0;
|
|
|
|
double ty = 0;
|
|
|
|
double cx = 0;
|
|
|
|
double cy = 0;
|
|
|
|
double rcx = 0;
|
|
|
|
double rcy = 0;
|
|
|
|
double angle2 = 0;
|
|
|
|
double sx2 = 0;
|
|
|
|
double sy2 = 0;
|
|
|
|
double tx2 = 0;
|
|
|
|
double ty2 = 0;
|
|
|
|
double basecol = 0;
|
2019-05-23 19:18:15 +00:00
|
|
|
double fx = 1.0 / (LED_WIDTH / PI);
|
|
|
|
double fy = 1.0 / (LED_HEIGHT / PI);
|
2019-05-22 04:52:41 +00:00
|
|
|
Matrix rotate;
|
|
|
|
|
|
|
|
public:
|
2019-05-23 19:18:15 +00:00
|
|
|
boolean supports_window = true;
|
2019-05-23 20:14:15 +00:00
|
|
|
boolean can_be_shown_with_clock() { return true; };
|
|
|
|
boolean clock_as_mask() { return true; };
|
2019-05-22 04:52:41 +00:00
|
|
|
Sinematrix3() {}
|
|
|
|
void loop() {
|
2019-05-23 19:18:15 +00:00
|
|
|
pangle = addmodpi( pangle, 0.0133 + (angle / 256) );
|
2019-05-22 04:52:41 +00:00
|
|
|
angle = cos(pangle) * PI;
|
|
|
|
sx = addmodpi( sx, 0.00673 );
|
|
|
|
sy = addmodpi( sy, 0.00437 );
|
|
|
|
tx = addmodpi( tx, 0.00239 );
|
|
|
|
ty = addmodpi( ty, 0.00293 );
|
|
|
|
cx = addmodpi( cx, 0.00197 );
|
|
|
|
cy = addmodpi( cy, 0.00227 );
|
2019-05-23 19:18:15 +00:00
|
|
|
rcx = (LED_WIDTH / 2) + (sin(cx) * LED_WIDTH);
|
|
|
|
rcy = (LED_HEIGHT / 2) + (sin(cy) * LED_HEIGHT);
|
2019-05-22 04:52:41 +00:00
|
|
|
angle2 = addmodpi( angle2, 0.0029 );
|
|
|
|
sx2 = addmodpi( sx2, 0.0041);
|
|
|
|
sy2 = addmodpi( sy2, 0.0031);
|
|
|
|
tx2 = addmodpi( tx2, 0.0011 );
|
|
|
|
ty2 = addmodpi( ty2, 0.0023 );
|
|
|
|
basecol = addmod( basecol, 1.0, 0.007 );
|
2019-05-23 19:18:15 +00:00
|
|
|
|
2019-05-22 04:52:41 +00:00
|
|
|
rotate = {
|
|
|
|
.a11 = cos(angle),
|
|
|
|
.a12 = -sin(angle),
|
|
|
|
.a21 = sin(angle),
|
|
|
|
.a22 = cos(angle)
|
|
|
|
};
|
|
|
|
Matrix zoom = {
|
2019-05-23 19:18:15 +00:00
|
|
|
.a11 = sin(sx) / 4.0 + 0.15,
|
2019-05-22 04:52:41 +00:00
|
|
|
.a12 = 0, //atan(cos(sx2)),
|
|
|
|
.a21 = 0, //atan(cos(sy2)),
|
2019-05-23 19:18:15 +00:00
|
|
|
.a22 = cos(sy) / 4.0 + 0.15
|
2019-05-22 04:52:41 +00:00
|
|
|
};
|
|
|
|
Vector translate = {
|
|
|
|
.x1 = sin(tx) * LED_WIDTH,
|
|
|
|
.x2 = sin(ty) * LED_HEIGHT
|
|
|
|
};
|
2019-05-23 19:18:15 +00:00
|
|
|
|
|
|
|
for ( int x = 0; x < LED_WIDTH; x++ ) {
|
|
|
|
for ( int y = 0; y < LED_HEIGHT; y++ ) {
|
|
|
|
Vector c = add(multiply( multiply(rotate, zoom), { .x1 = x - rcx, .x2 = y - rcy } ), translate);
|
2019-05-23 20:14:44 +00:00
|
|
|
int sat = (basecol + basefield(c.x1, c.x2)) * 255;
|
|
|
|
setPixel(window, x, y, CHSV(sat, 120, 255));
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class Static : public Effect {
|
|
|
|
private:
|
|
|
|
CRGB color;
|
|
|
|
public:
|
|
|
|
Static(CRGB col) {
|
|
|
|
color = col;
|
|
|
|
}
|
2019-05-23 19:18:15 +00:00
|
|
|
boolean supports_window = true;
|
2019-05-22 04:52:41 +00:00
|
|
|
void loop() {
|
|
|
|
EVERY_N_SECONDS(1) {
|
2019-05-23 19:18:15 +00:00
|
|
|
clear(window, color);
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class Animation : public Effect {
|
|
|
|
private:
|
|
|
|
AnimationData *animation;
|
|
|
|
int frame = 0;
|
2019-05-23 19:18:15 +00:00
|
|
|
CRGB background_color;
|
|
|
|
int xOffset, yOffset;
|
|
|
|
long frameSince = 0;
|
2019-05-22 04:52:41 +00:00
|
|
|
public:
|
|
|
|
Animation(AnimationData *anim) {
|
2019-05-23 19:18:15 +00:00
|
|
|
Animation(anim, CRGB(0), 0, 0);
|
|
|
|
}
|
|
|
|
Animation(AnimationData *anim, CRGB background_color) {
|
|
|
|
Animation(anim, background_color, 0, 0);
|
|
|
|
}
|
|
|
|
Animation(AnimationData *anim, CRGB bg_color, int x, int y) {
|
2019-05-22 04:52:41 +00:00
|
|
|
animation = anim;
|
2019-05-23 19:18:15 +00:00
|
|
|
background_color = bg_color;
|
|
|
|
xOffset = x;
|
|
|
|
yOffset = y;
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
void loop() {
|
2019-05-23 19:18:15 +00:00
|
|
|
Serial.printf("Animation.loop. Animation is %p.", (void *)animation);
|
2019-05-22 04:52:41 +00:00
|
|
|
CRGB colors[animation->color_count];
|
|
|
|
int led_index = 0;
|
2019-05-24 21:51:50 +00:00
|
|
|
uint8_t *color_data = new uint8_t[animation->color_count * 3];
|
|
|
|
memcpy_P(color_data, animation->colors, animation->color_count * 3);
|
|
|
|
for (int i = 0; i < animation->color_count; i++) colors[i] = CRGB(color_data[i * 3], color_data[i * 3 + 1], color_data[i * 3 + 2]);
|
|
|
|
free(color_data);
|
2019-05-24 04:43:16 +00:00
|
|
|
// Data is stored in progmem, so get it from there.
|
|
|
|
int length = animation->offsets[frame + 1] - animation->offsets[frame];
|
|
|
|
uint8_t *data = new uint8_t[length];
|
|
|
|
memcpy_P(data, animation->data + animation->offsets[frame], length);
|
|
|
|
for (int i = 0; i < length; i++) {
|
2019-05-24 21:51:50 +00:00
|
|
|
uint8_t color_index;
|
|
|
|
uint8_t count;
|
2019-05-24 04:43:16 +00:00
|
|
|
if (data[i] == 255) { // Run-length encoded data
|
2019-05-24 21:51:50 +00:00
|
|
|
color_index = data[i + 2];
|
|
|
|
count = data[i + 1];
|
2019-05-23 19:18:15 +00:00
|
|
|
i += 2;
|
2019-05-22 04:52:41 +00:00
|
|
|
} else {
|
2019-05-24 21:51:50 +00:00
|
|
|
color_index = data[i];
|
|
|
|
count = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (color_index == 0) { // color #0 = skip this pixels
|
|
|
|
led_index += count;
|
|
|
|
} else {
|
|
|
|
CRGB* color;
|
|
|
|
if (color_index == 1) {
|
|
|
|
color = &background_color;
|
|
|
|
} else if (color_index >= 2) {
|
|
|
|
color = &colors[color_index - 2];
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
2019-05-24 21:51:50 +00:00
|
|
|
|
|
|
|
for (int j = 0; j < count; j++) set(led_index++, color);
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-24 04:43:16 +00:00
|
|
|
free(data);
|
2019-05-23 19:18:15 +00:00
|
|
|
if (frameSince == 0 || frameSince + frameDelay(animation, frame) < millis() || frameSince > millis()) {
|
2019-05-22 04:52:41 +00:00
|
|
|
frame = (frame + 1) % animation->frame_count;
|
2019-05-23 19:18:15 +00:00
|
|
|
frameSince = millis();
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-25 18:29:08 +00:00
|
|
|
void set(int i, CRGB* color) {
|
|
|
|
setPixel(xOffset + (i % animation->w), yOffset + (i / animation->h), *color);
|
2019-05-23 19:18:15 +00:00
|
|
|
}
|
|
|
|
uint16_t frameDelay(AnimationData* animation, int frame) {
|
|
|
|
if (animation->individual_delays) return animation->delays[frame];
|
|
|
|
return animation->delays[0];
|
2019-05-22 04:52:41 +00:00
|
|
|
}
|
|
|
|
};
|
2019-05-24 03:13:35 +00:00
|
|
|
|
|
|
|
class SingleDynamic : public Effect {
|
|
|
|
protected:
|
|
|
|
static const int factor = 2;
|
|
|
|
static const int tile_count = LED_WIDTH/factor * LED_HEIGHT/factor;
|
|
|
|
CRGB tiles[tile_count];
|
|
|
|
public:
|
|
|
|
SingleDynamic() {
|
|
|
|
for (int i=0; i<tile_count; i++) tiles[i] = CHSV(random8(), 120, 255);
|
|
|
|
}
|
|
|
|
virtual void update() {
|
|
|
|
tiles[random8() % tile_count] = CHSV(random8(), 120, 255);
|
|
|
|
}
|
|
|
|
boolean can_be_shown_with_clock() { return true; }
|
|
|
|
void loop() {
|
|
|
|
EVERY_N_MILLISECONDS(400) { update(); }
|
|
|
|
|
|
|
|
for (int x=0; x<window.w; x++) for (int y=0; y<window.h; y++) {
|
|
|
|
int index = y/2 * window.w/2 + x/2;
|
|
|
|
setPixel(window, x, y, tiles[index]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class MultiDynamic : public SingleDynamic {
|
|
|
|
public:
|
|
|
|
void update() {
|
|
|
|
for (int i=0; i<tile_count; i++) tiles[i] = CHSV(random8(), 120, 255);
|
|
|
|
}
|
|
|
|
};
|
2019-05-24 04:25:22 +00:00
|
|
|
|
|
|
|
class MatrixColumn {
|
|
|
|
private:
|
|
|
|
int x, y;
|
|
|
|
int length;
|
|
|
|
Window* window;
|
|
|
|
int speed;
|
|
|
|
boolean running;
|
|
|
|
long last_move = 0;
|
|
|
|
public:
|
|
|
|
MatrixColumn() {}
|
|
|
|
MatrixColumn(Window* win, int xPos) {
|
|
|
|
window = win;
|
|
|
|
x = xPos;
|
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void start() {
|
|
|
|
y=-1;
|
|
|
|
length = random8()%16+4;
|
|
|
|
running = true;
|
|
|
|
speed = random8()/3 + 50;
|
|
|
|
}
|
|
|
|
|
|
|
|
void advance() {
|
|
|
|
y++;
|
|
|
|
if (y-length > window->h) running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void draw() {
|
|
|
|
for(int i=0; i<length; i++) {
|
|
|
|
CRGB color;
|
|
|
|
if (i==0) color=CHSV(85, 0, 192);
|
|
|
|
else color=CHSV(85, 255, 255/(length-1)*(length-i));
|
|
|
|
|
|
|
|
setPixel(*window, x, y-i, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
if (!running) {
|
|
|
|
if (random8() < 50) {
|
|
|
|
// Start the column again.
|
|
|
|
start();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (millis() - last_move > speed) {
|
|
|
|
advance();
|
|
|
|
last_move = millis();
|
|
|
|
}
|
|
|
|
|
|
|
|
draw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class MatrixEffect : public Effect {
|
|
|
|
private:
|
|
|
|
MatrixColumn columns[LED_WIDTH];
|
|
|
|
public:
|
|
|
|
boolean can_be_shown_with_clock() { return true; };
|
|
|
|
MatrixEffect() {
|
|
|
|
for (int i=0; i<LED_WIDTH; i++) columns[i]=MatrixColumn(&window, i);
|
|
|
|
}
|
|
|
|
void loop() {
|
|
|
|
clear(window);
|
|
|
|
for (int i=0; i<LED_WIDTH; i++) columns[i].loop();
|
|
|
|
}
|
|
|
|
};
|