From a2ce3d15ec15f32d29ab84d32ce2638a378d80e1 Mon Sep 17 00:00:00 2001 From: BaptHdm Date: Thu, 9 May 2024 13:53:01 +0200 Subject: [PATCH] add back color functions --- src/system/colors/animations.cpp | 781 +++++++++++++++++++++++++++++ src/system/colors/animations.h | 131 +++++ src/system/colors/colors.cpp | 80 +++ src/system/colors/colors.h | 313 ++++++++++++ src/system/colors/palettes.cpp | 215 ++++++++ src/system/colors/palettes.h | 256 ++++++++++ src/system/colors/wipes.cpp | 143 ++++++ src/system/colors/wipes.h | 77 +++ src/system/physical/IMU.cpp | 179 +++++++ src/system/physical/IMU.h | 5 + src/system/physical/MicroPhone.cpp | 174 ++++++- src/system/physical/MicroPhone.h | 16 + src/system/utils/constants.h | 5 +- src/system/utils/coordinates.cpp | 42 ++ src/system/utils/coordinates.h | 42 ++ src/system/utils/strip.h | 204 ++++++++ src/system/utils/text.h | 590 ++++++++++++++++++++++ src/user_constants.h | 26 +- src/user_functions.cpp | 489 +++++++++++++++++- src/user_functions.h | 4 + 20 files changed, 3766 insertions(+), 6 deletions(-) create mode 100644 src/system/colors/animations.cpp create mode 100644 src/system/colors/animations.h create mode 100644 src/system/colors/colors.cpp create mode 100644 src/system/colors/colors.h create mode 100644 src/system/colors/palettes.cpp create mode 100644 src/system/colors/palettes.h create mode 100644 src/system/colors/wipes.cpp create mode 100644 src/system/colors/wipes.h create mode 100644 src/system/utils/coordinates.cpp create mode 100644 src/system/utils/coordinates.h create mode 100644 src/system/utils/strip.h create mode 100644 src/system/utils/text.h diff --git a/src/system/colors/animations.cpp b/src/system/colors/animations.cpp new file mode 100644 index 0000000..d931ddc --- /dev/null +++ b/src/system/colors/animations.cpp @@ -0,0 +1,781 @@ +#include "animations.h" + +#include +#include + +#include +#include + +#include "../ext/math8.h" +#include "../ext/noise.h" +#include "../ext/random8.h" +#include "../utils/constants.h" +#include "../utils/coordinates.h" +#include "../utils/utils.h" +#include "palettes.h" +#include "wipes.h" + +namespace animations { + +void fill(const Color& color, LedStrip& strip, const float cutOff) { + const float adaptedCutoff = fmin(fmax(cutOff, 0.0), 1.0); + const uint16_t maxCutOff = + fmin(fmax(adaptedCutoff * LED_COUNT, 1.0), LED_COUNT); + for (uint16_t i = 0; i < LED_COUNT; ++i) { + const uint32_t c = color.get_color(i, LED_COUNT); + if (i >= maxCutOff) { + // set a color gradient + float intPart, fracPart; + fracPart = modf(adaptedCutoff * LED_COUNT, &intPart); + // adapt the color to have a nice gradient + COLOR newC; + newC.color = c; + const uint16_t hue = utils::ColorSpace::HSV(newC).get_scaled_hue(); + strip.setPixelColor(i, LedStrip::ColorHSV(hue, 255, fracPart * 255)); + break; + } + + strip.setPixelColor(i, c); + } +} + +bool dot_ping_pong(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff) { + static bool isPongMode = + false; // true: animation is climbing back the display + + // reset condition + if (restart) { + isPongMode = false; + dot_wipe_up(color, duration / 2, fadeOut, true, strip); + dot_wipe_down(color, duration / 2, fadeOut, true, strip); + } + + if (isPongMode) { + return dot_wipe_up(color, duration / 2, fadeOut, false, strip, cutOff); + } else { + // set pong mode to true when first animation is finished + isPongMode = + dot_wipe_down(color, duration / 2, fadeOut, false, strip, cutOff); + } + + // finished if the target index is over the led limit + return false; +} + +bool color_pulse(const Color& color, const uint32_t durationPulseUp, + const uint32_t durationPulseDown, const bool restart, + LedStrip& strip, const float cutOff) { + static GenerateSolidColor blackColor = GenerateSolidColor(0); + static bool isPongMode = + false; // true: animation is climbing back the display + + // reset condition + if (restart) { + isPongMode = false; + color_wipe_up(color, durationPulseUp, true, strip); + color_wipe_down(color, durationPulseDown, true, strip); + } + + if (isPongMode) { + // clear the displayed color with a black (turned of) color, from top to + // bottom, with a duration proportional to the pulse duration + return color_wipe_down(blackColor, durationPulseDown * cutOff, false, strip, + 1.0); + } else { + // set pong mode to true when first animation is finished + isPongMode = color_wipe_up(color, durationPulseUp, false, strip, cutOff); + } + + // finished if the target index is over the led limit + return false; +} + +bool double_side_fill(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip) { + if (restart) { + color_wipe_up(color, duration, true, strip, 0.51); + color_wipe_down(color, duration, true, strip, 0.51); + } + + return color_wipe_down(color, duration, false, strip, 0.51) or + color_wipe_up(color, duration, false, strip, 0.51); +} + +bool police(const uint32_t duration, const bool restart, LedStrip& strip) { + static unsigned long previousMillis = 0; + static uint8_t state = 0; + + if (restart) { + previousMillis = 0; + state = 0; + } + + // convert duration in delay + const uint16_t partDelay = duration / 3; + + const uint32_t bluePartLenght = 3.0 / 5.0 * partDelay; + const uint32_t clearPartLenght = 2.0 / 5.0 * bluePartLenght; + + const unsigned long currentMillis = millis(); + + switch (state) { + // first left blue flash + case 0: { + strip.clear(); + + // blue lights + strip.fill(LedStrip::Color(0, 0, 255), 0, + LED_COUNT / 2 + 1); // Set on the first part + + // set next state + state++; + break; + } + // first short clear + case 1: { + if (currentMillis - previousMillis >= bluePartLenght) { + previousMillis = currentMillis; + + strip.clear(); + + // set next state + state++; + } + break; + } + // second left blue flash + case 2: { + if (currentMillis - previousMillis >= bluePartLenght) { + previousMillis = currentMillis; + + strip.clear(); + + // blue lights + strip.fill(LedStrip::Color(0, 0, 255), 0, + LED_COUNT / 2 + 1); // Set on the first part + + // set next state + state++; + } + break; + } + + // first right blue flash + case 3: { + if (currentMillis - previousMillis >= clearPartLenght) { + previousMillis = currentMillis; + + strip.clear(); + strip.fill(LedStrip::Color(0, 0, 255), LED_COUNT / 2, + LED_COUNT); // Set on the second part + + // set next state + state++; + } + break; + } + // first short pause + case 4: { + if (currentMillis - previousMillis >= bluePartLenght) { + previousMillis = currentMillis; + + strip.clear(); + + // set next state + state++; + } + break; + } + case 5: { + if (currentMillis - previousMillis >= bluePartLenght) { + previousMillis = currentMillis; + + strip.clear(); + strip.fill(LedStrip::Color(0, 0, 255), LED_COUNT / 2, + LED_COUNT); // Set on the second part + + // set next state + state++; + } + break; + } + case 6: { + if (currentMillis - previousMillis >= bluePartLenght) { + previousMillis = currentMillis; + + strip.clear(); + state = 0; + return true; + } + break; + } + } + + // never ends + return false; +} + +bool fade_out(const uint32_t duration, const bool restart, LedStrip& strip) { + static unsigned long startMillis = 0; + static uint32_t maxFadeLevel = 0; + static uint32_t fadeLevel = 0; + + // shared buffer + static uint32_t* ledStates = strip.get_buffer_ptr(0); + + if (restart) { + fadeLevel = 0; + maxFadeLevel = duration / LOOP_UPDATE_PERIOD; + startMillis = millis(); + + // buffer the start values + strip.buffer_current_colors(0); + + return false; + } + + // get a fade level between 0 and max level + const uint8_t newFadeLevel = + fmax(0.0, fmin(1.0, (millis() - startMillis) / (float)duration)) * + maxFadeLevel; + if (newFadeLevel != fadeLevel) { + fadeLevel = newFadeLevel; + + // update all values of rgb + for (uint16_t i = 0; i < LED_COUNT; ++i) { + const uint32_t startColor = ledStates[i]; + // diminish fade + strip.setPixelColor( + i, + utils::get_gradient(startColor, 0, fadeLevel / float(maxFadeLevel))); + } + } + + if (fadeLevel >= maxFadeLevel) return true; + + return false; +} + +bool fade_in(const Color& color, const uint32_t duration, const bool restart, + LedStrip& strip, const float firstCutOff, + const float secondCutOff) { + static unsigned long startMillis = 0; + static uint32_t maxFadeLevel = 0; + static uint32_t fadeLevel = 0; + static uint32_t* ledStates = strip.get_buffer_ptr(0); + static uint32_t* targetStates = strip.get_buffer_ptr(1); + + if (restart) { + fadeLevel = 0; + maxFadeLevel = duration / LOOP_UPDATE_PERIOD; + startMillis = millis(); + + // buffer the start values + strip.buffer_current_colors(0); + + // save initial state + for (uint16_t i = 0; i < LED_COUNT; ++i) { + targetStates[i] = color.get_color(i, LED_COUNT); + } + + return false; + } + + // get a fade level between 0 and maxFadeLevel + const uint32_t newFadeLevel = + fmax(0.0, fmin(1.0, (millis() - startMillis) / (float)duration)) * + maxFadeLevel; + if (newFadeLevel != fadeLevel) { + fadeLevel = newFadeLevel; + + const uint16_t minIndex = firstCutOff * LED_COUNT; + const uint16_t maxIndex = secondCutOff * LED_COUNT; + // update all values of rgb + for (uint16_t i = minIndex; i < maxIndex; ++i) { + // fade in + strip.setPixelColor(i, + utils::get_gradient(ledStates[i], targetStates[i], + fadeLevel / (float)maxFadeLevel)); + } + } + if (fadeLevel >= maxFadeLevel) return true; + + return false; +} + +void fire(const uint8_t scalex, const uint8_t scaley, const uint8_t speed, + const palette_t& palette, LedStrip& strip) { + static uint32_t step = 0; + uint16_t zSpeed = step / (256 - speed); + uint16_t ySpeed = millis() / (256 - speed); + for (int i = 0; i < ceil(stripXCoordinates); i++) { + for (int j = 0; j < ceil(stripYCoordinates); j++) { + strip.setPixelColorXY( + stripXCoordinates - i, j, + get_color_from_palette( + qsub8(noise8::inoise(i * scalex, j * scaley + ySpeed, zSpeed), + abs8(j - (stripXCoordinates - 1)) * 255 / + (stripXCoordinates - 1)), + palette)); + } + } + step++; +} + +void random_noise(const palette_t& palette, LedStrip& strip, const bool restart, + const bool isColorLoop, const uint16_t scale) { + static const float speed = 3000; + + static uint16_t* noise = strip._buffer16b; + // noise coordinates + static float x = random16(); + static float y = random16(); + static float z = random16(); + + if (restart) { + x = random16(); + y = random16(); + z = random16(); + memset(strip._buffer16b, 0, sizeof(strip._buffer16b)); + } + + static uint32_t lastcall = 0; + if (millis() - lastcall > 30) { + lastcall = millis(); + + // how much the new value influences the last one + float dataSmoothing = 0.01; + for (int i = 0; i < LED_COUNT; i++) { + const auto res = strip.get_lamp_coordinates(i); + uint16_t data = noise16::inoise(x + scale * res.x, y + scale * res.y, + z + scale * res.z); + + // smooth over time to prevent suddent jumps + noise[i] = noise[i] * dataSmoothing + data * (1.0 - dataSmoothing); + } + + // apply slow drift to X and Y, just for visual variation. + x += speed / 8; + y -= speed / 16; + + static uint16_t ihue = 0; + for (int i = 0; i < LED_COUNT; i++) { + uint16_t index = noise[i]; + uint8_t bri = noise[LED_COUNT - 1 - i] >> 8; + + // if this palette is a 'loop', add a slowly-changing base value + if (isColorLoop) { + index += ihue; + } + + // brighten up, as the color palette itself often contains the + // light/dark dynamic range desired + if (bri > 127) { + bri = 255; + } else { + bri = dim8_raw(bri * 2); + } + + strip.setPixelColor(i, get_color_from_palette(index, palette, bri)); + } + + ihue += 1; + } +} + +void candle(const palette_t& palette, LedStrip& strip) { + constexpr uint32_t minPhaseDuration = 1000; + constexpr uint32_t maxPhaseDuration = 5000; + + constexpr uint32_t sequenceDividerPeriod = + 10; // milliseconds for each sequence phase + + constexpr uint8_t flickeringMin = 0; + constexpr uint8_t flickeringMax = 100; + + static uint32_t phaseDuration = minPhaseDuration; + static uint32_t phaseStartTime = 0; + static uint32_t targetPhaseAmplitude = 0; + static uint32_t targetPhaseStrength = 0; + + static float noisePosition = 0.0; + constexpr float noiseSpeed = 5; + constexpr float noiseScale = 10; + + static uint32_t sequenceTime = 0; + static uint32_t sequenceIndex = 0; + static uint32_t sequencePerPhase = 0; + static uint8_t lastStrenght = 100; + static uint8_t currentStrenght = 100; + + // phase is a flicker phase: last a few seconds during witch a flickering + // sequence will happen + const uint32_t currentMillis = millis(); + if (currentMillis - phaseStartTime > phaseDuration || + sequenceIndex > sequencePerPhase) { + // declare a new phase + phaseStartTime = millis(); + // set flame parameters + targetPhaseAmplitude = random(flickeringMin, flickeringMax) + 1; + targetPhaseStrength = random(targetPhaseAmplitude / 4, 255); + phaseDuration = random(minPhaseDuration, maxPhaseDuration); + lastStrenght = currentStrenght; + + sequencePerPhase = phaseDuration / sequenceDividerPeriod; + sequenceTime = 0; // start a new sequence + sequenceIndex = 0; + } + + if (currentMillis - sequenceTime > sequenceDividerPeriod) { + // sequence update + sequenceTime = currentMillis; + + const float endStrengthRatio = + (double)sequenceIndex / (double)sequencePerPhase; + const float oldStrengthRatio = 1.0 - endStrengthRatio; + + currentStrenght = (lastStrenght * oldStrengthRatio) + + (targetPhaseStrength * endStrengthRatio); + + const uint8_t minBrighness = + currentStrenght - min(currentStrenght, targetPhaseAmplitude / 2); + const uint8_t maxBrighness = + currentStrenght + min(255 - currentStrenght, targetPhaseAmplitude / 2); + + uint8_t brightness = random(minBrighness, maxBrighness); + for (uint16_t i = 0; i < LED_COUNT; ++i) { + // vary flicker wiht bigger amplitude + if (i % 4 == 0) brightness = random(minBrighness, maxBrighness); + strip.setPixelColor(i, get_color_from_palette( + noise8::inoise(noisePosition + noiseScale * i), + palette, brightness)); + } + + // faster speed when change is fast + noisePosition += noiseSpeed; + + // ready for new sequence + sequenceIndex += 1; + } +} + +void phases( + const bool moder, const uint8_t speed, const palette_t& palette, + LedStrip& strip) { // We're making sine waves here. By Andrew Tuline. + static uint32_t step = 0; + + uint8_t allfreq = 16; // Base frequency. + float* phase = + reinterpret_cast(&step); // Phase change value gets calculated + // (float fits into unsigned long). + uint8_t cutOff = + (255 - + 128); // You can change the number of pixels. AKA INTENSITY (was 192). + uint8_t modVal = 5; // SEGMENT.fft1/8+1; // You can + // change the modulus. AKA FFT1 (was 5). + + uint8_t index = millis() / 64; // Set color rotation speed + *phase += + speed / 32.0; // You can change the speed of the wave. AKA SPEED (was .4) + + for (int i = 0; i < LED_COUNT; i++) { + if (moder) + modVal = (noise8::inoise(i * 10 + i * 10) / + 16); // Let's randomize our mod length with some Perlin noise. + uint16_t val = (i + 1) * allfreq; // This sets the frequency of the waves. + // The +1 makes sure that led 0 is used. + if (modVal == 0) modVal = 1; + val += *phase * (i % modVal + 1) / 2; // This sets the varying phase change + // of the waves. By Andrew Tuline. + uint8_t b = cubicwave8(val); // Now we make an 8 bit sinewave. + b = (b > cutOff) ? (b - cutOff) + : 0; // A ternary operator to cutoff the light. + + COLOR c; + c.color = get_color_from_palette(index, palette); + + COLOR cFond; + cFond.color = strip.getPixelColor(i); + strip.setPixelColor(i, utils::color_blend(cFond, c, b)); + index += 256 / LED_COUNT; + if (LED_COUNT > 256) + index++; // Correction for segments longer than 256 LEDs + } +} + +void mode_2DPolarLights( + const uint8_t scale, const uint8_t speed, const palette_t& palette, + const bool reset, + LedStrip& + strip) { // By: Kostyantyn Matviyevskyy + // https://editor.soulmatelights.com/gallery/762-polar-lights + // , Modified by: Andrew Tuline + const uint16_t cols = stripXCoordinates; + const uint16_t rows = stripYCoordinates; + static uint32_t step = 0; + + if (reset) { + strip.clear(); + step = 0; + } + + float adjustHeight = (float)map(rows, 8, 32, 28, 12); // maybe use mapf() ??? + uint16_t adjScale = map(cols, 8, 64, 310, 63); + /* + if (SEGENV.aux1 != SEGMENT.custom1/12) { // Hacky palette rotation. We + need that black. SEGENV.aux1 = SEGMENT.custom1/12; for (int i = 0; i < 16; + i++) { long ilk; ilk = (long)currentPalette[i].r << 16; ilk += + (long)currentPalette[i].g << 8; ilk += (long)currentPalette[i].b; ilk = (ilk + << SEGENV.aux1) | (ilk >> (24 - SEGENV.aux1)); currentPalette[i].r = ilk >> + 16; currentPalette[i].g = ilk >> 8; currentPalette[i].b = ilk; + } + } + */ + uint16_t _scale = map(scale, 0, 255, 30, adjScale); + byte _speed = map(speed, 0, 255, 128, 16); + + for (int x = 0; x <= cols; x++) { + for (int y = 0; y < rows; y++) { + step++; + strip.setPixelColorXY( + x, y, + get_color_from_palette( + qsub8(noise8::inoise((step % 2) + x * _scale, y * 16 + step % 16, + step / _speed), + fabsf((float)rows / 2.0f - (float)y) * adjustHeight), + palette)); + } + } +} + +void mode_2DDrift( + const uint8_t intensity, const uint8_t speed, const palette_t& palette, + LedStrip& strip) { // By: Stepko + // https://editor.soulmatelights.com/gallery/884-drift + // , Modified by: Andrew Tuline + const uint16_t cols = ceil(stripXCoordinates); + const uint16_t rows = ceil(stripYCoordinates); + + const uint16_t colsCenter = (cols >> 1) + cols % 2; + const uint16_t rowsCenter = (rows >> 1) + rows % 2; + + strip.fadeToBlackBy(128); + const uint16_t maxDim = MAX(cols, rows) / 2; + unsigned long t = millis() / (32 - (speed >> 3)); + unsigned long t_20 = + t / 20; // softhack007: pre-calculating this gives about 10% speedup + for (float i = 1; i < maxDim; i += 0.25) { + float angle = radians(t * (maxDim - i)); + uint16_t myX = colsCenter + (sin_t(angle) * i); + uint16_t myY = rowsCenter + (cos_t(angle) * i); + + strip.setPixelColorXY( + myX, myY, get_color_from_palette((uint8_t)((i * 20) + t_20), palette)); + } + strip.blur(intensity >> 3); +} // mode_2DDrift() + +static const uint8_t exp_gamma[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, + 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 26, 27, 28, + 28, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, + 39, 40, 41, 42, 43, 44, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 82, 83, 84, 85, + 87, 89, 91, 92, 93, 95, 96, 98, 99, 100, 101, 102, 105, 106, 108, + 109, 111, 112, 114, 115, 117, 118, 120, 121, 123, 125, 126, 128, 130, 131, + 133, 135, 136, 138, 140, 142, 143, 145, 147, 149, 151, 152, 154, 156, 158, + 160, 162, 164, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, + 190, 192, 194, 196, 198, 200, 202, 204, 207, 209, 211, 213, 216, 218, 220, + 222, 225, 227, 229, 232, 234, 236, 239, 241, 244, 246, 249, 251, 253, 254, + 255}; + +void hiphotic(const uint8_t speed, LedStrip& strip) { + int a = millis() / (32 - (speed >> 3)); + for (int x = 0; x < ceil(stripXCoordinates); x++) { + for (int y = 0; y < ceil(stripYCoordinates); y++) { + COLOR c; + c.blue = exp_gamma[sin8((x - 8) * cos8((y + 20) * 4) / 4 + a)]; + c.green = exp_gamma[(sin8(x * 16 + a / 3) + cos8(y * 8 + a / 2)) / 2]; + c.red = exp_gamma[sin8(cos8(x * 8 + a / 3) + sin8(y * 8 + a / 4) + a)]; + + strip.setPixelColorXY(x, y, c); + } + } +} + +// Distortion waves - ldirko +// https://editor.soulmatelights.com/gallery/1089-distorsion-waves +// adapted for WLED by @blazoncek +void mode_2Ddistortionwaves(const uint8_t scale, const uint8_t speed, + LedStrip& strip) { + const uint16_t cols = ceil(stripXCoordinates); + const uint16_t rows = ceil(stripYCoordinates); + + uint8_t _speed = speed / 32; + uint8_t _scale = scale / 32; + + uint8_t w = 2; + + uint16_t a = millis() / 32; + uint16_t a2 = a / 2; + uint16_t a3 = a / 3; + + uint16_t cx = beatsin8(10 - _speed, 0, cols - 1) * _scale; + uint16_t cy = beatsin8(12 - _speed, 0, rows - 1) * _scale; + uint16_t cx1 = beatsin8(13 - _speed, 0, cols - 1) * _scale; + uint16_t cy1 = beatsin8(15 - _speed, 0, rows - 1) * _scale; + uint16_t cx2 = beatsin8(17 - _speed, 0, cols - 1) * _scale; + uint16_t cy2 = beatsin8(14 - _speed, 0, rows - 1) * _scale; + + uint16_t xoffs = 0; + for (int x = 0; x < cols; x++) { + xoffs += _scale; + uint16_t yoffs = 0; + + for (int y = 0; y < rows; y++) { + yoffs += _scale; + + byte rdistort = + cos8((cos8(((x << 3) + a) & 255) + cos8(((y << 3) - a2) & 255) + a3) & + 255) >> + 1; + byte gdistort = cos8((cos8(((x << 3) - a2) & 255) + + cos8(((y << 3) + a3) & 255) + a + 32) & + 255) >> + 1; + byte bdistort = cos8((cos8(((x << 3) + a3) & 255) + + cos8(((y << 3) - a) & 255) + a2 + 64) & + 255) >> + 1; + + COLOR c; + c.red = rdistort + w * (a - (((xoffs - cx) * (xoffs - cx) + + (yoffs - cy) * (yoffs - cy)) >> + 7)); + c.green = gdistort + w * (a2 - (((xoffs - cx1) * (xoffs - cx1) + + (yoffs - cy1) * (yoffs - cy1)) >> + 7)); + c.blue = bdistort + w * (a3 - (((xoffs - cx2) * (xoffs - cx2) + + (yoffs - cy2) * (yoffs - cy2)) >> + 7)); + + c.red = utils::gamma8(cos8(c.red)); + c.green = utils::gamma8(cos8(c.green)); + c.blue = utils::gamma8(cos8(c.blue)); + + strip.setPixelColorXY(x, y, c); + } + } +} + +// Calm effect, like a lake at night +void mode_lake(const uint8_t speed, const palette_t& palette, LedStrip& strip) { + uint8_t sp = speed / 10; + int wave1 = beatsin8(sp + 2, -64, 64); + int wave2 = beatsin8(sp + 1, -64, 64); + uint8_t wave3 = beatsin8(sp + 2, 0, 80); + // CRGB fastled_col; + + for (int i = 0; i < LED_COUNT; i++) { + uint8_t index = + cos8((i * 15) + wave1) / 2 + cubicwave8((i * 23) + wave2) / 2; + uint8_t lum = (index > wave3) ? index - wave3 : 0; + // fastled_col = ColorFromPalette(SEGPALETTE, map(index,0,255,0,240), lum, + // LINEARBLEND); SEGMENT.setPixelColor(i, fastled_col.red, + // fastled_col.green, fastled_col.blue); + strip.setPixelColor(i, get_color_from_palette(index, palette, lum)); + } +} + +void mode_sinewave(const uint8_t speed, const uint8_t intensity, + const palette_t& palette, LedStrip& strip) { + // Adjustable sinewave. By Andrew Tuline + // #define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned + // subtraction macro. if result <0, then => 0 + + static uint16_t step = 0; + + uint16_t colorIndex = + millis() / 32; //(256 - SEGMENT.fft1); // Amount of colour change. + + step += speed / 16; // Speed of animation. + uint16_t freq = + intensity / + 4; // SEGMENT.fft2/8; // Frequency of the signal. + + for (int i = 0; i < LED_COUNT; + i++) { // For each of the LED's in the strand, set a brightness based on + // a wave as follows: + uint8_t pixBri = cubicwave8( + (i * freq) + + step); // qsuba(cubicwave8((i*freq)+SEGENV.step), + // (255-SEGMENT.intensity)); // qsub sets a minimum value + // called thiscutoff. If < thiscutoff, then bright = 0. + // Otherwise, bright = 128 (as defined in qsub).. + // setPixCol(i, i*colorIndex/255, pixBri); + COLOR pixColor; + pixColor.color = + get_color_from_palette((uint8_t)(i * colorIndex / 255), palette); + COLOR back; + back.color = 0; + strip.setPixelColor(i, utils::color_blend(back, pixColor, pixBri)); + } +} + +// effect utility functions +uint8_t sin_gap(uint16_t in) { + if (in & 0x100) return 0; + return sin8( + in + + 192); // correct phase shift of sine so that it starts and stops at 0 +} + +void running_base(bool saw, bool dual, const uint8_t speed, + const uint8_t intensity, const palette_t& palette, + LedStrip& strip) { + uint8_t x_scale = intensity >> 2; + uint32_t counter = (millis() * speed) >> 9; + + COLOR c1; + c1.color = 0; + + COLOR c2; + + for (int i = 0; i < LED_COUNT; i++) { + uint16_t a = i * x_scale - counter; + if (saw) { + a &= 0xFF; + if (a < 16) { + a = 192 + a * 8; + } else { + a = map(a, 16, 255, 64, 192); + } + a = 255 - a; + } + uint8_t s = dual ? sin_gap(a) : sin8(a); + + c2.color = get_color_from_palette((uint8_t)i, palette); + COLOR ca = utils::color_blend(c1, c2, s); + if (dual) { + uint16_t b = (LED_COUNT - 1 - i) * x_scale - counter; + uint8_t t = sin_gap(b); + + c2.color = get_color_from_palette((uint8_t)i, palette); + + COLOR cb; + cb = utils::color_blend(c1, c2, t); + ca = utils::color_blend(ca, cb, 127); + } + strip.setPixelColor(i, ca); + } +} + +} // namespace animations \ No newline at end of file diff --git a/src/system/colors/animations.h b/src/system/colors/animations.h new file mode 100644 index 0000000..81f583f --- /dev/null +++ b/src/system/colors/animations.h @@ -0,0 +1,131 @@ +#ifndef ANIMATIONS_ANIMATIONS_H +#define ANIMATIONS_ANIMATIONS_H + +#include "../utils/colorspace.h" +#include "../utils/strip.h" +#include "colors.h" + +namespace animations { + +/** + * \brief Fill the display with a color, with an optional cutoff value + * \param[in] color class that returns a color to display + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of + */ +void fill(const Color& color, LedStrip& strip, const float cutOff = 1); + +/** + * \brief Do a wipe down followed by a wipe up animation + * \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] fadeOut The animation fade speed (0: no fade) + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of \return True if the animation is finished + */ +bool dot_ping_pong(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff = 1); + +/** + * \brief Do color pulse + * \param[in] color class that returns a color to display + * \param[in] durationPulseUp The duration of the color fill up animation, in + * milliseconds \param[in] durationPulseDown The duration of the pong animation, + * in milliseconds \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this color will fill the display + * before suddently cutting of \return True if the animation is finished + */ +bool color_pulse(const Color& color, const uint32_t durationPulseUp, + const uint32_t durationPulseDown, const bool restart, + LedStrip& strip, const float cutOff = 1); + +/** + * \brief Fill the display from both side simultaneously + * \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \return True if the animation is finished + */ +bool double_side_fill(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip); + +/** + * \brief Do police light animation + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \return True if the animation is finished + */ +bool police(const uint32_t duration, const bool restart, LedStrip& strip); + +/** + * \brief Do a fade out of the colors currently displayed + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \return True if the animation is finished + */ +bool fade_out(const uint32_t duration, const bool restart, LedStrip& strip); + +/** + * \brief Do a fade in of a color + * \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to contro + * \param[in] firstCutOff between 0 and 1, how much this color will fill the + * display before suddently cutting of \param[in] secondCutOff between 0 and 1, + * how much this color will fill the display before suddently cutting of \return + * True if the animation is finished + */ +bool fade_in(const Color& color, const uint32_t duration, const bool restart, + LedStrip& strip, const float firstCutOff = 0.0, + const float secondCutOff = 1.0); + +/** + * Fire animation + * https://editor.soulmatelights.com/gallery/234-fire + */ +void fire(const uint8_t scalex, const uint8_t scaley, const uint8_t speed, + const palette_t& palette, LedStrip& strip); + +void random_noise(const palette_t& palette, LedStrip& strip, const bool restart, + const bool isColorLoop, const uint16_t scale); + +void candle(const palette_t& palette, LedStrip& strip); + +/** + * \brief Display some sinewave of colors, going back and forth + * \param[in] moder: add some random noise + */ +void phases(const bool moder, const uint8_t speed, const palette_t& palette, + LedStrip& strip); +void mode_2DPolarLights(const uint8_t scale, const uint8_t speed, + const palette_t& palette, const bool reset, + LedStrip& strip); +void mode_2DDrift(const uint8_t intensity, const uint8_t speed, + const palette_t& palette, LedStrip& strip); +void hiphotic(const uint8_t speed, LedStrip& strip); + +void mode_2Ddistortionwaves(const uint8_t scale, const uint8_t speed, + LedStrip& strip); + +void mode_lake(const uint8_t speed, const palette_t& palette, LedStrip& strip); + +// Adjustable sinewave. By Andrew Tuline +void mode_sinewave(const uint8_t speed, const uint8_t intensity, + const palette_t& palette, LedStrip& strip); + +void running_base(bool saw, bool dual, const uint8_t speed, + const uint8_t intensity, const palette_t& palette, + LedStrip& strip); + +}; // namespace animations + +#endif \ No newline at end of file diff --git a/src/system/colors/colors.cpp b/src/system/colors/colors.cpp new file mode 100644 index 0000000..22a187e --- /dev/null +++ b/src/system/colors/colors.cpp @@ -0,0 +1,80 @@ +#include "colors.h" + +#include +#include + +#include "../utils/colorspace.h" +#include "../utils/constants.h" +#include "../utils/strip.h" +#include "../utils/utils.h" +#include "palettes.h" + +uint32_t GenerateSolidColor::get_color_internal(const uint16_t index, + const uint16_t maxIndex) const { + return _color; +} + +uint32_t GenerateRainbowColor::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + const uint16_t hue = map(constrain(index, 0, maxIndex), 0, maxIndex, 0, 360); + return utils::hue_to_rgb_sinus(hue); +} + +uint32_t GenerateGradientColor::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + return utils::get_gradient(_colorStart, _colorEnd, index / (float)maxIndex); +} + +uint32_t GenerateRoundColor::get_color_internal(const uint16_t index, + const uint16_t maxIndex) const { + const double segmentsPerTurns = 3.1; + const double modulo = std::fmod(index, segmentsPerTurns) / segmentsPerTurns; + return utils::hue_to_rgb_sinus(modulo * 360.0); +} + +uint32_t GenerateRainbowSwirl::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + const uint16_t pixelHue = _firstPixelHue + (index * UINT16_MAX / maxIndex); + return utils::hue_to_rgb_sinus(map(pixelHue, 0, UINT16_MAX, 0, 360)); +} + +uint32_t GeneratePaletteStep::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + return get_color_from_palette(_index, *_paletteRef); +} + +uint32_t GeneratePaletteIndexed::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + return get_color_from_palette(_index, *_paletteRef); +} + +uint32_t GenerateRainbowPulse::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + return LedStrip::ColorHSV(_currentPixelHue); +} + +uint32_t GenerateRainbowIndex::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + return LedStrip::ColorHSV(_currentPixelHue); +} + +uint32_t GeneratePastelPulse::get_color_internal( + const uint16_t index, const uint16_t maxIndex) const { + const double hue = double(_currentPixelHue) / double(UINT16_MAX) * 360.0; + + // using OKLCH will create softer colors + auto res = utils::ColorSpace::OKLCH(0.752, 0.126, hue); + return res.get_rgb().color; +} + +GenerateRandomColor::GenerateRandomColor() + : _color(utils::get_random_color()) {} + +void GenerateRandomColor::internal_update(const uint32_t deltaTimeMilli) { + _color = utils::get_random_color(); +} + +void GenerateComplementaryColor::internal_update( + const uint32_t deltaTimeMilli) { + _color = utils::get_random_complementary_color(_color, _randomVariation); +}; \ No newline at end of file diff --git a/src/system/colors/colors.h b/src/system/colors/colors.h new file mode 100644 index 0000000..2ea7938 --- /dev/null +++ b/src/system/colors/colors.h @@ -0,0 +1,313 @@ +#ifndef COLORS_H +#define COLORS_H + +#include + +#include + +#include "../utils/constants.h" +#include "palettes.h" + +// min color update frequency +constexpr uint32_t COLOR_TIMING_UPDATE = LOOP_UPDATE_PERIOD * 3; + +/** + * \brief Basic color generation class + */ +class Color { + public: + uint32_t get_color(const uint16_t index = 0, + const uint16_t maxIndex = 0) const { + // prevent the user to break the system + return get_color_internal(constrain(index, 0, maxIndex), maxIndex); + } + + /** + * \brief reset the color + */ + virtual void reset() = 0; + + private: + virtual uint32_t get_color_internal(const uint16_t index = 0, + const uint16_t maxIndex = 0) const = 0; +}; + +/** + * \brief Basic dynamic color generation class + */ +class DynamicColor : public Color { + public: + /** + * \brief check for animation update + * \return true if the animation was updated + */ + virtual bool update() { + const long unsigned currentMillis = millis(); + if (currentMillis - _lastUpdate > COLOR_TIMING_UPDATE) // ms update period + { + internal_update(currentMillis - _lastUpdate); + _lastUpdate = currentMillis; + return true; + } + return false; + } + + protected: + virtual void internal_update(const uint32_t deltaTimeMilli) = 0; + long unsigned _lastUpdate; // time of the last update, in ms +}; + +class IndexedColor : public Color { + public: + virtual void update(const uint8_t index) = 0; +}; + +/** + * \brief Generate a solid color + */ +class GenerateSolidColor : public Color { + public: + GenerateSolidColor(const uint32_t color) : _color(color){}; + + uint32_t get_color_internal(const uint16_t index = 0, + const uint16_t maxIndex = 0) const override; + + void reset() override{}; + + private: + uint32_t _color; +}; + +/** + * \brief Generate a rainbow color from top to bottom + */ +class GenerateRainbowColor : public Color { + public: + GenerateRainbowColor(){}; + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override{}; +}; + +/** + * \brief Generate a gradient color from top to bottom + */ +class GenerateGradientColor : public Color { + public: + GenerateGradientColor(const uint32_t colorStart, const uint32_t colorEnd) + : _colorStart(colorStart), _colorEnd(colorEnd){}; + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override{}; + + private: + uint32_t _colorStart; + uint32_t _colorEnd; +}; + +/** + * \brief Generate a gradient color from top to bottom + */ +class GenerateRoundColor : public Color { + public: + GenerateRoundColor(){}; + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override{}; +}; + +/** + * \brief Generate a rainbow color that loop around the display + * \param[in] period The period of the animation + */ +class GenerateRainbowSwirl : public DynamicColor { + public: + GenerateRainbowSwirl(const uint32_t period) + : _increment(UINT16_MAX / (period / COLOR_TIMING_UPDATE)), + _firstPixelHue(0) {} + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override { _firstPixelHue = 0; }; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override { + _firstPixelHue += _increment; + }; + + uint32_t _increment; + uint16_t _firstPixelHue; +}; + +/** + * \brief Step through a palette + * \param[in] palette The palette to use + */ +class GeneratePaletteStep : public DynamicColor { + public: + GeneratePaletteStep(const palette_t& palette) + : _index(0), _paletteRef(&palette) {} + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override { _index = 0; }; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override { _index += 1; }; + + uint8_t _index; + const palette_t* _paletteRef; +}; + +class GeneratePaletteIndexed : public IndexedColor { + public: + GeneratePaletteIndexed(const palette_t& palette) + : _index(0), _paletteRef(&palette) {} + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void update(const uint8_t index) override { _index = index; } + + void reset() override { _index = 0; }; + + private: + uint8_t _index; + const palette_t* _paletteRef; +}; + +/** + * \brief Generate a rainbow color that loop around the display + * \param[in] period The period of the animation + */ +class GenerateRainbowPulse : public DynamicColor { + public: + GenerateRainbowPulse(const uint8_t colorDivisions) : _currentPixelHue(0) { + _increment = fmax(float(UINT16_MAX) / float(colorDivisions), 1); + } + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override { _currentPixelHue = 0; }; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override { + _currentPixelHue += _increment; + }; + + uint16_t _increment; + uint16_t _currentPixelHue; +}; + +class GenerateRainbowIndex : public IndexedColor { + public: + GenerateRainbowIndex(const uint8_t colorDivisions) + : _increment(UINT16_MAX / float(colorDivisions)), _currentPixelHue(0) {} + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void update(const uint8_t index) override { + _currentPixelHue = float(index) * _increment; + } + + void reset() override { _currentPixelHue = 0; }; + + private: + float _increment; + uint16_t _currentPixelHue; +}; + +// generate a rainbow with pastel colors +class GeneratePastelPulse : public DynamicColor { + public: + GeneratePastelPulse(const uint8_t colorDivisions) : _currentPixelHue(0) { + _increment = fmax(float(UINT16_MAX) / float(colorDivisions), 1); + } + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override; + + void reset() override { _currentPixelHue = 0; }; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override { + _currentPixelHue += _increment; + }; + + uint16_t _increment; + uint16_t _currentPixelHue; +}; + +/** + * \brief Get a random color. Color changes at each update() call + */ +class GenerateRandomColor : public DynamicColor { + public: + GenerateRandomColor(); + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override { + return _color; + } + + void reset() override{}; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override; + + uint32_t _color; +}; + +/** + * \brief Get a color. Color changes to it's complement at each update() call + */ +class GenerateComplementaryColor : public DynamicColor { + public: + GenerateComplementaryColor(const float randomVariation) + : _color(0), _randomVariation(randomVariation) { + reset(); + } + + uint32_t get_color_internal(const uint16_t index, + const uint16_t maxIndex) const override { + return _color; + } + + void reset() override { internal_update(0); }; + + private: + /** + * \brief Call when you want the animation to progress + */ + void internal_update(const uint32_t deltaTimeMilli) override; + + uint32_t _color; + float _randomVariation; +}; + +#endif \ No newline at end of file diff --git a/src/system/colors/palettes.cpp b/src/system/colors/palettes.cpp new file mode 100644 index 0000000..0902b17 --- /dev/null +++ b/src/system/colors/palettes.cpp @@ -0,0 +1,215 @@ +#include "palettes.h" + +#include + +#include "../utils/utils.h" + +/// Cloudy color palette/ blue to blue-white +const palette_t PaletteCloudColors = {Blue, DarkBlue, DarkBlue, DarkBlue, + DarkBlue, DarkBlue, DarkBlue, DarkBlue, + Blue, DarkBlue, SkyBlue, SkyBlue, + LightBlue, White, LightBlue, SkyBlue}; + +/// Lava color palette +const palette_t PaletteLavaColors = { + Black, Maroon, Black, Maroon, DarkRed, Maroon, DarkRed, DarkRed, + DarkRed, Red, Orange, White, Orange, Red, DarkRed, Black}; + +/// Fire color palette +const palette_t PaletteFlameColors = { + Orange, Orange, Orange, Orange, Orange, Orange, Orange, Orange, + Orange, Orange, Orange, Orange, Candle, Orange, Orange, Gold}; + +/// Ocean colors, blues and whites +const palette_t PaletteOceanColors = { + MidnightBlue, DarkBlue, MidnightBlue, Navy, DarkBlue, MediumBlue, + SeaGreen, Teal, CadetBlue, Blue, DarkCyan, CornflowerBlue, + Aquamarine, SeaGreen, Aqua, LightSkyBlue}; + +/// Forest colors, greens +const palette_t PaletteForestColors = { + DarkGreen, DarkGreen, DarkOliveGreen, DarkGreen, + Green, ForestGreen, OliveDrab, Green, + SeaGreen, MediumAquamarine, LimeGreen, YellowGreen, + LightGreen, LawnGreen, MediumAquamarine, ForestGreen}; + +/// HSV Rainbow +const palette_t PaletteRainbowColors = {0xFF0000, 0xD52A00, 0xAB5500, 0xAB7F00, + 0xABAB00, 0x56D500, 0x00FF00, 0x00D52A, + 0x00AB55, 0x0056AA, 0x0000FF, 0x2A00D5, + 0x5500AB, 0x7F0081, 0xAB0055, 0xD5002B}; + +// basically, HSV with no green. looks better when lighing people +const palette_t PalettePartyColors = {0x5500AB, 0x84007C, 0xB5004B, 0xE5001B, + 0xE81700, 0xB84700, 0xAB7700, 0xABAB00, + 0xAB5500, 0xDD2200, 0xF2000E, 0xC2003E, + 0x8F0071, 0x5F00A1, 0x2F00D0, 0x0007F9}; + +// Black body radiation +const palette_t PaletteBlackBodyColors = { + 0xff3800, 0xff5300, 0xff6500, 0xff7300, 0xff7e00, 0xff8912, + 0xff932c, 0xff9d3f, 0xffa54f, 0xffad5e, 0xffb46b, 0xffbb78, + 0xffc184, 0xffc78f, 0xffcc99, 0xffd1a3}; + +const palette_t PaletteHeatColors = {0x000000, 0x330000, 0x660000, 0x990000, + 0xCC0000, 0xFF0000, 0xFF3300, 0xFF6600, + 0xFF9900, 0xFFCC00, 0xFFFF00, 0xFFFF33, + 0xFFFF66, 0xFFFF99, 0xFFFFCC}; + +const palette_t PaletteAuroraColors = {0x000000, 0x003300, 0x006600, 0x009900, + 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, + 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, + 0xff9900, 0xff6600, 0xff3300, 0xff0000}; + +uint32_t get_color_from_palette(const uint8_t index, const palette_t& palette, + const uint8_t brightness) { + const uint8_t renormIndex = index >> 4; // convert to [0; 15] (divide by 16) + const uint8_t blendIndex = + index & 0x0F; // mask with 15 (get the less significant part, that will + // be the blend factor) + + const uint32_t entry = palette[renormIndex]; + + // convert to rgb + union COLOR color; + color.color = entry; + + uint8_t red1 = color.red; + uint8_t green1 = color.green; + uint8_t blue1 = color.blue; + + // need to blenc the palette + if (blendIndex != 0) { + union COLOR nextColor; + if (renormIndex == 15) { + nextColor.color = palette[0]; + } else { + nextColor.color = palette[1 + renormIndex]; + } + + const uint8_t tmpF2 = blendIndex << 4; + const float f1 = (255 - tmpF2) / 256.0; + const float f2 = tmpF2 / 256.0; + + const uint8_t red2 = nextColor.red; + red1 = red1 * f1; + red1 += red2 * f2; + + const uint8_t green2 = nextColor.green; + green1 = green1 * f1; + green1 += green2 * f2; + + const uint8_t blue2 = nextColor.blue; + blue1 = blue1 * f1; + blue1 += blue2 * f2; + } + + if (brightness != 255) { + if (brightness != 0) { + const float adjustedBirghtness = + (brightness + 1) / 256.0; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full scale8_video + // logic; we can just to scale8 and then add one (unless scale8 fixed) to + // all nonzero inputs. + if (red1) { + red1 = red1 * adjustedBirghtness; + ++red1; + } + if (green1) { + green1 = green1 * adjustedBirghtness; + ++green1; + } + if (blue1) { + blue1 = blue1 * adjustedBirghtness; + ++blue1; + } + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + // convert back to color + color.red = red1; + color.green = green1; + color.blue = blue1; + // return color code + return color.color; +} + +uint32_t get_color_from_palette(const uint16_t index, const palette_t& palette, + const uint8_t brightness) { + const float ramp = (index / (float)UINT16_MAX) * 16; + + const uint8_t renormIndex = floor(ramp); // convert to [0; 15] (divide by 16) + const float blendIndex = ramp - renormIndex; // get the fractional part + + const uint32_t entry = palette[renormIndex]; + + // convert to rgb + union COLOR color; + color.color = entry; + + uint8_t red1 = color.red; + uint8_t green1 = color.green; + uint8_t blue1 = color.blue; + + // need to blenc the palette + if (blendIndex > 0) { + union COLOR nextColor; + if (renormIndex == 15) { + nextColor.color = palette[0]; + } else { + nextColor.color = palette[1 + renormIndex]; + } + + const float f1 = blendIndex; + const float f2 = 1.0 - blendIndex; + + const uint8_t red2 = nextColor.red; + red1 = red1 * f1; + red1 += red2 * f2; + + const uint8_t green2 = nextColor.green; + green1 = green1 * f1; + green1 += green2 * f2; + + const uint8_t blue2 = nextColor.blue; + blue1 = blue1 * f1; + blue1 += blue2 * f2; + } + + if (brightness != 255) { + if (brightness != 0) { + const float adjustedBirghtness = + (brightness + 1) / 256.0; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full scale8_video + // logic; we can just to scale8 and then add one (unless scale8 fixed) to + // all nonzero inputs. + if (red1) { + red1 = red1 * adjustedBirghtness; + ++red1; + } + if (green1) { + green1 = green1 * adjustedBirghtness; + ++green1; + } + if (blue1) { + blue1 = blue1 * adjustedBirghtness; + ++blue1; + } + } else { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + // convert back to color + color.red = red1; + color.green = green1; + color.blue = blue1; + // return color code + return color.color; +} \ No newline at end of file diff --git a/src/system/colors/palettes.h b/src/system/colors/palettes.h new file mode 100644 index 0000000..a131dd8 --- /dev/null +++ b/src/system/colors/palettes.h @@ -0,0 +1,256 @@ +#ifndef PALETTES_H +#define PALETTES_H + +#include + +typedef uint32_t palette_t[16]; + +/// @brief Color temperature values +/// @details These color values are separated into two groups: black body +/// radiators and gaseous light sources. +/// +/// Black body radiators emit a (relatively) continuous spectrum, +/// and can be described as having a Kelvin 'temperature'. This includes things +/// like candles, tungsten lightbulbs, and sunlight. +/// +/// Gaseous light sources emit discrete spectral bands, and while we can +/// approximate their aggregate hue with RGB values, they don't actually +/// have a proper Kelvin temperature. +/// +/// @see https://en.wikipedia.org/wiki/Color_temperature +typedef enum { + // Black Body Radiators + // @{ + /// 1900 Kelvin + Candle = 0xFF9329 /* 1900 K, 255, 147, 41 */, + /// 2600 Kelvin + Tungsten40W = 0xFFC58F /* 2600 K, 255, 197, 143 */, + /// 2850 Kelvin + Tungsten100W = 0xFFD6AA /* 2850 K, 255, 214, 170 */, + /// 3200 Kelvin + Halogen = 0xFFF1E0 /* 3200 K, 255, 241, 224 */, + /// 5200 Kelvin + CarbonArc = 0xFFFAF4 /* 5200 K, 255, 250, 244 */, + /// 5400 Kelvin + HighNoonSun = 0xFFFFFB /* 5400 K, 255, 255, 251 */, + /// 6000 Kelvin + DirectSunlight = 0xFFFFFF /* 6000 K, 255, 255, 255 */, + /// 7000 Kelvin + OvercastSky = 0xC9E2FF /* 7000 K, 201, 226, 255 */, + /// 20000 Kelvin + ClearBlueSky = 0x409CFF /* 20000 K, 64, 156, 255 */, + // @} + + // Gaseous Light Sources + // @{ + /// Warm (yellower) flourescent light bulbs + WarmFluorescent = 0xFFF4E5 /* 0 K, 255, 244, 229 */, + /// Standard flourescent light bulbs + StandardFluorescent = 0xF4FFFA /* 0 K, 244, 255, 250 */, + /// Cool white (bluer) flourescent light bulbs + CoolWhiteFluorescent = 0xD4EBFF /* 0 K, 212, 235, 255 */, + /// Full spectrum flourescent light bulbs + FullSpectrumFluorescent = 0xFFF4F2 /* 0 K, 255, 244, 242 */, + /// Grow light flourescent light bulbs + GrowLightFluorescent = 0xFFEFF7 /* 0 K, 255, 239, 247 */, + /// Black light flourescent light bulbs + BlackLightFluorescent = 0xA700FF /* 0 K, 167, 0, 255 */, + /// Mercury vapor light bulbs + MercuryVapor = 0xD8F7FF /* 0 K, 216, 247, 255 */, + /// Sodium vapor light bulbs + SodiumVapor = 0xFFD1B2 /* 0 K, 255, 209, 178 */, + /// Metal-halide light bulbs + MetalHalide = 0xF2FCFF /* 0 K, 242, 252, 255 */, + /// High-pressure sodium light bulbs + HighPressureSodium = 0xFFB74C /* 0 K, 255, 183, 76 */, + // @} + + /// Uncorrected temperature (0xFFFFFF) + UncorrectedTemperature = 0xFFFFFF /* 255, 255, 255 */ +} ColorTemperature; + +typedef enum { + AliceBlue = 0xF0F8FF, ///< @htmlcolorblock{F0F8FF} + Amethyst = 0x9966CC, ///< @htmlcolorblock{9966CC} + AntiqueWhite = 0xFAEBD7, ///< @htmlcolorblock{FAEBD7} + Aqua = 0x00FFFF, ///< @htmlcolorblock{00FFFF} + Aquamarine = 0x7FFFD4, ///< @htmlcolorblock{7FFFD4} + Azure = 0xF0FFFF, ///< @htmlcolorblock{F0FFFF} + Beige = 0xF5F5DC, ///< @htmlcolorblock{F5F5DC} + Bisque = 0xFFE4C4, ///< @htmlcolorblock{FFE4C4} + Black = 0x000000, ///< @htmlcolorblock{000000} + BlanchedAlmond = 0xFFEBCD, ///< @htmlcolorblock{FFEBCD} + Blue = 0x0000FF, ///< @htmlcolorblock{0000FF} + BlueViolet = 0x8A2BE2, ///< @htmlcolorblock{8A2BE2} + Brown = 0xA52A2A, ///< @htmlcolorblock{A52A2A} + BurlyWood = 0xDEB887, ///< @htmlcolorblock{DEB887} + CadetBlue = 0x5F9EA0, ///< @htmlcolorblock{5F9EA0} + Chartreuse = 0x7FFF00, ///< @htmlcolorblock{7FFF00} + Chocolate = 0xD2691E, ///< @htmlcolorblock{D2691E} + Coral = 0xFF7F50, ///< @htmlcolorblock{FF7F50} + CornflowerBlue = 0x6495ED, ///< @htmlcolorblock{6495ED} + Cornsilk = 0xFFF8DC, ///< @htmlcolorblock{FFF8DC} + Crimson = 0xDC143C, ///< @htmlcolorblock{DC143C} + Cyan = 0x00FFFF, ///< @htmlcolorblock{00FFFF} + DarkBlue = 0x00008B, ///< @htmlcolorblock{00008B} + DarkCyan = 0x008B8B, ///< @htmlcolorblock{008B8B} + DarkGoldenrod = 0xB8860B, ///< @htmlcolorblock{B8860B} + DarkGray = 0xA9A9A9, ///< @htmlcolorblock{A9A9A9} + DarkGrey = 0xA9A9A9, ///< @htmlcolorblock{A9A9A9} + DarkGreen = 0x006400, ///< @htmlcolorblock{006400} + DarkKhaki = 0xBDB76B, ///< @htmlcolorblock{BDB76B} + DarkMagenta = 0x8B008B, ///< @htmlcolorblock{8B008B} + DarkOliveGreen = 0x556B2F, ///< @htmlcolorblock{556B2F} + DarkOrange = 0xFF8C00, ///< @htmlcolorblock{FF8C00} + DarkOrchid = 0x9932CC, ///< @htmlcolorblock{9932CC} + DarkRed = 0x8B0000, ///< @htmlcolorblock{8B0000} + DarkSalmon = 0xE9967A, ///< @htmlcolorblock{E9967A} + DarkSeaGreen = 0x8FBC8F, ///< @htmlcolorblock{8FBC8F} + DarkSlateBlue = 0x483D8B, ///< @htmlcolorblock{483D8B} + DarkSlateGray = 0x2F4F4F, ///< @htmlcolorblock{2F4F4F} + DarkSlateGrey = 0x2F4F4F, ///< @htmlcolorblock{2F4F4F} + DarkTurquoise = 0x00CED1, ///< @htmlcolorblock{00CED1} + DarkViolet = 0x9400D3, ///< @htmlcolorblock{9400D3} + DeepPink = 0xFF1493, ///< @htmlcolorblock{FF1493} + DeepSkyBlue = 0x00BFFF, ///< @htmlcolorblock{00BFFF} + DimGray = 0x696969, ///< @htmlcolorblock{696969} + DimGrey = 0x696969, ///< @htmlcolorblock{696969} + DodgerBlue = 0x1E90FF, ///< @htmlcolorblock{1E90FF} + FireBrick = 0xB22222, ///< @htmlcolorblock{B22222} + FloralWhite = 0xFFFAF0, ///< @htmlcolorblock{FFFAF0} + ForestGreen = 0x228B22, ///< @htmlcolorblock{228B22} + Fuchsia = 0xFF00FF, ///< @htmlcolorblock{FF00FF} + Gainsboro = 0xDCDCDC, ///< @htmlcolorblock{DCDCDC} + GhostWhite = 0xF8F8FF, ///< @htmlcolorblock{F8F8FF} + Gold = 0xFFD700, ///< @htmlcolorblock{FFD700} + Goldenrod = 0xDAA520, ///< @htmlcolorblock{DAA520} + Gray = 0x808080, ///< @htmlcolorblock{808080} + Grey = 0x808080, ///< @htmlcolorblock{808080} + Green = 0x008000, ///< @htmlcolorblock{008000} + GreenYellow = 0xADFF2F, ///< @htmlcolorblock{ADFF2F} + Honeydew = 0xF0FFF0, ///< @htmlcolorblock{F0FFF0} + HotPink = 0xFF69B4, ///< @htmlcolorblock{FF69B4} + IndianRed = 0xCD5C5C, ///< @htmlcolorblock{CD5C5C} + Indigo = 0x4B0082, ///< @htmlcolorblock{4B0082} + Ivory = 0xFFFFF0, ///< @htmlcolorblock{FFFFF0} + Khaki = 0xF0E68C, ///< @htmlcolorblock{F0E68C} + Lavender = 0xE6E6FA, ///< @htmlcolorblock{E6E6FA} + LavenderBlush = 0xFFF0F5, ///< @htmlcolorblock{FFF0F5} + LawnGreen = 0x7CFC00, ///< @htmlcolorblock{7CFC00} + LemonChiffon = 0xFFFACD, ///< @htmlcolorblock{FFFACD} + LightBlue = 0xADD8E6, ///< @htmlcolorblock{ADD8E6} + LightCoral = 0xF08080, ///< @htmlcolorblock{F08080} + LightCyan = 0xE0FFFF, ///< @htmlcolorblock{E0FFFF} + LightGoldenrodYellow = 0xFAFAD2, ///< @htmlcolorblock{FAFAD2} + LightGreen = 0x90EE90, ///< @htmlcolorblock{90EE90} + LightGrey = 0xD3D3D3, ///< @htmlcolorblock{D3D3D3} + LightPink = 0xFFB6C1, ///< @htmlcolorblock{FFB6C1} + LightSalmon = 0xFFA07A, ///< @htmlcolorblock{FFA07A} + LightSeaGreen = 0x20B2AA, ///< @htmlcolorblock{20B2AA} + LightSkyBlue = 0x87CEFA, ///< @htmlcolorblock{87CEFA} + LightSlateGray = 0x778899, ///< @htmlcolorblock{778899} + LightSlateGrey = 0x778899, ///< @htmlcolorblock{778899} + LightSteelBlue = 0xB0C4DE, ///< @htmlcolorblock{B0C4DE} + LightYellow = 0xFFFFE0, ///< @htmlcolorblock{FFFFE0} + Lime = 0x00FF00, ///< @htmlcolorblock{00FF00} + LimeGreen = 0x32CD32, ///< @htmlcolorblock{32CD32} + Linen = 0xFAF0E6, ///< @htmlcolorblock{FAF0E6} + Magenta = 0xFF00FF, ///< @htmlcolorblock{FF00FF} + Maroon = 0x800000, ///< @htmlcolorblock{800000} + MediumAquamarine = 0x66CDAA, ///< @htmlcolorblock{66CDAA} + MediumBlue = 0x0000CD, ///< @htmlcolorblock{0000CD} + MediumOrchid = 0xBA55D3, ///< @htmlcolorblock{BA55D3} + MediumPurple = 0x9370DB, ///< @htmlcolorblock{9370DB} + MediumSeaGreen = 0x3CB371, ///< @htmlcolorblock{3CB371} + MediumSlateBlue = 0x7B68EE, ///< @htmlcolorblock{7B68EE} + MediumSpringGreen = 0x00FA9A, ///< @htmlcolorblock{00FA9A} + MediumTurquoise = 0x48D1CC, ///< @htmlcolorblock{48D1CC} + MediumVioletRed = 0xC71585, ///< @htmlcolorblock{C71585} + MidnightBlue = 0x191970, ///< @htmlcolorblock{191970} + MintCream = 0xF5FFFA, ///< @htmlcolorblock{F5FFFA} + MistyRose = 0xFFE4E1, ///< @htmlcolorblock{FFE4E1} + Moccasin = 0xFFE4B5, ///< @htmlcolorblock{FFE4B5} + NavajoWhite = 0xFFDEAD, ///< @htmlcolorblock{FFDEAD} + Navy = 0x000080, ///< @htmlcolorblock{000080} + OldLace = 0xFDF5E6, ///< @htmlcolorblock{FDF5E6} + Olive = 0x808000, ///< @htmlcolorblock{808000} + OliveDrab = 0x6B8E23, ///< @htmlcolorblock{6B8E23} + Orange = 0xFFA500, ///< @htmlcolorblock{FFA500} + OrangeRed = 0xFF4500, ///< @htmlcolorblock{FF4500} + Orchid = 0xDA70D6, ///< @htmlcolorblock{DA70D6} + PaleGoldenrod = 0xEEE8AA, ///< @htmlcolorblock{EEE8AA} + PaleGreen = 0x98FB98, ///< @htmlcolorblock{98FB98} + PaleTurquoise = 0xAFEEEE, ///< @htmlcolorblock{AFEEEE} + PaleVioletRed = 0xDB7093, ///< @htmlcolorblock{DB7093} + PapayaWhip = 0xFFEFD5, ///< @htmlcolorblock{FFEFD5} + PeachPuff = 0xFFDAB9, ///< @htmlcolorblock{FFDAB9} + Peru = 0xCD853F, ///< @htmlcolorblock{CD853F} + Pink = 0xFFC0CB, ///< @htmlcolorblock{FFC0CB} + Plaid = 0xCC5533, ///< @htmlcolorblock{CC5533} + Plum = 0xDDA0DD, ///< @htmlcolorblock{DDA0DD} + PowderBlue = 0xB0E0E6, ///< @htmlcolorblock{B0E0E6} + Purple = 0x800080, ///< @htmlcolorblock{800080} + Red = 0xFF0000, ///< @htmlcolorblock{FF0000} + RosyBrown = 0xBC8F8F, ///< @htmlcolorblock{BC8F8F} + RoyalBlue = 0x4169E1, ///< @htmlcolorblock{4169E1} + SaddleBrown = 0x8B4513, ///< @htmlcolorblock{8B4513} + Salmon = 0xFA8072, ///< @htmlcolorblock{FA8072} + SandyBrown = 0xF4A460, ///< @htmlcolorblock{F4A460} + SeaGreen = 0x2E8B57, ///< @htmlcolorblock{2E8B57} + Seashell = 0xFFF5EE, ///< @htmlcolorblock{FFF5EE} + Sienna = 0xA0522D, ///< @htmlcolorblock{A0522D} + Silver = 0xC0C0C0, ///< @htmlcolorblock{C0C0C0} + SkyBlue = 0x87CEEB, ///< @htmlcolorblock{87CEEB} + SlateBlue = 0x6A5ACD, ///< @htmlcolorblock{6A5ACD} + SlateGray = 0x708090, ///< @htmlcolorblock{708090} + SlateGrey = 0x708090, ///< @htmlcolorblock{708090} + Snow = 0xFFFAFA, ///< @htmlcolorblock{FFFAFA} + SpringGreen = 0x00FF7F, ///< @htmlcolorblock{00FF7F} + SteelBlue = 0x4682B4, ///< @htmlcolorblock{4682B4} + Tan = 0xD2B48C, ///< @htmlcolorblock{D2B48C} + Teal = 0x008080, ///< @htmlcolorblock{008080} + Thistle = 0xD8BFD8, ///< @htmlcolorblock{D8BFD8} + Tomato = 0xFF6347, ///< @htmlcolorblock{FF6347} + Turquoise = 0x40E0D0, ///< @htmlcolorblock{40E0D0} + Violet = 0xEE82EE, ///< @htmlcolorblock{EE82EE} + Wheat = 0xF5DEB3, ///< @htmlcolorblock{F5DEB3} + White = 0xFFFFFF, ///< @htmlcolorblock{FFFFFF} + WhiteSmoke = 0xF5F5F5, ///< @htmlcolorblock{F5F5F5} + Yellow = 0xFFFF00, ///< @htmlcolorblock{FFFF00} + YellowGreen = 0x9ACD32, ///< @htmlcolorblock{9ACD32} + + // LED RGB color that roughly approximates + // the color of incandescent fairy lights, + // assuming that you're using FastLED + // color correction on your LEDs (recommended). + FairyLight = 0xFFE42D, ///< @htmlcolorblock{FFE42D} + + // If you are using no color correction, use this + FairyLightNCC = 0xFF9D2A ///< @htmlcolorblock{FFE42D} + +} HTMLColorCode; + +extern const palette_t PaletteCloudColors; +extern const palette_t PaletteLavaColors; +extern const palette_t PaletteFlameColors; +extern const palette_t PaletteOceanColors; +extern const palette_t PaletteForestColors; +extern const palette_t PaletteRainbowColors; +extern const palette_t PalettePartyColors; +extern const palette_t PaletteBlackBodyColors; +extern const palette_t PaletteHeatColors; +extern const palette_t PaletteAuroraColors; + +/** + * \brief Return a color from a palette + * \param[in] index from 0 to 255, the index of color we want from the palette + * \param[in] palette The palette to sample from. Values are interpolated + * \param[in] brightness The brighness of the color, default is max at 255 + * \return The desired color + */ +uint32_t get_color_from_palette(const uint8_t index, const palette_t& palette, + const uint8_t brightness = 255); +uint32_t get_color_from_palette(const uint16_t index, const palette_t& palette, + const uint8_t brightness = 255); + +#endif \ No newline at end of file diff --git a/src/system/colors/wipes.cpp b/src/system/colors/wipes.cpp new file mode 100644 index 0000000..9cfb0df --- /dev/null +++ b/src/system/colors/wipes.cpp @@ -0,0 +1,143 @@ + +#include + +#include "../utils/constants.h" +#include "../utils/coordinates.h" +#include "../utils/strip.h" +#include "colors.h" + +namespace animations { + +bool dot_wipe_down(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff) { + static uint16_t targetIndex = 0; + + // reset condition + if (restart) { + targetIndex = 0; + return false; + } + + // finished if the target index is over the led limit + const uint16_t endIndex = + (cutOff <= 0.0 or cutOff >= 1.0) ? LED_COUNT : ceil(LED_COUNT * cutOff); + + // convert duration in delay for each segment + const unsigned long delay = + max(LOOP_UPDATE_PERIOD, duration / (float)LED_COUNT); + + if (targetIndex < LED_COUNT) { + strip.fadeToBlackBy(fadeOut); + // increment + for (uint32_t increment = LED_COUNT / ceil(duration / delay); increment > 0; + increment--) { + strip.setPixelColor(targetIndex, color.get_color(targetIndex, LED_COUNT)); + targetIndex += 1; + if (targetIndex >= endIndex or targetIndex >= LED_COUNT) break; + } + } + + return targetIndex >= endIndex or targetIndex >= LED_COUNT; +} + +bool dot_wipe_up(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff) { + static uint16_t targetIndex = LED_COUNT - 1; + + // reset condition + if (restart) { + targetIndex = LED_COUNT - 1; + return false; + } + + // finished if the target index is over the led limit + const uint16_t endIndex = + (cutOff <= 0.0 or cutOff >= 1.0) ? 0 : floor((1.0 - cutOff) * LED_COUNT); + + // convert duration in delay for each segment + const unsigned long delay = + max(LOOP_UPDATE_PERIOD, duration / (float)LED_COUNT); + + if (targetIndex >= 0 and targetIndex < LED_COUNT) { + strip.fadeToBlackBy(fadeOut); + // increment + for (uint32_t increment = LED_COUNT / ceil(duration / delay); increment > 0; + increment--) { + strip.setPixelColor(targetIndex, color.get_color(targetIndex, LED_COUNT)); + targetIndex -= 1; + if (targetIndex == UINT16_MAX or targetIndex <= endIndex) return true; + } + } + + return targetIndex == UINT16_MAX or targetIndex <= endIndex; +} + +bool color_wipe_down(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip, const float cutOff) { + return dot_wipe_down(color, duration, 0, restart, strip, cutOff); +} + +bool color_wipe_up(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip, const float cutOff) { + return dot_wipe_up(color, duration, 0, restart, strip, cutOff); +} + +bool color_vertical_wipe_right(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip) { + static uint16_t currentX = 0; + if (restart) { + currentX = 0; + } + + // convert duration in delay for each segment + const unsigned long delay = + max(LOOP_UPDATE_PERIOD, duration / stripXCoordinates); + if (duration / stripXCoordinates <= LOOP_UPDATE_PERIOD) { + for (uint16_t increment = stripXCoordinates / ceil(duration / delay); + increment > 0; increment--) { + for (uint16_t y = 0; y <= stripYCoordinates; ++y) { + const auto pixelIndex = to_strip(currentX, y); + strip.setPixelColor(pixelIndex, color.get_color(pixelIndex, LED_COUNT)); + } + + ++currentX; + if (currentX > stripXCoordinates) { + return true; + } + } + } else // delay is too long to increment cleanly, use gradients + { + static uint16_t lastSubstep = 0; + static auto buffer1 = strip.get_buffer_ptr(0); + const uint16_t maxSubstep = + LOOP_UPDATE_PERIOD / (stripXCoordinates / duration * 1000.0); + + if (lastSubstep == 0) { + strip.buffer_current_colors(0); + } + + const float level = lastSubstep / (float)maxSubstep; + + for (uint16_t y = 0; y <= stripYCoordinates; ++y) { + const auto pixelIndex = to_strip(currentX, y); + strip.setPixelColor( + pixelIndex, + utils::get_gradient(buffer1[pixelIndex], + color.get_color(pixelIndex, LED_COUNT), level)); + } + + lastSubstep++; + if (lastSubstep > maxSubstep) { + lastSubstep = 0; + ++currentX; + if (currentX > stripXCoordinates) { + return true; + } + } + } + return false; +} + +}; // namespace animations \ No newline at end of file diff --git a/src/system/colors/wipes.h b/src/system/colors/wipes.h new file mode 100644 index 0000000..7f86c08 --- /dev/null +++ b/src/system/colors/wipes.h @@ -0,0 +1,77 @@ +#ifndef ANIMATIONS_WIPES_H +#define ANIMATIONS_WIPES_H + +#include "../utils/strip.h" +#include "colors.h" + +namespace animations { + +/** + * \brief Pass a color dot around the display, with a color, during a certain + * duration. From top to bottom + * \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] fadeOut The animation fade speed (0: no fade) + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of \return True if the animation is finished + */ +bool dot_wipe_down(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff = 1); + +/** + * \brief Pass a color dot around the display, with a color, during a certain + * duration. From bottom to top + * \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] fadeOut The animation fade speed (0: no fade) + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of \return True if the animation is finished + */ +bool dot_wipe_up(const Color& color, const uint32_t duration, + const uint8_t fadeOut, const bool restart, LedStrip& strip, + const float cutOff = 1); + +/** + * \brief Progressivly fill the display with a color, during a certain duration. + * From top to bottom \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of \return True if the animation is finished + */ +bool color_wipe_down(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip, + const float cutOff = 1); + +/** + * \brief Progressivly fill the display with a color, during a certain duration. + * From bottom to top \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \param[in] cutOff between 0 and 1, how much this gradient will fill the + * display before suddently cutting of \return True if the animation is finished + */ +bool color_wipe_up(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip, const float cutOff = 1); + +/** + * \brief Progressivly fill the display with a color, during a certain duration. + * From left to right \param[in] color class that returns a color to display + * \param[in] duration The duration of the animation, in milliseconds + * \param[in] restart If true, the animation will restart + * \param[in, out] strip The led strip to control + * \return True if the animation is finished + */ +bool color_vertical_wipe_right(const Color& color, const uint32_t duration, + const bool restart, LedStrip& strip); + +}; // namespace animations + +#endif \ No newline at end of file diff --git a/src/system/physical/IMU.cpp b/src/system/physical/IMU.cpp index 755e972..b9420c3 100644 --- a/src/system/physical/IMU.cpp +++ b/src/system/physical/IMU.cpp @@ -103,4 +103,183 @@ struct Grain { uint16_t pos; } grain[N_GRAINS]; +void gravity_fluid(const uint8_t fade, const Color& color, LedStrip& strip, + const bool isReset) { + constexpr uint16_t WIDTH = stripXCoordinates; + constexpr uint16_t HEIGHT = stripYCoordinates; + + const float scaling = 100.0; + + constexpr uint16_t MAX_X = + (WIDTH * 256 - 1); // Maximum X coordinate in grain space + constexpr uint16_t MAX_Y = + (HEIGHT * 256 - 1); // Maximum Y coordinate in grain space + + static uint16_t* img = strip._buffer16b; + + static bool isInit = false; + if (!isInit or isReset) { + memset(strip._buffer16b, 0, sizeof(strip._buffer16b)); + + uint16_t j = 0; + for (uint16_t i = 0; i < N_GRAINS; i++) { // For each sand grain... + do { + grain[i].x = random(WIDTH * 256); // Assign random position within + grain[i].y = random(HEIGHT * 256); // the 'grain' coordinate space + // Check if corresponding pixel position is already occupied... + for (j = 0; (j < i) && (((grain[i].x / 256) != (grain[j].x / 256)) || + ((grain[i].y / 256) != (grain[j].y / 256))); + j++) + ; + } while (j < i); // Keep retrying until a clear spot is found + + img[(grain[i].y / 256) * WIDTH + (grain[i].x / 256)] = 255; // Mark it + + grain[i].pos = (grain[i].y / 256) * WIDTH + (grain[i].x / 256); + grain[i].vx = grain[i].vy = 0; // Initial velocity is zero + } + + // init set to true, must reset to go back to the start + isInit = true; + return; + } + + const auto& reading = get_reading(); + + constexpr float accelerometerScale = 9.81 * 256; + int16_t ax = reading.accel.y * scaling * accelerometerScale / + 256; // Transform accelerometer axes + int16_t ay = reading.accel.x * scaling * accelerometerScale / + 256; // to grain coordinate space + int16_t az = reading.accel.z * scaling * accelerometerScale / 256; + + int16_t az2 = az * 2 + 1; // Range of random motion to add back in + + // ...and apply 2D accel vector to grain velocities... + int32_t v2; // Velocity squared + float v; // Absolute velocity + for (int i = 0; i < N_GRAINS; i++) { + grain[i].vx += ax; // A little randomness makes + grain[i].vy += ay + random(az2); // tall stacks topple better! + // Terminal velocity (in any direction) is 256 units -- equal to + // 1 pixel -- which keeps moving grains from passing through each other + // and other such mayhem. Though it takes some extra math, velocity is + // clipped as a 2D vector (not separately-limited X & Y) so that + // diagonal movement isn't faster + v2 = + (int32_t)grain[i].vx * grain[i].vx + (int32_t)grain[i].vy * grain[i].vy; + if (v2 > 65536) { // If v^2 > 65536, then v > 256 + v = sqrt((float)v2); // Velocity vector magnitude + grain[i].vx = (int)(256.0 * (float)grain[i].vx / v); // Maintain heading + grain[i].vy = (int)(256.0 * (float)grain[i].vy / v); // Limit magnitude + } + } + + // ...then update position of each grain, one at a time, checking for + // collisions and having them react. This really seems like it shouldn't + // work, as only one grain is considered at a time while the rest are + // regarded as stationary. Yet this naive algorithm, taking many not- + // technically-quite-correct steps, and repeated quickly enough, + // visually integrates into something that somewhat resembles physics. + // (I'd initially tried implementing this as a bunch of concurrent and + // "realistic" elastic collisions among circular grains, but the + // calculations and volument of code quickly got out of hand for both + // the tiny 8-bit AVR microcontroller and my tiny dinosaur brain.) + + uint16_t i, bytes, oldidx, newidx, delta; + int16_t newx, newy; + + for (i = 0; i < N_GRAINS; i++) { + newx = grain[i].x + grain[i].vx; // New position in grain space + newy = grain[i].y + grain[i].vy; + if (newx > MAX_X) { // If grain would go out of bounds + newx = MAX_X; // keep it inside, and + grain[i].vx /= -2; // give a slight bounce off the wall + } else if (newx < 0) { + newx = 0; + grain[i].vx /= -2; + } + // bounce of the walls + if (newy > MAX_Y) { + newy = MAX_Y; + grain[i].vy /= -2; + } else if (newy < 0) { + newy = 0; + grain[i].vy /= -2; + } + + oldidx = (grain[i].y / 256) * WIDTH + (grain[i].x / 256); // Prior pixel # + newidx = (newy / 256) * WIDTH + (newx / 256); // New pixel # + if ((oldidx != newidx) && // If grain is moving to a new pixel... + img[newidx]) { // but if that pixel is already occupied... + delta = abs(newidx - oldidx); // What direction when blocked? + if (delta == 1) { // 1 pixel left or right) + newx = grain[i].x; // Cancel X motion + grain[i].vx /= -2; // and bounce X velocity (Y is OK) + newidx = oldidx; // No pixel change + } else if (delta == WIDTH) { // 1 pixel up or down + newy = grain[i].y; // Cancel Y motion + grain[i].vy /= -2; // and bounce Y velocity (X is OK) + newidx = oldidx; // No pixel change + } else { // Diagonal intersection is more tricky... + // Try skidding along just one axis of motion if possible (start w/ + // faster axis). Because we've already established that diagonal + // (both-axis) motion is occurring, moving on either axis alone WILL + // change the pixel index, no need to check that again. + if ((abs(grain[i].vx) - abs(grain[i].vy)) >= 0) { // X axis is faster + newidx = (grain[i].y / 256) * WIDTH + (newx / 256); + if (!img[newidx]) { // That pixel's free! Take it! But... + newy = grain[i].y; // Cancel Y motion + grain[i].vy /= -2; // and bounce Y velocity + } else { // X pixel is taken, so try Y... + newidx = (newy / 256) * WIDTH + (grain[i].x / 256); + if (!img[newidx]) { // Pixel is free, take it, but first... + newx = grain[i].x; // Cancel X motion + grain[i].vx /= -2; // and bounce X velocity + } else { // Both spots are occupied + newx = grain[i].x; // Cancel X & Y motion + newy = grain[i].y; + grain[i].vx /= -2; // Bounce X & Y velocity + grain[i].vy /= -2; + newidx = oldidx; // Not moving + } + } + } else { // Y axis is faster, start there + newidx = (newy / 256) * WIDTH + (grain[i].x / 256); + if (!img[newidx]) { // Pixel's free! Take it! But... + newx = grain[i].x; // Cancel X motion + grain[i].vy /= -2; // and bounce X velocity + } else { // Y pixel is taken, so try X... + newidx = (grain[i].y / 256) * WIDTH + (newx / 256); + if (!img[newidx]) { // Pixel is free, take it, but first... + newy = grain[i].y; // Cancel Y motion + grain[i].vy /= -2; // and bounce Y velocity + } else { // Both spots are occupied + newx = grain[i].x; // Cancel X & Y motion + newy = grain[i].y; + grain[i].vx /= -2; // Bounce X & Y velocity + grain[i].vy /= -2; + newidx = oldidx; // Not moving + } + } + } + } + } + grain[i].x = newx; // Update grain position + grain[i].y = newy; + img[oldidx] = 0; // Clear old spot (might be same as new, that's OK) + img[newidx] = 255; // Set new spot + grain[i].pos = newidx; + } + + strip.fadeToBlackBy(fade); + for (i = 0; i < N_GRAINS; i++) { + int yPos = grain[i].pos / WIDTH; + int xPos = grain[i].pos % WIDTH; + + const auto& stripCoord = to_strip(xPos, yPos); + strip.setPixelColor(stripCoord, color.get_color(i, N_GRAINS)); + } +} + } // namespace imu \ No newline at end of file diff --git a/src/system/physical/IMU.h b/src/system/physical/IMU.h index 69660f3..0d8b1f9 100644 --- a/src/system/physical/IMU.h +++ b/src/system/physical/IMU.h @@ -1,6 +1,8 @@ #ifndef IMU_H #define IMU_H +#include "../colors/animations.h" + /// Contains the handling of the gyroscope and accelerometer, and some /// associated animations namespace imu { @@ -10,6 +12,9 @@ void enable(); // close the imu readings void disable(); +void gravity_fluid(const uint8_t fade, const Color& color, LedStrip& strip, + const bool isReset); + } // namespace imu #endif \ No newline at end of file diff --git a/src/system/physical/MicroPhone.cpp b/src/system/physical/MicroPhone.cpp index 61926f5..8507511 100644 --- a/src/system/physical/MicroPhone.cpp +++ b/src/system/physical/MicroPhone.cpp @@ -144,4 +144,176 @@ bool processFFT(const bool runFFT = true) { return true; } -} // namespace microphone \ No newline at end of file +void vu_meter(const Color& vuColor, const uint8_t fadeOut, LedStrip& strip) { + const float decibels = get_sound_level_Db(); + // convert to 0 - 1 + const float vuLevel = (decibels + abs(silenceLevelDb)) / highLevelDb; + + // display the gradient + strip.fadeToBlackBy(fadeOut); + animations::fill(vuColor, strip, vuLevel); +} + +void fftDisplay(const uint8_t speed, const uint8_t scale, + const palette_t& palette, const bool reset, LedStrip& strip, + const uint8_t nbBands) { + const int NUM_BANDS = map(nbBands, 0, 255, 1, 16); + const uint16_t cols = ceil(stripXCoordinates); + const uint16_t rows = ceil(stripYCoordinates); + + static uint32_t lastCall = 0; + static uint32_t call = 0; + + static uint16_t* previousBarHeight = strip._buffer16b; + + if (reset or call == 0) { + lastCall = 0; + call = 0; + + memset(strip._buffer16b, 0, sizeof(strip._buffer16b)); + } + + bool rippleTime = false; + if (millis() - lastCall >= (256U - scale)) { + lastCall = millis(); + rippleTime = true; + } + + // process the sound input + if (!processFFT()) { + return; + } + + int fadeoutDelay = (256 - speed) / 64; + if ((fadeoutDelay <= 1) || ((call % fadeoutDelay) == 0)) + strip.fadeToBlackBy(speed); + + for (int x = 0; x < cols; x++) { + uint8_t band = map(x, 0, cols - 1, 0, NUM_BANDS - 1); + if (NUM_BANDS < 16) + band = map(band, 0, NUM_BANDS - 1, 0, + 15); // always use full range. comment out this line to get + // the previous behaviour. + band = constrain(band, 0, 15); + uint16_t barHeight = map(fftResult[band], 0, 255, 0, + rows); // do not subtract -1 from rows here + if (barHeight > previousBarHeight[x]) + previousBarHeight[x] = barHeight; // drive the peak up + uint32_t ledColor = 0; // black + for (int y = 0; y < barHeight; y++) { + uint8_t colorIndex = map(y, 0, rows - 1, 0, 255); + + ledColor = get_color_from_palette(colorIndex, palette); + strip.setPixelColorXY(x, rows - y, ledColor); + } + if (previousBarHeight[x] > 0) { + strip.setPixelColorXY(x, rows - 1 - previousBarHeight[x], ledColor); + } + + if (rippleTime && previousBarHeight[x] > 0) + previousBarHeight[x]--; // delay/ripple effect + } +} + +// 4 bytes +typedef struct Ripple { + uint8_t state; + uint8_t color; + uint16_t pos; +} ripple; + +void mode_ripplepeak(const uint8_t rippleNumber, const palette_t& palette, + LedStrip& strip) { // * Ripple peak. By Andrew Tuline. + // This currently has no controls. +#define maxsteps 16 // Case statement wouldn't allow a variable. + + static uint32_t* ripplesBuffer = strip.get_buffer_ptr(0); + static uint8_t fadeLevel = 255; + + uint16_t maxRipples = 128; + Ripple* ripples = reinterpret_cast(ripplesBuffer); + + strip.fadeToBlackBy( + 240); // Lower frame rate means less effective fading than FastLED + strip.fadeToBlackBy(240); + + COLOR cFond; + COLOR c; + + processFFT(); // ignore return + + const uint8_t rippleCount = map(rippleNumber, 0, 255, 0, maxRipples); + for (int i = 0; i < rippleCount; i++) { // Limit the number of ripples. + if (samplePeak) ripples[i].state = 255; + + switch (ripples[i].state) { + case 254: // Inactive mode + break; + + case 255: // Initialize ripple variables. + ripples[i].pos = random16(LED_COUNT); + if (FFT_MajorPeak > 1) // log10(0) is "forbidden" (throws exception) + ripples[i].color = (int)(log10f(FFT_MajorPeak) * 128); + else + ripples[i].color = 0; + + ripples[i].state = 0; + break; + + case 0: + cFond.color = strip.getPixelColor(i); + + c.color = get_color_from_palette(ripples[i].color, palette); + strip.setPixelColor(ripples[i].pos, + utils::color_blend(cFond, c, fadeLevel)); + ripples[i].state++; + break; + + case maxsteps: // At the end of the ripples. 254 is an inactive mode. + ripples[i].state = 254; + break; + + default: // Middle of the ripples. + cFond.color = strip.getPixelColor(i); + c.color = get_color_from_palette(ripples[i].color, palette); + strip.setPixelColor( + (ripples[i].pos + ripples[i].state + LED_COUNT) % LED_COUNT, + utils::color_blend(cFond, c, fadeLevel / ripples[i].state * 2)); + strip.setPixelColor( + (ripples[i].pos - ripples[i].state + LED_COUNT) % LED_COUNT, + utils::color_blend(cFond, c, fadeLevel / ripples[i].state * 2)); + ripples[i].state++; // Next step. + break; + } // switch step + } // for i +} // mode_ripplepeak() + +void mode_2DWaverly(const uint8_t speed, const uint8_t scale, + const palette_t& palette, LedStrip& strip) { + const uint16_t cols = ceil(stripXCoordinates); + const uint16_t rows = ceil(stripYCoordinates); + + if (!processFFT(false)) return; + + strip.fadeToBlackBy(speed); + + long t = millis() / 2; + for (int i = 0; i < cols; i++) { + uint16_t thisVal = (1 + scale / 64) * noise8::inoise(i * 45, t, t) / 2; + // use audio if available + thisVal /= 32; // reduce intensity of inoise8() + thisVal *= + max(10.0, volumeSmth); // set a min size to always have the visual + thisVal = constrain(thisVal, 0, 512); + uint16_t thisMax = map(thisVal, 0, 512, 0, rows / 2); + for (int j = 0; j < thisMax; j++) { + const auto color = + get_color_from_palette((uint8_t)map(j, 0, thisMax, 250, 0), palette); + strip.addPixelColorXY(i, j, color); + strip.addPixelColorXY((cols - 1) - i, (rows - 1) - j, color); + } + } + strip.blur(16); +} // mode_2DWaverly() + +} // namespace microphone diff --git a/src/system/physical/MicroPhone.h b/src/system/physical/MicroPhone.h index 24dd662..df98d32 100644 --- a/src/system/physical/MicroPhone.h +++ b/src/system/physical/MicroPhone.h @@ -3,6 +3,8 @@ #include +#include "../colors/animations.h" + namespace microphone { // decibel level for a silent room @@ -20,6 +22,20 @@ extern void disable(); */ extern float get_sound_level_Db(); +/** + * \brief Vu meter: should be reactive + */ +void vu_meter(const Color& vuColor, const uint8_t fadeOut, LedStrip& strip); + +void fftDisplay(const uint8_t speed, const uint8_t scale, + const palette_t& palette, const bool reset, LedStrip& strip, + const uint8_t nbBands = stripXCoordinates); + +void mode_ripplepeak(const uint8_t rippleNumber, const palette_t& palette, + LedStrip& strip); +void mode_2DWaverly(const uint8_t speed, const uint8_t scale, + const palette_t& palette, LedStrip& strip); + } // namespace microphone #endif \ No newline at end of file diff --git a/src/system/utils/constants.h b/src/system/utils/constants.h index 79fe093..bd3a227 100644 --- a/src/system/utils/constants.h +++ b/src/system/utils/constants.h @@ -56,7 +56,10 @@ constexpr float maxStripConsumption_A = totalCons_Watt / inputVoltage_V; // compute the expected average loop runtime, scaled with the number of led +25% // for computations -constexpr uint32_t LOOP_UPDATE_PERIOD = 40; +constexpr uint32_t LOOP_UPDATE_PERIOD = + ceil(1.25 * + (0.0483333 * LED_COUNT + + 1.5983333)); // milliseconds (average): depends of the number of leds constexpr float batteryCritical = 3; // % constexpr float batteryLow = 5; // % diff --git a/src/system/utils/coordinates.cpp b/src/system/utils/coordinates.cpp new file mode 100644 index 0000000..5574555 --- /dev/null +++ b/src/system/utils/coordinates.cpp @@ -0,0 +1,42 @@ +#include "coordinates.h" + +#include +#include + +#include "../ext/math8.h" +#include "constants.h" + +uint16_t to_screen_x(const uint16_t ledIndex) { + if (ledIndex > LED_COUNT) return 0; + + return round(std::fmod(ledIndex, stripXCoordinates)); +} + +uint16_t to_screen_y(const uint16_t ledIndex) { + if (ledIndex > LED_COUNT) return 0; + static constexpr float divider = 1.0 / stripXCoordinates; + return floor(ledIndex * divider); +} + +uint16_t to_screen_z(const uint16_t ledIndex) { return 1; } + +uint16_t to_strip(uint16_t screenX, uint16_t screenY) { + if (screenX > stripXCoordinates) screenX = stripXCoordinates; + if (screenY > stripYCoordinates) screenY = stripYCoordinates; + + return constrain(screenX + screenY * stripXCoordinates, 0, LED_COUNT - 1); +} + +Cartesian to_lamp(const uint16_t ledIndex) { + // save some processing power + static constexpr float calc = 1.0 / stripXCoordinates * UINT16_MAX; + const uint16_t traj = to_screen_x(ledIndex) * calc; + + const int16_t x = cos16(traj); + const int16_t y = sin16(traj); + + static constexpr float calc2 = (1.0 / stripYCoordinates) * INT16_MAX * 2.0; + const int16_t z = to_screen_y(ledIndex) * calc2; + + return Cartesian(x, y, z); +} \ No newline at end of file diff --git a/src/system/utils/coordinates.h b/src/system/utils/coordinates.h new file mode 100644 index 0000000..a72b586 --- /dev/null +++ b/src/system/utils/coordinates.h @@ -0,0 +1,42 @@ +#ifndef UTILS_COORDINATES_H +#define UTILS_COORDINATES_H + +#include + +struct Cartesian { + int16_t x; + int16_t y; + int16_t z; + + Cartesian(const int16_t x, const int16_t y, const int16_t z) + : x(x), y(y), z(z){}; + + Cartesian() : x(0), y(0), z(0) {} +}; + +/** + * X is the vertical axis, starting at zero and ending at stripXCoordinates + */ +uint16_t to_screen_x(const uint16_t ledIndex); + +/** + * Y is the horizontal axis, starting at zero and ending at stripYCoordinates + */ +uint16_t to_screen_y(const uint16_t ledIndex); + +/** + * Z is the depth axis, fixed to one (screen space) + */ +uint16_t to_screen_z(const uint16_t ledIndex); + +/** + * Given the x and y, return the led index + */ +uint16_t to_strip(const uint16_t screenX, const uint16_t screenY); + +/** + * project to lamp space (3D space centered on the lamp axis) + */ +Cartesian to_lamp(const uint16_t ledIndex); + +#endif \ No newline at end of file diff --git a/src/system/utils/strip.h b/src/system/utils/strip.h new file mode 100644 index 0000000..dc33b42 --- /dev/null +++ b/src/system/utils/strip.h @@ -0,0 +1,204 @@ +#ifndef STRIP_H +#define STRIP_H + +#include + +#include +#include + +#include "../../user_constants.h" +#include "../ext/scale8.h" +#include "constants.h" +#include "coordinates.h" +#include "utils.h" + +class LedStrip : public Adafruit_NeoPixel { + public: + LedStrip(int16_t pin, neoPixelType type = NEO_RGB + NEO_KHZ800) + : Adafruit_NeoPixel(LED_COUNT, pin, type) { + COLOR c; + c.color = 0; + for (uint16_t i = 0; i < LED_COUNT; ++i) { + _colors[i] = c; + + lampCoordinates[i] = to_lamp(i); + } + } + + void show() { + if (hasSomeChanges) { + // only show if some changes were made + Adafruit_NeoPixel::show(); + } + hasSomeChanges = false; + } + + void setPixelColor(uint16_t n, COLOR c) { + n = constrain(n, 0, LED_COUNT - 1); + _colors[n] = c; + hasSomeChanges = true; + Adafruit_NeoPixel::setPixelColor(n, c.color); + } + + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) { + COLOR c; + c.red = r; + c.green = g; + c.blue = b; + + setPixelColor(n, c); + } + + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + COLOR c; + c.red = r; + c.green = g; + c.blue = b; + c.white = w; + + setPixelColor(n, c); + } + + void setPixelColor(uint16_t n, uint32_t c) { + COLOR co; + co.color = c; + + setPixelColor(n, co); + } + + void setPixelColorXY(uint16_t x, uint16_t y, COLOR c) { + setPixelColor(to_strip(x, y), c); + } + + void setPixelColorXY(uint16_t x, uint16_t y, uint32_t c) { + setPixelColor(to_strip(x, y), c); + } + + void fadeToBlackBy(const uint8_t fadeBy) { + if (fadeBy == 0) return; // optimization - no scaling to apply + + for (uint16_t i = 0; i < LED_COUNT; ++i) { + COLOR c; + c.color = getPixelColor(i); + setPixelColor(i, utils::color_fade(c, 255 - fadeBy)); + } + } + + /* + * Put a value 0 to 255 in to get a color value. + * The colours are a transition r -> g -> b -> back to r + * Inspired by the Adafruit examples. + */ + uint32_t color_wheel(uint8_t pos) { + pos = 255 - pos; + if (pos < 85) { + return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | + (pos * 3); + } else if (pos < 170) { + pos -= 85; + return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | + (255 - pos * 3); + } else { + pos -= 170; + return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | + (0); + } + } + + void blur(uint8_t blur_amount) { + if (blur_amount == 0) return; // optimization: 0 means "don't blur" + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + COLOR carryover; + carryover.color = 0; + for (unsigned i = 0; i < LED_COUNT; i++) { + COLOR cur; + cur.color = getPixelColor(i); + COLOR c = cur; + COLOR part = utils::color_fade(c, seep); + cur = utils::color_add(utils::color_fade(c, keep), carryover, true); + if (i > 0) { + c.color = getPixelColor(i - 1); + setPixelColor(i - 1, utils::color_add(c, part, true)); + } + setPixelColor(i, cur); + carryover = part; + } + } + + uint32_t getPixelColor(uint16_t n) const { + return _colors[constrain(n, 0, LED_COUNT - 1)].color; + } + uint32_t getPixelColorXY(int16_t x, int16_t y) const { + return _colors[constrain(to_strip(x, y), 0, LED_COUNT - 1)].color; + } + + // Blends the specified color with the existing pixel color. + void blendPixelColor(uint16_t n, uint32_t color, uint8_t blend) { + COLOR c1; + c1.color = getPixelColor(n); + COLOR c2; + c2.color = color; + setPixelColor(n, utils::color_blend(c1, c2, blend)); + } + + // Adds the specified color with the existing pixel color perserving color + // balance. + void addPixelColor(uint16_t n, uint32_t color, bool fast = false) { + COLOR c1; + c1.color = getPixelColor(n); + COLOR c2; + c2.color = color; + + setPixelColor(n, utils::color_add(c1, c2, fast)); + } + + void addPixelColorXY(uint16_t x, uint16_t y, uint32_t color, + bool fast = false) { + addPixelColor(to_strip(x, y), color, fast); + } + + uint32_t getRawPixelColor(uint16_t n) const { + return Adafruit_NeoPixel::getPixelColor(n); + } + + void clear() { + COLOR c; + c.color = 0; + for (uint16_t i = 0; i < LED_COUNT; ++i) { + _colors[i] = c; + }; + hasSomeChanges = true; + Adafruit_NeoPixel::clear(); + } + + inline Cartesian get_lamp_coordinates(uint16_t n) const { + return lampCoordinates[constrain(n, 0, LED_COUNT - 1)]; + } + + uint32_t* get_buffer_ptr(const uint8_t index) { return _buffers[index]; } + + void buffer_current_colors(const uint8_t index) { + memcpy(_buffers[index], _colors, sizeof(_colors)); + } + + void fill_buffer(const uint8_t index, const uint32_t value) { + memset(_buffers[index], value, sizeof(_buffers[index])); + } + + uint8_t _buffer8b[LED_COUNT]; + uint16_t _buffer16b[LED_COUNT]; + + private: + COLOR _colors[LED_COUNT]; + // buffers for computations + uint32_t _buffers[2][LED_COUNT]; + + // save the expensive computation on world coordinates + Cartesian lampCoordinates[LED_COUNT]; + + private: + bool hasSomeChanges; +}; + +#endif \ No newline at end of file diff --git a/src/system/utils/text.h b/src/system/utils/text.h new file mode 100644 index 0000000..1e12e72 --- /dev/null +++ b/src/system/utils/text.h @@ -0,0 +1,590 @@ +#define TEXT_H + +#include + +#include +#include + +#include "../colors/colors.h" +#include "constants.h" +#include "strip.h" + +namespace text { + +// fonts from: +// http://www.piclist.com/techref/datafile/charset/extractor/charset_extractor.htm + +struct IFont { + virtual const uint8_t* get_letter(const u_char letter) const = 0; + + virtual uint8_t get_height() const = 0; + virtual uint8_t get_width() const = 0; + + inline int8_t get_arrayLenght() const { + return get_height() * get_width() / 8; + }; +}; + +struct SmallFont : public IFont { + static inline constexpr uint8_t font[96][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // + {0x10, 0xE3, 0x84, 0x10, 0x01, 0x00}, // ! + {0x6D, 0xB4, 0x80, 0x00, 0x00, 0x00}, // " + {0x00, 0xA7, 0xCA, 0x29, 0xF2, 0x80}, // # + {0x20, 0xE4, 0x0C, 0x09, 0xC1, 0x00}, // $ + {0x65, 0x90, 0x84, 0x21, 0x34, 0xC0}, // % + {0x21, 0x45, 0x08, 0x55, 0x23, 0x40}, // & + {0x30, 0xC2, 0x00, 0x00, 0x00, 0x00}, // ' + {0x10, 0x82, 0x08, 0x20, 0x81, 0x00}, // ( + {0x20, 0x41, 0x04, 0x10, 0x42, 0x00}, // ) + {0x00, 0xA3, 0x9F, 0x38, 0xA0, 0x00}, // * + {0x00, 0x41, 0x1F, 0x10, 0x40, 0x00}, // + + {0x00, 0x00, 0x00, 0x00, 0xC3, 0x08}, // , + {0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, // - + {0x00, 0x00, 0x00, 0x00, 0xC3, 0x00}, // . + {0x00, 0x10, 0x84, 0x21, 0x00, 0x00}, // / + {0x39, 0x14, 0xD5, 0x65, 0x13, 0x80}, // 0 + {0x10, 0xC1, 0x04, 0x10, 0x43, 0x80}, // 1 + {0x39, 0x10, 0x46, 0x21, 0x07, 0xC0}, // 2 + {0x39, 0x10, 0x4E, 0x05, 0x13, 0x80}, // 3 + {0x08, 0x62, 0x92, 0x7C, 0x20, 0x80}, // 4 + {0x7D, 0x04, 0x1E, 0x05, 0x13, 0x80}, // 5 + {0x18, 0x84, 0x1E, 0x45, 0x13, 0x80}, // 6 + {0x7C, 0x10, 0x84, 0x20, 0x82, 0x00}, // 7 + {0x39, 0x14, 0x4E, 0x45, 0x13, 0x80}, // 8 + {0x39, 0x14, 0x4F, 0x04, 0x23, 0x00}, // 9 + {0x00, 0x03, 0x0C, 0x00, 0xC3, 0x00}, // : + {0x00, 0x03, 0x0C, 0x00, 0xC3, 0x08}, // ; + {0x08, 0x42, 0x10, 0x20, 0x40, 0x80}, // < + {0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00}, // = + {0x20, 0x40, 0x81, 0x08, 0x42, 0x00}, // > + {0x39, 0x10, 0x46, 0x10, 0x01, 0x00}, // ? + {0x39, 0x15, 0xD5, 0x5D, 0x03, 0x80}, // @ + {0x39, 0x14, 0x51, 0x7D, 0x14, 0x40}, // A + {0x79, 0x14, 0x5E, 0x45, 0x17, 0x80}, // B + {0x39, 0x14, 0x10, 0x41, 0x13, 0x80}, // C + {0x79, 0x14, 0x51, 0x45, 0x17, 0x80}, // D + {0x7D, 0x04, 0x1E, 0x41, 0x07, 0xC0}, // E + {0x7D, 0x04, 0x1E, 0x41, 0x04, 0x00}, // F + {0x39, 0x14, 0x17, 0x45, 0x13, 0xC0}, // G + {0x45, 0x14, 0x5F, 0x45, 0x14, 0x40}, // H + {0x38, 0x41, 0x04, 0x10, 0x43, 0x80}, // I + {0x04, 0x10, 0x41, 0x45, 0x13, 0x80}, // J + {0x45, 0x25, 0x18, 0x51, 0x24, 0x40}, // K + {0x41, 0x04, 0x10, 0x41, 0x07, 0xC0}, // L + {0x45, 0xB5, 0x51, 0x45, 0x14, 0x40}, // M + {0x45, 0x95, 0x53, 0x45, 0x14, 0x40}, // N + {0x39, 0x14, 0x51, 0x45, 0x13, 0x80}, // O + {0x79, 0x14, 0x5E, 0x41, 0x04, 0x00}, // P + {0x39, 0x14, 0x51, 0x55, 0x23, 0x40}, // Q + {0x79, 0x14, 0x5E, 0x49, 0x14, 0x40}, // R + {0x39, 0x14, 0x0E, 0x05, 0x13, 0x80}, // S + {0x7C, 0x41, 0x04, 0x10, 0x41, 0x00}, // T + {0x45, 0x14, 0x51, 0x45, 0x13, 0x80}, // U + {0x45, 0x14, 0x51, 0x44, 0xA1, 0x00}, // V + {0x45, 0x15, 0x55, 0x55, 0x52, 0x80}, // W + {0x45, 0x12, 0x84, 0x29, 0x14, 0x40}, // X + {0x45, 0x14, 0x4A, 0x10, 0x41, 0x00}, // Y + {0x78, 0x21, 0x08, 0x41, 0x07, 0x80}, // Z + {0x38, 0x82, 0x08, 0x20, 0x83, 0x80}, // [ + {0x01, 0x02, 0x04, 0x08, 0x10, 0x00}, // / + {0x38, 0x20, 0x82, 0x08, 0x23, 0x80}, // ] + {0x10, 0xA4, 0x40, 0x00, 0x00, 0x00}, // ^ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x3F}, // _ + {0x30, 0xC1, 0x00, 0x00, 0x00, 0x00}, // ` + {0x00, 0x03, 0x81, 0x3D, 0x13, 0xC0}, // a + {0x41, 0x07, 0x91, 0x45, 0x17, 0x80}, // b + {0x00, 0x03, 0x91, 0x41, 0x13, 0x80}, // c + {0x04, 0x13, 0xD1, 0x45, 0x13, 0xC0}, // d + {0x00, 0x03, 0x91, 0x79, 0x03, 0x80}, // e + {0x18, 0x82, 0x1E, 0x20, 0x82, 0x00}, // f + {0x00, 0x03, 0xD1, 0x44, 0xF0, 0x4E}, // g + {0x41, 0x07, 0x12, 0x49, 0x24, 0x80}, // h + {0x10, 0x01, 0x04, 0x10, 0x41, 0x80}, // i + {0x08, 0x01, 0x82, 0x08, 0x24, 0x8C}, // j + {0x41, 0x04, 0x94, 0x61, 0x44, 0x80}, // k + {0x10, 0x41, 0x04, 0x10, 0x41, 0x80}, // l + {0x00, 0x06, 0x95, 0x55, 0x14, 0x40}, // m + {0x00, 0x07, 0x12, 0x49, 0x24, 0x80}, // n + {0x00, 0x03, 0x91, 0x45, 0x13, 0x80}, // o + {0x00, 0x07, 0x91, 0x45, 0x17, 0x90}, // p + {0x00, 0x03, 0xD1, 0x45, 0x13, 0xC1}, // q + {0x00, 0x05, 0x89, 0x20, 0x87, 0x00}, // r + {0x00, 0x03, 0x90, 0x38, 0x13, 0x80}, // s + {0x00, 0x87, 0x88, 0x20, 0xA1, 0x00}, // t + {0x00, 0x04, 0x92, 0x49, 0x62, 0x80}, // u + {0x00, 0x04, 0x51, 0x44, 0xA1, 0x00}, // v + {0x00, 0x04, 0x51, 0x55, 0xF2, 0x80}, // w + {0x00, 0x04, 0x92, 0x31, 0x24, 0x80}, // x + {0x00, 0x04, 0x92, 0x48, 0xE1, 0x18}, // y + {0x00, 0x07, 0x82, 0x31, 0x07, 0x80}, // z + {0x18, 0x82, 0x18, 0x20, 0x81, 0x80}, // { + {0x10, 0x41, 0x00, 0x10, 0x41, 0x00}, // | + {0x30, 0x20, 0x83, 0x08, 0x23, 0x00}, // } + {0x29, 0x40, 0x00, 0x00, 0x00, 0x00}, // ~ + {0x10, 0xE6, 0xD1, 0x45, 0xF0, 0x00} //  + }; + + const uint8_t* get_letter(const u_char letter) const override { + if (letter >= 32 and letter < 128) + // array starts at ascii 32 + return SmallFont::font[letter - 32]; + return SmallFont::font[1]; // return the char corresponding to '!' + } + + inline uint8_t get_height() const override { return 8; }; + inline uint8_t get_width() const override { return 6; }; +}; +static const SmallFont smallFont; + +struct BigFont : public IFont { + static inline constexpr uint8_t font[96][24] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // + {0x06, 0x00, 0x60, 0x0F, 0x00, 0xF0, 0x0F, 0x00, + 0xF0, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // ! + {0x00, 0x00, 0x00, 0x19, 0x81, 0x98, 0x19, 0x81, + 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // " + {0x00, 0x00, 0x66, 0x06, 0x60, 0x66, 0x3F, 0xF0, + 0xCC, 0x0C, 0xC1, 0x98, 0x19, 0x87, 0xFC, 0x33, + 0x03, 0x30, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00}, // # + {0x06, 0x00, 0x60, 0x1F, 0x83, 0xFC, 0x36, 0x03, + 0x60, 0x3F, 0x81, 0xFC, 0x06, 0xC0, 0x6C, 0x3F, + 0xC1, 0xF8, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // $ + {0x00, 0x00, 0x00, 0x00, 0x13, 0x83, 0x38, 0x73, + 0x8E, 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, + 0x03, 0x8E, 0x70, 0xE6, 0x0E, 0x00, 0x00, 0x00}, // % + {0x00, 0x00, 0x70, 0x0D, 0x81, 0x98, 0x19, 0x81, + 0xB0, 0x0E, 0x01, 0xE0, 0x3E, 0x03, 0x36, 0x33, + 0xC3, 0x18, 0x3B, 0xC1, 0xE6, 0x00, 0x00, 0x00}, // & + {0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x06, 0x00, + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ' + {0x03, 0x80, 0x60, 0x0E, 0x00, 0xC0, 0x1C, 0x01, + 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xC0, 0x0C, + 0x00, 0xE0, 0x06, 0x00, 0x38, 0x00, 0x00, 0x00}, // ( + {0x1C, 0x00, 0x60, 0x07, 0x00, 0x30, 0x03, 0x80, + 0x38, 0x03, 0x80, 0x38, 0x03, 0x80, 0x38, 0x03, + 0x00, 0x70, 0x06, 0x01, 0xC0, 0x00, 0x00, 0x00}, // ) + {0x00, 0x00, 0x00, 0x00, 0x03, 0x6C, 0x36, 0xC1, + 0xF8, 0x0F, 0x03, 0xFC, 0x0F, 0x01, 0xF8, 0x36, + 0xC3, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // * + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x60, 0x06, 0x03, 0xFC, 0x3F, 0xC0, 0x60, 0x06, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x06, 0x00, 0xC0}, // , + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xFC, 0x3F, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // - + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x00, 0x00, 0x00}, // . + {0x00, 0x00, 0x01, 0x00, 0x30, 0x07, 0x00, 0xE0, + 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0, 0x38, + 0x07, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, // / + {0x0F, 0x83, 0xFE, 0x30, 0x66, 0x07, 0x60, 0xF6, + 0x1B, 0x63, 0x36, 0x63, 0x6C, 0x37, 0x83, 0x70, + 0x33, 0x06, 0x3F, 0xE0, 0xF8, 0x00, 0x00, 0x00}, // 0 + {0x03, 0x00, 0x70, 0x1F, 0x01, 0xF0, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0x00, 0x30, 0x1F, 0xE1, 0xFE, 0x00, 0x00, 0x00}, // 1 + {0x1F, 0xC3, 0xFE, 0x70, 0x76, 0x03, 0x60, 0x70, + 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0x1C, + 0x03, 0x80, 0x7F, 0xF7, 0xFF, 0x00, 0x00, 0x00}, // 2 + {0x1F, 0xC3, 0xFE, 0x70, 0x76, 0x03, 0x00, 0x30, + 0x07, 0x0F, 0xE0, 0xFC, 0x00, 0x60, 0x03, 0x60, + 0x37, 0x07, 0x3F, 0xE1, 0xFC, 0x00, 0x00, 0x00}, // 3 + {0x01, 0xC0, 0x3C, 0x07, 0xC0, 0xEC, 0x1C, 0xC3, + 0x8C, 0x70, 0xC6, 0x0C, 0x7F, 0xF7, 0xFF, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x00, 0x00}, // 4 + {0x7F, 0xF7, 0xFF, 0x60, 0x06, 0x00, 0x60, 0x07, + 0xFC, 0x3F, 0xE0, 0x07, 0x00, 0x30, 0x03, 0x60, + 0x37, 0x07, 0x3F, 0xE1, 0xFC, 0x00, 0x00, 0x00}, // 5 + {0x03, 0xC0, 0x7C, 0x0E, 0x01, 0xC0, 0x38, 0x03, + 0x00, 0x7F, 0xC7, 0xFE, 0x70, 0x76, 0x03, 0x60, + 0x37, 0x07, 0x3F, 0xE1, 0xFC, 0x00, 0x00, 0x00}, // 6 + {0x7F, 0xF7, 0xFF, 0x00, 0x60, 0x06, 0x00, 0xC0, + 0x0C, 0x01, 0x80, 0x18, 0x03, 0x00, 0x30, 0x06, + 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x00, 0x00, 0x00}, // 7 + {0x0F, 0x81, 0xFC, 0x38, 0xE3, 0x06, 0x30, 0x63, + 0x8E, 0x1F, 0xC3, 0xFE, 0x70, 0x76, 0x03, 0x60, + 0x37, 0x07, 0x3F, 0xE1, 0xFC, 0x00, 0x00, 0x00}, // 8 + {0x1F, 0xC3, 0xFE, 0x70, 0x76, 0x03, 0x60, 0x37, + 0x07, 0x3F, 0xF1, 0xFF, 0x00, 0x60, 0x0E, 0x01, + 0xC0, 0x38, 0x1F, 0x01, 0xE0, 0x00, 0x00, 0x00}, // 9 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, // : + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0x60, 0x06, 0x00, 0xC0}, // ; + {0x00, 0xC0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, + 0xC0, 0x38, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, + 0x00, 0x38, 0x01, 0xC0, 0x0C, 0x00, 0x00, 0x00}, // < + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xFE, 0x3F, 0xE0, 0x00, 0x00, 0x03, 0xFE, 0x3F, + 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // = + {0x30, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, + 0x38, 0x01, 0xC0, 0x1C, 0x03, 0x80, 0x70, 0x0E, + 0x01, 0xC0, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00}, // > + {0x1F, 0x83, 0xFC, 0x70, 0xE6, 0x06, 0x60, 0xE0, + 0x1C, 0x03, 0x80, 0x70, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x00, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // ? + {0x1F, 0xC3, 0xFE, 0x30, 0x66, 0x7B, 0x6F, 0xB6, + 0xDB, 0x6D, 0xB6, 0xDB, 0x6D, 0xB6, 0xFE, 0x67, + 0xC7, 0x00, 0x3F, 0xC0, 0xFC, 0x00, 0x00, 0x00}, // @ + {0x06, 0x00, 0x60, 0x0F, 0x00, 0xF0, 0x0F, 0x01, + 0x98, 0x19, 0x81, 0x98, 0x30, 0xC3, 0xFC, 0x3F, + 0xC6, 0x06, 0x60, 0x66, 0x06, 0x00, 0x00, 0x00}, // A + {0x7F, 0x07, 0xF8, 0x61, 0xC6, 0x0C, 0x60, 0xC6, + 0x1C, 0x7F, 0x87, 0xFC, 0x60, 0xE6, 0x06, 0x60, + 0x66, 0x0E, 0x7F, 0xC7, 0xF8, 0x00, 0x00, 0x00}, // B + {0x0F, 0x81, 0xFC, 0x38, 0xE3, 0x06, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x30, + 0x63, 0x8E, 0x1F, 0xC0, 0xF8, 0x00, 0x00, 0x00}, // C + {0x7F, 0x07, 0xF8, 0x61, 0xC6, 0x0C, 0x60, 0x66, + 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, + 0xC6, 0x1C, 0x7F, 0x87, 0xF0, 0x00, 0x00, 0x00}, // D + {0x7F, 0xE7, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x7F, 0x87, 0xF8, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x7F, 0xE7, 0xFE, 0x00, 0x00, 0x00}, // E + {0x7F, 0xE7, 0xFE, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x7F, 0x87, 0xF8, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00}, // F + {0x0F, 0xC1, 0xFE, 0x38, 0x63, 0x00, 0x60, 0x06, + 0x00, 0x63, 0xE6, 0x3E, 0x60, 0x66, 0x06, 0x30, + 0x63, 0x86, 0x1F, 0xE0, 0xFE, 0x00, 0x00, 0x00}, // G + {0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, + 0x06, 0x7F, 0xE7, 0xFE, 0x60, 0x66, 0x06, 0x60, + 0x66, 0x06, 0x60, 0x66, 0x06, 0x00, 0x00, 0x00}, // H + {0x1F, 0x81, 0xF8, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x1F, 0x81, 0xF8, 0x00, 0x00, 0x00}, // I + {0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x66, 0x06, 0x60, + 0x67, 0x0C, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // J + {0x60, 0x66, 0x0E, 0x61, 0xC6, 0x38, 0x67, 0x06, + 0xE0, 0x7C, 0x07, 0xC0, 0x6E, 0x06, 0x70, 0x63, + 0x86, 0x1C, 0x60, 0xE6, 0x06, 0x00, 0x00, 0x00}, // K + {0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x7F, 0xE7, 0xFE, 0x00, 0x00, 0x00}, // L + {0x60, 0x67, 0x0E, 0x70, 0xE7, 0x9E, 0x79, 0xE6, + 0xF6, 0x6F, 0x66, 0x66, 0x66, 0x66, 0x06, 0x60, + 0x66, 0x06, 0x60, 0x66, 0x06, 0x00, 0x00, 0x00}, // M + {0x60, 0x67, 0x06, 0x70, 0x67, 0x86, 0x6C, 0x66, + 0xC6, 0x66, 0x66, 0x66, 0x63, 0x66, 0x36, 0x61, + 0xE6, 0x0E, 0x60, 0xE6, 0x06, 0x00, 0x00, 0x00}, // N + {0x0F, 0x01, 0xF8, 0x39, 0xC3, 0x0C, 0x60, 0x66, + 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x30, + 0xC3, 0x9C, 0x1F, 0x80, 0xF0, 0x00, 0x00, 0x00}, // O + {0x7F, 0x87, 0xFC, 0x60, 0xE6, 0x06, 0x60, 0x66, + 0x06, 0x60, 0xE7, 0xFC, 0x7F, 0x86, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00}, // P + {0x0F, 0x01, 0xF8, 0x39, 0xC3, 0x0C, 0x60, 0x66, + 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, 0x36, 0x33, + 0xC3, 0x9C, 0x1F, 0xE0, 0xF6, 0x00, 0x00, 0x00}, // Q + {0x7F, 0x87, 0xFC, 0x60, 0xE6, 0x06, 0x60, 0x66, + 0x06, 0x60, 0xE7, 0xFC, 0x7F, 0x86, 0x70, 0x63, + 0x86, 0x1C, 0x60, 0xE6, 0x06, 0x00, 0x00, 0x00}, // R + {0x1F, 0x83, 0xFC, 0x70, 0xE6, 0x06, 0x60, 0x07, + 0x00, 0x3F, 0x81, 0xFC, 0x00, 0xE0, 0x06, 0x60, + 0x67, 0x0E, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // S + {0x3F, 0xC3, 0xFC, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // T + {0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, + 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, + 0x63, 0x0C, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // U + {0x60, 0x66, 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC3, + 0x0C, 0x19, 0x81, 0x98, 0x19, 0x80, 0xF0, 0x0F, + 0x00, 0xF0, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // V + {0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, + 0x06, 0x60, 0x66, 0x66, 0x66, 0x66, 0xF6, 0x79, + 0xE7, 0x0E, 0x70, 0xE6, 0x06, 0x00, 0x00, 0x00}, // W + {0x60, 0x66, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x80, + 0xF0, 0x06, 0x00, 0x60, 0x0F, 0x01, 0x98, 0x30, + 0xC3, 0x0C, 0x60, 0x66, 0x06, 0x00, 0x00, 0x00}, // X + {0x60, 0x66, 0x06, 0x30, 0xC3, 0x0C, 0x19, 0x81, + 0x98, 0x0F, 0x00, 0xF0, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // Y + {0x7F, 0xE7, 0xFE, 0x00, 0xC0, 0x0C, 0x01, 0x80, + 0x30, 0x06, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, + 0x03, 0x00, 0x7F, 0xE7, 0xFE, 0x00, 0x00, 0x00}, // Z + {0x1F, 0x81, 0xF8, 0x18, 0x01, 0x80, 0x18, 0x01, + 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, + 0x01, 0x80, 0x1F, 0x81, 0xF8, 0x00, 0x00, 0x00}, // [ + {0x00, 0x04, 0x00, 0x60, 0x07, 0x00, 0x38, 0x01, + 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, + 0xE0, 0x07, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00}, // / + {0x1F, 0x81, 0xF8, 0x01, 0x80, 0x18, 0x01, 0x80, + 0x18, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, + 0x80, 0x18, 0x1F, 0x81, 0xF8, 0x00, 0x00, 0x00}, // ] + {0x02, 0x00, 0x70, 0x0F, 0x81, 0xDC, 0x38, 0xE7, + 0x07, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ^ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF7, 0xFF}, // _ + {0x00, 0x00, 0x70, 0x07, 0x00, 0x70, 0x06, 0x00, + 0x60, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ` + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFC, 0x3F, 0xE0, 0x06, 0x1F, 0xE3, 0xFE, 0x60, + 0x66, 0x06, 0x7F, 0xE3, 0xFE, 0x00, 0x00, 0x00}, // a + {0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0xF8, 0x7F, 0xC7, 0x0E, 0x60, 0x66, 0x06, 0x60, + 0x66, 0x0E, 0x7F, 0xC7, 0xF8, 0x00, 0x00, 0x00}, // b + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xF8, 0x3F, 0xC7, 0x06, 0x60, 0x06, 0x00, 0x60, + 0x07, 0x06, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // c + {0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x61, + 0xF6, 0x3F, 0xE7, 0x1E, 0x60, 0x66, 0x06, 0x60, + 0x67, 0x06, 0x3F, 0xE1, 0xFE, 0x00, 0x00, 0x00}, // d + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xF8, 0x3F, 0xC7, 0x06, 0x7F, 0xE7, 0xFC, 0x60, + 0x07, 0x00, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // e + {0x07, 0x80, 0xF8, 0x1C, 0x01, 0x80, 0x18, 0x01, + 0x80, 0x7F, 0x07, 0xF0, 0x18, 0x01, 0x80, 0x18, + 0x01, 0x80, 0x18, 0x01, 0x80, 0x00, 0x00, 0x00}, // f + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFE, 0x3F, 0xE7, 0x06, 0x60, 0x67, 0x0E, 0x3F, + 0xE1, 0xF6, 0x00, 0x60, 0x0E, 0x3F, 0xC3, 0xF8}, // g + {0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0xF0, 0x7F, 0x87, 0x1C, 0x60, 0xC6, 0x0C, 0x60, + 0xC6, 0x0C, 0x60, 0xC6, 0x0C, 0x00, 0x00, 0x00}, // h + {0x00, 0x00, 0x00, 0x06, 0x00, 0x60, 0x00, 0x00, + 0xE0, 0x0E, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x1F, 0x81, 0xF8, 0x00, 0x00, 0x00}, // i + {0x00, 0x00, 0x00, 0x01, 0x80, 0x18, 0x00, 0x00, + 0x38, 0x03, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, + 0x80, 0x18, 0x01, 0x81, 0x98, 0x1F, 0x80, 0xF0}, // j + {0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0x18, 0x33, 0x83, 0x70, 0x3E, 0x03, 0xE0, 0x37, + 0x03, 0x38, 0x31, 0xC3, 0x0C, 0x00, 0x00, 0x00}, // k + {0x0E, 0x00, 0xE0, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x1F, 0x81, 0xF8, 0x00, 0x00, 0x00}, // l + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x98, 0x7F, 0xC7, 0xFE, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, // m + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xF8, 0x3F, 0xC3, 0x0E, 0x30, 0x63, 0x06, 0x30, + 0x63, 0x06, 0x30, 0x63, 0x06, 0x00, 0x00, 0x00}, // n + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xF8, 0x3F, 0xC7, 0x0E, 0x60, 0x66, 0x06, 0x60, + 0x67, 0x0E, 0x3F, 0xC1, 0xF8, 0x00, 0x00, 0x00}, // o + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xF8, 0x7F, 0xC6, 0x0E, 0x60, 0x66, 0x06, 0x70, + 0xE7, 0xFC, 0x6F, 0x86, 0x00, 0x60, 0x06, 0x00}, // p + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFE, 0x3F, 0xE7, 0x06, 0x60, 0x66, 0x06, 0x70, + 0xE3, 0xFE, 0x1F, 0x60, 0x06, 0x00, 0x60, 0x06}, // q + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x7C, 0x3F, 0xE3, 0x86, 0x30, 0x03, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00}, // r + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xF0, 0x7F, 0x86, 0x00, 0x7F, 0x03, 0xF8, 0x01, + 0x80, 0x18, 0x7F, 0x83, 0xF0, 0x00, 0x00, 0x00}, // s + {0x00, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x07, + 0xF0, 0x7F, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, + 0x01, 0x80, 0x1F, 0x80, 0xF8, 0x00, 0x00, 0x00}, // t + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x06, 0x60, 0x66, 0x06, 0x60, 0x66, 0x06, 0x60, + 0x67, 0x0E, 0x3F, 0xE1, 0xF6, 0x00, 0x00, 0x00}, // u + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x06, 0x60, 0x63, 0x0C, 0x30, 0xC1, 0x98, 0x19, + 0x80, 0xF0, 0x0F, 0x00, 0x60, 0x00, 0x00, 0x00}, // v + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6F, + 0x63, 0xFC, 0x39, 0xC1, 0x08, 0x00, 0x00, 0x00}, // w + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x0C, 0x71, 0xC3, 0xB8, 0x1F, 0x00, 0xE0, 0x1F, + 0x03, 0xB8, 0x71, 0xC6, 0x0C, 0x00, 0x00, 0x00}, // x + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x0C, 0x30, 0xC1, 0x98, 0x19, 0x80, 0xF0, 0x0F, + 0x00, 0x60, 0x06, 0x00, 0xC0, 0x0C, 0x01, 0x80}, // y + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xFC, 0x7F, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, + 0x03, 0x00, 0x7F, 0xC7, 0xFC, 0x00, 0x00, 0x00}, // z + {0x03, 0xC0, 0x7C, 0x0E, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x1C, 0x03, 0x80, 0x1C, 0x00, 0xC0, 0x0C, + 0x00, 0xC0, 0x0E, 0x00, 0x7C, 0x03, 0xC0, 0x00}, // { + {0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x00, 0x00, 0x00}, // | + {0x3C, 0x03, 0xE0, 0x07, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x80, 0x1C, 0x03, 0x80, 0x30, 0x03, + 0x00, 0x30, 0x07, 0x03, 0xE0, 0x3C, 0x00, 0x00}, // } + {0x00, 0x00, 0x00, 0x1C, 0x63, 0x6C, 0x63, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ~ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0xF0, 0x19, 0x83, 0x0C, 0x60, 0x66, 0x06, 0x7F, + 0xE7, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} //  + }; + + const uint8_t* get_letter(const u_char letter) const override { + if (letter >= 32 and letter < 128) + // array starts at ascii 32 + return BigFont::font[letter - 32]; + return BigFont::font[1]; // return the char corresponding to '!' + } + + inline uint8_t get_height() const override { return 16; }; + inline uint8_t get_width() const override { return 12; }; +}; +static const BigFont bigFont; + +IFont const* font_from_scale(float& scale) { + scale = min(max(scale, 0.2), 1.0); + if (scale <= 0.5) { + scale = 1 - (0.5 - scale); + return &smallFont; + } else { + return &bigFont; + } +} + +/** + * Display a letter starting at the given position, with the given scale + * The font will be adjusted to correspond to the scale + */ +uint8_t display_letter(const u_char letter, const int16_t startXIndex, + const int16_t startYIndex, float scale, + const Color& color, bool& isDisplayedOut, + LedStrip& strip) { + uint8_t xIndex = 0; + uint8_t yIndex = 0; + + IFont const* font = font_from_scale(scale); + + bool hasCutoffX = false; // letter was displayed out of screen, or cutout by + // the screen (above zero) + isDisplayedOut = + true; // letter was displayed completly out of screen, under zero + + const auto& letterArray = font->get_letter(letter); + const uint8_t arrayLen = font->get_arrayLenght(); + for (uint8_t i = 0; i < arrayLen; i++) { + uint8_t letterPart = letterArray[i]; + // unpack the mask + for (uint8_t j = 0; j < 8; ++j) { + const int16_t targetX = startXIndex + xIndex * scale; + const int16_t targetY = startYIndex + yIndex * scale; + const bool isCutoff = + targetX > stripXCoordinates or targetY > stripXCoordinates; + if (not isCutoff) { + const bool shouldSet = (letterPart & 0x80) != 0; + if (shouldSet) { + if (targetX >= 0 and targetY >= 0) { + isDisplayedOut = false; + const auto pixelIndex = to_strip(targetX, targetY); + strip.setPixelColor(pixelIndex, + color.get_color(pixelIndex, LED_COUNT)); + } + } + } else { + isDisplayedOut = false; + } + // store the fact that a letter has been cutoff in the x axis + hasCutoffX = hasCutoffX or (targetX > stripXCoordinates); + + // remove the first set bit + letterPart = letterPart << 1; + + xIndex++; + if (xIndex > font->get_width() - 1) { + xIndex = 0; + yIndex++; + } + } + } + + if (hasCutoffX) return 0; + return font->get_width(); +} + +/** + * Display a letter starting at the given position, with the given scale. + * If the text is bigger than the lamp, it will be cutoff + * The font will be adjusted to correspond to the scale + * + * return true if the text was display completly + */ +bool display_text(const Color& color, const std::string& text, + const int16_t startXIndex, const int16_t startYIndex, + float scale, const bool paddEnd, LedStrip& strip) { + bool lastLetterDisappeared = true; + int16_t displayColumn = startXIndex; + for (const char c : text) { + const auto res = display_letter(c, displayColumn, startYIndex, scale, color, + lastLetterDisappeared, strip); + if (res == 0) // still some letters to display + return false; + displayColumn += res; + } + + // animation with padding does not stop until the last letter is gone + if (paddEnd) return lastLetterDisappeared; + + return true; +} + +/** + * paddEnd: if true, will not consider the animation over until the last letter + * disappears from the strip (looks nicer) + */ +bool display_scrolling_text(const Color& color, const std::string& text, + const int16_t startYIndex, float scale, + const uint32_t duration, const bool reset, + const bool paddEnd, const uint8_t fadeOut, + LedStrip& strip) { + IFont const* font = font_from_scale(scale); + const size_t textLength = font->get_width() * text.size(); + + static int16_t xIndex = stripXCoordinates; + if (reset) { + xIndex = stripXCoordinates; + } + + strip.fadeToBlackBy(fadeOut); + + // convert duration in delay for each segment + const uint32_t delay = max(LOOP_UPDATE_PERIOD, duration / textLength); + if (duration / textLength < LOOP_UPDATE_PERIOD) { + // fast animation: must skip some indexes + const int16_t increment = textLength / ceil(duration / delay); + // return true when the whole text has been displayed + if (display_text(color, text, xIndex, startYIndex, scale, paddEnd, strip)) { + return true; + } + xIndex -= increment; + } else { + // slow animation: blend + static uint32_t lastSubstep = 0; + const uint32_t maxSubstep = + LOOP_UPDATE_PERIOD / ((float)textLength / (float)duration * 1000.0); + + if (display_text(color, text, xIndex, startYIndex, scale, paddEnd, strip)) { + return true; + } + + lastSubstep++; + if (lastSubstep > maxSubstep) { + lastSubstep = 0; + xIndex--; + } + } + + return false; +} + +} // namespace text + +#endif \ No newline at end of file diff --git a/src/user_constants.h b/src/user_constants.h index 9d1fbf3..2badf4a 100644 --- a/src/user_constants.h +++ b/src/user_constants.h @@ -1,9 +1,29 @@ #ifndef USER_CONSTANTS #define USER_CONSTANTS +#include + +constexpr float lampBodyRadius_mm = 25; // external radius of the lamp body + // parameters of the led strip used -constexpr float consWattByMeter = 0; // power consumption (in Watt/meters) -constexpr float inputVoltage_V = 12; // voltage (volts) -constexpr float ledStripLenght_mm = 0; +constexpr uint16_t LED_COUNT = + 618; // How many indexable leds are attached to the controler +constexpr float consWattByMeter = 5; // power consumption (in Watt/meters) +constexpr float inputVoltage_V = 12; // voltage (volts) +constexpr uint16_t ledByMeter = 160.0; // the indexable led by meters +constexpr float ledStripWidth_mm = 5.2; // width of the led strip + +// physical parameters computations +constexpr float ledSize_mm = + 1.0 / ledByMeter * 1000.0; // size of the individual led +constexpr float lampBodyCircumpherence_mm = + 2.0 * 3.14159265 * lampBodyRadius_mm; +constexpr float ledStripLenght_mm = LED_COUNT * ledSize_mm; + +constexpr float stripXCoordinates = + lampBodyCircumpherence_mm / ledSize_mm + 0.35; +constexpr float stripYCoordinates = + ledStripLenght_mm / lampBodyCircumpherence_mm; +constexpr float lampBodyHeight_mm = stripYCoordinates * ledStripWidth_mm; #endif \ No newline at end of file diff --git a/src/user_functions.cpp b/src/user_functions.cpp index 17157f8..f9f98f2 100644 --- a/src/user_functions.cpp +++ b/src/user_functions.cpp @@ -1,3 +1,490 @@ #include "user_functions.h" -namespace user {} \ No newline at end of file +#include "system/behavior.h" +#include "system/colors/animations.h" +#include "system/colors/colors.h" +#include "system/colors/palettes.h" +#include "system/colors/wipes.h" +#include "system/physical/IMU.h" +#include "system/physical/MicroPhone.h" +#include "system/physical/fileSystem.h" + +namespace user { + +LedStrip strip(D2); + +bool modeChange = true; // signal a color mode change +bool categoryChange = true; // signal a color category change + +uint8_t colorMode = 0; // color mode: main wheel of the color mode +const char* colorModeKey = "colorMode"; + +uint8_t colorState = 0; // color state: subwheel of the current color mode +const char* colorStateKey = "colorState"; + +uint8_t colorCodeIndex = 0; // color code index, used for color indexion +uint8_t lastColorCodeIndex = colorCodeIndex; + +uint8_t colorCodeIndexForWarmLight = + 0; // color code index, used for color indexion of warm to white +const char* colorCodeIndexForWarmLightKey = "warmLight"; + +uint8_t colorCodeIndexForColoredLight = + 0; // color code index, used for color indexion of RGB +const char* colorCodeIndexForColoredLightKey = "colorLight"; + +bool isFinished = true; +bool switchMode = true; + +void reset_globals() { + isFinished = true; + switchMode = false; + colorCodeIndex = 0; + lastColorCodeIndex = 0; +} + +void increment_color_mode() { + colorMode += 1; + modeChange = true; + + // reset color state + colorState = 0; + categoryChange = true; + + reset_globals(); + + strip.clear(); +} + +void decrement_color_mode() { + colorMode -= 1; + modeChange = true; + + // reset color state + colorState = 0; + categoryChange = true; + + reset_globals(); + + strip.clear(); +} + +void increment_color_state() { + colorState += 1; + + // signal a change of category + categoryChange = true; + + reset_globals(); + + strip.clear(); +} + +void decrement_color_state() { + colorState -= 1; + + // signal a change of category + categoryChange = true; + + reset_globals(); + + strip.clear(); +} + +uint8_t clamp_state_values(uint8_t& state, const uint8_t maxValue) { + // incrmeent one too much, loop around + if (state == maxValue + 1) state = 0; + + // default return value + else if (state <= maxValue) + return state; + + // got below 0, set to max value + else if (state > maxValue) + state = maxValue; + + return state; +} + +void gradient_mode_update() { + static auto lastColorStep = colorCodeIndex; + + constexpr uint8_t maxGradientColorState = 1; + switch (clamp_state_values(colorState, maxGradientColorState)) { + case 0: // kelvin mode + static auto paletteHeatColor = + GeneratePaletteIndexed(PaletteBlackBodyColors); + if (categoryChange) { + lastColorStep = 1; + colorCodeIndex = colorCodeIndexForWarmLight; + lastColorCodeIndex = colorCodeIndexForWarmLight; + paletteHeatColor.reset(); + } + + if (colorCodeIndex != lastColorStep) { + lastColorStep = colorCodeIndex; + colorCodeIndexForWarmLight = colorCodeIndex; + paletteHeatColor.update(colorCodeIndex); + } + animations::fill(paletteHeatColor, strip); + break; + + case 1: // rainbow mode + static auto rainbowIndex = GenerateRainbowIndex( + UINT8_MAX); // pulse around a rainbow, with a certain color division + if (categoryChange) { + lastColorStep = 1; + colorCodeIndex = colorCodeIndexForColoredLight; + lastColorCodeIndex = colorCodeIndexForColoredLight; + rainbowIndex.reset(); + } + + if (colorCodeIndex != lastColorStep) { + lastColorStep = colorCodeIndex; + colorCodeIndexForColoredLight = colorCodeIndex; + rainbowIndex.update(colorCodeIndex); + } + animations::fill(rainbowIndex, strip); + break; + + default: // error + colorState = 0; + colorCodeIndex = 0; + strip.clear(); + break; + } + + // reset category change + categoryChange = false; +} + +void calm_mode_update() { + constexpr uint8_t maxCalmColorState = 9; + switch (clamp_state_values(colorState, maxCalmColorState)) { + case 0: // rainbow swirl animation + { // display a color animation + static GenerateRainbowSwirl rainbowSwirl = + GenerateRainbowSwirl(5000); // swirl animation (5 seconds) + if (categoryChange) rainbowSwirl.reset(); + + // animations::fill(rainbowSwirl, strip); + + imu::gravity_fluid(128, rainbowSwirl, strip, categoryChange); + + rainbowSwirl.update(); // update + break; + } + case 1: // party wheel + { + static auto lastColorStep = colorCodeIndex; + static auto palettePartyColor = + GeneratePaletteIndexed(PalettePartyColors); + static uint8_t currentIndex = 0; + if (categoryChange) { + currentIndex = 0; + palettePartyColor.reset(); + } + + isFinished = + animations::fade_in(palettePartyColor, 100, isFinished, strip); + if (isFinished) { + currentIndex++; + palettePartyColor.update(currentIndex); + } + break; + } + case 2: { + animations::random_noise(PaletteLavaColors, strip, categoryChange, true, + 3); + break; + } + case 3: { + animations::random_noise(PaletteForestColors, strip, categoryChange, true, + 3); + break; + } + case 4: { + animations::random_noise(PaletteOceanColors, strip, categoryChange, true, + 3); + break; + } + case 5: { // polar light + animations::mode_2DPolarLights(255, 128, PaletteAuroraColors, + categoryChange, strip); + break; + } + case 6: { + animations::fire(60, 60, 255, PaletteHeatColors, strip); + // animations::mode_lake(128, PaletteOceanColors, strip); + break; + } + case 7: { + animations::mode_sinewave(128, 128, PaletteRainbowColors, strip); + break; + } + case 8: { + animations::mode_2DDrift(64, 64, PaletteRainbowColors, strip); + break; + } + case 9: { + animations::mode_2Ddistortionwaves(128, 128, strip); + break; + } + + default: // error + { + colorState = 0; + strip.clear(); + break; + } + } + + // reset category change + categoryChange = false; +} + +void party_mode_update() { + constexpr uint8_t maxPartyState = 2; + switch (clamp_state_values(colorState, maxPartyState)) { + case 0: + static GenerateComplementaryColor complementaryColor = + GenerateComplementaryColor(0.3); + if (categoryChange) complementaryColor.reset(); + + isFinished = switchMode ? animations::color_wipe_up( + complementaryColor, 500, isFinished, strip) + : animations::color_wipe_down( + complementaryColor, 500, isFinished, strip); + if (isFinished) { + switchMode = !switchMode; + complementaryColor.update(); // update color + } + break; + + case 1: + // random solid color + static GenerateRandomColor randomColor = GenerateRandomColor(); + if (categoryChange) randomColor.reset(); + + isFinished = + animations::double_side_fill(randomColor, 500, isFinished, strip); + if (isFinished) randomColor.update(); // update color + break; + + case 2: + static GenerateComplementaryColor complementaryPingPongColor = + GenerateComplementaryColor(0.4); + if (categoryChange) complementaryPingPongColor.reset(); + + // ping pong a color for infinity + isFinished = animations::dot_ping_pong(complementaryPingPongColor, 1000.0, + 128, isFinished, strip); + if (isFinished) complementaryPingPongColor.update(); // update color + break; + + default: // error + colorState = 0; + strip.clear(); + break; + } + + // reset category change + categoryChange = false; +} + +void sound_mode_update() { + constexpr uint8_t maxSoundState = 2; + switch (clamp_state_values(colorState, maxSoundState)) { + case 0: // vue meter + static GenerateGradientColor redToGreenGradient = GenerateGradientColor( + LedStrip::Color(0, 255, 0), + LedStrip::Color(255, 0, 0)); // gradient from red to green + if (categoryChange) redToGreenGradient.reset(); + + microphone::vu_meter(redToGreenGradient, 128, strip); + break; + + case 1: // pulse soud + microphone::fftDisplay(128, 128, PaletteHeatColors, categoryChange, strip, + 255); + + break; + + case 2: + microphone::mode_2DWaverly(128, 64, PalettePartyColors, strip); + break; + + default: // error + colorState = 0; + strip.clear(); + break; + } + + // reset category change + categoryChange = false; +} + +void gyro_mode_update() { + constexpr uint8_t maxGyroState = 0; + switch (clamp_state_values(colorState, maxGyroState)) { + case 0: + animations::police(500, false, strip); + break; + + default: // error + colorState = 0; + strip.clear(); + break; + } + + // reset category change + categoryChange = false; +} + +void color_mode_update() { + constexpr uint8_t maxColorMode = 4; + switch (clamp_state_values(colorMode, maxColorMode)) { + case 0: // gradient mode + gradient_mode_update(); + break; + + case 1: // calm mode + calm_mode_update(); + break; + + case 2: + party_mode_update(); + break; + + case 3: // sound mode + sound_mode_update(); + break; + + case 4: // gyrophare + gyro_mode_update(); + break; + + default: + colorMode = 0; + break; + } + + // reset mode change + modeChange = false; +} + +void power_on_sequence() { + // initialize the strip object + strip.begin(); + strip.clear(); + strip.show(); // Turn OFF all pixels ASAP + strip.setBrightness(BRIGHTNESS); +} + +void power_off_sequence() { + strip.clear(); + strip.show(); // Clear all pixels +} + +void brightness_update(const uint8_t brightness) { + strip.setBrightness(brightness); +} + +void write_parameters() { + fileSystem::set_value(std::string(colorModeKey), colorMode); + fileSystem::set_value(std::string(colorStateKey), colorState); + fileSystem::set_value(std::string(colorCodeIndexForWarmLightKey), + colorCodeIndexForWarmLight); + fileSystem::set_value(std::string(colorCodeIndexForColoredLightKey), + colorCodeIndexForColoredLight); +} + +void read_parameters() { + uint32_t mode = 0; + if (fileSystem::get_value(std::string(colorModeKey), mode)) { + colorMode = mode; + } + + uint32_t state = 0; + if (fileSystem::get_value(std::string(colorStateKey), state)) { + colorState = state; + } + + uint32_t warmLight = 0; + if (fileSystem::get_value(std::string(colorCodeIndexForWarmLightKey), + warmLight)) { + colorCodeIndexForWarmLight = warmLight; + } + + uint32_t coloredLight = 0; + if (fileSystem::get_value(std::string(colorCodeIndexForColoredLightKey), + coloredLight)) { + colorCodeIndexForColoredLight = coloredLight; + } +} + +void button_clicked(const uint8_t clicks) { + switch (clicks) { + case 2: // 2 clicks: increment color state + increment_color_state(); + break; + + case 3: // 3 clicks: decrement color state + decrement_color_state(); + break; + + case 4: // 4 clicks: increment color mode + increment_color_mode(); + break; + + case 5: // 5 clicks: decrement color mode + decrement_color_mode(); + break; + } +} + +void button_hold(const uint8_t clicks, const uint32_t holdDuration) { + bool isEndOfHoldEvent = holdDuration <= 1; + + switch (clicks) { + case 3: // 3 clicks and hold + if (!isEndOfHoldEvent) { + constexpr uint32_t colorStepDuration_ms = 6000; + const uint32_t timeShift = + (colorStepDuration_ms * lastColorCodeIndex) / 255; + const uint32_t colorStep = + (holdDuration + timeShift) % colorStepDuration_ms; + colorCodeIndex = map(colorStep, 0, colorStepDuration_ms, 0, UINT8_MAX); + } else { + lastColorCodeIndex = colorCodeIndex; + } + break; + + case 4: // 4 clicks and hold + if (!isEndOfHoldEvent) { + constexpr uint32_t colorStepDuration_ms = 6000; + const uint32_t timeShift = + (colorStepDuration_ms * lastColorCodeIndex) / 255; + + uint32_t buttonHoldDuration = holdDuration; + if (holdDuration < timeShift) { + buttonHoldDuration = + colorStepDuration_ms - (timeShift - holdDuration); + } else { + buttonHoldDuration -= timeShift; + } + const uint32_t colorStep = buttonHoldDuration % colorStepDuration_ms; + colorCodeIndex = map(colorStep, 0, colorStepDuration_ms, UINT8_MAX, 0); + } else { + lastColorCodeIndex = colorCodeIndex; + } + break; + } +} + +void loop() { + color_mode_update(); + strip.show(); // show at the end of the loop (only does it if needed) +} + +} // namespace user \ No newline at end of file diff --git a/src/user_functions.h b/src/user_functions.h index bc02784..92feef1 100644 --- a/src/user_functions.h +++ b/src/user_functions.h @@ -2,9 +2,13 @@ #define SYSTEM_BASE_H #include "Arduino.h" +#include "system/utils/strip.h" namespace user { +// extern declarations +extern LedStrip strip; + // Called when the system is powered off // must be non blocking function void power_on_sequence();