Browse Source

[Keymap] Add vitoni keymap for GMMK Pro (ISO) (#15006)

* [Keymap] Add vitoni layout for GMMK Pro (ISO)

Keymap has layered cursor keys similar to laptop keyboards.

* Configure RGB defaults for startup

* Configure encoder to change value/brightness on FN layer

* Remove FN layer and add dedicated RGB layer

* Make RGB layer sticky (using TG) to avoid holding FN while configuring RGB

* Add RGB indicators for active layers

* Add RGB indicator for active RESET mode

Signed-off-by: Victor Toni <victor.toni@gmail.com>

* Configure idle / USB suspend settings

* Add RGB fade in when resuming after suspend

* Add RGB fade out before suspend

* Add fade out before idle

* Add breathe effect when idle
pull/15593/head
Victor Toni 2 years ago
committed by GitHub
parent
commit
6209122213
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1072 additions and 0 deletions
  1. +20
    -0
      keyboards/gmmk/pro/iso/keymaps/vitoni/config.h
  2. +148
    -0
      keyboards/gmmk/pro/iso/keymaps/vitoni/keymap.c
  3. +104
    -0
      keyboards/gmmk/pro/iso/keymaps/vitoni/readme.adoc
  4. +16
    -0
      users/vitoni/readme.adoc
  5. +236
    -0
      users/vitoni/rgb_matrix_effects.c
  6. +174
    -0
      users/vitoni/rgb_matrix_effects.h
  7. +4
    -0
      users/vitoni/rules.mk
  8. +129
    -0
      users/vitoni/utils.c
  9. +80
    -0
      users/vitoni/utils.h
  10. +131
    -0
      users/vitoni/vitoni.c
  11. +30
    -0
      users/vitoni/vitoni.h

+ 20
- 0
keyboards/gmmk/pro/iso/keymaps/vitoni/config.h View File

@ -0,0 +1,20 @@
// Copyright 2021 Victor Toni (@vitoni)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#if defined(RGB_MATRIX_ENABLE)
#define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_SOLID_COLOR
// number of milliseconds to wait until turning off RGB automatically
#define RGB_DISABLE_TIMEOUT 300000 // 300 seconds / 5 min
// start fading out before getting disabled
// fading out is timed (depending on the rgb_matrix_config.speed) to have finished before reaching RGB_DISABLE_TIMEOUT
#define RGB_DISABLE_WITH_FADE_OUT
#define RGB_DISABLE_WHEN_USB_SUSPENDED
// number of milliseconds to wait until activating RGB idle effects
#define RGB_IDLE_TIMEOUT 4500 // 4.5 seconds
// activate breathe effect when idle
#define RGB_IDLE_BREATHE
// fade in when we have been suspended
#define RGB_FADE_IN
#endif

+ 148
- 0
keyboards/gmmk/pro/iso/keymaps/vitoni/keymap.c View File

@ -0,0 +1,148 @@
// Copyright 2021 Glorious, LLC <salman@pcgamingrace.com>,
// Copyright 2021 Victor Toni (@vitoni)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
#include "vitoni.h"
enum layer_names {
_BASE,
_MOV,
_RGB
};
// clang-format off
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
// ESC F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 Prt Rotary(Mute)
// ~ 1 2 3 4 5 6 7 8 9 0 - (=) BackSpc Del
// Tab Q W E R T Y U I O P [ ] PgUp
// Caps A S D F G H J K L ; " # Enter PgDn
// Sh_L / Z X C V B N M , . ? Sh_R Up End
// Ct_L Win_L Alt_L SPACE Alt_R FN Ct_R Left Down Right
//
// This keyboard defaults to 6KRO instead of NKRO for compatibility reasons (some KVMs and BIOSes are incompatible with NKRO).
// Since this is, among other things, a "gaming" keyboard, a key combination to enable NKRO on the fly is provided for convenience.
// Press CAPS+N to toggle between 6KRO and NKRO. This setting is persisted to the EEPROM and thus persists between restarts.
[_BASE] = LAYOUT(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_PSCR, KC_MUTE,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_DEL,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_PGUP,
MO(_MOV), KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT, KC_PGDN,
KC_LSFT, KC_NUBS, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP, KC_END,
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, TG(_RGB),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
),
[_MOV] = LAYOUT(
RESET, KC_MYCM, KC_WHOM, KC_CALC, KC_MSEL, KC_MPRV, KC_MPLY, KC_MSTP, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_INS,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, NK_TOGG, _______, _______, _______, _______, _______, KC_PGUP, _______,
_______, _______, _______, _______, _______, _______, _______, KC_HOME, KC_PGDN, KC_END
),
[_RGB] = LAYOUT(
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RESET, RGB_MOD,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_RMOD,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_TOG, RGB_SPI,
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_SAI, RGB_SPD,
_______, _______, _______, _______, _______, _______, _______, RGB_HUD, RGB_SAD, RGB_HUI
),
};
// clang-format on
#if defined(ENCODER_ENABLE)
bool encoder_update_user(uint8_t index, bool clockwise) {
switch (get_highest_layer(layer_state)) {
case _MOV:
if (clockwise) {
tap_code16(C(KC_TAB));
} else {
tap_code16(S(C(KC_TAB)));
}
break;
#if defined(RGB_MATRIX_ENABLE)
case _RGB:
if (clockwise) {
rgb_matrix_increase_val_noeeprom();
} else {
rgb_matrix_decrease_val_noeeprom();
}
break;
#endif // RGB_MATRIX_ENABLE
default:
if (clockwise) {
tap_code(KC_VOLU);
} else {
tap_code(KC_VOLD);
}
break;
}
return true;
}
#endif // ENCODER_ENABLE
#if defined(RGB_MATRIX_ENABLE)
/*
* Set up default RGB color.
*/
void rgb_matrix_set_default_color(void) {
rgb_matrix_sethsv_noeeprom_user(HSV_CHARTREUSE);
}
/*
* Set up RGB defaults.
*/
void rgb_matrix_configure_default_settings(void) {
rgb_matrix_set_default_color();
}
void keyboard_post_init_user(void) {
rgb_matrix_enable_noeeprom();
rgb_matrix_configure_default_settings();
}
/*
* Use RGB underglow to indicate specific layers.
*/
layer_state_t layer_state_set_user(layer_state_t state) {
switch (get_highest_layer(state)) {
case _MOV:
rgb_matrix_sethsv_noeeprom_user(HSV_SPRINGGREEN);
break;
case _RGB:
rgb_matrix_sethsv_noeeprom_user(HSV_GREEN);
break;
default: // for any other layer
rgb_matrix_set_default_color();
break;
}
return state;
}
void matrix_scan_user(void) {
matrix_scan_user_rgb();
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (!process_record_user_rgb(keycode, record)) {
return false;
}
switch (keycode) {
case RESET: // when activating RESET mode for flashing
if (record->event.pressed) {
rgb_matrix_set_color_all(63, 0, 0);
rgb_matrix_driver.flush();
}
return true;
}
return true; // Process all other keycodes normally
}
#endif // RGB_MATRIX_ENABLE

