From 352fe394680e57261d4cd7ae856de5c9c61224f4 Mon Sep 17 00:00:00 2001 From: Kuba Birecki Date: Thu, 2 Dec 2021 12:56:29 +0100 Subject: [PATCH] Add an implementation for the core animations system --- app/CMakeLists.txt | 3 + app/include/zmk/animation.h | 70 ++++++++++++++ app/src/animation/animation.c | 166 ++++++++++++++++++++++++++++++++++ app/src/animation/color.c | 84 +++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 app/include/zmk/animation.h create mode 100644 app/src/animation/animation.c create mode 100644 app/src/animation/color.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a647e883..d35ca3cb 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -80,6 +80,9 @@ target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_bac target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/battery.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/animation.c) +target_sources_ifdef(CONFIG_ZMK_ANIMATION app PRIVATE src/animation/color.c) + target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c) add_subdirectory(src/split) diff --git a/app/include/zmk/animation.h b/app/include/zmk/animation.h new file mode 100644 index 00000000..df488851 --- /dev/null +++ b/app/include/zmk/animation.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct animation_pixel_position { + const uint8_t x; + const uint8_t y; +}; + +struct animation_pixel { + const struct device *animation; + const struct animation_pixel_position position; +}; + +struct zmk_color_rgb { + float r; + float g; + float b; +}; + +struct zmk_color_hsl { + uint16_t h; + uint8_t s; + uint8_t l; +}; + +/** + * Converts color from HSL to RGB. + * + * @param hsl [description] + * @param rgb [description] + */ +void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb); + +/** + * Converts the internal RGB representation into a led_rgb struct + * for use with led_strip drivers. + * + * @param rgb [description] + * @param led [description] + */ +void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led); + +/** + * Returns true if two HSL colors are the same. + * + * @param a [description] + * @param b [description] + * @return [description] + */ +bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b); + +/** + * Perform linear interpolation between HSL values of two colors + * at a given distance (step) and store the resulting value in the given pointer. + * + * @param from [description] + * @param to [description] + * @param result [description] + * @param step [description] + */ +void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, + struct zmk_color_hsl *result, float step); diff --git a/app/src/animation/animation.c b/app/src/animation/animation.c new file mode 100644 index 00000000..c34dd9e2 --- /dev/null +++ b/app/src/animation/animation.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_animation + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define PHANDLE_TO_DEVICE(node_id, prop, idx) \ + DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +#define PHANDLE_TO_CHAIN_LENGTH(node_id, prop, idx) \ + DT_PROP_BY_PHANDLE_IDX(node_id, prop, idx, chain_length), + +#define PHANDLE_TO_PIXEL(node_id, prop, idx) \ + { \ + .animation = DEVICE_DT_GET(DT_PHA_BY_IDX(node_id, prop, idx, animation)), \ + .position = { \ + .x = DT_PHA_BY_IDX(node_id, prop, idx, position_x), \ + .y = DT_PHA_BY_IDX(node_id, prop, idx, position_y), \ + }, \ + }, + +/** + * LED Driver device pointers. + */ +static const struct device *drivers[] = { + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_DEVICE) +}; + +/** + * Size of the LED driver device pointers array. + */ +static const size_t drivers_size = DT_INST_PROP_LEN(0, drivers); + +/** + * Array containing the number of LEDs handled by each device. + */ +static const uint8_t pixels_per_driver[] = { + DT_INST_FOREACH_PROP_ELEM(0, drivers, PHANDLE_TO_CHAIN_LENGTH) +}; + +/** + * Pointers to all active animation devices. + */ +static const struct device *animations[] = { + DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_DEVICE) +}; + +/** + * Size of the animation device pointers array. + */ +static const size_t animations_size = DT_INST_PROP_LEN(0, animations); + +/** + * Pixel configuration. + */ +static const struct animation_pixel pixels[] = { + DT_INST_FOREACH_PROP_ELEM(0, animations, PHANDLE_TO_PIXEL) +}; + +/** + * Size of the pixels array. + */ +const size_t pixels_size = DT_INST_PROP_LEN(0, pixels); + +/** + * Buffer for RGB values ready to be sent to the drivers. + */ +struct led_rgb px_buffer[DT_INST_PROP_LEN(0, pixels)]; + +void zmk_animation_tick() { + for (size_t i = 0; i < ZMK_ANIMATIONS_LENGTH; ++i) { + animation_prep_next_frame(animations[i]); + } + + for (size_t i = 0; i < pixels_size; ++i) { + zmk_color_rgb rgb = { + .r = 0, + .g = 0, + .b = 0, + }; + + animation_get_pixel(pixel.animation, &pixel.position, &rgb); + + zmk_rgb_to_led_rgb(&rgb, &px_buffer[i]); + } + + size_t pixels_updated = 0; + + for (size_t i = 0; i < drivers_size; ++i) { + led_strip_update_rgb( + drivers[i], + &px_buffer[pixels_updated], + pixels_per_driver[i] + ); + + pixels_updated += pixels_per_driver; + } +} + +// Set up timer here + +K_WORK_DEFINE(animation_work, zmk_animation_tick); + +static void zmk_animation_tick_handler(struct k_timer *timer) { + k_work_submit(&animation_work); +} + +K_TIMER_DEFINE(animation_tick, zmk_animation_tick_handler, NULL); + +// Init + +void zmk_animation_init(const struct device *dev) { + // default FPS: 24, 30 or 60? + k_timer_start(&animation_tick, K_NO_WAIT, K_MSEC(1000 / ZMK_ANIMATION_FPS)); +} + +// Actually, all of this might be a little hard to represent inside a config file. +// We need: +// +// - Each animation has its own node +// +// - animations[] = phandle array of animations +// +// - pixel.pos = x,y coordinates of each pixels, could be a byte array +// +// - pixel.animations = Number array, but it's per pixel and it's not fixed length per pixel!! PROBLEM!!! +// - pixel.blending_modes = Could be a number array again. Use some macros. +// +// - unless we say there can only be 32 different animations and use uint32_t as a bitmask? +// - but then blending modes suck and you can't order them +// + +// ALTERNATIVE: +// +// Eventually we don't want to be using the device tree to set these up anyway. +// It should be possible to instantiate animations dynamically and the settings should be stored in flash. +// Maybe it's something to look into now? +// + +// ALTERNATIVE no2: +// +// Look at behaviors. +// It seems that pixels themselves could be 'drivers' ? +// +// That's probably best tbh + +// NOTE WHEN DEFINING PIXELS: +// See if it's maybe possible to re-use default_transform to assign pixels to keys? +// Assuming the first batch is always the keys which is not an easy assumption to make. +// Otherwise need it's own map diff --git a/app/src/animation/color.c b/app/src/animation/color.c new file mode 100644 index 00000000..5681b667 --- /dev/null +++ b/app/src/animation/color.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +/** + * HSL chosen over HSV/HSB as it shares the same parameters with LCh or HSLuv. + * The latter color spaces could be interesting to experiment with because of their + * perceptual uniformity, but it would come at the cost of some performance. + * Using the same parameters would make it easy to toggle any such behavior using a single config flag. + * + * Algorithm source: https://www.tlbx.app/color-converter + */ +void zmk_hsl_to_rgb(const struct zmk_color_hsl *hsl, struct zmk_color_rgb *rgb) { + // float a = hsl->h / 60; + uint8_t a = hsl->h / 60; + float chroma = hsl->s * (1 - abs(2 * hsl->l - 1)); + // I think 'a' actually needs to be a float here or this doesn't make sense. + // If uint, possible values are: (0,1), if float: (0...1) + float x = chroma * (1 - abs(a % 2 - 1)); + float m = hsl->l - chroma / 2; + + switch (a) { + case 0: + rgb->r = m + chroma; + rgb->g = m + x; + rgb->b = m; + case 1: + rgb->r = m + x; + rgb->g = m + chroma; + rgb->b = m; + case 2: + rgb->r = m; + rgb->g = m + chroma; + rgb->b = m + x; + case 3: + rgb->r = m; + rgb->g = m + x; + rgb->b = m + chroma; + case 4: + rgb->r = m + x; + rgb->g = m; + rgb->b = m + chroma; + case 5: + rgb->r = m + chroma; + rgb->g = m; + rgb->b = m + x; + } +} + +/** + * Converts ZMKs RGB (float) to Zephyr's led_rgb (uint8_t) format. + */ +void zmk_rgb_to_led_rgb(const struct zmk_color_rgb *rgb, struct led_rgb *led) { + led->r = rgb->r * 255; + led->g = rgb->g * 255; + led->b = rgb->b * 255; +} + +/** + * Compares two HSL colors. + */ +bool zmk_cmp_hsl(const struct zmk_color_hsl *a, const struct zmk_color_hsl *b) { + return a->h == b->h && a->s == b->s && a->l == b->l; +} + +/** + * Interpolate between two colors using the cylindrical model (HSL). + */ +void zmk_interpolate_hsl(const struct zmk_color_hsl *from, const struct zmk_color_hsl *to, + struct zmk_color_hsl *result, float step) { + int16_t hue_delta; + + hue_delta = from->h - to->h; + hue_delta = hue_delta + (180 < abs(hue_delta) ? (hue_delta < 0 ? 360 : -360) : 0); + + result->h = (uint16_t) (360 + from->h - (hue_delta * step)) % 360; + result->s = from->s - (from->s - to->s) * step; + result->l = from->l - (from->l - to->l) * step; +}