diff --git a/NEWS.md b/NEWS.md index 52731fb1..5d9e9baa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -106,6 +106,12 @@ The [IdleLEDs](doc/plugin/IdleLEDs.md) plugin is a simple, yet, useful one: it w The [LEDActiveLayerColor][doc/plugin/LEDActiveLayerColor.md] plugin makes it possible to set the color of all LEDs to the same color, depending on which layer is active topmost. +### LED-Wavepool + +We integrated the [LEDWavepool](doc/plugin/LED-Wavepool.md) plugin by [ToyKeeper][wavepool:origin], with a few updates and new features added. + + [wavepool:origin]: https://github.com/ToyKeeper/Kaleidoscope-LED-Wavepool + ### WinKeyToggle The [WinKeyToggle](doc/plugin/WinKeyToggle.md) plugin assists with toggling the Windows key on and off - a little something for those of us who game under Windows and are tired of accidentally popping up the start menu. diff --git a/doc/plugin/LED-Wavepool.md b/doc/plugin/LED-Wavepool.md new file mode 100644 index 00000000..d12fbb49 --- /dev/null +++ b/doc/plugin/LED-Wavepool.md @@ -0,0 +1,61 @@ +# Kaleidoscope-LED-Wavepool + +The `WavepoolEffect` plugin makes waves of light splash out from each keypress. +When idle, it will also simulate gentle rainfall on the keyboard. + +## Using the plugin + +To use the plugin, one needs to include the header and select the effect. + +```c++ +#include +#include +#include + +KALEIDOSCOPE_INIT_PLUGINS(LEDControl, WavepoolEffect); + +void setup (){ + Kaleidoscope.setup(); + + WavepoolEffect.idle_timeout = 5000; // 5 seconds + WavepoolEffect.activate(); +} +``` + +It is recommended to place the activation of the plugin as early as possible, so +the plugin can catch all relevant key presses. + +## Plugin properties + +The plugin provides the `WavepoolEffect` object, which has the following +properties: + +### `.idle_timeout` + +> When to keys are being pressed, light will periodically splash across +> the keyboard. This value sets the delay in ms before that starts. +> +> To disable the idle animation entirely, set this to 0. +> +> Default is 5000 (5 seconds). + +### `.ripple_hue` + +> The Hue of the ripple animation. If set, the light splashing across the +> keyboard will use this value instead of all colors of the rainbow. +> +> Setting it to the special value of `Wavepool.rainbow_hue` will cause the +> plugin to use all colors again. +> +> Defaults to `Wavepool.rainbow_hue`. + +## Dependencies + +* [Kaleidoscope-LEDControl](LEDControl.md) + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + + [plugin:example]: ../../examples/LEDS/LED-Wavepool/LED-Wavepool.ino diff --git a/examples/LEDs/LED-Wavepool/LED-Wavepool.ino b/examples/LEDs/LED-Wavepool/LED-Wavepool.ino new file mode 100644 index 00000000..b86e05e9 --- /dev/null +++ b/examples/LEDs/LED-Wavepool/LED-Wavepool.ino @@ -0,0 +1,59 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-LED-Wavepool + * Copyright (C) 2017 Selene Scriven + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +const Key keymaps[][ROWS][COLS] PROGMEM = { + [0] = KEYMAP_STACKED + ( + Key_LEDEffectNext, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, + Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, + Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, + Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, + + Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, + Key_NoKey, + + Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip, + Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, + Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, + Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + + Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, + Key_NoKey), +}; + +KALEIDOSCOPE_INIT_PLUGINS( + LEDControl, + LEDOff, + WavepoolEffect +); + +void setup() { + Kaleidoscope.setup(); + + WavepoolEffect.idle_timeout = 5000; // 5 seconds + WavepoolEffect.ripple_hue = WavepoolEffect.rainbow_hue; + WavepoolEffect.activate(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/src/Kaleidoscope-LED-Wavepool.h b/src/Kaleidoscope-LED-Wavepool.h new file mode 100644 index 00000000..093c1ef7 --- /dev/null +++ b/src/Kaleidoscope-LED-Wavepool.h @@ -0,0 +1,21 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-LED-Wavepool + * Copyright (C) 2017 Selene Scriven + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include diff --git a/src/kaleidoscope/plugin/LED-Wavepool.cpp b/src/kaleidoscope/plugin/LED-Wavepool.cpp new file mode 100644 index 00000000..d0a5ee37 --- /dev/null +++ b/src/kaleidoscope/plugin/LED-Wavepool.cpp @@ -0,0 +1,225 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-LED-Wavepool + * Copyright (C) 2017 Selene Scriven + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef ARDUINO_AVR_MODEL01 + +#include + +namespace kaleidoscope { +namespace plugin { + +#define INTERPOLATE 1 // smoother, slower animation +#define MS_PER_FRAME 40 // 40 = 25 fps +#define FRAMES_PER_DROP 120 // max time between raindrops during idle animation + +int8_t WavepoolEffect::surface[2][WP_WID * WP_HGT]; +uint8_t WavepoolEffect::page = 0; +uint8_t WavepoolEffect::frames_since_event = 0; +uint16_t WavepoolEffect::idle_timeout = 5000; // 5 seconds +int16_t WavepoolEffect::ripple_hue = WavepoolEffect::rainbow_hue; // automatic hue + +// map native keyboard coordinates (16x4) into geometric space (14x5) +PROGMEM const uint8_t WavepoolEffect::rc2pos[ROWS * COLS] = { + 0, 1, 2, 3, 4, 5, 6, 59, 66, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 34, 60, 65, 35, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 48, 61, 64, 49, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 58, 62, 63, 67, 50, 51, 52, 53, 54, 55, +}; + +EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { + if (row >= ROWS || col >= COLS) + return EventHandlerResult::OK; + + if (keyIsPressed(key_state)) { + uint8_t offset = (row * COLS) + col; + surface[page][pgm_read_byte(rc2pos + offset)] = 0x7f; + frames_since_event = 0; + } + + return EventHandlerResult::OK; +} + +void WavepoolEffect::raindrop(uint8_t x, uint8_t y, int8_t *page) { + uint8_t rainspot = (y * WP_WID) + x; + + page[rainspot] = 0x7f; + if (y > 0) page[rainspot - WP_WID] = 0x60; + if (y < (WP_HGT - 1)) page[rainspot + WP_WID] = 0x60; + if (x > 0) page[rainspot - 1] = 0x60; + if (x < (WP_WID - 1)) page[rainspot + 1] = 0x60; +} + +// this is a lot smaller than the standard library's rand(), +// and still looks random-ish +uint8_t WavepoolEffect::wp_rand() { + static uint16_t offset = 0x400; + offset = ((offset + 1) & 0x4fff) | 0x400; + return (millis() / MS_PER_FRAME) + pgm_read_byte(offset); +} + +void WavepoolEffect::update(void) { + + // limit the frame rate; one frame every 64 ms + static uint8_t prev_time = 0; + uint8_t now = millis() / MS_PER_FRAME; + if (now != prev_time) { + prev_time = now; + } else { + return; + } + + // rotate the colors over time + // (side note: it's weird that this is a 16-bit int instead of 8-bit, + // but that's what the library function wants) + static uint8_t current_hue = 0; + current_hue ++; + + frames_since_event ++; + + // needs two pages of height map to do the calculations + int8_t *newpg = &surface[page ^ 1][0]; + int8_t *oldpg = &surface[page][0]; + + // rain a bit while idle + static uint8_t frames_till_next_drop = 0; + static int8_t prev_x = -1; + static int8_t prev_y = -1; +#ifdef INTERPOLATE + // even frames: water movement and page flipping + // odd frames: raindrops and tweening + // (this arrangement seems to look best overall) + if (((now & 1)) && (idle_timeout > 0)) { +#else + if (idle_timeout > 0) { +#endif + // repeat previous raindrop to give it a slightly better effect + if (prev_x >= 0) { + raindrop(prev_x, prev_y, oldpg); + prev_x = prev_y = -1; + } + if (frames_since_event + >= (frames_till_next_drop + + (idle_timeout / MS_PER_FRAME))) { + frames_till_next_drop = 4 + (wp_rand() % FRAMES_PER_DROP); + frames_since_event = idle_timeout / MS_PER_FRAME; + + uint8_t x = wp_rand() % WP_WID; + uint8_t y = wp_rand() % WP_HGT; + raindrop(x, y, oldpg); + + prev_x = x; + prev_y = y; + } + } + + // calculate water movement + // (originally skipped edges, but this keyboard is too small for that) + //for (uint8_t y = 1; y < WP_HGT-1; y++) { + // for (uint8_t x = 1; x < WP_WID-1; x++) { +#ifdef INTERPOLATE + if (!(now & 1)) { // even frames only +#endif + for (uint8_t y = 0; y < WP_HGT; y++) { + for (uint8_t x = 0; x < WP_WID; x++) { + uint8_t offset = (y * WP_WID) + x; + + int16_t value; + int8_t offsets[] = { -WP_WID, WP_WID, + -1, 1, + -WP_WID - 1, -WP_WID + 1, + WP_WID - 1, WP_WID + 1 + }; + // don't wrap around edges or go out of bounds + if (y == 0) { + offsets[0] = 0; + offsets[4] += WP_WID; + offsets[5] += WP_WID; + } else if (y == WP_HGT - 1) { + offsets[1] = 0; + offsets[6] -= WP_WID; + offsets[7] -= WP_WID; + } + if (x == 0) { + offsets[2] = 0; + offsets[4] += 1; + offsets[6] += 1; + } else if (x == WP_WID - 1) { + offsets[3] = 0; + offsets[5] -= 1; + offsets[7] -= 1; + } + + // add up all samples, divide, subtract prev frame's center + int8_t *p; + for (p = offsets, value = 0; p < offsets + 8; p++) + value += oldpg[offset + (*p)]; + value = (value >> 2) - newpg[offset]; + + // reduce intensity gradually over time + newpg[offset] = value - (value >> 3); + } + } +#ifdef INTERPOLATE + } +#endif + + // draw the water on the keys + for (byte r = 0; r < ROWS; r++) { + for (byte c = 0; c < COLS; c++) { + uint8_t offset = (r * COLS) + c; + int8_t height = oldpg[pgm_read_byte(rc2pos + offset)]; +#ifdef INTERPOLATE + if (now & 1) { // odd frames only + // average height with other frame + height = ((int16_t)height + newpg[pgm_read_byte(rc2pos + offset)]) >> 1; + } +#endif + + uint8_t intensity = abs(height) * 2; + uint8_t saturation = 0xff - intensity; + uint8_t value = (intensity >= 128) ? 255 : intensity << 1; + int16_t hue = ripple_hue; + + if (ripple_hue == rainbow_hue) { + // color starts white but gets dimmer and more saturated as it fades, + // with hue wobbling according to height map + hue = (current_hue + height + (height >> 1)) & 0xff; + } + + cRGB color = hsvToRgb(hue, saturation, value); + + ::LEDControl.setCrgbAt(r, c, color); + } + } + +#ifdef INTERPOLATE + // swap pages every other frame + if (!(now & 1)) page ^= 1; +#else + // swap pages every frame + page ^= 1; +#endif + +} + +} +} + +kaleidoscope::plugin::WavepoolEffect WavepoolEffect; + +#endif diff --git a/src/kaleidoscope/plugin/LED-Wavepool.h b/src/kaleidoscope/plugin/LED-Wavepool.h new file mode 100644 index 00000000..c6fed02b --- /dev/null +++ b/src/kaleidoscope/plugin/LED-Wavepool.h @@ -0,0 +1,60 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-LED-Wavepool + * Copyright (C) 2017 Selene Scriven + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifdef ARDUINO_AVR_MODEL01 + +#include +#include + +#define WP_WID 14 +#define WP_HGT 5 + +namespace kaleidoscope { +namespace plugin { +class WavepoolEffect : public LEDMode { + public: + WavepoolEffect(void) {} + + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + + // ms before idle animation starts after last keypress + static uint16_t idle_timeout; + static int16_t ripple_hue; + + static constexpr int16_t rainbow_hue = INT16_MAX; + protected: + void update(void) final; + + private: + static uint8_t frames_since_event; + static int8_t surface[2][WP_WID * WP_HGT]; + static uint8_t page; + static PROGMEM const uint8_t rc2pos[ROWS * COLS]; + + static void raindrop(uint8_t x, uint8_t y, int8_t *page); + static uint8_t wp_rand(); +}; + +} +} + +extern kaleidoscope::plugin::WavepoolEffect WavepoolEffect; + +#endif