+ 104
- 0
keyboards/gmmk/pro/iso/keymaps/vitoni/readme.adoc View File

@ -0,0 +1,104 @@
= ViToni's keymap for GMMK Pro ISO
== Layout
Based on the stock layout but making use of CAPS as FN similar to laptop keyboards.
This frees up the left row for other uses (although not remapped yet).
Since both Delete and Insert are used for coding they are part of the CAPS layer as well.
The differences are as follows:
=== Layer 0 (`_BASE`)
Mostly stock + CAPS goes to layer `_MOV`.
FN toggles the layer `_RGB`.
=== Layer 1 (`_MOV`), accessed by pressing `CAPS` on layer `_BASE`
[%header]
|===
| Key / Action | Mapped to
| ESC | _RESET_
| F1 | KC_MYCM
| F2 | KC_WHOM
| F3 | KC_CALC
| F4 | KC_MSEL
| F5 | KC_MPRV
| F6 | KC_MPLY
| F7 | KC_MSTP
| F8 | KC_MNXT
| F9 | KC_MUTE
| F10 | KC_VOLD
| F11 | KC_VOLU
| N | NK_TOGG
| Delete | Insert
| Left | Home
| Right | End
| Up | PgUp
| Down | PgDn
|===
=== Layer 2 (`_RGB`), accessed by pressing `FN` on layer `_BASE`
Revamped the stock FN layer to focus on RGB only.
[%header]
|===
| Key / Action | Mapped to
| Knob clockwise | Value/Brightness up
| Knob anti-clockwise | Value/Brightness down
| Backspace | _RESET_
| Enter | RGB_TOG
| Del | RGB_MOD
| PgUp | RGB_RMOD
| PgDn | RGB_SPI
| End | RGB_SPD
| Left | RGB_HUD
| Right | RGB_HUI
| Up | RGB_SAI
| Down | RGB_SAD
|===
No other changes have been made.
== RGB light
The code customizing RGB light usage is decribed here:
* link:../../../../../../users/vitoni/readme.adoc[/users/vitoni/readme.adoc]
When using `RGB_DISABLE_TIMEOUT` addtional options are available:
* `RGB_FADE_IN` makes the RGB lights fade in instead of setting the value/brightness to 100% (implicitly due to HSV including the brightness) when resuming after RGB lights have been turned off.
Fade in occurs when the keyboard is initialized and when the RGB brightness has been changed (e.g. suspending, fade out, etc.).
* `RGB_DISABLE_WITH_FADE_OUT` activates fade out before the keyboard is disabled by `RGB_DISABLE_TIMEOUT`.
Parameters used to define the behavior are:
[%header]
|===
|Key | Default | Description
| RGB_MATRIX_MAXIMUM_BRIGHTNESS
| 200 (&lt;= UNIT8_MAX)
| Maximum assumed value for brightness.
Used to calculate lead time for fade out before suspend timeout.
|===
`RGB_IDLE_TIMEOUT` enables fading out after being idle for the defined time and allows
* `RGB_IDLE_BREATHE` also activates a brethe effect while idling.
[%header]
|===
|Key | Default | Description
|RGB_IDLE_TIMEOUT
|4500
|Time in milliseconds without activity before considered to be idle.
|RGB_IDLE_MINIMUM_BRIGHTNESS
|`RGB_MATRIX_MAXIMUM_BRIGHTNESS` / 5
|Brightness value RGB is dimmed to when starting to idle. +
When breathing used as the lower bound of the brightness value.
|RGB_IDLE_MAXIMUM_BRIGHTNESS
|`RGB_MATRIX_MAXIMUM_BRIGHTNESS` * 2/5
|Upper bound of brightness value of the RGB light while breathing.
|===

