summaryrefslogtreecommitdiffstats
path: root/users
diff options
context:
space:
mode:
Diffstat (limited to 'users')
-rw-r--r--users/cedrikl/cedrikl.c36
-rw-r--r--users/cedrikl/cedrikl.h21
-rw-r--r--users/cedrikl/config.h25
-rw-r--r--users/cedrikl/rules.mk4
-rw-r--r--users/drashna/oled/oled_stuff.c2
-rw-r--r--users/drashna/pointing/pointing.c4
-rw-r--r--users/vitoni/readme.adoc16
-rw-r--r--users/vitoni/rgb_matrix_effects.c236
-rw-r--r--users/vitoni/rgb_matrix_effects.h174
-rw-r--r--users/vitoni/rules.mk4
-rw-r--r--users/vitoni/utils.c129
-rw-r--r--users/vitoni/utils.h80
-rw-r--r--users/vitoni/vitoni.c131
-rw-r--r--users/vitoni/vitoni.h30
-rw-r--r--users/wanleg/rules.mk2
15 files changed, 890 insertions, 4 deletions
diff --git a/users/cedrikl/cedrikl.c b/users/cedrikl/cedrikl.c
new file mode 100644
index 0000000000..0479e22b5d
--- /dev/null
+++ b/users/cedrikl/cedrikl.c
@@ -0,0 +1,36 @@
+/* Copyright 2021 Cedrik Lussier @cedrikl
+.* Directly inspired from the work of jonavin https://github.com/qmk/qmk_firmware/tree/master/keyboards/gmmk/pro/ansi/keymaps/jonavin
+
+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 2 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#include QMK_KEYBOARD_H
+#include "cedrikl.h"
+
+// Turn on/off NUM LOCK if current state is different
+void activate_numlock(bool turn_on) {
+ if (host_keyboard_led_state().num_lock != turn_on) {
+ tap_code(KC_NUMLOCK);
+ }
+}
+
+
+// INITIAL STARTUP
+
+void keyboard_post_init_user(void) {
+ #ifdef STARTUP_NUMLOCK_ON
+ activate_numlock(true); // turn on Num lock by default so that the numpad layer always has predictable results
+ #endif // STARTUP_NUMLOC_ON
+}
diff --git a/users/cedrikl/cedrikl.h b/users/cedrikl/cedrikl.h
new file mode 100644
index 0000000000..e25d274664
--- /dev/null
+++ b/users/cedrikl/cedrikl.h
@@ -0,0 +1,21 @@
+/* Copyright 2021 Cedrik Lussier @cedrikl
+.* Directly inspired from the work of jonavin https://github.com/qmk/qmk_firmware/tree/master/keyboards/gmmk/pro/ansi/keymaps/jonavin
+
+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 2 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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+// OTHER FUNCTION PROTOTYPE
+void activate_numlock(bool turn_on);
diff --git a/users/cedrikl/config.h b/users/cedrikl/config.h
new file mode 100644
index 0000000000..741a2fe470
--- /dev/null
+++ b/users/cedrikl/config.h
@@ -0,0 +1,25 @@
+/* Copyright 2021 Cedrik Lussier @cedrikl
+.* Directly inspired from the work of jonavin https://github.com/qmk/qmk_firmware/tree/master/keyboards/gmmk/pro/ansi/keymaps/jonavin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifdef RGB_MATRIX_ENABLE
+ #define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_SOLID_COLOR
+ //#define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_CYCLE_ALL
+ #define RGB_DISABLE_WHEN_USB_SUSPENDED
+ #define RGB_MATRIX_STARTUP_VAL 120
+#endif
diff --git a/users/cedrikl/rules.mk b/users/cedrikl/rules.mk
new file mode 100644
index 0000000000..b5ee62c43a
--- /dev/null
+++ b/users/cedrikl/rules.mk
@@ -0,0 +1,4 @@
+SRC += cedrikl.c
+ifeq ($(strip $(STARTUP_NUMLOCK_ON)), yes)
+ OPT_DEFS += -DSTARTUP_NUMLOCK_ON
+endif
diff --git a/users/drashna/oled/oled_stuff.c b/users/drashna/oled/oled_stuff.c
index c850c5336f..752829e0fa 100644
--- a/users/drashna/oled/oled_stuff.c
+++ b/users/drashna/oled/oled_stuff.c
@@ -953,7 +953,7 @@ void oled_driver_render_logo_left(void) {
render_matrix_scan_rate(2);
# endif
oled_set_cursor(7, 2);
-# if defined(KEYBOARD_bastardkb_charybdis)
+# if defined(KEYBOARD_bastardkb_charybdis) || defined(KEYBOARD_handwired_tractyl_manuform)
render_pointing_dpi_status(charybdis_get_pointer_sniping_enabled() ? charybdis_get_pointer_sniping_dpi() : charybdis_get_pointer_default_dpi(), 1);
// credit and thanks to jaspertandy on discord for these images
diff --git a/users/drashna/pointing/pointing.c b/users/drashna/pointing/pointing.c
index 0dcfe73f34..d0a83e2c26 100644
--- a/users/drashna/pointing/pointing.c
+++ b/users/drashna/pointing/pointing.c
@@ -80,9 +80,9 @@ bool process_record_pointing(uint16_t keycode, keyrecord_t* record) {
}
break;
case MO(_MOUSE):
-#if defined(KEYBOARD_ploopy) || defined(KEYBOARD_handwired_tractyl_manuform)
+#if defined(KEYBOARD_ploopy)
case DPI_CONFIG:
-#elif defined(KEYBOARD_bastardkb_charybdis) && !defined(NO_CHARYBDIS_KEYCODES)
+#elif (defined(KEYBOARD_bastardkb_charybdis) || defined(KEYBOARD_handwired_tractyl_manuform)) && !defined(NO_CHARYBDIS_KEYCODES)
case SAFE_RANGE ... (CHARYBDIS_SAFE_RANGE-1):
#endif
case KC_MS_UP ... KC_MS_WH_RIGHT:
diff --git a/users/vitoni/readme.adoc b/users/vitoni/readme.adoc
new file mode 100644
index 0000000000..acf65793d2
--- /dev/null
+++ b/users/vitoni/readme.adoc
@@ -0,0 +1,16 @@
+= User functions
+
+Functions are mostly related to changing the RGB lights depending on user interaction and when idling.
+
+== utils.h
+
+Common functions are declared in link:utils.h[]. These function are not directly RGB related but used to modify state and calculate values.
+
+== rgb_matrix_effects.h
+
+Functions in link:rgb_matrix_effects.h[] make use of common function in `utils.h` and are used to create to RGB matrix effects such as fading or breathing.
+
+== vitoni.h
+
+The functions declared in link:vitoni.h[] are used as entry points for usage of RGB effects.
+One entry point is `matrix_scan` based for regular task while the other is `process_record` based for user activity tasks.
diff --git a/users/vitoni/rgb_matrix_effects.c b/users/vitoni/rgb_matrix_effects.c
new file mode 100644
index 0000000000..3a13e99bc7
--- /dev/null
+++ b/users/vitoni/rgb_matrix_effects.c
@@ -0,0 +1,236 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "rgb_matrix_effects.h"
+
+#include <rgb_matrix.h>
+#include <lib/lib8tion/lib8tion.h>
+
+#include "utils.h"
+
+/*
+ Offset used to start at the right point in th curve to avoid big jumps in brightness
+ 0 => 0% (signed) => 50% (unsigned)
+ 64 => 100% (signed) => 100% (unsigned)
+ 128 => 0% (signed) => 50% (unsigned)
+ 192 => -100% (signed) => 0% (unsigned)
+*/
+enum PHASE {
+ PHASE_ZERO_RAISING
+ ,PHASE_HIGH
+ ,PHASE_ZERO_FALLING
+ ,PHASE_LOW
+};
+
+/**
+ * @brief Calculates the offset so that a specific time is aligned to a specific point in the sine curve.
+ * @param[in] time The time for which the offset shopuld be calculated.
+ * @param[in] phase Phase which should be reached with the offset
+ * @see PHASE
+ */
+uint8_t offset_for_time(const uint8_t time, const uint8_t phase) {
+ switch (phase) {
+ case PHASE_ZERO_RAISING:
+ return 0 - time;
+ case PHASE_HIGH:
+ return 64 - time;
+ case PHASE_ZERO_FALLING:
+ return 128 - time;
+ case PHASE_LOW:
+ return 192 - time;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @return scaled down timer
+ * @see rgb_time_2_scale_w_factor()
+ */
+uint8_t rgb_time_2_scale(void) {
+ static const uint8_t factor = 1;
+ return rgb_time_2_scale_w_factor(factor);
+}
+
+/*
+ * Used to slow down RGB speed.
+ */
+static const uint8_t rgb_speed_divisor = 8;
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @details Usually these calculations aredone internally by some RGB effects.
+ This method exposed to scaling so that all effects to have same timebase. If `rgb_matrix_config.speed` all effects are affected the same.
+ * @param[in] factor The factor can be used to speed up some operations in relation to others.
+ * @return scaled down timer taking into account the given factor
+ * @see g_rgb_timer
+ * @see rgb_matrix_config.speed
+ */
+uint8_t rgb_time_2_scale_w_factor(const uint8_t rgb_speed_factor) {
+ const uint8_t scaled_time = scale16by8(g_rgb_timer, rgb_matrix_config.speed * rgb_speed_factor / rgb_speed_divisor);
+
+ return scaled_time;
+}
+
+/**
+ * @brief Inverse function to calculate time required to execute `timer` steps.
+ * @details This method allows calculation of the time needed to execute N `timer`steps.
+ Usefull when using a scaled down time but requiring the time needed to perform these steps.
+ * @param[in] scaled_time scaled down timer to inverse to time
+ * @return time corresponding to scaled down time
+ * @see rgb_time_2_scale()
+ */
+uint16_t scale_2_rgb_time(const uint8_t scaled_time) {
+ const uint16_t time = scaled_time * rgb_speed_divisor * UINT8_MAX / rgb_matrix_config.speed;
+
+ return time;
+}
+
+bool fade_in_ranged(const uint8_t time, const uint8_t range_min, const uint8_t range_max) {
+ static const uint8_t max_delta = 1;
+ return scaled_sin_up(time, range_min, range_max, max_delta, &(rgb_matrix_config.hsv.v));
+}
+
+bool fade_out_ranged(const uint8_t time, const uint8_t range_min, const uint8_t range_max) {
+ static const uint8_t max_delta = 1;
+ return scaled_sin_down(time, range_min, range_max, max_delta, &(rgb_matrix_config.hsv.v));
+}
+
+/**
+ * @brief Convenience method to eventually skip the value part when setting HSV.
+ * @details When setting HSV this includes the value/brightness.
+ As changing brightness might interfer with fading or breathing effects,
+ this method can skip the value part of HSV (depending on the preprocessor flag: RGB_FADE_IN).
+ * @param[in] hue Hue
+ * @param[in] sat Saturation
+ * @param[in] hue Value (brightness)
+ * @see rgb_matrix_sethsv_noeeprom()
+ */
+void rgb_matrix_sethsv_noeeprom_user(const uint16_t hue, const uint8_t sat, const uint8_t val) {
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+ rgb_matrix_config.hsv.h = hue;
+ rgb_matrix_config.hsv.s = sat;
+ // omitting setting the value to avoid interfering with effects
+// rgb_matrix_config.hsv.v = val;
+#else
+ rgb_matrix_sethsv_noeeprom(hue, sat, val);
+#endif
+}
+
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade in.
+ * @details Using an arbitrary timer any point on the sine curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the lowest point in the curve and the curve is raising
+ * b) the point is near the current brightness (eg. fade in might be called while fading out and the lowest value has not yet been reached).
+ * @param[in] time Current time usually represented by (usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_in_offset(const uint8_t time) {
+ static const uint8_t max_steps = UINT8_MAX/2;
+ static const uint8_t range_min = 0;
+ static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+ // start at the right point in the sine curve
+ uint8_t time_offset = offset_for_time(time, PHASE_LOW);
+
+ // find the right offset to match the current brightness
+ for (int i = 1; i < max_steps; i++) {
+ const uint8_t value = scaled_sin(time + time_offset + 1, range_min, range_max);
+ if (in_range(value, range_min, range_max) && value < rgb_matrix_config.hsv.v) {
+ time_offset++;
+ } else {
+ break;
+ }
+ }
+
+ return time_offset;
+}
+
+/**
+ * @brief Increases value/brightness until reaching RGB_MATRIX_MAXIMUM_BRIGHTNESS based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if RGB_MATRIX_MAXIMUM_BRIGHTNESS has been reached, `false` otherwise.
+ */
+bool fade_in(const uint8_t time) {
+ static const uint8_t range_min = 0;
+ static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+ return fade_in_ranged(time, range_min, range_max);
+}
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade out.
+ * @details Using an arbitrary timer any point on the Sinus curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the highest point in the curve and the curve is failing
+ * b) the point is near the current brightness (eg. fade out might be called while on breath effect).
+ * @param[in] time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_out_offset(const uint8_t time) {
+ static const uint8_t range_min = 0;
+ static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+ // start at the right point in the sin() curve
+ uint8_t time_offset = offset_for_time(time, PHASE_HIGH);
+
+ // find the right offset to match the current brightness
+ for (int i = 1; i < 127; i++) {
+ const uint8_t value = scaled_sin(time + time_offset + 1, range_min, range_max);
+ if (in_range(value, range_min, range_max) && rgb_matrix_config.hsv.v < value) {
+ time_offset++;
+ } else {
+ break;
+ }
+ }
+
+ return time_offset;
+}
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+/**
+ * @brief Decreases value/brightness until reaching 0 based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if 0 has been reached, `false` otherwise.
+ */
+bool fade_out(const uint8_t time) {
+ static const uint8_t range_min = 0;
+ static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+ return fade_out_ranged(time, range_min, range_max);
+}
+#endif
+
+#if defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Decreases value/brightness until reaching `RGB_IDLE_MINIMUM_BRIGHTNESS` based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if `RGB_IDLE_MINIMUM_BRIGHTNESS` has been reached, `false` otherwise.
+ */
+bool idle_fade_out(const uint8_t time) {
+ static const uint8_t range_min = RGB_IDLE_MINIMUM_BRIGHTNESS;
+ static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+ return fade_out_ranged(time, range_min, range_max);
+}
+
+#if defined(RGB_IDLE_BREATHE)
+/**
+ * @brief Changes value/brightness to create a breathing effect based on given timer.
+ * @details Brightness will breathe in the range starting from `RGB_IDLE_MINIMUM_BRIGHTNESS` to `RGB_IDLE_MAXIMUM_BRIGHTNESS`.
+ * @param[in] time A (usually scaled) timer
+ */
+void idle_breathe(const uint8_t time) {
+ static const uint8_t range_min = RGB_IDLE_MINIMUM_BRIGHTNESS;
+ static const uint8_t range_max = RGB_IDLE_MAXIMUM_BRIGHTNESS;
+
+ rgb_matrix_config.hsv.v = scaled_sin(time, range_min, range_max);
+}
+#endif // RGB_IDLE_BREATHE
+#endif // RGB_IDLE_TIMEOUT
diff --git a/users/vitoni/rgb_matrix_effects.h b/users/vitoni/rgb_matrix_effects.h
new file mode 100644
index 0000000000..ed74500b18
--- /dev/null
+++ b/users/vitoni/rgb_matrix_effects.h
@@ -0,0 +1,174 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * States reflecting the state of the keyboard.
+ * Dependeing on these states various effects can set for the RGB matrix.
+ */
+enum states {
+ REGULAR //!< when in regular use
+#if defined(RGB_IDLE_TIMEOUT)
+ ,IDLE_FADE_OUT //!< when started idling
+ ,IDLE //!< when idling
+#endif
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+ ,FADE_IN //!< when starting initially or before going back to REGULAR
+#endif
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+ ,FADE_OUT //!< before supending
+#endif
+ ,SUSPENDED //!< expecting to be suspended by RGB_DISABLE_TIMEOUT any time
+};
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @details Usually these calculations aredone internally by some RGB effects.
+ This method exposed to scaling so that all effects to have same timebase. If `rgb_matrix_config.speed` all effects are affected the same.
+ * @param[in] factor The factor can be used to speed up some operations in relation to others.
+ * @return scaled down timer taking into account the given factor
+ * @see g_rgb_timer
+ * @see rgb_matrix_config.speed
+ */
+uint8_t rgb_time_2_scale_w_factor(const uint8_t factor);
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @return scaled down timer
+ * @see rgb_time_2_scale_w_factor()
+ */
+uint8_t rgb_time_2_scale(void);
+
+/**
+ * @brief Inverse function to calculate time required to execute `timer` steps.
+ * @details This method allows calculation of the time needed to execute N `timer`steps.
+ Usefull when using a scaled down time but requiring the time needed to perform these steps.
+ * @param[in] scaled_time scaled down timer to inverse to time
+ * @return time corresponding to scaled down time
+ * @see rgb_time_2_scale()
+ */
+uint16_t scale_2_rgb_time(const uint8_t scaled_time);
+
+/**
+ * @brief Convenience method to eventually skip the value part when setting HSV.
+ * @details When setting HSV this includes the value/brightness.
+ As changing brightness might interfer with fading or breathing effects,
+ this method can skip the value part of HSV (depending on the preprocessor flag: RGB_FADE_IN).
+ * @param[in] hue Hue
+ * @param[in] sat Saturation
+ * @param[in] hue Value (brightness)
+ * @see rgb_matrix_sethsv_noeeprom()
+ */
+void rgb_matrix_sethsv_noeeprom_user(const uint16_t hue, const uint8_t sat, const uint8_t val);
+
+#if defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+# if defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+# if (RGB_MATRIX_MAXIMUM_BRIGHTNESS) < 1
+# error "RGB_MATRIX_MAXIMUM_BRIGHTNESS must not be less than ONE"
+# endif
+# if UINT8_MAX < (RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+# error "RGB_MATRIX_MAXIMUM_BRIGHTNESS must not be larger than UINT8_MAX"
+# endif
+# else
+# define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200
+# endif
+#endif
+
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade in.
+ * @details Using an arbitrary timer any point on the sine curve might be pointed to.
+ * The offset is calculated so that
+ * a) the point is at the lowest point in the curve and the curve is raising
+ * b) the point is near the current brightness (eg. fade in might be called while fading out and the lowest value has not yet been reached).
+ * @param[in] time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_in_offset(const uint8_t time);
+
+/**
+ * @brief Increases value/brightness until reaching RGB_MATRIX_MAXIMUM_BRIGHTNESS based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if RGB_MATRIX_MAXIMUM_BRIGHTNESS has been reached, `false` otherwise.
+ */
+bool fade_in(const uint8_t time);
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+# if !defined(RGB_DISABLE_TIMEOUT)
+# warning "RGB_DISABLE_WITH_FADE_OUT expects RGB_DISABLE_TIMEOUT to be defined"
+# endif
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade out.
+ * @details Using an arbitrary timer any point on the Sinus curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the highest point in the curve and the curve is failing
+ * b) the point is near the current brightness (eg. fade out might be called while on breath effect).
+ * @param[in] time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_out_offset(const uint8_t time);
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+/**
+ * @brief Decreases value/brightness until reaching 0 based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if 0 has been reached, `false` otherwise.
+ */
+bool fade_out(const uint8_t time);
+#endif
+
+#if defined(RGB_IDLE_TIMEOUT)
+# if RGB_IDLE_TIMEOUT < 0
+# error "RGB_IDLE_TIMEOUT must not be less than ZERO"
+# endif
+# if !defined(RGB_IDLE_MINIMUM_BRIGHTNESS)
+ // minimum brightness when idling
+# define RGB_IDLE_MINIMUM_BRIGHTNESS (RGB_MATRIX_MAXIMUM_BRIGHTNESS/5)
+# endif
+# if RGB_IDLE_MINIMUM_BRIGHTNESS < 0
+# error "RGB_IDLE_MINIMUM_BRIGHTNESS must not be less than ZERO"
+# endif // RGB_IDLE_MINIMUM_BRIGHTNESS < 0
+# if RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+# error "RGB_IDLE_MINIMUM_BRIGHTNESS must be less than RGB_MATRIX_MAXIMUM_BRIGHTNESS"
+# endif // RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+
+/**
+ * @brief Decreases value/brightness until reaching `RGB_IDLE_MINIMUM_BRIGHTNESS` based on given timer.
+ * @param[in] time A (usually scaled) timer
+ * @return Returns `true` if `RGB_IDLE_MINIMUM_BRIGHTNESS` has been reached, `false` otherwise.
+ */
+bool idle_fade_out(const uint8_t time);
+
+#if defined(RGB_IDLE_BREATHE)
+# if !defined(RGB_IDLE_MAXIMUM_BRIGHTNESS)
+ // maximum brightness when idling
+# define RGB_IDLE_MAXIMUM_BRIGHTNESS (RGB_MATRIX_MAXIMUM_BRIGHTNESS*3/5)
+# endif
+# if !(0 <= RGB_IDLE_MAXIMUM_BRIGHTNESS)
+# error "RGB_IDLE_MINIMUM_BRIGHTNESS must not be less than ZERO, was: " RGB_IDLE_MAXIMUM_BRIGHTNESS
+# endif // RGB_IDLE_MAXIMUM_BRIGHTNESS < 0
+# if !(RGB_IDLE_MINIMUM_BRIGHTNESS < RGB_IDLE_MAXIMUM_BRIGHTNESS)
+# error "RGB_IDLE_MINIMUM_BRIGHTNESS must be less than RGB_IDLE_MAXIMUM_BRIGHTNESS"
+# endif // RGB_IDLE_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+# if !(RGB_IDLE_MAXIMUM_BRIGHTNESS <= RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+# error "RGB_IDLE_MAXIMUM_BRIGHTNESS must be less than or equal to RGB_MATRIX_MAXIMUM_BRIGHTNESS"
+# endif // RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MAXIMUM_BRIGHTNESS
+
+/**
+ * @brief Changes value/brightness to create a breathing effect based on given timer.
+ * @details Brightness will breathe in the range starting from `RGB_IDLE_MINIMUM_BRIGHTNESS` to `RGB_IDLE_MAXIMUM_BRIGHTNESS`.
+ * @param[in] time A (usually scaled) timer
+ */
+void idle_breathe(const uint8_t time);
+#endif // RGB_IDLE_BREATHE
+
+#endif // RGB_IDLE_TIMEOUT
diff --git a/users/vitoni/rules.mk b/users/vitoni/rules.mk
new file mode 100644
index 0000000000..2f3b0d15e5
--- /dev/null
+++ b/users/vitoni/rules.mk
@@ -0,0 +1,4 @@
+SRC += \
+ vitoni.c \
+ utils.c \
+ rgb_matrix_effects.c
diff --git a/users/vitoni/utils.c b/users/vitoni/utils.c
new file mode 100644
index 0000000000..fb011570e4
--- /dev/null
+++ b/users/vitoni/utils.c
@@ -0,0 +1,129 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "utils.h"
+
+#include <lib/lib8tion/lib8tion.h>
+
+/**
+* @brief Changes `*value` to `new_value`.
+* @param[in,out] value Pointer to variable to be changed.
+* @param[in] new_value Value to be changed.
+* @param[in,out] changed Flag indicating `*value` and `new_value` were different.
+*/
+void update_value(uint8_t *value, const uint8_t new_value, bool *changed) {
+ if (new_value != (*value)) {
+ (*changed) = true;
+ (*value) = new_value;
+ }
+}
+
+/**
+* @brief Checks whether a value is in the given range.
+* @param[in] value Value to be checked.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @return `true` if (range_min <= value <= range_max), `false` otherwise
+*/
+bool in_range(const uint8_t value, const uint8_t range_min, const uint8_t range_max) {
+ return range_min <= value && value <= range_max;
+}
+
+/**
+* @brief Calculates the sine value based on sin8() and scales it to the given range (unsigned).
+*
+* Table of values for unscaled sin8() eg. a theta of 0 results to 128 and a theta of 255 (240+15) results to 125.
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ +----------------------------------------------------------------
+ 0: 128 131 134 137 140 143 146 149 152 155 158 161 164 167 170 173
+ 16: 177 179 182 184 187 189 192 194 197 200 202 205 207 210 212 215
+ 32: 218 219 221 223 224 226 228 229 231 233 234 236 238 239 241 243
+ 48: 245 245 246 246 247 248 248 249 250 250 251 251 252 253 253 254
+ 64: 255 254 253 253 252 251 251 250 250 249 248 248 247 246 246 245
+ 80: 245 243 241 239 238 236 234 233 231 229 228 226 224 223 221 219
+ 96: 218 215 212 210 207 205 202 200 197 194 192 189 187 184 182 179
+ 112: 177 173 170 167 164 161 158 155 152 149 146 143 140 137 134 131
+ 128: 128 125 122 119 116 113 110 107 104 101 98 95 92 89 86 83
+ 144: 79 77 74 72 69 67 64 62 59 56 54 51 49 46 44 41
+ 160: 38 37 35 33 32 30 28 27 25 23 22 20 18 17 15 13
+ 176: 11 11 10 10 9 8 8 7 6 6 5 5 4 3 3 2
+ 192: 1 2 3 3 4 5 5 6 6 7 8 8 9 10 10 11
+ 208: 11 13 15 17 18 20 22 23 25 27 28 30 32 33 35 37
+ 224: 38 41 44 46 49 51 54 56 59 62 64 67 69 72 74 77
+ 240: 79 83 86 89 92 95 98 101 104 107 110 113 116 119 122 125
+*
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @return Calculated sine value mapped to the given range.
+*/
+uint8_t scaled_sin(const uint8_t theta, const uint8_t range_min, const uint8_t range_max) {
+ const uint8_t range = range_max - range_min;
+ return scale8(sin8(theta), range) + range_min;
+}
+
+/**
+* @brief Increases the given value until reaching range_max.
+* The increments occur following an upwards sine wave (scaled from range_min to range_max).
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @param[in] max_delta Maximum delta between value and range_max (due to values being integers and eventually not fully matching).
+* @param[in,out] value Reference of variable to be increased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_up(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value) {
+ // ensure upper range bound
+ if (range_max <= (*value)) {
+ (*value) = range_max;
+ return true;
+ }
+
+ const uint8_t new_value = scaled_sin(theta, range_min, range_max);
+ if (in_range(new_value, range_min, range_max) && (*value) < new_value) {
+ (*value) = new_value;
+
+ return range_max == (*value);
+ }
+
+ const uint8_t delta = range_max - (*value);
+ if (delta <= max_delta) {
+ (*value) = range_max;
+ }
+
+ return delta <= max_delta;
+}
+
+/**
+* @brief Decreases the given value until reaching range_min.
+* The decrements occur following an downwards sinus wave (scaled from range_min to range_max).
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sinus calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @param[in] max_delta Maximum delta between value and range_min (due to values being integers and eventually not fully matching).
+* @param[in,out] value Reference of variable to be decreased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_down(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value) {
+ // ensure lower range bound
+ if ((*value) <= range_min) {
+ (*value) = range_min;
+ return true;
+ }
+
+ const uint8_t new_value = scaled_sin(theta, range_min, range_max);
+ if (in_range(new_value, range_min, range_max) && new_value < (*value)) {
+ (*value) = new_value;
+
+ return range_min == (*value);
+ }
+
+ const uint8_t delta = (*value) - range_min;
+ if (delta <= max_delta) {
+ (*value) = range_min;
+ }
+
+ return delta <= max_delta;
+}
diff --git a/users/vitoni/utils.h b/users/vitoni/utils.h
new file mode 100644
index 0000000000..987b612d58
--- /dev/null
+++ b/users/vitoni/utils.h
@@ -0,0 +1,80 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+* @brief Changes `*value` to `new_value`.
+* @param[in,out] value Pointer to variable to be changed.
+* @param[in] new_value Value to be changed.
+* @param[in,out] changed Flag indicating `*value` and `new_value` were different.
+*/
+void update_value(uint8_t *value, const uint8_t new_value, bool *changed);
+
+/**
+* @brief Checks whether a value is in the given range.
+* @param[in] value Value to be checked.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @return `true` if (range_min <= value <= range_max), `false` otherwise
+*/
+bool in_range(const uint8_t value, const uint8_t range_min, const uint8_t range_max);
+
+/**
+* @brief Calculates the sine value based on sin8() and scales it to the given range (unsigned).
+*
+* Table of values for unscaled sin8() eg. a theta of 0 results to 128 and a theta of 255 (240+15) results to 125.
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ +----------------------------------------------------------------
+ 0: 128 131 134 137 140 143 146 149 152 155 158 161 164 167 170 173
+ 16: 177 179 182 184 187 189 192 194 197 200 202 205 207 210 212 215
+ 32: 218 219 221 223 224 226 228 229 231 233 234 236 238 239 241 243
+ 48: 245 245 246 246 247 248 248 249 250 250 251 251 252 253 253 254
+ 64: 255 254 253 253 252 251 251 250 250 249 248 248 247 246 246 245
+ 80: 245 243 241 239 238 236 234 233 231 229 228 226 224 223 221 219
+ 96: 218 215 212 210 207 205 202 200 197 194 192 189 187 184 182 179
+ 112: 177 173 170 167 164 161 158 155 152 149 146 143 140 137 134 131
+ 128: 128 125 122 119 116 113 110 107 104 101 98 95 92 89 86 83
+ 144: 79 77 74 72 69 67 64 62 59 56 54 51 49 46 44 41
+ 160: 38 37 35 33 32 30 28 27 25 23 22 20 18 17 15 13
+ 176: 11 11 10 10 9 8 8 7 6 6 5 5 4 3 3 2
+ 192: 1 2 3 3 4 5 5 6 6 7 8 8 9 10 10 11
+ 208: 11 13 15 17 18 20 22 23 25 27 28 30 32 33 35 37
+ 224: 38 41 44 46 49 51 54 56 59 62 64 67 69 72 74 77
+ 240: 79 83 86 89 92 95 98 101 104 107 110 113 116 119 122 125
+*
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @return Calculated sine value mapped to the given range.
+*/
+uint8_t scaled_sin(const uint8_t theta, const uint8_t range_min, const uint8_t range_max);
+
+/**
+* @brief Increases the given value until reaching range_max.
+* The increments occur following an upwards sine wave (scaled from range_min to range_max).
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @param[in] max_delta Maximum delta between value and range_max (due to values being integers and eventually not fully matching).
+* @param[in,out] value Reference of variable to be increased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_up(const uint8_t thea, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value);
+
+/**
+* @brief Decreases the given value until reaching range_min.
+* The decrements occur following an downwards sinus wave (scaled from range_min to range_max).
+* @param[in] theta Angle (a full circle mapped to 0-255) used as input for sinus calculation.
+* @param[in] range_min Lower bound of range (inclusive).
+* @param[in] range_max Upper bound of range (inclusive).
+* @param[in] max_delta Maximum delta between value and range_min (due to values being integers and eventually not fully matching).
+* @param[in,out] value Reference of variable to be decreased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_down(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value);
diff --git a/users/vitoni/vitoni.c b/users/vitoni/vitoni.c
new file mode 100644
index 0000000000..2a0ff5c46f
--- /dev/null
+++ b/