class Effect { protected: Window window = {0, 0, LED_WIDTH, LED_HEIGHT}; // Use a full screen window per default. public: virtual void loop() = 0; boolean supports_window = false; void setWindow(Window win) { window = win; }; }; struct EffectEntry { char* name; Effect* effect; }; class Bell : public Effect { private: CRGB color_on = CRGB(0xFFFF00); CRGB color_off = CRGB(0x000000); boolean invert = false; public: void loop() { 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; } } } EVERY_N_MILLISECONDS(300) { invert = !invert; } } }; class BigClock : public Effect { 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: BigClock() {} 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); } }; class Clock : public Effect { private: CRGB color_h = CRGB(0xFF0000); CRGB color_m = CRGB(0x00FF00); CRGB color_colon = CRGB(0xFFFF00); Effect* secondary_effect = 0; Window window = {0, LED_HEIGHT - 5, LED_WIDTH, 5}; Window secondary_window = {0, 0, LED_WIDTH, LED_HEIGHT - 6}; long secondary_effect_started_at = 0; public: EffectEntry* effects; void setEffects(EffectEntry* e) { effects = e; } Clock() {} void loop() { clear(window); int h = ntpClient.getHours(); drawDigit(window, numbers3x5, 3, 5, 0, 0, h / 10, color_h); drawDigit(window, numbers3x5, 3, 5, 4, 0, h % 10, color_h); int m = ntpClient.getMinutes(); drawDigit(window, numbers3x5, 3, 5, 9, 0, m / 10, color_m); drawDigit(window, numbers3x5, 3, 5, 13, 0, m % 10, color_m); if (ntpClient.getSeconds() & 1) { setPixel(window, 7, 1, color_colon); setPixel(window, 7, 3, color_colon); } // Change effect? if (secondary_effect == 0 || secondary_effect_started_at == 0 || millis() - secondary_effect_started_at > EFFECT_CYCLE_TIME) { secondary_effect = effects[0].effect; secondary_effect_started_at = millis(); secondary_effect->setWindow(secondary_window); } secondary_effect->loop(); } }; 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; double fx = 1.0 / (LED_WIDTH / PI); double fy = 1.0 / (LED_HEIGHT / PI); Matrix rotate; public: boolean supports_window = true; Sinematrix3() {} void loop() { pangle = addmodpi( pangle, 0.0133 + (angle / 256) ); 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 ); rcx = (LED_WIDTH / 2) + (sin(cx) * LED_WIDTH); rcy = (LED_HEIGHT / 2) + (sin(cy) * LED_HEIGHT); 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 ); rotate = { .a11 = cos(angle), .a12 = -sin(angle), .a21 = sin(angle), .a22 = cos(angle) }; Matrix zoom = { .a11 = sin(sx) / 4.0 + 0.15, .a12 = 0, //atan(cos(sx2)), .a21 = 0, //atan(cos(sy2)), .a22 = cos(sy) / 4.0 + 0.15 }; Vector translate = { .x1 = sin(tx) * LED_WIDTH, .x2 = sin(ty) * LED_HEIGHT }; 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); //Vector c2 = add(multiply( multiply(zoom2, rotate2), { .x1 = x, .x2 = y } ), translate2); setPixel(window, x, y, CHSV((basecol + basefield(c.x1, c.x2)) * 255, 255, 255)); //leds[XYsafe(x,y)] = CHSV((basecol+basefield(c.x1, c.x2))*255, 255, 255); //31+(sines(c2.x1-10, c2.x2-10)*224)); } } } }; class Static : public Effect { private: CRGB color; public: Static(CRGB col) { color = col; } boolean supports_window = true; void loop() { EVERY_N_SECONDS(1) { clear(window, color); } } }; class Animation : public Effect { private: AnimationData *animation; int frame = 0; CRGB background_color; int xOffset, yOffset; long frameSince = 0; public: Animation(AnimationData *anim) { 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) { animation = anim; background_color = bg_color; xOffset = x; yOffset = y; } void loop() { Serial.printf("Animation.loop. Animation is %p.", (void *)animation); CRGB colors[animation->color_count]; int led_index = 0; for (int i = 0; i < animation->color_count; i++) colors[i] = CRGB(animation->colors[i]); for (int i = animation->offsets[frame]; i < animation->offsets[frame + 1]; i++) { if (animation->data[i] == 255) { // Run-length encoded data uint8_t color = animation->data[i + 2]; for (int j = 0; j < animation->data[i + 1]; j++) { if (color > 1) { set(led_index, colors[animation->data[i + 2]]); } else if (color==1) { set(led_index, background_color); } led_index++; } i += 2; } else { uint8_t color = animation->data[i]; if (color > 1) { set(led_index, colors[animation->data[i]]); } else if (color == 1) { set(led_index, background_color); } led_index++; } } if (frameSince == 0 || frameSince + frameDelay(animation, frame) < millis() || frameSince > millis()) { frame = (frame + 1) % animation->frame_count; frameSince = millis(); } } void set(int i, CRGB color) { setPixel(xOffset + (i % animation->w), yOffset + (i / animation->h), color); } uint16_t frameDelay(AnimationData* animation, int frame) { if (animation->individual_delays) return animation->delays[frame]; return animation->delays[0]; } };