+ 16
- 0
users/vitoni/readme.adoc View File

@ -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.

+ 236
- 0
users/vitoni/rgb_matrix_effects.c View File

@ -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

+ 174
- 0
users/vitoni/rgb_matrix_effects.h View File

@ -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

+ 4
- 0
users/vitoni/rules.mk View File

@ -0,0 +1,4 @@
SRC += \
vitoni.c \
utils.c \
rgb_matrix_effects.c

+ 129
- 0
users/vitoni/utils.c View File

@ -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;
}

+ 80
- 0
users/vitoni/utils.h View File

@ -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);

+ 131
- 0
users/vitoni/vitoni.c View File

@ -0,0 +1,131 @@
// Copyright 2021 Victor Toni (@vitoni)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "vitoni.h"
#include <rgb_matrix.h>
#include <lib/lib8tion/lib8tion.h>
#include "rgb_matrix_effects.h"
#include "utils.h"
#if defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
static uint8_t state;
// flag used to indicate that offset calculation is needed to adjust the timer,
// so that it matches the index used for sine calculation
static bool calc_offset;
void matrix_scan_user_rgb(void) {
#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
const uint8_t time = rgb_time_2_scale();
#endif
static uint8_t time_offset;
const uint32_t inactivity_millis = last_input_activity_elapsed();
#if defined(RGB_IDLE_TIMEOUT)
if (IDLE != state && RGB_IDLE_TIMEOUT <= inactivity_millis) {
update_value(&state, IDLE_FADE_OUT, &calc_offset);
}
#endif
#if defined(RGB_DISABLE_WITH_FADE_OUT)
const uint32_t fade_out_duration = scale_2_rgb_time(128);
const uint32_t start_fade_out_after_millis = (RGB_DISABLE_TIMEOUT) > fade_out_duration
? (RGB_DISABLE_TIMEOUT) - fade_out_duration
: 0;
if (start_fade_out_after_millis <= inactivity_millis) {
update_value(&state, FADE_OUT, &calc_offset);
}
#elif defined(RGB_DISABLE_TIMEOUT)
// having to set brightness "manually" to black as starting point for fade in
// for the time when returning from suspended state
if (RGB_DISABLE_TIMEOUT <= inactivity_millis + 15) {
rgb_matrix_config.hsv.v = 0;
state = SUSPENDED;
}
#endif
switch(state) {
#if defined(RGB_IDLE_TIMEOUT)
case IDLE_FADE_OUT:
if (calc_offset) {
time_offset = calc_fade_out_offset(time);
// resetting flag for subsequent calls
calc_offset = false;
}
if (idle_fade_out(time + time_offset)) {
update_value(&state, IDLE, &calc_offset);
}
break;
case IDLE:
#if defined(RGB_IDLE_BREATHE)
if (calc_offset) {
// no need to calculate time_offset since we are aligned already due to IDLE_FADE_OUT
// resetting flag for subsequent calls
calc_offset = false;
}
idle_breathe(time + time_offset);
#endif
break;
#endif
#if defined(RGB_DISABLE_WITH_FADE_OUT)
case FADE_OUT:
if (calc_offset) {
time_offset = calc_fade_out_offset(time);
// resetting flag for subsequent calls
calc_offset = false;
}
if (fade_out(time + time_offset)) {
update_value(&state, SUSPENDED, &calc_offset);
}
break;
#endif
#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
case FADE_IN:
{
// since we want to be active, fade in should be faster than e.g. fading out
const uint8_t fade_in_time = rgb_time_2_scale_w_factor(4);
if (calc_offset) {
time_offset = calc_fade_in_offset(fade_in_time);
// resetting flag for subsequent calls
calc_offset = false;
}
if (fade_in(fade_in_time + time_offset)) {
update_value(&state, REGULAR, &calc_offset);
}
}
break;
#endif
default:
break;
}
}
#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
bool process_record_user_rgb(const uint16_t keycode, const keyrecord_t *record) {
// if we are in a non regular state we might have faded out (eventually partially)
// so we restore brightness (to max as we don't keep track of manually changed brightness)
// if (REGULAR != state && FADE_IN != state) {
if (FADE_IN != state && REGULAR != state) {
update_value(&state, FADE_IN, &calc_offset);
}
return true; // Process all other keycodes normally
}
void suspend_wakeup_init_user(void) {
if (FADE_IN != state) {
// setting brightness to black as starting point for fade in
rgb_matrix_config.hsv.v = 0;
update_value(&state, FADE_IN, &calc_offset);
}
}
#endif // defined(RGB_FADE_IN)
#endif // defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT)

+ 30
- 0
users/vitoni/vitoni.h View File

@ -0,0 +1,30 @@
// Copyright 2021 Victor Toni (@vitoni)
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <quantum/action.h>
#include "rgb_matrix_effects.h"
/**
* @brief Executes periodic tasks, eg. fading or checking for upcoming supend.
* @details Function declaration as weak as the implementation might "disappear" depending on the RGB settings used.
* The weak declaration avoids having to change `keymap.c`.
*/
__attribute__((weak))
void matrix_scan_user_rgb(void);
/**
* @brief Executes tasks based on user activity, eg. fading in.
* @details Function declaration as weak as the implementation might "disappear" depending on the RGB settings used.
* The weak declaration avoids having to change `keymap.c`.
* @param[in] keycode
* @param[in] record
* @return `false` if further processing should be stopped, `true` otherwise
*/
__attribute__((weak))
bool process_record_user_rgb(const uint16_t keycode, const keyrecord_t *record);

Loading…
Cancel
Save