RGB Matrix Overhaul (#5372)

* RGB Matrix overhaul
Breakout of animations to separate files
Integration of optimized int based math lib
Overhaul of rgb_matrix.c and animations for performance

* Updating effect function api for future extensions

* Combined the keypresses || keyreleases define checks into a single define so I stop forgetting it where necessary

* Moving define RGB_MATRIX_KEYREACTIVE_ENABLED earlier in the include chain
pull/5613/head
XScorpion2 6 years ago committed by Drashna Jaelre
parent c846f82fa7
commit fd770232d9
No known key found for this signature in database
GPG Key ID: 4C4221222CD5F9F0

@ -109,7 +109,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DRIVER_1_LED_TOTAL 24 #define DRIVER_1_LED_TOTAL 24
#define DRIVER_2_LED_TOTAL 24 #define DRIVER_2_LED_TOTAL 24
#define DRIVER_LED_TOTAL DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL #define DRIVER_LED_TOTAL DRIVER_1_LED_TOTAL + DRIVER_2_LED_TOTAL
#define RGB_MATRIX_SKIP_FRAMES 10
// #define RGBLIGHT_COLOR_LAYER_0 0x00, 0x00, 0xFF // #define RGBLIGHT_COLOR_LAYER_0 0x00, 0x00, 0xFF
/* #define RGBLIGHT_COLOR_LAYER_1 0x00, 0x00, 0xFF */ /* #define RGBLIGHT_COLOR_LAYER_1 0x00, 0x00, 0xFF */

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 FastLED
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,242 @@
#define FASTLED_INTERNAL
#include <stdint.h>
#define RAND16_SEED 1337
uint16_t rand16seed = RAND16_SEED;
// memset8, memcpy8, memmove8:
// optimized avr replacements for the standard "C" library
// routines memset, memcpy, and memmove.
//
// There are two techniques that make these routines
// faster than the standard avr-libc routines.
// First, the loops are unrolled 2X, meaning that
// the average loop overhead is cut in half.
// And second, the compare-and-branch at the bottom
// of each loop decrements the low byte of the
// counter, and if the carry is clear, it branches
// back up immediately. Only if the low byte math
// causes carry do we bother to decrement the high
// byte and check that result for carry as well.
// Results for a 100-byte buffer are 20-40% faster
// than standard avr-libc, at a cost of a few extra
// bytes of code.
#if defined(__AVR__)
//__attribute__ ((noinline))
void * memset8 ( void * ptr, uint8_t val, uint16_t num )
{
asm volatile(
" movw r26, %[ptr] \n\t"
" sbrs %A[num], 0 \n\t"
" rjmp Lseteven_%= \n\t"
" rjmp Lsetodd_%= \n\t"
"Lsetloop_%=: \n\t"
" st X+, %[val] \n\t"
"Lsetodd_%=: \n\t"
" st X+, %[val] \n\t"
"Lseteven_%=: \n\t"
" subi %A[num], 2 \n\t"
" brcc Lsetloop_%= \n\t"
" sbci %B[num], 0 \n\t"
" brcc Lsetloop_%= \n\t"
: [num] "+r" (num)
: [ptr] "r" (ptr),
[val] "r" (val)
: "memory"
);
return ptr;
}
//__attribute__ ((noinline))
void * memcpy8 ( void * dst, const void* src, uint16_t num )
{
asm volatile(
" movw r30, %[src] \n\t"
" movw r26, %[dst] \n\t"
" sbrs %A[num], 0 \n\t"
" rjmp Lcpyeven_%= \n\t"
" rjmp Lcpyodd_%= \n\t"
"Lcpyloop_%=: \n\t"
" ld __tmp_reg__, Z+ \n\t"
" st X+, __tmp_reg__ \n\t"
"Lcpyodd_%=: \n\t"
" ld __tmp_reg__, Z+ \n\t"
" st X+, __tmp_reg__ \n\t"
"Lcpyeven_%=: \n\t"
" subi %A[num], 2 \n\t"
" brcc Lcpyloop_%= \n\t"
" sbci %B[num], 0 \n\t"
" brcc Lcpyloop_%= \n\t"
: [num] "+r" (num)
: [src] "r" (src),
[dst] "r" (dst)
: "memory"
);
return dst;
}
//__attribute__ ((noinline))
void * memmove8 ( void * dst, const void* src, uint16_t num )
{
if( src > dst) {
// if src > dst then we can use the forward-stepping memcpy8
return memcpy8( dst, src, num);
} else {
// if src < dst then we have to step backward:
dst = (char*)dst + num;
src = (char*)src + num;
asm volatile(
" movw r30, %[src] \n\t"
" movw r26, %[dst] \n\t"
" sbrs %A[num], 0 \n\t"
" rjmp Lmoveven_%= \n\t"
" rjmp Lmovodd_%= \n\t"
"Lmovloop_%=: \n\t"
" ld __tmp_reg__, -Z \n\t"
" st -X, __tmp_reg__ \n\t"
"Lmovodd_%=: \n\t"
" ld __tmp_reg__, -Z \n\t"
" st -X, __tmp_reg__ \n\t"
"Lmoveven_%=: \n\t"
" subi %A[num], 2 \n\t"
" brcc Lmovloop_%= \n\t"
" sbci %B[num], 0 \n\t"
" brcc Lmovloop_%= \n\t"
: [num] "+r" (num)
: [src] "r" (src),
[dst] "r" (dst)
: "memory"
);
return dst;
}
}
#endif /* AVR */
#if 0
// TEST / VERIFICATION CODE ONLY BELOW THIS POINT
#include <Arduino.h>
#include "lib8tion.h"
void test1abs( int8_t i)
{
Serial.print("abs("); Serial.print(i); Serial.print(") = ");
int8_t j = abs8(i);
Serial.print(j); Serial.println(" ");
}
void testabs()
{
delay(5000);
for( int8_t q = -128; q != 127; q++) {
test1abs(q);
}
for(;;){};
}
void testmul8()
{
delay(5000);
byte r, c;
Serial.println("mul8:");
for( r = 0; r <= 20; r += 1) {
Serial.print(r); Serial.print(" : ");
for( c = 0; c <= 20; c += 1) {
byte t;
t = mul8( r, c);
Serial.print(t); Serial.print(' ');
}
Serial.println(' ');
}
Serial.println("done.");
for(;;){};
}
void testscale8()
{
delay(5000);
byte r, c;
Serial.println("scale8:");
for( r = 0; r <= 240; r += 10) {
Serial.print(r); Serial.print(" : ");
for( c = 0; c <= 240; c += 10) {
byte t;
t = scale8( r, c);
Serial.print(t); Serial.print(' ');
}
Serial.println(' ');
}
Serial.println(' ');
Serial.println("scale8_video:");
for( r = 0; r <= 100; r += 4) {
Serial.print(r); Serial.print(" : ");
for( c = 0; c <= 100; c += 4) {
byte t;
t = scale8_video( r, c);
Serial.print(t); Serial.print(' ');
}
Serial.println(' ');
}
Serial.println("done.");
for(;;){};
}
void testqadd8()
{
delay(5000);
byte r, c;
for( r = 0; r <= 240; r += 10) {
Serial.print(r); Serial.print(" : ");
for( c = 0; c <= 240; c += 10) {
byte t;
t = qadd8( r, c);
Serial.print(t); Serial.print(' ');
}
Serial.println(' ');
}
Serial.println("done.");
for(;;){};
}
void testnscale8x3()
{
delay(5000);
byte r, g, b, sc;
for( byte z = 0; z < 10; z++) {
r = random8(); g = random8(); b = random8(); sc = random8();
Serial.print("nscale8x3_video( ");
Serial.print(r); Serial.print(", ");
Serial.print(g); Serial.print(", ");
Serial.print(b); Serial.print(", ");
Serial.print(sc); Serial.print(") = [ ");
nscale8x3_video( r, g, b, sc);
Serial.print(r); Serial.print(", ");
Serial.print(g); Serial.print(", ");
Serial.print(b); Serial.print("]");
Serial.println(' ');
}
Serial.println("done.");
for(;;){};
}
#endif

@ -0,0 +1,934 @@
#ifndef __INC_LIB8TION_H
#define __INC_LIB8TION_H
/*
Fast, efficient 8-bit math functions specifically
designed for high-performance LED programming.
Because of the AVR(Arduino) and ARM assembly language
implementations provided, using these functions often
results in smaller and faster code than the equivalent
program using plain "C" arithmetic and logic.
Included are:
- Saturating unsigned 8-bit add and subtract.
Instead of wrapping around if an overflow occurs,
these routines just 'clamp' the output at a maxumum
of 255, or a minimum of 0. Useful for adding pixel
values. E.g., qadd8( 200, 100) = 255.
qadd8( i, j) == MIN( (i + j), 0xFF )
qsub8( i, j) == MAX( (i - j), 0 )
- Saturating signed 8-bit ("7-bit") add.
qadd7( i, j) == MIN( (i + j), 0x7F)
- Scaling (down) of unsigned 8- and 16- bit values.
Scaledown value is specified in 1/256ths.
scale8( i, sc) == (i * sc) / 256
scale16by8( i, sc) == (i * sc) / 256
Example: scaling a 0-255 value down into a
range from 0-99:
downscaled = scale8( originalnumber, 100);
A special version of scale8 is provided for scaling
LED brightness values, to make sure that they don't
accidentally scale down to total black at low
dimming levels, since that would look wrong:
scale8_video( i, sc) = ((i * sc) / 256) +? 1
Example: reducing an LED brightness by a
dimming factor:
new_bright = scale8_video( orig_bright, dimming);
- Fast 8- and 16- bit unsigned random numbers.
Significantly faster than Arduino random(), but
also somewhat less random. You can add entropy.
random8() == random from 0..255
random8( n) == random from 0..(N-1)
random8( n, m) == random from N..(M-1)
random16() == random from 0..65535
random16( n) == random from 0..(N-1)
random16( n, m) == random from N..(M-1)
random16_set_seed( k) == seed = k
random16_add_entropy( k) == seed += k
- Absolute value of a signed 8-bit value.
abs8( i) == abs( i)
- 8-bit math operations which return 8-bit values.
These are provided mostly for completeness,
not particularly for performance.
mul8( i, j) == (i * j) & 0xFF
add8( i, j) == (i + j) & 0xFF
sub8( i, j) == (i - j) & 0xFF
- Fast 16-bit approximations of sin and cos.
Input angle is a uint16_t from 0-65535.
Output is a signed int16_t from -32767 to 32767.
sin16( x) == sin( (x/32768.0) * pi) * 32767
cos16( x) == cos( (x/32768.0) * pi) * 32767
Accurate to more than 99% in all cases.
- Fast 8-bit approximations of sin and cos.
Input angle is a uint8_t from 0-255.
Output is an UNsigned uint8_t from 0 to 255.
sin8( x) == (sin( (x/128.0) * pi) * 128) + 128
cos8( x) == (cos( (x/128.0) * pi) * 128) + 128
Accurate to within about 2%.
- Fast 8-bit "easing in/out" function.
ease8InOutCubic(x) == 3(x^i) - 2(x^3)
ease8InOutApprox(x) ==
faster, rougher, approximation of cubic easing
ease8InOutQuad(x) == quadratic (vs cubic) easing
- Cubic, Quadratic, and Triangle wave functions.
Input is a uint8_t representing phase withing the wave,
similar to how sin8 takes an angle 'theta'.
Output is a uint8_t representing the amplitude of
the wave at that point.
cubicwave8( x)
quadwave8( x)
triwave8( x)
- Square root for 16-bit integers. About three times
faster and five times smaller than Arduino's built-in
generic 32-bit sqrt routine.
sqrt16( uint16_t x ) == sqrt( x)
- Dimming and brightening functions for 8-bit
light values.
dim8_video( x) == scale8_video( x, x)
dim8_raw( x) == scale8( x, x)
dim8_lin( x) == (x<128) ? ((x+1)/2) : scale8(x,x)
brighten8_video( x) == 255 - dim8_video( 255 - x)
brighten8_raw( x) == 255 - dim8_raw( 255 - x)
brighten8_lin( x) == 255 - dim8_lin( 255 - x)
The dimming functions in particular are suitable
for making LED light output appear more 'linear'.
- Linear interpolation between two values, with the
fraction between them expressed as an 8- or 16-bit
fixed point fraction (fract8 or fract16).
lerp8by8( fromU8, toU8, fract8 )
lerp16by8( fromU16, toU16, fract8 )
lerp15by8( fromS16, toS16, fract8 )
== from + (( to - from ) * fract8) / 256)
lerp16by16( fromU16, toU16, fract16 )
== from + (( to - from ) * fract16) / 65536)
map8( in, rangeStart, rangeEnd)
== map( in, 0, 255, rangeStart, rangeEnd);
- Optimized memmove, memcpy, and memset, that are
faster than standard avr-libc 1.8.
memmove8( dest, src, bytecount)
memcpy8( dest, src, bytecount)
memset8( buf, value, bytecount)
- Beat generators which return sine or sawtooth
waves in a specified number of Beats Per Minute.
Sine wave beat generators can specify a low and
high range for the output. Sawtooth wave beat
generators always range 0-255 or 0-65535.
beatsin8( BPM, low8, high8)
= (sine(beatphase) * (high8-low8)) + low8
beatsin16( BPM, low16, high16)
= (sine(beatphase) * (high16-low16)) + low16
beatsin88( BPM88, low16, high16)
= (sine(beatphase) * (high16-low16)) + low16
beat8( BPM) = 8-bit repeating sawtooth wave
beat16( BPM) = 16-bit repeating sawtooth wave
beat88( BPM88) = 16-bit repeating sawtooth wave
BPM is beats per minute in either simple form
e.g. 120, or Q8.8 fixed-point form.
BPM88 is beats per minute in ONLY Q8.8 fixed-point
form.
Lib8tion is pronounced like 'libation': lie-BAY-shun
*/
#include <stdint.h>
#define LIB8STATIC __attribute__ ((unused)) static inline
#define LIB8STATIC_ALWAYS_INLINE __attribute__ ((always_inline)) static inline
#if !defined(__AVR__)
#include <string.h>
// for memmove, memcpy, and memset if not defined here
#endif
#if defined(__arm__)
#if defined(FASTLED_TEENSY3)
// Can use Cortex M4 DSP instructions
#define QADD8_C 0
#define QADD7_C 0
#define QADD8_ARM_DSP_ASM 1
#define QADD7_ARM_DSP_ASM 1
#else
// Generic ARM
#define QADD8_C 1
#define QADD7_C 1
#endif
#define QSUB8_C 1
#define SCALE8_C 1
#define SCALE16BY8_C 1
#define SCALE16_C 1
#define ABS8_C 1
#define MUL8_C 1
#define QMUL8_C 1
#define ADD8_C 1
#define SUB8_C 1
#define EASE8_C 1
#define AVG8_C 1
#define AVG7_C 1
#define AVG16_C 1
#define AVG15_C 1
#define BLEND8_C 1
#elif defined(__AVR__)
// AVR ATmega and friends Arduino
#define QADD8_C 0
#define QADD7_C 0
#define QSUB8_C 0
#define ABS8_C 0
#define ADD8_C 0
#define SUB8_C 0
#define AVG8_C 0
#define AVG7_C 0
#define AVG16_C 0
#define AVG15_C 0
#define QADD8_AVRASM 1
#define QADD7_AVRASM 1
#define QSUB8_AVRASM 1
#define ABS8_AVRASM 1
#define ADD8_AVRASM 1
#define SUB8_AVRASM 1
#define AVG8_AVRASM 1
#define AVG7_AVRASM 1
#define AVG16_AVRASM 1
#define AVG15_AVRASM 1
// Note: these require hardware MUL instruction
// -- sorry, ATtiny!
#if !defined(LIB8_ATTINY)
#define SCALE8_C 0
#define SCALE16BY8_C 0
#define SCALE16_C 0
#define MUL8_C 0
#define QMUL8_C 0
#define EASE8_C 0
#define BLEND8_C 0
#define SCALE8_AVRASM 1
#define SCALE16BY8_AVRASM 1
#define SCALE16_AVRASM 1
#define MUL8_AVRASM 1
#define QMUL8_AVRASM 1
#define EASE8_AVRASM 1
#define CLEANUP_R1_AVRASM 1
#define BLEND8_AVRASM 1
#else
// On ATtiny, we just use C implementations
#define SCALE8_C 1
#define SCALE16BY8_C 1
#define SCALE16_C 1
#define MUL8_C 1
#define QMUL8_C 1
#define EASE8_C 1
#define BLEND8_C 1
#define SCALE8_AVRASM 0
#define SCALE16BY8_AVRASM 0
#define SCALE16_AVRASM 0
#define MUL8_AVRASM 0
#define QMUL8_AVRASM 0
#define EASE8_AVRASM 0
#define BLEND8_AVRASM 0
#endif
#else
// unspecified architecture, so
// no ASM, everything in C
#define QADD8_C 1
#define QADD7_C 1
#define QSUB8_C 1
#define SCALE8_C 1
#define SCALE16BY8_C 1
#define SCALE16_C 1
#define ABS8_C 1
#define MUL8_C 1
#define QMUL8_C 1
#define ADD8_C 1
#define SUB8_C 1
#define EASE8_C 1
#define AVG8_C 1
#define AVG7_C 1
#define AVG16_C 1
#define AVG15_C 1
#define BLEND8_C 1
#endif
///@defgroup lib8tion Fast math functions
///A variety of functions for working with numbers.
///@{
///////////////////////////////////////////////////////////////////////
//
// typdefs for fixed-point fractional types.
//
// sfract7 should be interpreted as signed 128ths.
// fract8 should be interpreted as unsigned 256ths.
// sfract15 should be interpreted as signed 32768ths.
// fract16 should be interpreted as unsigned 65536ths.
//
// Example: if a fract8 has the value "64", that should be interpreted
// as 64/256ths, or one-quarter.
//
//
// fract8 range is 0 to 0.99609375
// in steps of 0.00390625
//
// sfract7 range is -0.9921875 to 0.9921875
// in steps of 0.0078125
//
// fract16 range is 0 to 0.99998474121
// in steps of 0.00001525878
//
// sfract15 range is -0.99996948242 to 0.99996948242
// in steps of 0.00003051757
//
/// ANSI unsigned short _Fract. range is 0 to 0.99609375
/// in steps of 0.00390625
typedef uint8_t fract8; ///< ANSI: unsigned short _Fract
/// ANSI: signed short _Fract. range is -0.9921875 to 0.9921875
/// in steps of 0.0078125
typedef int8_t sfract7; ///< ANSI: signed short _Fract
/// ANSI: unsigned _Fract. range is 0 to 0.99998474121
/// in steps of 0.00001525878
typedef uint16_t fract16; ///< ANSI: unsigned _Fract
/// ANSI: signed _Fract. range is -0.99996948242 to 0.99996948242
/// in steps of 0.00003051757
typedef int16_t sfract15; ///< ANSI: signed _Fract
// accumXY types should be interpreted as X bits of integer,
// and Y bits of fraction.
// E.g., accum88 has 8 bits of int, 8 bits of fraction
typedef uint16_t accum88; ///< ANSI: unsigned short _Accum. 8 bits int, 8 bits fraction
typedef int16_t saccum78; ///< ANSI: signed short _Accum. 7 bits int, 8 bits fraction
typedef uint32_t accum1616;///< ANSI: signed _Accum. 16 bits int, 16 bits fraction
typedef int32_t saccum1516;///< ANSI: signed _Accum. 15 bits int, 16 bits fraction
typedef uint16_t accum124; ///< no direct ANSI counterpart. 12 bits int, 4 bits fraction
typedef int32_t saccum114;///< no direct ANSI counterpart. 1 bit int, 14 bits fraction
#include "math8.h"
#include "scale8.h"
#include "random8.h"
#include "trig8.h"
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
//
// float-to-fixed and fixed-to-float conversions
//
// Note that anything involving a 'float' on AVR will be slower.
/// sfract15ToFloat: conversion from sfract15 fixed point to
/// IEEE754 32-bit float.
LIB8STATIC float sfract15ToFloat( sfract15 y)
{
return y / 32768.0;
}
/// conversion from IEEE754 float in the range (-1,1)
/// to 16-bit fixed point. Note that the extremes of
/// one and negative one are NOT representable. The
/// representable range is basically
LIB8STATIC sfract15 floatToSfract15( float f)
{
return f * 32768.0;
}
///////////////////////////////////////////////////////////////////////
//
// memmove8, memcpy8, and memset8:
// alternatives to memmove, memcpy, and memset that are
// faster on AVR than standard avr-libc 1.8
#if defined(__AVR__)
void * memmove8( void * dst, const void * src, uint16_t num );
void * memcpy8 ( void * dst, const void * src, uint16_t num ) __attribute__ ((noinline));
void * memset8 ( void * ptr, uint8_t value, uint16_t num ) __attribute__ ((noinline)) ;
#else
// on non-AVR platforms, these names just call standard libc.
#define memmove8 memmove
#define memcpy8 memcpy
#define memset8 memset
#endif
///////////////////////////////////////////////////////////////////////
//
// linear interpolation, such as could be used for Perlin noise, etc.
//
// A note on the structure of the lerp functions:
// The cases for b>a and b<=a are handled separately for
// speed: without knowing the relative order of a and b,
// the value (a-b) might be overflow the width of a or b,
// and have to be promoted to a wider, slower type.
// To avoid that, we separate the two cases, and are able
// to do all the math in the same width as the arguments,
// which is much faster and smaller on AVR.
/// linear interpolation between two unsigned 8-bit values,
/// with 8-bit fraction
LIB8STATIC uint8_t lerp8by8( uint8_t a, uint8_t b, fract8 frac)
{
uint8_t result;
if( b > a) {
uint8_t delta = b - a;
uint8_t scaled = scale8( delta, frac);
result = a + scaled;
} else {
uint8_t delta = a - b;
uint8_t scaled = scale8( delta, frac);
result = a - scaled;
}
return result;
}
/// linear interpolation between two unsigned 16-bit values,
/// with 16-bit fraction
LIB8STATIC uint16_t lerp16by16( uint16_t a, uint16_t b, fract16 frac)
{
uint16_t result;
if( b > a ) {
uint16_t delta = b - a;
uint16_t scaled = scale16(delta, frac);
result = a + scaled;
} else {
uint16_t delta = a - b;
uint16_t scaled = scale16( delta, frac);
result = a - scaled;
}
return result;
}
/// linear interpolation between two unsigned 16-bit values,
/// with 8-bit fraction
LIB8STATIC uint16_t lerp16by8( uint16_t a, uint16_t b, fract8 frac)
{
uint16_t result;
if( b > a) {
uint16_t delta = b - a;
uint16_t scaled = scale16by8( delta, frac);
result = a + scaled;
} else {
uint16_t delta = a - b;
uint16_t scaled = scale16by8( delta, frac);
result = a - scaled;
}
return result;
}
/// linear interpolation between two signed 15-bit values,
/// with 8-bit fraction
LIB8STATIC int16_t lerp15by8( int16_t a, int16_t b, fract8 frac)
{
int16_t result;
if( b > a) {
uint16_t delta = b - a;
uint16_t scaled = scale16by8( delta, frac);
result = a + scaled;
} else {
uint16_t delta = a - b;
uint16_t scaled = scale16by8( delta, frac);
result = a - scaled;
}
return result;
}
/// linear interpolation between two signed 15-bit values,
/// with 8-bit fraction
LIB8STATIC int16_t lerp15by16( int16_t a, int16_t b, fract16 frac)
{
int16_t result;
if( b > a) {
uint16_t delta = b - a;
uint16_t scaled = scale16( delta, frac);
result = a + scaled;
} else {
uint16_t delta = a - b;
uint16_t scaled = scale16( delta, frac);
result = a - scaled;
}
return result;
}
/// map8: map from one full-range 8-bit value into a narrower
/// range of 8-bit values, possibly a range of hues.
///
/// E.g. map myValue into a hue in the range blue..purple..pink..red
/// hue = map8( myValue, HUE_BLUE, HUE_RED);
///
/// Combines nicely with the waveform functions (like sin8, etc)
/// to produce continuous hue gradients back and forth:
///
/// hue = map8( sin8( myValue), HUE_BLUE, HUE_RED);
///
/// Mathematically simiar to lerp8by8, but arguments are more
/// like Arduino's "map"; this function is similar to
///
/// map( in, 0, 255, rangeStart, rangeEnd)
///
/// but faster and specifically designed for 8-bit values.
LIB8STATIC uint8_t map8( uint8_t in, uint8_t rangeStart, uint8_t rangeEnd)
{
uint8_t rangeWidth = rangeEnd - rangeStart;
uint8_t out = scale8( in, rangeWidth);
out += rangeStart;
return out;
}
///////////////////////////////////////////////////////////////////////
//
// easing functions; see http://easings.net
//
/// ease8InOutQuad: 8-bit quadratic ease-in / ease-out function
/// Takes around 13 cycles on AVR
#if EASE8_C == 1
LIB8STATIC uint8_t ease8InOutQuad( uint8_t i)
{
uint8_t j = i;
if( j & 0x80 ) {
j = 255 - j;
}
uint8_t jj = scale8( j, j);
uint8_t jj2 = jj << 1;
if( i & 0x80 ) {
jj2 = 255 - jj2;
}
return jj2;
}
#elif EASE8_AVRASM == 1
// This AVR asm version of ease8InOutQuad preserves one more
// low-bit of precision than the C version, and is also slightly
// smaller and faster.
LIB8STATIC uint8_t ease8InOutQuad(uint8_t val) {
uint8_t j=val;
asm volatile (
"sbrc %[val], 7 \n"
"com %[j] \n"
"mul %[j], %[j] \n"
"add r0, %[j] \n"
"ldi %[j], 0 \n"
"adc %[j], r1 \n"
"lsl r0 \n" // carry = high bit of low byte of mul product
"rol %[j] \n" // j = (j * 2) + carry // preserve add'l bit of precision
"sbrc %[val], 7 \n"
"com %[j] \n"
"clr __zero_reg__ \n"
: [j] "+&a" (j)
: [val] "a" (val)
: "r0", "r1"
);
return j;
}
#else
#error "No implementation for ease8InOutQuad available."
#endif
/// ease16InOutQuad: 16-bit quadratic ease-in / ease-out function
// C implementation at this point
LIB8STATIC uint16_t ease16InOutQuad( uint16_t i)
{
uint16_t j = i;
if( j & 0x8000 ) {
j = 65535 - j;
}
uint16_t jj = scale16( j, j);
uint16_t jj2 = jj << 1;
if( i & 0x8000 ) {
jj2 = 65535 - jj2;
}
return jj2;
}
/// ease8InOutCubic: 8-bit cubic ease-in / ease-out function
/// Takes around 18 cycles on AVR
LIB8STATIC fract8 ease8InOutCubic( fract8 i)
{
uint8_t ii = scale8_LEAVING_R1_DIRTY( i, i);
uint8_t iii = scale8_LEAVING_R1_DIRTY( ii, i);
uint16_t r1 = (3 * (uint16_t)(ii)) - ( 2 * (uint16_t)(iii));
/* the code generated for the above *'s automatically
cleans up R1, so there's no need to explicitily call
cleanup_R1(); */
uint8_t result = r1;
// if we got "256", return 255:
if( r1 & 0x100 ) {
result = 255;
}
return result;
}
/// ease8InOutApprox: fast, rough 8-bit ease-in/ease-out function
/// shaped approximately like 'ease8InOutCubic',
/// it's never off by more than a couple of percent
/// from the actual cubic S-curve, and it executes
/// more than twice as fast. Use when the cycles
/// are more important than visual smoothness.
/// Asm version takes around 7 cycles on AVR.
#if EASE8_C == 1
LIB8STATIC fract8 ease8InOutApprox( fract8 i)
{
if( i < 64) {
// start with slope 0.5
i /= 2;
} else if( i > (255 - 64)) {
// end with slope 0.5
i = 255 - i;
i /= 2;
i = 255 - i;
} else {
// in the middle, use slope 192/128 = 1.5
i -= 64;
i += (i / 2);
i += 32;
}
return i;
}
#elif EASE8_AVRASM == 1
LIB8STATIC uint8_t ease8InOutApprox( fract8 i)
{
// takes around 7 cycles on AVR
asm volatile (
" subi %[i], 64 \n\t"
" cpi %[i], 128 \n\t"
" brcc Lshift_%= \n\t"
// middle case
" mov __tmp_reg__, %[i] \n\t"
" lsr __tmp_reg__ \n\t"
" add %[i], __tmp_reg__ \n\t"
" subi %[i], 224 \n\t"
" rjmp Ldone_%= \n\t"
// start or end case
"Lshift_%=: \n\t"
" lsr %[i] \n\t"
" subi %[i], 96 \n\t"
"Ldone_%=: \n\t"
: [i] "+&a" (i)
:
: "r0", "r1"
);
return i;
}
#else
#error "No implementation for ease8 available."
#endif
/// triwave8: triangle (sawtooth) wave generator. Useful for
/// turning a one-byte ever-increasing value into a
/// one-byte value that oscillates up and down.
///
/// input output
/// 0..127 0..254 (positive slope)
/// 128..255 254..0 (negative slope)
///
/// On AVR this function takes just three cycles.
///
LIB8STATIC uint8_t triwave8(uint8_t in)
{
if( in & 0x80) {
in = 255 - in;
}
uint8_t out = in << 1;
return out;
}
// quadwave8 and cubicwave8: S-shaped wave generators (like 'sine').
// Useful for turning a one-byte 'counter' value into a
// one-byte oscillating value that moves smoothly up and down,
// with an 'acceleration' and 'deceleration' curve.
//
// These are even faster than 'sin8', and have
// slightly different curve shapes.
//
/// quadwave8: quadratic waveform generator. Spends just a little more
/// time at the limits than 'sine' does.
LIB8STATIC uint8_t quadwave8(uint8_t in)
{
return ease8InOutQuad( triwave8( in));
}
/// cubicwave8: cubic waveform generator. Spends visibly more time
/// at the limits than 'sine' does.
LIB8STATIC uint8_t cubicwave8(uint8_t in)
{
return ease8InOutCubic( triwave8( in));
}
/// squarewave8: square wave generator. Useful for
/// turning a one-byte ever-increasing value
/// into a one-byte value that is either 0 or 255.
/// The width of the output 'pulse' is
/// determined by the pulsewidth argument:
///
///~~~
/// If pulsewidth is 255, output is always 255.
/// If pulsewidth < 255, then
/// if input < pulsewidth then output is 255
/// if input >= pulsewidth then output is 0
///~~~
///
/// the output looking like:
///
///~~~
/// 255 +--pulsewidth--+
/// . | |
/// 0 0 +--------(256-pulsewidth)--------
///~~~
///
/// @param in
/// @param pulsewidth
/// @returns square wave output
LIB8STATIC uint8_t squarewave8( uint8_t in, uint8_t pulsewidth)
{
if( in < pulsewidth || (pulsewidth == 255)) {
return 255;
} else {
return 0;
}
}
// Beat generators - These functions produce waves at a given
// number of 'beats per minute'. Internally, they use
// the Arduino function 'millis' to track elapsed time.
// Accuracy is a bit better than one part in a thousand.
//
// beat8( BPM ) returns an 8-bit value that cycles 'BPM' times
// per minute, rising from 0 to 255, resetting to zero,
// rising up again, etc.. The output of this function
// is suitable for feeding directly into sin8, and cos8,
// triwave8, quadwave8, and cubicwave8.
// beat16( BPM ) returns a 16-bit value that cycles 'BPM' times
// per minute, rising from 0 to 65535, resetting to zero,
// rising up again, etc. The output of this function is
// suitable for feeding directly into sin16 and cos16.
// beat88( BPM88) is the same as beat16, except that the BPM88 argument
// MUST be in Q8.8 fixed point format, e.g. 120BPM must
// be specified as 120*256 = 30720.
// beatsin8( BPM, uint8_t low, uint8_t high) returns an 8-bit value that
// rises and falls in a sine wave, 'BPM' times per minute,
// between the values of 'low' and 'high'.
// beatsin16( BPM, uint16_t low, uint16_t high) returns a 16-bit value
// that rises and falls in a sine wave, 'BPM' times per
// minute, between the values of 'low' and 'high'.
// beatsin88( BPM88, ...) is the same as beatsin16, except that the
// BPM88 argument MUST be in Q8.8 fixed point format,
// e.g. 120BPM must be specified as 120*256 = 30720.
//
// BPM can be supplied two ways. The simpler way of specifying BPM is as
// a simple 8-bit integer from 1-255, (e.g., "120").
// The more sophisticated way of specifying BPM allows for fractional
// "Q8.8" fixed point number (an 'accum88') with an 8-bit integer part and
// an 8-bit fractional part. The easiest way to construct this is to multiply
// a floating point BPM value (e.g. 120.3) by 256, (e.g. resulting in 30796
// in this case), and pass that as the 16-bit BPM argument.
// "BPM88" MUST always be specified in Q8.8 format.
//
// Originally designed to make an entire animation project pulse with brightness.
// For that effect, add this line just above your existing call to "FastLED.show()":
//
// uint8_t bright = beatsin8( 60 /*BPM*/, 192 /*dimmest*/, 255 /*brightest*/ ));
// FastLED.setBrightness( bright );
// FastLED.show();
//
// The entire animation will now pulse between brightness 192 and 255 once per second.
// The beat generators need access to a millisecond counter.
// On Arduino, this is "millis()". On other platforms, you'll
// need to provide a function with this signature:
// uint32_t get_millisecond_timer();
// that provides similar functionality.
// You can also force use of the get_millisecond_timer function
// by #defining USE_GET_MILLISECOND_TIMER.
#if (defined(ARDUINO) || defined(SPARK) || defined(FASTLED_HAS_MILLIS)) && !defined(USE_GET_MILLISECOND_TIMER)
// Forward declaration of Arduino function 'millis'.
//uint32_t millis();
#define GET_MILLIS millis
#else
uint32_t get_millisecond_timer(void);
#define GET_MILLIS get_millisecond_timer
#endif
// beat16 generates a 16-bit 'sawtooth' wave at a given BPM,
/// with BPM specified in Q8.8 fixed-point format; e.g.
/// for this function, 120 BPM MUST BE specified as
/// 120*256 = 30720.
/// If you just want to specify "120", use beat16 or beat8.
LIB8STATIC uint16_t beat88( accum88 beats_per_minute_88, uint32_t timebase)
{
// BPM is 'beats per minute', or 'beats per 60000ms'.
// To avoid using the (slower) division operator, we
// want to convert 'beats per 60000ms' to 'beats per 65536ms',
// and then use a simple, fast bit-shift to divide by 65536.
//
// The ratio 65536:60000 is 279.620266667:256; we'll call it 280:256.
// The conversion is accurate to about 0.05%, more or less,
// e.g. if you ask for "120 BPM", you'll get about "119.93".
return (((GET_MILLIS()) - timebase) * beats_per_minute_88 * 280) >> 16;
}
/// beat16 generates a 16-bit 'sawtooth' wave at a given BPM
LIB8STATIC uint16_t beat16( accum88 beats_per_minute, uint32_t timebase)
{
// Convert simple 8-bit BPM's to full Q8.8 accum88's if needed
if( beats_per_minute < 256) beats_per_minute <<= 8;
return beat88(beats_per_minute, timebase);
}
/// beat8 generates an 8-bit 'sawtooth' wave at a given BPM
LIB8STATIC uint8_t beat8( accum88 beats_per_minute, uint32_t timebase)
{
return beat16( beats_per_minute, timebase) >> 8;
}
/// beatsin88 generates a 16-bit sine wave at a given BPM,
/// that oscillates within a given range.
/// For this function, BPM MUST BE SPECIFIED as
/// a Q8.8 fixed-point value; e.g. 120BPM must be
/// specified as 120*256 = 30720.
/// If you just want to specify "120", use beatsin16 or beatsin8.
LIB8STATIC uint16_t beatsin88( accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat88( beats_per_minute_88, timebase);
uint16_t beatsin = (sin16( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
/// beatsin16 generates a 16-bit sine wave at a given BPM,
/// that oscillates within a given range.
LIB8STATIC uint16_t beatsin16(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
{
uint16_t beat = beat16( beats_per_minute, timebase);
uint16_t beatsin = (sin16( beat + phase_offset) + 32768);
uint16_t rangewidth = highest - lowest;
uint16_t scaledbeat = scale16( beatsin, rangewidth);
uint16_t result = lowest + scaledbeat;
return result;
}
/// beatsin8 generates an 8-bit sine wave at a given BPM,
/// that oscillates within a given range.
LIB8STATIC uint8_t beatsin8( accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
{
uint8_t beat = beat8( beats_per_minute, timebase);
uint8_t beatsin = sin8( beat + phase_offset);
uint8_t rangewidth = highest - lowest;
uint8_t scaledbeat = scale8( beatsin, rangewidth);
uint8_t result = lowest + scaledbeat;
return result;
}
/// Return the current seconds since boot in a 16-bit value. Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint16_t seconds16(void)
{
uint32_t ms = GET_MILLIS();
uint16_t s16;
s16 = ms / 1000;
return s16;
}
/// Return the current minutes since boot in a 16-bit value. Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint16_t minutes16(void)
{
uint32_t ms = GET_MILLIS();
uint16_t m16;
m16 = (ms / (60000L)) & 0xFFFF;
return m16;
}
/// Return the current hours since boot in an 8-bit value. Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint8_t hours8(void)
{
uint32_t ms = GET_MILLIS();
uint8_t h8;
h8 = (ms / (3600000L)) & 0xFF;
return h8;
}
///@}
#endif

@ -0,0 +1,552 @@
#ifndef __INC_LIB8TION_MATH_H
#define __INC_LIB8TION_MATH_H
#include "scale8.h"
///@ingroup lib8tion
///@defgroup Math Basic math operations
/// Fast, efficient 8-bit math functions specifically
/// designed for high-performance LED programming.
///
/// Because of the AVR(Arduino) and ARM assembly language
/// implementations provided, using these functions often
/// results in smaller and faster code than the equivalent
/// program using plain "C" arithmetic and logic.
///@{
/// add one byte to another, saturating at 0xFF
/// @param i - first byte to add
/// @param j - second byte to add
/// @returns the sum of i & j, capped at 0xFF
LIB8STATIC_ALWAYS_INLINE uint8_t qadd8( uint8_t i, uint8_t j)
{
#if QADD8_C == 1
uint16_t t = i + j;
if (t > 255) t = 255;
return t;
#elif QADD8_AVRASM == 1
asm volatile(
/* First, add j to i, conditioning the C flag */
"add %0, %1 \n\t"
/* Now test the C flag.
If C is clear, we branch around a load of 0xFF into i.
If C is set, we go ahead and load 0xFF into i.
*/
"brcc L_%= \n\t"
"ldi %0, 0xFF \n\t"
"L_%=: "
: "+a" (i)
: "a" (j) );
return i;
#elif QADD8_ARM_DSP_ASM == 1
asm volatile( "uqadd8 %0, %0, %1" : "+r" (i) : "r" (j));
return i;
#else
#error "No implementation for qadd8 available."
#endif
}
/// Add one byte to another, saturating at 0x7F
/// @param i - first byte to add
/// @param j - second byte to add
/// @returns the sum of i & j, capped at 0xFF
LIB8STATIC_ALWAYS_INLINE int8_t qadd7( int8_t i, int8_t j)
{
#if QADD7_C == 1
int16_t t = i + j;
if (t > 127) t = 127;
return t;
#elif QADD7_AVRASM == 1
asm volatile(
/* First, add j to i, conditioning the V flag */
"add %0, %1 \n\t"
/* Now test the V flag.
If V is clear, we branch around a load of 0x7F into i.
If V is set, we go ahead and load 0x7F into i.
*/
"brvc L_%= \n\t"
"ldi %0, 0x7F \n\t"
"L_%=: "
: "+a" (i)
: "a" (j) );
return i;
#elif QADD7_ARM_DSP_ASM == 1
asm volatile( "qadd8 %0, %0, %1" : "+r" (i) : "r" (j));
return i;
#else
#error "No implementation for qadd7 available."
#endif
}
/// subtract one byte from another, saturating at 0x00
/// @returns i - j with a floor of 0
LIB8STATIC_ALWAYS_INLINE uint8_t qsub8( uint8_t i, uint8_t j)
{
#if QSUB8_C == 1
int16_t t = i - j;
if (t < 0) t = 0;
return t;
#elif QSUB8_AVRASM == 1
asm volatile(
/* First, subtract j from i, conditioning the C flag */
"sub %0, %1 \n\t"
/* Now test the C flag.
If C is clear, we branch around a load of 0x00 into i.
If C is set, we go ahead and load 0x00 into i.
*/
"brcc L_%= \n\t"
"ldi %0, 0x00 \n\t"
"L_%=: "
: "+a" (i)
: "a" (j) );
return i;
#else
#error "No implementation for qsub8 available."
#endif
}
/// add one byte to another, with one byte result
LIB8STATIC_ALWAYS_INLINE uint8_t add8( uint8_t i, uint8_t j)
{
#if ADD8_C == 1
uint16_t t = i + j;
return t;
#elif ADD8_AVRASM == 1
// Add j to i, period.
asm volatile( "add %0, %1" : "+a" (i) : "a" (j));
return i;
#else
#error "No implementation for add8 available."
#endif
}
/// add one byte to another, with one byte result
LIB8STATIC_ALWAYS_INLINE uint16_t add8to16( uint8_t i, uint16_t j)
{
#if ADD8_C == 1
uint16_t t = i + j;
return t;
#elif ADD8_AVRASM == 1
// Add i(one byte) to j(two bytes)
asm volatile( "add %A[j], %[i] \n\t"
"adc %B[j], __zero_reg__ \n\t"
: [j] "+a" (j)
: [i] "a" (i)
);
return i;
#else
#error "No implementation for add8to16 available."
#endif
}
/// subtract one byte from another, 8-bit result
LIB8STATIC_ALWAYS_INLINE uint8_t sub8( uint8_t i, uint8_t j)
{
#if SUB8_C == 1
int16_t t = i - j;
return t;
#elif SUB8_AVRASM == 1
// Subtract j from i, period.
asm volatile( "sub %0, %1" : "+a" (i) : "a" (j));
return i;
#else
#error "No implementation for sub8 available."
#endif
}
/// Calculate an integer average of two unsigned
/// 8-bit integer values (uint8_t).
/// Fractional results are rounded down, e.g. avg8(20,41) = 30
LIB8STATIC_ALWAYS_INLINE uint8_t avg8( uint8_t i, uint8_t j)
{
#if AVG8_C == 1
return (i + j) >> 1;
#elif AVG8_AVRASM == 1
asm volatile(
/* First, add j to i, 9th bit overflows into C flag */
"add %0, %1 \n\t"
/* Divide by two, moving C flag into high 8th bit */
"ror %0 \n\t"
: "+a" (i)
: "a" (j) );
return i;
#else
#error "No implementation for avg8 available."
#endif
}
/// Calculate an integer average of two unsigned
/// 16-bit integer values (uint16_t).
/// Fractional results are rounded down, e.g. avg16(20,41) = 30
LIB8STATIC_ALWAYS_INLINE uint16_t avg16( uint16_t i, uint16_t j)
{
#if AVG16_C == 1
return (uint32_t)((uint32_t)(i) + (uint32_t)(j)) >> 1;
#elif AVG16_AVRASM == 1
asm volatile(
/* First, add jLo (heh) to iLo, 9th bit overflows into C flag */
"add %A[i], %A[j] \n\t"
/* Now, add C + jHi to iHi, 17th bit overflows into C flag */
"adc %B[i], %B[j] \n\t"
/* Divide iHi by two, moving C flag into high 16th bit, old 9th bit now in C */
"ror %B[i] \n\t"
/* Divide iLo by two, moving C flag into high 8th bit */
"ror %A[i] \n\t"
: [i] "+a" (i)
: [j] "a" (j) );
return i;
#else
#error "No implementation for avg16 available."
#endif
}
/// Calculate an integer average of two signed 7-bit
/// integers (int8_t)
/// If the first argument is even, result is rounded down.
/// If the first argument is odd, result is result up.
LIB8STATIC_ALWAYS_INLINE int8_t avg7( int8_t i, int8_t j)
{
#if AVG7_C == 1
return ((i + j) >> 1) + (i & 0x1);
#elif AVG7_AVRASM == 1
asm volatile(
"asr %1 \n\t"
"asr %0 \n\t"
"adc %0, %1 \n\t"
: "+a" (i)
: "a" (j) );
return i;
#else
#error "No implementation for avg7 available."
#endif
}
/// Calculate an integer average of two signed 15-bit
/// integers (int16_t)
/// If the first argument is even, result is rounded down.
/// If the first argument is odd, result is result up.
LIB8STATIC_ALWAYS_INLINE int16_t avg15( int16_t i, int16_t j)
{
#if AVG15_C == 1
return ((int32_t)((int32_t)(i) + (int32_t)(j)) >> 1) + (i & 0x1);
#elif AVG15_AVRASM == 1
asm volatile(
/* first divide j by 2, throwing away lowest bit */
"asr %B[j] \n\t"
"ror %A[j] \n\t"
/* now divide i by 2, with lowest bit going into C */
"asr %B[i] \n\t"
"ror %A[i] \n\t"
/* add j + C to i */
"adc %A[i], %A[j] \n\t"
"adc %B[i], %B[j] \n\t"
: [i] "+a" (i)
: [j] "a" (j) );
return i;
#else
#error "No implementation for avg15 available."
#endif
}
/// Calculate the remainder of one unsigned 8-bit
/// value divided by anoter, aka A % M.
/// Implemented by repeated subtraction, which is
/// very compact, and very fast if A is 'probably'
/// less than M. If A is a large multiple of M,
/// the loop has to execute multiple times. However,
/// even in that case, the loop is only two
/// instructions long on AVR, i.e., quick.
LIB8STATIC_ALWAYS_INLINE uint8_t mod8( uint8_t a, uint8_t m)
{
#if defined(__AVR__)
asm volatile (
"L_%=: sub %[a],%[m] \n\t"
" brcc L_%= \n\t"
" add %[a],%[m] \n\t"
: [a] "+r" (a)
: [m] "r" (m)
);
#else
while( a >= m) a -= m;
#endif
return a;
}
/// Add two numbers, and calculate the modulo
/// of the sum and a third number, M.
/// In other words, it returns (A+B) % M.
/// It is designed as a compact mechanism for
/// incrementing a 'mode' switch and wrapping
/// around back to 'mode 0' when the switch
/// goes past the end of the available range.
/// e.g. if you have seven modes, this switches
/// to the next one and wraps around if needed:
/// mode = addmod8( mode, 1, 7);
///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance.
LIB8STATIC uint8_t addmod8( uint8_t a, uint8_t b, uint8_t m)
{
#if defined(__AVR__)
asm volatile (
" add %[a],%[b] \n\t"
"L_%=: sub %[a],%[m] \n\t"
" brcc L_%= \n\t"
" add %[a],%[m] \n\t"
: [a] "+r" (a)
: [b] "r" (b), [m] "r" (m)
);
#else
a += b;
while( a >= m) a -= m;
#endif
return a;
}
/// Subtract two numbers, and calculate the modulo
/// of the difference and a third number, M.
/// In other words, it returns (A-B) % M.
/// It is designed as a compact mechanism for
/// incrementing a 'mode' switch and wrapping
/// around back to 'mode 0' when the switch
/// goes past the end of the available range.
/// e.g. if you have seven modes, this switches
/// to the next one and wraps around if needed:
/// mode = addmod8( mode, 1, 7);
///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance.
LIB8STATIC uint8_t submod8( uint8_t a, uint8_t b, uint8_t m)
{
#if defined(__AVR__)
asm volatile (
" sub %[a],%[b] \n\t"
"L_%=: sub %[a],%[m] \n\t"
" brcc L_%= \n\t"
" add %[a],%[m] \n\t"
: [a] "+r" (a)
: [b] "r" (b), [m] "r" (m)
);
#else
a -= b;
while( a >= m) a -= m;
#endif
return a;
}
/// 8x8 bit multiplication, with 8 bit result
LIB8STATIC_ALWAYS_INLINE uint8_t mul8( uint8_t i, uint8_t j)
{
#if MUL8_C == 1
return ((uint16_t)i * (uint16_t)(j) ) & 0xFF;
#elif MUL8_AVRASM == 1
asm volatile(
/* Multiply 8-bit i * 8-bit j, giving 16-bit r1,r0 */
"mul %0, %1 \n\t"
/* Extract the LOW 8-bits (r0) */
"mov %0, r0 \n\t"
/* Restore r1 to "0"; it's expected to always be that */
"clr __zero_reg__ \n\t"
: "+a" (i)
: "a" (j)
: "r0", "r1");
return i;
#else
#error "No implementation for mul8 available."
#endif
}
/// saturating 8x8 bit multiplication, with 8 bit result
/// @returns the product of i * j, capping at 0xFF
LIB8STATIC_ALWAYS_INLINE uint8_t qmul8( uint8_t i, uint8_t j)
{
#if QMUL8_C == 1
int p = ((uint16_t)i * (uint16_t)(j) );
if( p > 255) p = 255;
return p;
#elif QMUL8_AVRASM == 1
asm volatile(
/* Multiply 8-bit i * 8-bit j, giving 16-bit r1,r0 */
" mul %0, %1 \n\t"
/* If high byte of result is zero, all is well. */
" tst r1 \n\t"
" breq Lnospill_%= \n\t"
/* If high byte of result > 0, saturate low byte to 0xFF */
" ldi %0,0xFF \n\t"
" rjmp Ldone_%= \n\t"
"Lnospill_%=: \n\t"
/* Extract the LOW 8-bits (r0) */
" mov %0, r0 \n\t"
"Ldone_%=: \n\t"
/* Restore r1 to "0"; it's expected to always be that */
" clr __zero_reg__ \n\t"
: "+a" (i)
: "a" (j)
: "r0", "r1");
return i;
#else
#error "No implementation for qmul8 available."
#endif
}
/// take abs() of a signed 8-bit uint8_t
LIB8STATIC_ALWAYS_INLINE int8_t abs8( int8_t i)
{
#if ABS8_C == 1
if( i < 0) i = -i;
return i;
#elif ABS8_AVRASM == 1
asm volatile(
/* First, check the high bit, and prepare to skip if it's clear */
"sbrc %0, 7 \n"
/* Negate the value */
"neg %0 \n"
: "+r" (i) : "r" (i) );
return i;
#else
#error "No implementation for abs8 available."
#endif
}
/// square root for 16-bit integers
/// About three times faster and five times smaller
/// than Arduino's general sqrt on AVR.
LIB8STATIC uint8_t sqrt16(uint16_t x)
{
if( x <= 1) {
return x;
}
uint8_t low = 1; // lower bound
uint8_t hi, mid;
if( x > 7904) {
hi = 255;
} else {
hi = (x >> 5) + 8; // initial estimate for upper bound
}
do {
mid = (low + hi) >> 1;
if ((uint16_t)(mid * mid) > x) {
hi = mid - 1;
} else {
if( mid == 255) {
return 255;
}
low = mid + 1;
}
} while (hi >= low);
return low - 1;
}
/// blend a variable proproportion(0-255) of one byte to another
/// @param a - the starting byte value
/// @param b - the byte value to blend toward
/// @param amountOfB - the proportion (0-255) of b to blend
/// @returns a byte value between a and b, inclusive
#if (FASTLED_BLEND_FIXED == 1)
LIB8STATIC uint8_t blend8( uint8_t a, uint8_t b, uint8_t amountOfB)
{
#if BLEND8_C == 1
uint16_t partial;
uint8_t result;
uint8_t amountOfA = 255 - amountOfB;
partial = (a * amountOfA);
#if (FASTLED_SCALE8_FIXED == 1)
partial += a;
//partial = add8to16( a, partial);
#endif
partial += (b * amountOfB);
#if (FASTLED_SCALE8_FIXED == 1)
partial += b;
//partial = add8to16( b, partial);
#endif
result = partial >> 8;
return result;
#elif BLEND8_AVRASM == 1
uint16_t partial;
uint8_t result;
asm volatile (
/* partial = b * amountOfB */
" mul %[b], %[amountOfB] \n\t"
" movw %A[partial], r0 \n\t"
/* amountOfB (aka amountOfA) = 255 - amountOfB */
" com %[amountOfB] \n\t"
/* partial += a * amountOfB (aka amountOfA) */
" mul %[a], %[amountOfB] \n\t"
" add %A[partial], r0 \n\t"
" adc %B[partial], r1 \n\t"
" clr __zero_reg__ \n\t"
#if (FASTLED_SCALE8_FIXED == 1)
/* partial += a */
" add %A[partial], %[a] \n\t"
" adc %B[partial], __zero_reg__ \n\t"
// partial += b
" add %A[partial], %[b] \n\t"
" adc %B[partial], __zero_reg__ \n\t"
#endif
: [partial] "=r" (partial),
[amountOfB] "+a" (amountOfB)
: [a] "a" (a),
[b] "a" (b)
: "r0", "r1"
);
result = partial >> 8;
return result;
#else
#error "No implementation for blend8 available."
#endif
}
#else
LIB8STATIC uint8_t blend8( uint8_t a, uint8_t b, uint8_t amountOfB)
{
// This version loses precision in the integer math
// and can actually return results outside of the range
// from a to b. Its use is not recommended.
uint8_t result;
uint8_t amountOfA = 255 - amountOfB;
result = scale8_LEAVING_R1_DIRTY( a, amountOfA)
+ scale8_LEAVING_R1_DIRTY( b, amountOfB);
cleanup_R1();
return result;
}
#endif
///@}
#endif

@ -0,0 +1,94 @@
#ifndef __INC_LIB8TION_RANDOM_H
#define __INC_LIB8TION_RANDOM_H
///@ingroup lib8tion
///@defgroup Random Fast random number generators
/// Fast 8- and 16- bit unsigned random numbers.
/// Significantly faster than Arduino random(), but
/// also somewhat less random. You can add entropy.
///@{
// X(n+1) = (2053 * X(n)) + 13849)
#define FASTLED_RAND16_2053 ((uint16_t)(2053))
#define FASTLED_RAND16_13849 ((uint16_t)(13849))
/// random number seed
extern uint16_t rand16seed;// = RAND16_SEED;
/// Generate an 8-bit random number
LIB8STATIC uint8_t random8(void)
{
rand16seed = (rand16seed * FASTLED_RAND16_2053) + FASTLED_RAND16_13849;
// return the sum of the high and low bytes, for better
// mixing and non-sequential correlation
return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) +
((uint8_t)(rand16seed >> 8)));
}
/// Generate a 16 bit random number
LIB8STATIC uint16_t random16(void)
{
rand16seed = (rand16seed * FASTLED_RAND16_2053) + FASTLED_RAND16_13849;
return rand16seed;
}
/// Generate an 8-bit random number between 0 and lim
/// @param lim the upper bound for the result
LIB8STATIC uint8_t random8_max(uint8_t lim)
{
uint8_t r = random8();
r = (r*lim) >> 8;
return r;
}
/// Generate an 8-bit random number in the given range
/// @param min the lower bound for the random number
/// @param lim the upper bound for the random number
LIB8STATIC uint8_t random8_min_max(uint8_t min, uint8_t lim)
{
uint8_t delta = lim - min;
uint8_t r = random8_max(delta) + min;
return r;
}
/// Generate an 16-bit random number between 0 and lim
/// @param lim the upper bound for the result
LIB8STATIC uint16_t random16_max(uint16_t lim)
{
uint16_t r = random16();
uint32_t p = (uint32_t)lim * (uint32_t)r;
r = p >> 16;
return r;
}
/// Generate an 16-bit random number in the given range
/// @param min the lower bound for the random number
/// @param lim the upper bound for the random number
LIB8STATIC uint16_t random16_min_max( uint16_t min, uint16_t lim)
{
uint16_t delta = lim - min;
uint16_t r = random16_max(delta) + min;
return r;
}
/// Set the 16-bit seed used for the random number generator
LIB8STATIC void random16_set_seed(uint16_t seed)
{
rand16seed = seed;
}
/// Get the current seed value for the random number generator
LIB8STATIC uint16_t random16_get_seed(void)
{
return rand16seed;
}
/// Add entropy into the random number generator
LIB8STATIC void random16_add_entropy(uint16_t entropy)
{
rand16seed += entropy;
}
///@}
#endif

@ -0,0 +1,542 @@
#ifndef __INC_LIB8TION_SCALE_H
#define __INC_LIB8TION_SCALE_H
///@ingroup lib8tion
///@defgroup Scaling Scaling functions
/// Fast, efficient 8-bit scaling functions specifically
/// designed for high-performance LED programming.
///
/// Because of the AVR(Arduino) and ARM assembly language
/// implementations provided, using these functions often
/// results in smaller and faster code than the equivalent
/// program using plain "C" arithmetic and logic.
///@{
/// scale one byte by a second one, which is treated as
/// the numerator of a fraction whose denominator is 256
/// In other words, it computes i * (scale / 256)
/// 4 clocks AVR with MUL, 2 clocks ARM
LIB8STATIC_ALWAYS_INLINE uint8_t scale8( uint8_t i, fract8 scale)
{
#if SCALE8_C == 1
#if (FASTLED_SCALE8_FIXED == 1)
return (((uint16_t)i) * (1+(uint16_t)(scale))) >> 8;
#else
return ((uint16_t)i * (uint16_t)(scale) ) >> 8;
#endif
#elif SCALE8_AVRASM == 1
#if defined(LIB8_ATTINY)
#if (FASTLED_SCALE8_FIXED == 1)
uint8_t work=i;
#else
uint8_t work=0;
#endif
uint8_t cnt=0x80;
asm volatile(
#if (FASTLED_SCALE8_FIXED == 1)
" inc %[scale] \n\t"
" breq DONE_%= \n\t"
" clr %[work] \n\t"
#endif
"LOOP_%=: \n\t"
/*" sbrc %[scale], 0 \n\t"
" add %[work], %[i] \n\t"
" ror %[work] \n\t"
" lsr %[scale] \n\t"
" clc \n\t"*/
" sbrc %[scale], 0 \n\t"
" add %[work], %[i] \n\t"
" ror %[work] \n\t"
" lsr %[scale] \n\t"
" lsr %[cnt] \n\t"
"brcc LOOP_%= \n\t"
"DONE_%=: \n\t"
: [work] "+r" (work), [cnt] "+r" (cnt)
: [scale] "r" (scale), [i] "r" (i)
:
);
return work;
#else
asm volatile(
#if (FASTLED_SCALE8_FIXED==1)
// Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0
"mul %0, %1 \n\t"
// Add i to r0, possibly setting the carry flag
"add r0, %0 \n\t"
// load the immediate 0 into i (note, this does _not_ touch any flags)
"ldi %0, 0x00 \n\t"
// walk and chew gum at the same time
"adc %0, r1 \n\t"
#else
/* Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0 */
"mul %0, %1 \n\t"
/* Move the high 8-bits of the product (r1) back to i */
"mov %0, r1 \n\t"
/* Restore r1 to "0"; it's expected to always be that */
#endif
"clr __zero_reg__ \n\t"
: "+a" (i) /* writes to i */
: "a" (scale) /* uses scale */
: "r0", "r1" /* clobbers r0, r1 */ );
/* Return the result */
return i;
#endif
#else
#error "No implementation for scale8 available."
#endif
}
/// The "video" version of scale8 guarantees that the output will
/// be only be zero if one or both of the inputs are zero. If both
/// inputs are non-zero, the output is guaranteed to be non-zero.
/// This makes for better 'video'/LED dimming, at the cost of
/// several additional cycles.
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video( uint8_t i, fract8 scale)
{
#if SCALE8_C == 1 || defined(LIB8_ATTINY)
uint8_t j = (((int)i * (int)scale) >> 8) + ((i&&scale)?1:0);
// uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
// uint8_t j = (i == 0) ? 0 : (((int)i * (int)(scale) ) >> 8) + nonzeroscale;
return j;
#elif SCALE8_AVRASM == 1
uint8_t j=0;
asm volatile(
" tst %[i]\n\t"
" breq L_%=\n\t"
" mul %[i], %[scale]\n\t"
" mov %[j], r1\n\t"
" clr __zero_reg__\n\t"
" cpse %[scale], r1\n\t"
" subi %[j], 0xFF\n\t"
"L_%=: \n\t"
: [j] "+a" (j)
: [i] "a" (i), [scale] "a" (scale)
: "r0", "r1");
return j;
// uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
// asm volatile(
// " tst %0 \n"
// " breq L_%= \n"
// " mul %0, %1 \n"
// " mov %0, r1 \n"
// " add %0, %2 \n"
// " clr __zero_reg__ \n"
// "L_%=: \n"
// : "+a" (i)
// : "a" (scale), "a" (nonzeroscale)
// : "r0", "r1");
// // Return the result
// return i;
#else
#error "No implementation for scale8_video available."
#endif
}
/// This version of scale8 does not clean up the R1 register on AVR
/// If you are doing several 'scale8's in a row, use this, and
/// then explicitly call cleanup_R1.
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_LEAVING_R1_DIRTY( uint8_t i, fract8 scale)
{
#if SCALE8_C == 1
#if (FASTLED_SCALE8_FIXED == 1)
return (((uint16_t)i) * ((uint16_t)(scale)+1)) >> 8;
#else
return ((int)i * (int)(scale) ) >> 8;
#endif
#elif SCALE8_AVRASM == 1
asm volatile(
#if (FASTLED_SCALE8_FIXED==1)
// Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0
"mul %0, %1 \n\t"
// Add i to r0, possibly setting the carry flag
"add r0, %0 \n\t"
// load the immediate 0 into i (note, this does _not_ touch any flags)
"ldi %0, 0x00 \n\t"
// walk and chew gum at the same time
"adc %0, r1 \n\t"
#else
/* Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0 */
"mul %0, %1 \n\t"
/* Move the high 8-bits of the product (r1) back to i */
"mov %0, r1 \n\t"
#endif
/* R1 IS LEFT DIRTY HERE; YOU MUST ZERO IT OUT YOURSELF */
/* "clr __zero_reg__ \n\t" */
: "+a" (i) /* writes to i */
: "a" (scale) /* uses scale */
: "r0", "r1" /* clobbers r0, r1 */ );
// Return the result
return i;
#else
#error "No implementation for scale8_LEAVING_R1_DIRTY available."
#endif
}
/// This version of scale8_video does not clean up the R1 register on AVR
/// If you are doing several 'scale8_video's in a row, use this, and
/// then explicitly call cleanup_R1.
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video_LEAVING_R1_DIRTY( uint8_t i, fract8 scale)
{
#if SCALE8_C == 1 || defined(LIB8_ATTINY)
uint8_t j = (((int)i * (int)scale) >> 8) + ((i&&scale)?1:0);
// uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
// uint8_t j = (i == 0) ? 0 : (((int)i * (int)(scale) ) >> 8) + nonzeroscale;
return j;
#elif SCALE8_AVRASM == 1
uint8_t j=0;
asm volatile(
" tst %[i]\n\t"
" breq L_%=\n\t"
" mul %[i], %[scale]\n\t"
" mov %[j], r1\n\t"
" breq L_%=\n\t"
" subi %[j], 0xFF\n\t"
"L_%=: \n\t"
: [j] "+a" (j)
: [i] "a" (i), [scale] "a" (scale)
: "r0", "r1");
return j;
// uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
// asm volatile(
// " tst %0 \n"
// " breq L_%= \n"
// " mul %0, %1 \n"
// " mov %0, r1 \n"
// " add %0, %2 \n"
// " clr __zero_reg__ \n"
// "L_%=: \n"
// : "+a" (i)
// : "a" (scale), "a" (nonzeroscale)
// : "r0", "r1");
// // Return the result
// return i;
#else
#error "No implementation for scale8_video_LEAVING_R1_DIRTY available."
#endif
}
/// Clean up the r1 register after a series of *LEAVING_R1_DIRTY calls
LIB8STATIC_ALWAYS_INLINE void cleanup_R1(void)
{
#if CLEANUP_R1_AVRASM == 1
// Restore r1 to "0"; it's expected to always be that
asm volatile( "clr __zero_reg__ \n\t" : : : "r1" );
#endif
}
/// scale a 16-bit unsigned value by an 8-bit value,
/// considered as numerator of a fraction whose denominator
/// is 256. In other words, it computes i * (scale / 256)
LIB8STATIC_ALWAYS_INLINE uint16_t scale16by8( uint16_t i, fract8 scale )
{
#if SCALE16BY8_C == 1
uint16_t result;
#if FASTLED_SCALE8_FIXED == 1
result = (i * (1+((uint16_t)scale))) >> 8;
#else
result = (i * scale) / 256;
#endif
return result;
#elif SCALE16BY8_AVRASM == 1
#if FASTLED_SCALE8_FIXED == 1
uint16_t result = 0;
asm volatile(
// result.A = HighByte( (i.A x scale) + i.A )
" mul %A[i], %[scale] \n\t"
" add r0, %A[i] \n\t"
// " adc r1, [zero] \n\t"
// " mov %A[result], r1 \n\t"
" adc %A[result], r1 \n\t"
// result.A-B += i.B x scale
" mul %B[i], %[scale] \n\t"
" add %A[result], r0 \n\t"
" adc %B[result], r1 \n\t"
// cleanup r1
" clr __zero_reg__ \n\t"
// result.A-B += i.B
" add %A[result], %B[i] \n\t"
" adc %B[result], __zero_reg__ \n\t"
: [result] "+r" (result)
: [i] "r" (i), [scale] "r" (scale)
: "r0", "r1"
);
return result;
#else
uint16_t result = 0;
asm volatile(
// result.A = HighByte(i.A x j )
" mul %A[i], %[scale] \n\t"
" mov %A[result], r1 \n\t"
//" clr %B[result] \n\t"
// result.A-B += i.B x j
" mul %B[i], %[scale] \n\t"
" add %A[result], r0 \n\t"
" adc %B[result], r1 \n\t"
// cleanup r1
" clr __zero_reg__ \n\t"
: [result] "+r" (result)
: [i] "r" (i), [scale] "r" (scale)
: "r0", "r1"
);
return result;
#endif
#else
#error "No implementation for scale16by8 available."
#endif
}
/// scale a 16-bit unsigned value by a 16-bit value,
/// considered as numerator of a fraction whose denominator
/// is 65536. In other words, it computes i * (scale / 65536)
LIB8STATIC uint16_t scale16( uint16_t i, fract16 scale )
{
#if SCALE16_C == 1
uint16_t result;
#if FASTLED_SCALE8_FIXED == 1
result = ((uint32_t)(i) * (1+(uint32_t)(scale))) / 65536;
#else
result = ((uint32_t)(i) * (uint32_t)(scale)) / 65536;
#endif
return result;
#elif SCALE16_AVRASM == 1
#if FASTLED_SCALE8_FIXED == 1
// implemented sort of like
// result = ((i * scale) + i ) / 65536
//
// why not like this, you may ask?
// result = (i * (scale+1)) / 65536
// the answer is that if scale is 65535, then scale+1
// will be zero, which is not what we want.
uint32_t result;
asm volatile(
// result.A-B = i.A x scale.A
" mul %A[i], %A[scale] \n\t"
// save results...
// basic idea:
//" mov %A[result], r0 \n\t"
//" mov %B[result], r1 \n\t"
// which can be written as...
" movw %A[result], r0 \n\t"
// Because we're going to add i.A-B to
// result.A-D, we DO need to keep both
// the r0 and r1 portions of the product
// UNlike in the 'unfixed scale8' version.
// So the movw here is needed.
: [result] "=r" (result)
: [i] "r" (i),
[scale] "r" (scale)
: "r0", "r1"
);
asm volatile(
// result.C-D = i.B x scale.B
" mul %B[i], %B[scale] \n\t"
//" mov %C[result], r0 \n\t"
//" mov %D[result], r1 \n\t"
" movw %C[result], r0 \n\t"
: [result] "+r" (result)
: [i] "r" (i),
[scale] "r" (scale)
: "r0", "r1"
);
const uint8_t zero = 0;
asm volatile(
// result.B-D += i.B x scale.A
" mul %B[i], %A[scale] \n\t"
" add %B[result], r0 \n\t"
" adc %C[result], r1 \n\t"
" adc %D[result], %[zero] \n\t"
// result.B-D += i.A x scale.B
" mul %A[i], %B[scale] \n\t"
" add %B[result], r0 \n\t"
" adc %C[result], r1 \n\t"
" adc %D[result], %[zero] \n\t"
// cleanup r1
" clr r1 \n\t"
: [result] "+r" (result)
: [i] "r" (i),
[scale] "r" (scale),
[zero] "r" (zero)
: "r0", "r1"
);
asm volatile(
// result.A-D += i.A-B
" add %A[result], %A[i] \n\t"
" adc %B[result], %B[i] \n\t"
" adc %C[result], %[zero] \n\t"
" adc %D[result], %[zero] \n\t"
: [result] "+r" (result)
: [i] "r" (i),
[zero] "r" (zero)
);
result = result >> 16;
return result;
#else
uint32_t result;
asm volatile(
// result.A-B = i.A x scale.A
" mul %A[i], %A[scale] \n\t"
// save results...
// basic idea:
//" mov %A[result], r0 \n\t"
//" mov %B[result], r1 \n\t"
// which can be written as...
" movw %A[result], r0 \n\t"
// We actually don't need to do anything with r0,
// as result.A is never used again here, so we
// could just move the high byte, but movw is
// one clock cycle, just like mov, so might as
// well, in case we want to use this code for
// a generic 16x16 multiply somewhere.
: [result] "=r" (result)
: [i] "r" (i),
[scale] "r" (scale)
: "r0", "r1"
);
asm volatile(
// result.C-D = i.B x scale.B
" mul %B[i], %B[scale] \n\t"
//" mov %C[result], r0 \n\t"
//" mov %D[result], r1 \n\t"
" movw %C[result], r0 \n\t"
: [result] "+r" (result)
: [i] "r" (i),
[scale] "r" (scale)
: "r0", "r1"
);
const uint8_t zero = 0;
asm volatile(
// result.B-D += i.B x scale.A
" mul %B[i], %A[scale] \n\t"
" add %B[result], r0 \n\t"
" adc %C[result], r1 \n\t"
" adc %D[result], %[zero] \n\t"
// result.B-D += i.A x scale.B
" mul %A[i], %B[scale] \n\t"
" add %B[result], r0 \n\t"
" adc %C[result], r1 \n\t"
" adc %D[result], %[zero] \n\t"
// cleanup r1
" clr r1 \n\t"
: [result] "+r" (result)
: [i] "r" (i),
[scale] "r" (scale),
[zero] "r" (zero)
: "r0", "r1"
);
result = result >> 16;
return result;
#endif
#else
#error "No implementation for scale16 available."
#endif
}
///@}
///@defgroup Dimming Dimming and brightening functions
///
/// Dimming and brightening functions
///
/// The eye does not respond in a linear way to light.
/// High speed PWM'd LEDs at 50% duty cycle appear far
/// brighter then the 'half as bright' you might expect.
///
/// If you want your midpoint brightness leve (128) to
/// appear half as bright as 'full' brightness (255), you
/// have to apply a 'dimming function'.
///@{
/// Adjust a scaling value for dimming
LIB8STATIC uint8_t dim8_raw( uint8_t x)
{
return scale8( x, x);
}
/// Adjust a scaling value for dimming for video (value will never go below 1)
LIB8STATIC uint8_t dim8_video( uint8_t x)
{
return scale8_video( x, x);
}
/// Linear version of the dimming function that halves for values < 128
LIB8STATIC uint8_t dim8_lin( uint8_t x )
{
if( x & 0x80 ) {
x = scale8( x, x);
} else {
x += 1;
x /= 2;
}
return x;
}
/// inverse of the dimming function, brighten a value
LIB8STATIC uint8_t brighten8_raw( uint8_t x)
{
uint8_t ix = 255 - x;
return 255 - scale8( ix, ix);
}
/// inverse of the dimming function, brighten a value
LIB8STATIC uint8_t brighten8_video( uint8_t x)
{
uint8_t ix = 255 - x;
return 255 - scale8_video( ix, ix);
}
/// inverse of the dimming function, brighten a value
LIB8STATIC uint8_t brighten8_lin( uint8_t x )
{
uint8_t ix = 255 - x;
if( ix & 0x80 ) {
ix = scale8( ix, ix);
} else {
ix += 1;
ix /= 2;
}
return 255 - ix;
}
///@}
#endif

@ -0,0 +1,259 @@
#ifndef __INC_LIB8TION_TRIG_H
#define __INC_LIB8TION_TRIG_H
///@ingroup lib8tion
///@defgroup Trig Fast trig functions
/// Fast 8 and 16-bit approximations of sin(x) and cos(x).
/// Don't use these approximations for calculating the
/// trajectory of a rocket to Mars, but they're great
/// for art projects and LED displays.
///
/// On Arduino/AVR, the 16-bit approximation is more than
/// 10X faster than floating point sin(x) and cos(x), while
/// the 8-bit approximation is more than 20X faster.
///@{
#if defined(__AVR__)
#define sin16 sin16_avr
#else
#define sin16 sin16_C
#endif
/// Fast 16-bit approximation of sin(x). This approximation never varies more than
/// 0.69% from the floating point value you'd get by doing
///
/// float s = sin(x) * 32767.0;
///
/// @param theta input angle from 0-65535
/// @returns sin of theta, value between -32767 to 32767.
LIB8STATIC int16_t sin16_avr( uint16_t theta )
{
static const uint8_t data[] =
{ 0, 0, 49, 0, 6393%256, 6393/256, 48, 0,
12539%256, 12539/256, 44, 0, 18204%256, 18204/256, 38, 0,
23170%256, 23170/256, 31, 0, 27245%256, 27245/256, 23, 0,
30273%256, 30273/256, 14, 0, 32137%256, 32137/256, 4 /*,0*/ };
uint16_t offset = (theta & 0x3FFF);
// AVR doesn't have a multi-bit shift instruction,
// so if we say "offset >>= 3", gcc makes a tiny loop.
// Inserting empty volatile statements between each
// bit shift forces gcc to unroll the loop.
offset >>= 1; // 0..8191
asm volatile("");
offset >>= 1; // 0..4095
asm volatile("");
offset >>= 1; // 0..2047
if( theta & 0x4000 ) offset = 2047 - offset;
uint8_t sectionX4;
sectionX4 = offset / 256;
sectionX4 *= 4;
uint8_t m;
union {
uint16_t b;
struct {
uint8_t blo;
uint8_t bhi;
};
} u;
//in effect u.b = blo + (256 * bhi);
u.blo = data[ sectionX4 ];
u.bhi = data[ sectionX4 + 1];
m = data[ sectionX4 + 2];
uint8_t secoffset8 = (uint8_t)(offset) / 2;
uint16_t mx = m * secoffset8;
int16_t y = mx + u.b;
if( theta & 0x8000 ) y = -y;
return y;
}
/// Fast 16-bit approximation of sin(x). This approximation never varies more than
/// 0.69% from the floating point value you'd get by doing
///
/// float s = sin(x) * 32767.0;
///
/// @param theta input angle from 0-65535
/// @returns sin of theta, value between -32767 to 32767.
LIB8STATIC int16_t sin16_C( uint16_t theta )
{
static const uint16_t base[] =
{ 0, 6393, 12539, 18204, 23170, 27245, 30273, 32137 };
static const uint8_t slope[] =
{ 49, 48, 44, 38, 31, 23, 14, 4 };
uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047
if( theta & 0x4000 ) offset = 2047 - offset;
uint8_t section = offset / 256; // 0..7
uint16_t b = base[section];
uint8_t m = slope[section];
uint8_t secoffset8 = (uint8_t)(offset) / 2;
uint16_t mx = m * secoffset8;
int16_t y = mx + b;
if( theta & 0x8000 ) y = -y;
return y;
}
/// Fast 16-bit approximation of cos(x). This approximation never varies more than
/// 0.69% from the floating point value you'd get by doing
///
/// float s = cos(x) * 32767.0;
///
/// @param theta input angle from 0-65535
/// @returns sin of theta, value between -32767 to 32767.
LIB8STATIC int16_t cos16( uint16_t theta)
{
return sin16( theta + 16384);
}
///////////////////////////////////////////////////////////////////////
// sin8 & cos8
// Fast 8-bit approximations of sin(x) & cos(x).
// Input angle is an unsigned int from 0-255.
// Output is an unsigned int from 0 to 255.
//
// This approximation can vary to to 2%
// from the floating point value you'd get by doing
// float s = (sin( x ) * 128.0) + 128;
//
// Don't use this approximation for calculating the
// "real" trigonometric calculations, but it's great
// for art projects and LED displays.
//
// On Arduino/AVR, this approximation is more than
// 20X faster than floating point sin(x) and cos(x)
#if defined(__AVR__) && !defined(LIB8_ATTINY)
#define sin8 sin8_avr
#else
#define sin8 sin8_C
#endif
const uint8_t b_m16_interleave[] = { 0, 49, 49, 41, 90, 27, 117, 10 };
/// Fast 8-bit approximation of sin(x). This approximation never varies more than
/// 2% from the floating point value you'd get by doing
///
/// float s = (sin(x) * 128.0) + 128;
///
/// @param theta input angle from 0-255
/// @returns sin of theta, value between 0 and 255
LIB8STATIC uint8_t sin8_avr( uint8_t theta)
{
uint8_t offset = theta;
asm volatile(
"sbrc %[theta],6 \n\t"
"com %[offset] \n\t"
: [theta] "+r" (theta), [offset] "+r" (offset)
);
offset &= 0x3F; // 0..63
uint8_t secoffset = offset & 0x0F; // 0..15
if( theta & 0x40) secoffset++;
uint8_t m16; uint8_t b;
uint8_t section = offset >> 4; // 0..3
uint8_t s2 = section * 2;
const uint8_t* p = b_m16_interleave;
p += s2;
b = *p;
p++;
m16 = *p;
uint8_t mx;
uint8_t xr1;
asm volatile(
"mul %[m16],%[secoffset] \n\t"
"mov %[mx],r0 \n\t"
"mov %[xr1],r1 \n\t"
"eor r1, r1 \n\t"
"swap %[mx] \n\t"
"andi %[mx],0x0F \n\t"
"swap %[xr1] \n\t"
"andi %[xr1], 0xF0 \n\t"
"or %[mx], %[xr1] \n\t"
: [mx] "=d" (mx), [xr1] "=d" (xr1)
: [m16] "d" (m16), [secoffset] "d" (secoffset)
);
int8_t y = mx + b;
if( theta & 0x80 ) y = -y;
y += 128;
return y;
}
/// Fast 8-bit approximation of sin(x). This approximation never varies more than
/// 2% from the floating point value you'd get by doing
///
/// float s = (sin(x) * 128.0) + 128;
///
/// @param theta input angle from 0-255
/// @returns sin of theta, value between 0 and 255
LIB8STATIC uint8_t sin8_C( uint8_t theta)
{
uint8_t offset = theta;
if( theta & 0x40 ) {
offset = (uint8_t)255 - offset;
}
offset &= 0x3F; // 0..63
uint8_t secoffset = offset & 0x0F; // 0..15
if( theta & 0x40) secoffset++;
uint8_t section = offset >> 4; // 0..3
uint8_t s2 = section * 2;
const uint8_t* p = b_m16_interleave;
p += s2;
uint8_t b = *p;
p++;
uint8_t m16 = *p;
uint8_t mx = (m16 * secoffset) >> 4;
int8_t y = mx + b;
if( theta & 0x80 ) y = -y;
y += 128;
return y;
}
/// Fast 8-bit approximation of cos(x). This approximation never varies more than
/// 2% from the floating point value you'd get by doing
///
/// float s = (cos(x) * 128.0) + 128;
///
/// @param theta input angle from 0-255
/// @returns sin of theta, value between 0 and 255
LIB8STATIC uint8_t cos8( uint8_t theta)
{
return sin8( theta + 64);
}
///@}
#endif

@ -78,9 +78,11 @@ RGB hsv_to_rgb( HSV hsv )
break; break;
} }
#ifdef USE_CIE1931_CURVE
rgb.r = pgm_read_byte( &CIE1931_CURVE[rgb.r] ); rgb.r = pgm_read_byte( &CIE1931_CURVE[rgb.r] );
rgb.g = pgm_read_byte( &CIE1931_CURVE[rgb.g] ); rgb.g = pgm_read_byte( &CIE1931_CURVE[rgb.g] );
rgb.b = pgm_read_byte( &CIE1931_CURVE[rgb.b] ); rgb.b = pgm_read_byte( &CIE1931_CURVE[rgb.b] );
#endif
return rgb; return rgb;
} }

@ -274,10 +274,10 @@ bool process_record_quantum(keyrecord_t *record) {
#ifdef HAPTIC_ENABLE #ifdef HAPTIC_ENABLE
process_haptic(keycode, record) && process_haptic(keycode, record) &&
#endif //HAPTIC_ENABLE #endif //HAPTIC_ENABLE
process_record_kb(keycode, record) && #if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_KEYREACTIVE_ENABLED)
#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_KEYPRESSES)
process_rgb_matrix(keycode, record) && process_rgb_matrix(keycode, record) &&
#endif #endif
process_record_kb(keycode, record) &&
#if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED) #if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED)
process_midi(keycode, record) && process_midi(keycode, record) &&
#endif #endif
@ -1049,12 +1049,6 @@ void matrix_init_quantum() {
matrix_init_kb(); matrix_init_kb();
} }
uint8_t rgb_matrix_task_counter = 0;
#ifndef RGB_MATRIX_SKIP_FRAMES
#define RGB_MATRIX_SKIP_FRAMES 1
#endif
void matrix_scan_quantum() { void matrix_scan_quantum() {
#if defined(AUDIO_ENABLE) && !defined(NO_MUSIC_MODE) #if defined(AUDIO_ENABLE) && !defined(NO_MUSIC_MODE)
matrix_scan_music(); matrix_scan_music();
@ -1078,10 +1072,6 @@ void matrix_scan_quantum() {
#ifdef RGB_MATRIX_ENABLE #ifdef RGB_MATRIX_ENABLE
rgb_matrix_task(); rgb_matrix_task();
if (rgb_matrix_task_counter == 0) {
rgb_matrix_update_pwm_buffers();
}
rgb_matrix_task_counter = ((rgb_matrix_task_counter + 1) % (RGB_MATRIX_SKIP_FRAMES + 1));
#endif #endif
#ifdef ENCODER_ENABLE #ifdef ENCODER_ENABLE

@ -24,15 +24,26 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
rgb_config_t rgb_matrix_config; #include "lib/lib8tion/lib8tion.h"
#ifndef MAX #include "rgb_matrix_animations/solid_color_anim.h"
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) #include "rgb_matrix_animations/alpha_mods_anim.h"
#endif #include "rgb_matrix_animations/dual_beacon_anim.h"
#include "rgb_matrix_animations/gradient_up_down_anim.h"
#ifndef MIN #include "rgb_matrix_animations/raindrops_anim.h"
#define MIN(a,b) ((a) < (b)? (a): (b)) #include "rgb_matrix_animations/cycle_all_anim.h"
#endif #include "rgb_matrix_animations/cycle_left_right_anim.h"
#include "rgb_matrix_animations/cycle_up_down_anim.h"
#include "rgb_matrix_animations/rainbow_beacon_anim.h"
#include "rgb_matrix_animations/rainbow_pinwheels_anim.h"
#include "rgb_matrix_animations/rainbow_moving_chevron_anim.h"
#include "rgb_matrix_animations/jellybean_raindrops_anim.h"
#include "rgb_matrix_animations/digital_rain_anim.h"
#include "rgb_matrix_animations/solid_reactive_simple_anim.h"
#include "rgb_matrix_animations/solid_reactive_anim.h"
#include "rgb_matrix_animations/splash_anim.h"
#include "rgb_matrix_animations/solid_splash_anim.h"
#include "rgb_matrix_animations/breathing_anim.h"
#ifndef RGB_DISABLE_AFTER_TIMEOUT #ifndef RGB_DISABLE_AFTER_TIMEOUT
#define RGB_DISABLE_AFTER_TIMEOUT 0 #define RGB_DISABLE_AFTER_TIMEOUT 0
@ -46,40 +57,47 @@ rgb_config_t rgb_matrix_config;
#define EECONFIG_RGB_MATRIX EECONFIG_RGBLIGHT #define EECONFIG_RGB_MATRIX EECONFIG_RGBLIGHT
#endif #endif
#if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > 255 #if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX
#define RGB_MATRIX_MAXIMUM_BRIGHTNESS 255 #undef RGB_MATRIX_MAXIMUM_BRIGHTNESS
#define RGB_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX
#endif #endif
#ifndef RGB_DIGITAL_RAIN_DROPS #if !defined(RGB_MATRIX_HUE_STEP)
// lower the number for denser effect/wider keyboard #define RGB_MATRIX_HUE_STEP 8
#define RGB_DIGITAL_RAIN_DROPS 24
#endif #endif
#if !defined(DISABLE_RGB_MATRIX_RAINDROPS) || !defined(DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS) || !defined(DISABLE_RGB_MATRIX_DIGITAL_RAIN) #if !defined(RGB_MATRIX_SAT_STEP)
#define TRACK_PREVIOUS_EFFECT #define RGB_MATRIX_SAT_STEP 16
#endif #endif
bool g_suspend_state = false; #if !defined(RGB_MATRIX_VAL_STEP)
#define RGB_MATRIX_VAL_STEP 16
#endif
// Global tick at 20 Hz #if !defined(RGB_MATRIX_SPD_STEP)
uint32_t g_tick = 0; #define RGB_MATRIX_SPD_STEP 16
#endif
// Ticks since this key was last hit. bool g_suspend_state = false;
uint8_t g_key_hit[DRIVER_LED_TOTAL];
// Ticks since any key was last hit. rgb_config_t rgb_matrix_config;
uint32_t g_any_key_hit = 0;
#ifndef PI rgb_counters_t g_rgb_counters;
#define PI 3.14159265 static uint32_t rgb_counters_buffer;
#endif
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
last_hit_t g_last_hit_tracker;
static last_hit_t last_hit_buffer;
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
uint32_t eeconfig_read_rgb_matrix(void) { uint32_t eeconfig_read_rgb_matrix(void) {
return eeprom_read_dword(EECONFIG_RGB_MATRIX); return eeprom_read_dword(EECONFIG_RGB_MATRIX);
} }
void eeconfig_update_rgb_matrix(uint32_t val) { void eeconfig_update_rgb_matrix(uint32_t val) {
eeprom_update_dword(EECONFIG_RGB_MATRIX, val); eeprom_update_dword(EECONFIG_RGB_MATRIX, val);
} }
void eeconfig_update_rgb_matrix_default(void) { void eeconfig_update_rgb_matrix_default(void) {
dprintf("eeconfig_update_rgb_matrix_default\n"); dprintf("eeconfig_update_rgb_matrix_default\n");
rgb_matrix_config.enable = 1; rgb_matrix_config.enable = 1;
@ -90,11 +108,12 @@ void eeconfig_update_rgb_matrix_default(void) {
rgb_matrix_config.mode = RGB_MATRIX_SOLID_COLOR; rgb_matrix_config.mode = RGB_MATRIX_SOLID_COLOR;
#endif #endif
rgb_matrix_config.hue = 0; rgb_matrix_config.hue = 0;
rgb_matrix_config.sat = 255; rgb_matrix_config.sat = UINT8_MAX;
rgb_matrix_config.val = RGB_MATRIX_MAXIMUM_BRIGHTNESS; rgb_matrix_config.val = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
rgb_matrix_config.speed = 0; rgb_matrix_config.speed = UINT8_MAX / 2;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void eeconfig_debug_rgb_matrix(void) { void eeconfig_debug_rgb_matrix(void) {
dprintf("rgb_matrix_config eprom\n"); dprintf("rgb_matrix_config eprom\n");
dprintf("rgb_matrix_config.enable = %d\n", rgb_matrix_config.enable); dprintf("rgb_matrix_config.enable = %d\n", rgb_matrix_config.enable);
@ -105,23 +124,17 @@ void eeconfig_debug_rgb_matrix(void) {
dprintf("rgb_matrix_config.speed = %d\n", rgb_matrix_config.speed); dprintf("rgb_matrix_config.speed = %d\n", rgb_matrix_config.speed);
} }
// Last led hit uint8_t rgb_matrix_map_row_column_to_led(uint8_t row, uint8_t column, uint8_t *led_i) {
#define LED_HITS_TO_REMEMBER 8 // TODO: This is kinda expensive, fix this soonish
uint8_t g_last_led_hit[LED_HITS_TO_REMEMBER] = {255}; uint8_t led_count = 0;
uint8_t g_last_led_count = 0; for (uint8_t i = 0; i < DRIVER_LED_TOTAL && led_count < LED_HITS_TO_REMEMBER; i++) {
matrix_co_t matrix_co = g_rgb_leds[i].matrix_co;
void map_row_column_to_led( uint8_t row, uint8_t column, uint8_t *led_i, uint8_t *led_count) { if (row == matrix_co.row && column == matrix_co.col) {
rgb_led led; led_i[led_count] = i;
*led_count = 0; led_count++;
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
// map_index_to_led(i, &led);
led = g_rgb_leds[i];
if (row == led.matrix_co.row && column == led.matrix_co.col) {
led_i[*led_count] = i;
(*led_count)++;
} }
} }
return led_count;
} }
void rgb_matrix_update_pwm_buffers(void) { void rgb_matrix_update_pwm_buffers(void) {
@ -137,673 +150,299 @@ void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) {
} }
bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) { bool process_rgb_matrix(uint16_t keycode, keyrecord_t *record) {
if ( record->event.pressed ) { #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
uint8_t led[8], led_count; uint8_t led[LED_HITS_TO_REMEMBER];
map_row_column_to_led(record->event.key.row, record->event.key.col, led, &led_count); uint8_t led_count = 0;
if (led_count > 0) {
for (uint8_t i = LED_HITS_TO_REMEMBER; i > 1; i--) { #if defined(RGB_MATRIX_KEYRELEASES)
g_last_led_hit[i - 1] = g_last_led_hit[i - 2]; if (!record->event.pressed) {
} led_count = rgb_matrix_map_row_column_to_led(record->event.key.row, record->event.key.col, led);
g_last_led_hit[0] = led[0]; g_rgb_counters.any_key_hit = 0;
g_last_led_count = MIN(LED_HITS_TO_REMEMBER, g_last_led_count + 1); }
} #elif defined(RGB_MATRIX_KEYPRESSES)
for(uint8_t i = 0; i < led_count; i++) if (record->event.pressed) {
g_key_hit[led[i]] = 0; led_count = rgb_matrix_map_row_column_to_led(record->event.key.row, record->event.key.col, led);
g_any_key_hit = 0; g_rgb_counters.any_key_hit = 0;
} else { }
#ifdef RGB_MATRIX_KEYRELEASES #endif // defined(RGB_MATRIX_KEYRELEASES)
uint8_t led[8], led_count;
map_row_column_to_led(record->event.key.row, record->event.key.col, led, &led_count); if (last_hit_buffer.count + led_count > LED_HITS_TO_REMEMBER) {
for(uint8_t i = 0; i < led_count; i++) memcpy(&last_hit_buffer.x[0], &last_hit_buffer.x[led_count], LED_HITS_TO_REMEMBER - led_count);
g_key_hit[led[i]] = 255; memcpy(&last_hit_buffer.y[0], &last_hit_buffer.y[led_count], LED_HITS_TO_REMEMBER - led_count);
memcpy(&last_hit_buffer.tick[0], &last_hit_buffer.tick[led_count], (LED_HITS_TO_REMEMBER - led_count) * 2); // 16 bit
g_any_key_hit = 255; memcpy(&last_hit_buffer.index[0], &last_hit_buffer.index[led_count], LED_HITS_TO_REMEMBER - led_count);
#endif last_hit_buffer.count--;
} }
for(uint8_t i = 0; i < led_count; i++) {
uint8_t index = last_hit_buffer.count;
last_hit_buffer.x[index] = g_rgb_leds[led[i]].point.x;
last_hit_buffer.y[index] = g_rgb_leds[led[i]].point.y;
last_hit_buffer.index[index] = led[i];
last_hit_buffer.tick[index] = 0;
last_hit_buffer.count++;
}
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
return true; return true;
} }
void rgb_matrix_set_suspend_state(bool state) {
g_suspend_state = state;
}
void rgb_matrix_test(void) { void rgb_matrix_test(void) {
// Mask out bits 4 and 5 // Mask out bits 4 and 5
// Increase the factor to make the test animation slower (and reduce to make it faster) // Increase the factor to make the test animation slower (and reduce to make it faster)
uint8_t factor = 10; uint8_t factor = 10;
switch ( (g_tick & (0b11 << factor)) >> factor ) switch ( (g_rgb_counters.tick & (0b11 << factor)) >> factor )
{
case 0:
{ {
case 0: {
rgb_matrix_set_color_all( 20, 0, 0 ); rgb_matrix_set_color_all( 20, 0, 0 );
break; break;
} }
case 1: case 1: {
{
rgb_matrix_set_color_all( 0, 20, 0 ); rgb_matrix_set_color_all( 0, 20, 0 );
break; break;
} }
case 2: case 2: {
{
rgb_matrix_set_color_all( 0, 0, 20 ); rgb_matrix_set_color_all( 0, 0, 20 );
break; break;
} }
case 3: case 3: {
{
rgb_matrix_set_color_all( 20, 20, 20 ); rgb_matrix_set_color_all( 20, 20, 20 );
break; break;
} }
} }
} }
// All LEDs off static bool rgb_matrix_none(effect_params_t* params) {
void rgb_matrix_all_off(void) { if (!params->init) {
rgb_matrix_set_color_all( 0, 0, 0 ); return false;
}
// Solid color
void rgb_matrix_solid_color(void) {
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val };
RGB rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color_all( rgb.r, rgb.g, rgb.b );
}
void rgb_matrix_solid_reactive(void) {
// Relies on hue being 8-bit and wrapping
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
uint16_t offset2 = g_key_hit[i]<<2;
offset2 = (offset2<=130) ? (130-offset2) : 0;
HSV hsv = { .h = rgb_matrix_config.hue+offset2, .s = 255, .v = rgb_matrix_config.val };
RGB rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_solid_reactive_simple(void)
{
HSV hsv = {.h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val};
RGB rgb;
for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
uint16_t offset2 = g_key_hit[i] << 2;
offset2 = (offset2 <= 255) ? (255 - offset2) : 0;
hsv.v = offset2 * rgb_matrix_config.val / RGB_MATRIX_MAXIMUM_BRIGHTNESS;
rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
}
// alphas = color1, mods = color2
void rgb_matrix_alphas_mods(void) {
RGB rgb1 = hsv_to_rgb( (HSV){ .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val } );
RGB rgb2 = hsv_to_rgb( (HSV){ .h = (rgb_matrix_config.hue + 180) % 360, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val } );
rgb_led led;
for (int i = 0; i < DRIVER_LED_TOTAL; i++) {
led = g_rgb_leds[i];
if ( led.matrix_co.raw < 0xFF ) {
if ( led.modifier )
{
rgb_matrix_set_color( i, rgb2.r, rgb2.g, rgb2.b );
}
else
{
rgb_matrix_set_color( i, rgb1.r, rgb1.g, rgb1.b );
}
}
}
}
void rgb_matrix_gradient_up_down(void) {
int16_t h1 = rgb_matrix_config.hue;
int16_t h2 = (rgb_matrix_config.hue + 180) % 360;
int16_t deltaH = h2 - h1;
// Take the shortest path between hues
if ( deltaH > 127 )
{
deltaH -= 256;
}
else if ( deltaH < -127 )
{
deltaH += 256;
}
// Divide delta by 4, this gives the delta per row
deltaH /= 4;
int16_t s1 = rgb_matrix_config.sat;
int16_t s2 = rgb_matrix_config.hue;
int16_t deltaS = ( s2 - s1 ) / 4;
HSV hsv = { .h = 0, .s = 255, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
// map_led_to_point( i, &point );
point = g_rgb_leds[i].point;
// The y range will be 0..64, map this to 0..4
uint8_t y = (point.y>>4);
// Relies on hue being 8-bit and wrapping
hsv.h = rgb_matrix_config.hue + ( deltaH * y );
hsv.s = rgb_matrix_config.sat + ( deltaS * y );
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_raindrops(bool initialize) {
int16_t h1 = rgb_matrix_config.hue;
int16_t h2 = (rgb_matrix_config.hue + 180) % 360;
int16_t deltaH = h2 - h1;
deltaH /= 4;
// Take the shortest path between hues
if ( deltaH > 127 )
{
deltaH -= 256;
}
else if ( deltaH < -127 )
{
deltaH += 256;
} }
int16_t s1 = rgb_matrix_config.sat; RGB_MATRIX_USE_LIMITS(led_min, led_max);
int16_t s2 = rgb_matrix_config.sat; for (uint8_t i = led_min; i < led_max; i++) {
int16_t deltaS = ( s2 - s1 ) / 4; rgb_matrix_set_color(i, 0, 0, 0);
HSV hsv;
RGB rgb;
// Change one LED every tick, make sure speed is not 0
uint8_t led_to_change = ( g_tick & ( 0x0A / (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed) ) ) == 0 ? rand() % (DRIVER_LED_TOTAL) : 255;
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
// If initialize, all get set to random colors
// If not, all but one will stay the same as before.
if ( initialize || i == led_to_change )
{
hsv.h = h1 + ( deltaH * ( rand() & 0x03 ) );
hsv.s = s1 + ( deltaS * ( rand() & 0x03 ) );
// Override brightness with global brightness control
hsv.v = rgb_matrix_config.val;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
} }
return led_max < DRIVER_LED_TOTAL;
} }
void rgb_matrix_cycle_all(void) { static uint8_t rgb_last_enable = UINT8_MAX;
uint8_t offset = ( g_tick << rgb_matrix_config.speed ) & 0xFF; static uint8_t rgb_last_effect = UINT8_MAX;
static effect_params_t rgb_effect_params = { 0, 0 };
rgb_led led; static rgb_task_states rgb_task_state = SYNCING;
// Relies on hue being 8-bit and wrapping
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
// map_index_to_led(i, &led);
led = g_rgb_leds[i];
if (led.matrix_co.raw < 0xFF) {
uint16_t offset2 = g_key_hit[i]<<2;
offset2 = (offset2<=63) ? (63-offset2) : 0;
HSV hsv = { .h = offset+offset2, .s = 255, .v = rgb_matrix_config.val };
RGB rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
}
void rgb_matrix_cycle_left_right(void) {
uint8_t offset = ( g_tick << rgb_matrix_config.speed ) & 0xFF;
HSV hsv = { .h = 0, .s = 255, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
rgb_led led;
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
// map_index_to_led(i, &led);
led = g_rgb_leds[i];
if (led.matrix_co.raw < 0xFF) {
uint16_t offset2 = g_key_hit[i]<<2;
offset2 = (offset2<=63) ? (63-offset2) : 0;
// map_led_to_point( i, &point );
point = g_rgb_leds[i].point;
// Relies on hue being 8-bit and wrapping
hsv.h = point.x + offset + offset2;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
}
void rgb_matrix_cycle_up_down(void) {
uint8_t offset = ( g_tick << rgb_matrix_config.speed ) & 0xFF;
HSV hsv = { .h = 0, .s = 255, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
rgb_led led;
for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
{
// map_index_to_led(i, &led);
led = g_rgb_leds[i];
if (led.matrix_co.raw < 0xFF) {
uint16_t offset2 = g_key_hit[i]<<2;
offset2 = (offset2<=63) ? (63-offset2) : 0;
// map_led_to_point( i, &point );
point = g_rgb_leds[i].point;
// Relies on hue being 8-bit and wrapping
hsv.h = point.y + offset + offset2;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
}
void rgb_matrix_dual_beacon(void) {
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
double cos_value = cos(g_tick * PI / 128) / 32;
double sin_value = sin(g_tick * PI / 128) / 112;
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
point = g_rgb_leds[i].point;
hsv.h = ((point.y - 32.0)* cos_value + (point.x - 112.0) * sin_value) * (180) + rgb_matrix_config.hue;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_rainbow_beacon(void) {
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
double cos_value = cos(g_tick * PI / 128);
double sin_value = sin(g_tick * PI / 128);
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
point = g_rgb_leds[i].point;
hsv.h = (1.5 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * (point.y - 32.0)* cos_value + (1.5 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * (point.x - 112.0) * sin_value + rgb_matrix_config.hue;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_rainbow_pinwheels(void) {
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
double cos_value = cos(g_tick * PI / 128);
double sin_value = sin(g_tick * PI / 128);
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
point = g_rgb_leds[i].point;
hsv.h = (2 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * (point.y - 32.0)* cos_value + (2 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * (66 - abs(point.x - 112.0)) * sin_value + rgb_matrix_config.hue;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_rainbow_moving_chevron(void) {
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val };
RGB rgb;
Point point;
uint8_t r = 128;
double cos_value = cos(r * PI / 128);
double sin_value = sin(r * PI / 128);
double multiplier = (g_tick / 256.0 * 224);
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
point = g_rgb_leds[i].point;
hsv.h = (1.5 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * abs(point.y - 32.0)* sin_value + (1.5 * (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed)) * (point.x - multiplier) * cos_value + rgb_matrix_config.hue;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
void rgb_matrix_jellybean_raindrops( bool initialize ) {
HSV hsv;
RGB rgb;
// Change one LED every tick, make sure speed is not 0
uint8_t led_to_change = ( g_tick & ( 0x0A / (rgb_matrix_config.speed == 0 ? 1 : rgb_matrix_config.speed) ) ) == 0 ? rand() % (DRIVER_LED_TOTAL) : 255;
for ( int i=0; i<DRIVER_LED_TOTAL; i++ ) static void rgb_task_timers(void) {
{ // Update double buffer timers
// If initialize, all get set to random colors uint16_t deltaTime = timer_elapsed32(rgb_counters_buffer);
// If not, all but one will stay the same as before. rgb_counters_buffer = timer_read32();
if ( initialize || i == led_to_change ) if (g_rgb_counters.any_key_hit < UINT32_MAX) {
{ if (UINT32_MAX - deltaTime < g_rgb_counters.any_key_hit) {
hsv.h = rand() & 0xFF; g_rgb_counters.any_key_hit = UINT32_MAX;
hsv.s = rand() & 0xFF; } else {
// Override brightness with global brightness control g_rgb_counters.any_key_hit += deltaTime;
hsv.v = rgb_matrix_config.val;
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
}
}
void rgb_matrix_digital_rain( const bool initialize ) {
// algorithm ported from https://github.com/tremby/Kaleidoscope-LEDEffect-DigitalRain
const uint8_t drop_ticks = 28;
const uint8_t pure_green_intensity = 0xd0;
const uint8_t max_brightness_boost = 0xc0;
const uint8_t max_intensity = 0xff;
static uint8_t map[MATRIX_COLS][MATRIX_ROWS] = {{0}};
static uint8_t drop = 0;
if (initialize) {
rgb_matrix_set_color_all(0, 0, 0);
memset(map, 0, sizeof map);
drop = 0;
}
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
if (row == 0 && drop == 0 && rand() < RAND_MAX / RGB_DIGITAL_RAIN_DROPS) {
// top row, pixels have just fallen and we're
// making a new rain drop in this column
map[col][row] = max_intensity;
}
else if (map[col][row] > 0 && map[col][row] < max_intensity) {
// neither fully bright nor dark, decay it
map[col][row]--;
}
// set the pixel colour
uint8_t led, led_count;
map_row_column_to_led(row, col, &led, &led_count);
if (map[col][row] > pure_green_intensity) {
const uint8_t boost = (uint8_t) ((uint16_t) max_brightness_boost
* (map[col][row] - pure_green_intensity) / (max_intensity - pure_green_intensity));
rgb_matrix_set_color(led, boost, max_intensity, boost);
}
else {
const uint8_t green = (uint8_t) ((uint16_t) max_intensity * map[col][row] / pure_green_intensity);
rgb_matrix_set_color(led, 0, green, 0);
}
}
}
if (++drop > drop_ticks) {
// reset drop timer
drop = 0;
for (uint8_t row = MATRIX_ROWS - 1; row > 0; row--) {
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
// if ths is on the bottom row and bright allow decay
if (row == MATRIX_ROWS - 1 && map[col][row] == max_intensity) {
map[col][row]--;
}
// check if the pixel above is bright
if (map[col][row - 1] == max_intensity) {
// allow old bright pixel to decay
map[col][row - 1]--;
// make this pixel bright
map[col][row] = max_intensity;
}
}
} }
} }
}
void rgb_matrix_multisplash(void) { // Update double buffer last hit timers
// if (g_any_key_hit < 0xFF) { #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val }; uint8_t count = last_hit_buffer.count;
RGB rgb; for (uint8_t i = 0; i < count; ++i) {
rgb_led led; if (UINT16_MAX - deltaTime < last_hit_buffer.tick[i]) {
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) { last_hit_buffer.count--;
led = g_rgb_leds[i]; continue;
uint16_t c = 0, d = 0;
rgb_led last_led;
// if (g_last_led_count) {
for (uint8_t last_i = 0; last_i < g_last_led_count; last_i++) {
last_led = g_rgb_leds[g_last_led_hit[last_i]];
uint16_t dist = (uint16_t)sqrt(pow(led.point.x - last_led.point.x, 2) + pow(led.point.y - last_led.point.y, 2));
uint16_t effect = (g_key_hit[g_last_led_hit[last_i]] << 2) - dist;
c += MIN(MAX(effect, 0), 255);
d += 255 - MIN(MAX(effect, 0), 255);
} }
// } else { last_hit_buffer.tick[i] += deltaTime;
// d = 255;
// }
hsv.h = (rgb_matrix_config.hue + c) % 256;
hsv.v = MAX(MIN(d, 255), 0);
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
} }
// } else { #endif // RGB_MATRIX_KEYREACTIVE_ENABLED
// rgb_matrix_set_color_all( 0, 0, 0 );
// }
} }
static void rgb_task_sync(void) {
void rgb_matrix_splash(void) { // next task
g_last_led_count = MIN(g_last_led_count, 1); if (timer_elapsed32(g_rgb_counters.tick) >= RGB_MATRIX_LED_FLUSH_LIMIT)
rgb_matrix_multisplash(); rgb_task_state = STARTING;
} }
static void rgb_task_start(void) {
// reset iter
rgb_effect_params.iter = 0;
void rgb_matrix_solid_multisplash(void) { // update double buffers
// if (g_any_key_hit < 0xFF) { g_rgb_counters.tick = rgb_counters_buffer;
HSV hsv = { .h = rgb_matrix_config.hue, .s = rgb_matrix_config.sat, .v = rgb_matrix_config.val }; #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
RGB rgb; g_last_hit_tracker = last_hit_buffer;
rgb_led led; #endif // RGB_MATRIX_KEYREACTIVE_ENABLED
for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
led = g_rgb_leds[i];
uint16_t d = 0;
rgb_led last_led;
// if (g_last_led_count) {
for (uint8_t last_i = 0; last_i < g_last_led_count; last_i++) {
last_led = g_rgb_leds[g_last_led_hit[last_i]];
uint16_t dist = (uint16_t)sqrt(pow(led.point.x - last_led.point.x, 2) + pow(led.point.y - last_led.point.y, 2));
uint16_t effect = (g_key_hit[g_last_led_hit[last_i]] << 2) - dist;
d += 255 - MIN(MAX(effect, 0), 255);
}
// } else {
// d = 255;
// }
hsv.v = MAX(MIN(d, 255), 0);
rgb = hsv_to_rgb( hsv );
rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
}
// } else {
// rgb_matrix_set_color_all( 0, 0, 0 );
// }
}
void rgb_matrix_solid_splash(void) { // next task
g_last_led_count = MIN(g_last_led_count, 1); rgb_task_state = RENDERING;
rgb_matrix_solid_multisplash();
} }
static void rgb_task_render(uint8_t effect) {
bool rendering = false;
rgb_effect_params.init = (effect != rgb_last_effect) || (rgb_matrix_config.enable != rgb_last_enable);
// Needs eeprom access that we don't have setup currently
void rgb_matrix_custom(void) {
// HSV hsv;
// RGB rgb;
// for ( int i=0; i<DRIVER_LED_TOTAL; i++ )
// {
// backlight_get_key_color(i, &hsv);
// // Override brightness with global brightness control
// hsv.v = rgb_matrix_config.val;
// rgb = hsv_to_rgb( hsv );
// rgb_matrix_set_color( i, rgb.r, rgb.g, rgb.b );
// }
}
void rgb_matrix_task(void) {
#ifdef TRACK_PREVIOUS_EFFECT
static uint8_t toggle_enable_last = 255;
#endif
if (!rgb_matrix_config.enable) {
rgb_matrix_all_off();
rgb_matrix_indicators();
#ifdef TRACK_PREVIOUS_EFFECT
toggle_enable_last = rgb_matrix_config.enable;
#endif
return;
}
// delay 1 second before driving LEDs or doing anything else
static uint8_t startup_tick = 0;
if ( startup_tick < 20 ) {
startup_tick++;
return;
}
g_tick++;
if ( g_any_key_hit < 0xFFFFFFFF ) {
g_any_key_hit++;
}
for ( int led = 0; led < DRIVER_LED_TOTAL; led++ ) {
if ( g_key_hit[led] < 255 ) {
if (g_key_hit[led] == 254)
g_last_led_count = MAX(g_last_led_count - 1, 0);
g_key_hit[led]++;
}
}
// Factory default magic value
if ( rgb_matrix_config.mode == 255 ) {
rgb_matrix_test();
return;
}
// Ideally we would also stop sending zeros to the LED driver PWM buffers
// while suspended and just do a software shutdown. This is a cheap hack for now.
bool suspend_backlight = ((g_suspend_state && RGB_DISABLE_WHEN_USB_SUSPENDED) ||
(RGB_DISABLE_AFTER_TIMEOUT > 0 && g_any_key_hit > RGB_DISABLE_AFTER_TIMEOUT * 60 * 20));
uint8_t effect = suspend_backlight ? 0 : rgb_matrix_config.mode;
#ifdef TRACK_PREVIOUS_EFFECT
// Keep track of the effect used last time,
// detect change in effect, so each effect can
// have an optional initialization.
static uint8_t effect_last = 255;
bool initialize = (effect != effect_last) || (rgb_matrix_config.enable != toggle_enable_last);
effect_last = effect;
toggle_enable_last = rgb_matrix_config.enable;
#endif
// this gets ticked at 20 Hz.
// each effect can opt to do calculations // each effect can opt to do calculations
// and/or request PWM buffer updates. // and/or request PWM buffer updates.
switch ( effect ) { switch (effect) {
case RGB_MATRIX_NONE:
rendering = rgb_matrix_none(&rgb_effect_params);
break;
case RGB_MATRIX_SOLID_COLOR: case RGB_MATRIX_SOLID_COLOR:
rgb_matrix_solid_color(); rendering = rgb_matrix_solid_color(&rgb_effect_params); // Max 1ms Avg 0ms
break; break;
#ifndef DISABLE_RGB_MATRIX_ALPHAS_MODS #ifndef DISABLE_RGB_MATRIX_ALPHAS_MODS
case RGB_MATRIX_ALPHAS_MODS: case RGB_MATRIX_ALPHAS_MODS:
rgb_matrix_alphas_mods(); rendering = rgb_matrix_alphas_mods(&rgb_effect_params); // Max 2ms Avg 1ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_ALPHAS_MODS
#ifndef DISABLE_RGB_MATRIX_DUAL_BEACON #ifndef DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
case RGB_MATRIX_DUAL_BEACON:
rgb_matrix_dual_beacon();
break;
#endif
#ifndef DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
case RGB_MATRIX_GRADIENT_UP_DOWN: case RGB_MATRIX_GRADIENT_UP_DOWN:
rgb_matrix_gradient_up_down(); rendering = rgb_matrix_gradient_up_down(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
#ifndef DISABLE_RGB_MATRIX_RAINDROPS #ifndef DISABLE_RGB_MATRIX_BREATHING
case RGB_MATRIX_RAINDROPS: case RGB_MATRIX_BREATHING:
rgb_matrix_raindrops( initialize ); rendering = rgb_matrix_breathing(&rgb_effect_params); // Max 1ms Avg 0ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_BREATHING
#ifndef DISABLE_RGB_MATRIX_CYCLE_ALL #ifndef DISABLE_RGB_MATRIX_CYCLE_ALL
case RGB_MATRIX_CYCLE_ALL: case RGB_MATRIX_CYCLE_ALL:
rgb_matrix_cycle_all(); rendering = rgb_matrix_cycle_all(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_CYCLE_ALL
#ifndef DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT #ifndef DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
case RGB_MATRIX_CYCLE_LEFT_RIGHT: case RGB_MATRIX_CYCLE_LEFT_RIGHT:
rgb_matrix_cycle_left_right(); rendering = rgb_matrix_cycle_left_right(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
#ifndef DISABLE_RGB_MATRIX_CYCLE_UP_DOWN #ifndef DISABLE_RGB_MATRIX_CYCLE_UP_DOWN
case RGB_MATRIX_CYCLE_UP_DOWN: case RGB_MATRIX_CYCLE_UP_DOWN:
rgb_matrix_cycle_up_down(); rendering = rgb_matrix_cycle_up_down(&rgb_effect_params); // Max 4ms Avg 3ms
break;
#endif // DISABLE_RGB_MATRIX_CYCLE_UP_DOWN
#ifndef DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
case RGB_MATRIX_RAINBOW_MOVING_CHEVRON:
rendering = rgb_matrix_rainbow_moving_chevron(&rgb_effect_params); // Max 4ms Avg 3ms
break;
#endif // DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
#ifndef DISABLE_RGB_MATRIX_DUAL_BEACON
case RGB_MATRIX_DUAL_BEACON:
rendering = rgb_matrix_dual_beacon(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_DUAL_BEACON
#ifndef DISABLE_RGB_MATRIX_RAINBOW_BEACON #ifndef DISABLE_RGB_MATRIX_RAINBOW_BEACON
case RGB_MATRIX_RAINBOW_BEACON: case RGB_MATRIX_RAINBOW_BEACON:
rgb_matrix_rainbow_beacon(); rendering = rgb_matrix_rainbow_beacon(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_RAINBOW_BEACON
#ifndef DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS #ifndef DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS
case RGB_MATRIX_RAINBOW_PINWHEELS: case RGB_MATRIX_RAINBOW_PINWHEELS:
rgb_matrix_rainbow_pinwheels(); rendering = rgb_matrix_rainbow_pinwheels(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS
#ifndef DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON #ifndef DISABLE_RGB_MATRIX_RAINDROPS
case RGB_MATRIX_RAINBOW_MOVING_CHEVRON: case RGB_MATRIX_RAINDROPS:
rgb_matrix_rainbow_moving_chevron(); rendering = rgb_matrix_raindrops(&rgb_effect_params); // Max 1ms Avg 0ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_RAINDROPS
#ifndef DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS #ifndef DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
case RGB_MATRIX_JELLYBEAN_RAINDROPS: case RGB_MATRIX_JELLYBEAN_RAINDROPS:
rgb_matrix_jellybean_raindrops( initialize ); rendering = rgb_matrix_jellybean_raindrops(&rgb_effect_params); // Max 1ms Avg 0ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
#ifndef DISABLE_RGB_MATRIX_DIGITAL_RAIN #ifndef DISABLE_RGB_MATRIX_DIGITAL_RAIN
case RGB_MATRIX_DIGITAL_RAIN: case RGB_MATRIX_DIGITAL_RAIN:
rgb_matrix_digital_rain( initialize ); rendering = rgb_matrix_digital_rain(&rgb_effect_params); // Max 9ms Avg 8ms | this is expensive, fix it
break;
#endif
#ifdef RGB_MATRIX_KEYPRESSES
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE
case RGB_MATRIX_SOLID_REACTIVE:
rgb_matrix_solid_reactive();
break; break;
#endif #endif // DISABLE_RGB_MATRIX_DIGITAL_RAIN
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
case RGB_MATRIX_SOLID_REACTIVE_SIMPLE: case RGB_MATRIX_SOLID_REACTIVE_SIMPLE:
rgb_matrix_solid_reactive_simple(); rendering = rgb_matrix_solid_reactive_simple(&rgb_effect_params);// Max 4ms Avg 3ms
break;
#endif
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE
case RGB_MATRIX_SOLID_REACTIVE:
rendering = rgb_matrix_solid_reactive(&rgb_effect_params); // Max 4ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_SOLID_REACTIVE
#ifndef DISABLE_RGB_MATRIX_SPLASH #ifndef DISABLE_RGB_MATRIX_SPLASH
case RGB_MATRIX_SPLASH: case RGB_MATRIX_SPLASH:
rgb_matrix_splash(); rendering = rgb_matrix_splash(&rgb_effect_params); // Max 5ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_SPLASH
#ifndef DISABLE_RGB_MATRIX_MULTISPLASH #ifndef DISABLE_RGB_MATRIX_MULTISPLASH
case RGB_MATRIX_MULTISPLASH: case RGB_MATRIX_MULTISPLASH:
rgb_matrix_multisplash(); rendering = rgb_matrix_multisplash(&rgb_effect_params); // Max 10ms Avg 5ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_MULTISPLASH
#ifndef DISABLE_RGB_MATRIX_SOLID_SPLASH #ifndef DISABLE_RGB_MATRIX_SOLID_SPLASH
case RGB_MATRIX_SOLID_SPLASH: case RGB_MATRIX_SOLID_SPLASH:
rgb_matrix_solid_splash(); rendering = rgb_matrix_solid_splash(&rgb_effect_params); // Max 5ms Avg 3ms
break; break;
#endif #endif // DISABLE_RGB_MATRIX_SOLID_SPLASH
#ifndef DISABLE_RGB_MATRIX_SOLID_MULTISPLASH #ifndef DISABLE_RGB_MATRIX_SOLID_MULTISPLASH
case RGB_MATRIX_SOLID_MULTISPLASH: case RGB_MATRIX_SOLID_MULTISPLASH:
rgb_matrix_solid_multisplash(); rendering = rgb_matrix_solid_multisplash(&rgb_effect_params); // Max 10ms Avg 5ms
break;
#endif // DISABLE_RGB_MATRIX_SOLID_MULTISPLASH
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
// Factory default magic value
case UINT8_MAX: {
rgb_matrix_test();
rgb_task_state = FLUSHING;
}
return;
}
rgb_effect_params.iter++;
// next task
if (!rendering) {
rgb_task_state = FLUSHING;
if (!rgb_effect_params.init && effect == RGB_MATRIX_NONE) {
// We only need to flush once if we are RGB_MATRIX_NONE
rgb_task_state = SYNCING;
}
}
}
static void rgb_task_flush(uint8_t effect) {
// update last trackers after the first full render so we can init over several frames
rgb_last_effect = effect;
rgb_last_enable = rgb_matrix_config.enable;
// update pwm buffers
rgb_matrix_update_pwm_buffers();
// next task
rgb_task_state = SYNCING;
}
void rgb_matrix_task(void) {
rgb_task_timers();
// Ideally we would also stop sending zeros to the LED driver PWM buffers
// while suspended and just do a software shutdown. This is a cheap hack for now.
bool suspend_backlight = ((g_suspend_state && RGB_DISABLE_WHEN_USB_SUSPENDED) || (RGB_DISABLE_AFTER_TIMEOUT > 0 && g_rgb_counters.any_key_hit > RGB_DISABLE_AFTER_TIMEOUT * 60 * 20));
uint8_t effect = suspend_backlight || !rgb_matrix_config.enable ? 0 : rgb_matrix_config.mode;
switch (rgb_task_state) {
case STARTING:
rgb_task_start();
break; break;
#endif case RENDERING:
#endif rgb_task_render(effect);
default: break;
rgb_matrix_custom(); case FLUSHING:
rgb_task_flush(effect);
break;
case SYNCING:
rgb_task_sync();
break; break;
} }
if ( ! suspend_backlight ) { if (!suspend_backlight) {
rgb_matrix_indicators(); rgb_matrix_indicators();
} }
} }
void rgb_matrix_indicators(void) { void rgb_matrix_indicators(void) {
@ -817,41 +456,31 @@ void rgb_matrix_indicators_kb(void) {}
__attribute__((weak)) __attribute__((weak))
void rgb_matrix_indicators_user(void) {} void rgb_matrix_indicators_user(void) {}
// void rgb_matrix_set_indicator_index( uint8_t *index, uint8_t row, uint8_t column )
// {
// if ( row >= MATRIX_ROWS )
// {
// // Special value, 255=none, 254=all
// *index = row;
// }
// else
// {
// // This needs updated to something like
// // uint8_t led[8], led_count;
// // map_row_column_to_led(row,column,led,&led_count);
// // for(uint8_t i = 0; i < led_count; i++)
// map_row_column_to_led( row, column, index );
// }
// }
void rgb_matrix_init(void) { void rgb_matrix_init(void) {
rgb_matrix_driver.init(); rgb_matrix_driver.init();
// TODO: put the 1 second startup delay here? // TODO: put the 1 second startup delay here?
// clear the key hits #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
for ( int led=0; led<DRIVER_LED_TOTAL; led++ ) { g_last_hit_tracker.count = 0;
g_key_hit[led] = 255; for (uint8_t i = 0; i < LED_HITS_TO_REMEMBER; ++i) {
g_last_hit_tracker.tick[i] = UINT16_MAX;
} }
last_hit_buffer.count = 0;
for (uint8_t i = 0; i < LED_HITS_TO_REMEMBER; ++i) {
last_hit_buffer.tick[i] = UINT16_MAX;
}
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
if (!eeconfig_is_enabled()) { if (!eeconfig_is_enabled()) {
dprintf("rgb_matrix_init_drivers eeconfig is not enabled.\n"); dprintf("rgb_matrix_init_drivers eeconfig is not enabled.\n");
eeconfig_init(); eeconfig_init();
eeconfig_update_rgb_matrix_default(); eeconfig_update_rgb_matrix_default();
} }
rgb_matrix_config.raw = eeconfig_read_rgb_matrix(); rgb_matrix_config.raw = eeconfig_read_rgb_matrix();
rgb_matrix_config.speed = UINT8_MAX / 2; //EECONFIG needs to be increased to support this
if (!rgb_matrix_config.mode) { if (!rgb_matrix_config.mode) {
dprintf("rgb_matrix_init_drivers rgb_matrix_config.mode = 0. Write default values to EEPROM.\n"); dprintf("rgb_matrix_init_drivers rgb_matrix_config.mode = 0. Write default values to EEPROM.\n");
eeconfig_update_rgb_matrix_default(); eeconfig_update_rgb_matrix_default();
@ -860,54 +489,15 @@ void rgb_matrix_init(void) {
eeconfig_debug_rgb_matrix(); // display current eeprom values eeconfig_debug_rgb_matrix(); // display current eeprom values
} }
// Deals with the messy details of incrementing an integer void rgb_matrix_set_suspend_state(bool state) {
static uint8_t increment( uint8_t value, uint8_t step, uint8_t min, uint8_t max ) { g_suspend_state = state;
int16_t new_value = value;
new_value += step;
return MIN( MAX( new_value, min ), max );
}
static uint8_t decrement( uint8_t value, uint8_t step, uint8_t min, uint8_t max ) {
int16_t new_value = value;
new_value -= step;
return MIN( MAX( new_value, min ), max );
}
// void *backlight_get_custom_key_color_eeprom_address( uint8_t led )
// {
// // 3 bytes per color
// return EECONFIG_RGB_MATRIX + ( led * 3 );
// }
// void backlight_get_key_color( uint8_t led, HSV *hsv )
// {
// void *address = backlight_get_custom_key_color_eeprom_address( led );
// hsv->h = eeprom_read_byte(address);
// hsv->s = eeprom_read_byte(address+1);
// hsv->v = eeprom_read_byte(address+2);
// }
// void backlight_set_key_color( uint8_t row, uint8_t column, HSV hsv )
// {
// uint8_t led[8], led_count;
// map_row_column_to_led(row,column,led,&led_count);
// for(uint8_t i = 0; i < led_count; i++) {
// if ( led[i] < DRIVER_LED_TOTAL )
// {
// void *address = backlight_get_custom_key_color_eeprom_address(led[i]);
// eeprom_update_byte(address, hsv.h);
// eeprom_update_byte(address+1, hsv.s);
// eeprom_update_byte(address+2, hsv.v);
// }
// }
// }
uint32_t rgb_matrix_get_tick(void) {
return g_tick;
} }
void rgb_matrix_toggle(void) { void rgb_matrix_toggle(void) {
rgb_matrix_config.enable ^= 1; rgb_matrix_config.enable ^= 1;
if (!rgb_matrix_config.enable) {
rgb_task_state = STARTING;
}
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
@ -933,6 +523,7 @@ void rgb_matrix_step(void) {
rgb_matrix_config.mode++; rgb_matrix_config.mode++;
if (rgb_matrix_config.mode >= RGB_MATRIX_EFFECT_MAX) if (rgb_matrix_config.mode >= RGB_MATRIX_EFFECT_MAX)
rgb_matrix_config.mode = 1; rgb_matrix_config.mode = 1;
rgb_task_state = STARTING;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
@ -940,51 +531,55 @@ void rgb_matrix_step_reverse(void) {
rgb_matrix_config.mode--; rgb_matrix_config.mode--;
if (rgb_matrix_config.mode < 1) if (rgb_matrix_config.mode < 1)
rgb_matrix_config.mode = RGB_MATRIX_EFFECT_MAX - 1; rgb_matrix_config.mode = RGB_MATRIX_EFFECT_MAX - 1;
rgb_task_state = STARTING;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_increase_hue(void) { void rgb_matrix_increase_hue(void) {
rgb_matrix_config.hue = increment( rgb_matrix_config.hue, 8, 0, 255 ); rgb_matrix_config.hue += RGB_MATRIX_HUE_STEP;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_decrease_hue(void) { void rgb_matrix_decrease_hue(void) {
rgb_matrix_config.hue = decrement( rgb_matrix_config.hue, 8, 0, 255 ); rgb_matrix_config.hue -= RGB_MATRIX_HUE_STEP;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_increase_sat(void) { void rgb_matrix_increase_sat(void) {
rgb_matrix_config.sat = increment( rgb_matrix_config.sat, 8, 0, 255 ); rgb_matrix_config.sat = qadd8(rgb_matrix_config.sat, RGB_MATRIX_SAT_STEP);
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_decrease_sat(void) { void rgb_matrix_decrease_sat(void) {
rgb_matrix_config.sat = decrement( rgb_matrix_config.sat, 8, 0, 255 ); rgb_matrix_config.sat = qsub8(rgb_matrix_config.sat, RGB_MATRIX_SAT_STEP);
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_increase_val(void) { void rgb_matrix_increase_val(void) {
rgb_matrix_config.val = increment( rgb_matrix_config.val, 8, 0, RGB_MATRIX_MAXIMUM_BRIGHTNESS ); rgb_matrix_config.val = qadd8(rgb_matrix_config.val, RGB_MATRIX_VAL_STEP);
if (rgb_matrix_config.val > RGB_MATRIX_MAXIMUM_BRIGHTNESS)
rgb_matrix_config.val = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_decrease_val(void) { void rgb_matrix_decrease_val(void) {
rgb_matrix_config.val = decrement( rgb_matrix_config.val, 8, 0, RGB_MATRIX_MAXIMUM_BRIGHTNESS ); rgb_matrix_config.val = qsub8(rgb_matrix_config.val, RGB_MATRIX_VAL_STEP);
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
void rgb_matrix_increase_speed(void) { void rgb_matrix_increase_speed(void) {
rgb_matrix_config.speed = increment( rgb_matrix_config.speed, 1, 0, 3 ); rgb_matrix_config.speed = qadd8(rgb_matrix_config.speed, RGB_MATRIX_SPD_STEP);
eeconfig_update_rgb_matrix(rgb_matrix_config.raw);//EECONFIG needs to be increased to support this eeconfig_update_rgb_matrix(rgb_matrix_config.raw);//EECONFIG needs to be increased to support this
} }
void rgb_matrix_decrease_speed(void) { void rgb_matrix_decrease_speed(void) {
rgb_matrix_config.speed = decrement( rgb_matrix_config.speed, 1, 0, 3 ); rgb_matrix_config.speed = qsub8(rgb_matrix_config.speed, RGB_MATRIX_SPD_STEP);
eeconfig_update_rgb_matrix(rgb_matrix_config.raw);//EECONFIG needs to be increased to support this eeconfig_update_rgb_matrix(rgb_matrix_config.raw);//EECONFIG needs to be increased to support this
} }
void rgb_matrix_mode(uint8_t mode) { void rgb_matrix_mode(uint8_t mode) {
rgb_matrix_config.mode = mode; rgb_matrix_config.mode = mode;
rgb_task_state = STARTING;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
@ -997,9 +592,7 @@ uint8_t rgb_matrix_get_mode(void) {
} }
void rgb_matrix_sethsv(uint16_t hue, uint8_t sat, uint8_t val) { void rgb_matrix_sethsv(uint16_t hue, uint8_t sat, uint8_t val) {
rgb_matrix_config.hue = hue; rgb_matrix_sethsv_noeeprom(hue, sat, val);
rgb_matrix_config.sat = sat;
rgb_matrix_config.val = val;
eeconfig_update_rgb_matrix(rgb_matrix_config.raw); eeconfig_update_rgb_matrix(rgb_matrix_config.raw);
} }
@ -1007,4 +600,6 @@ void rgb_matrix_sethsv_noeeprom(uint16_t hue, uint8_t sat, uint8_t val) {
rgb_matrix_config.hue = hue; rgb_matrix_config.hue = hue;
rgb_matrix_config.sat = sat; rgb_matrix_config.sat = sat;
rgb_matrix_config.val = val; rgb_matrix_config.val = val;
if (rgb_matrix_config.val > RGB_MATRIX_MAXIMUM_BRIGHTNESS)
rgb_matrix_config.val = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
} }

@ -21,6 +21,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "rgb_matrix_types.h"
#include "color.h" #include "color.h"
#include "quantum.h" #include "quantum.h"
@ -30,23 +31,23 @@
#include "is31fl3733.h" #include "is31fl3733.h"
#endif #endif
typedef struct Point { #ifndef RGB_MATRIX_LED_FLUSH_LIMIT
uint8_t x; #define RGB_MATRIX_LED_FLUSH_LIMIT 16
uint8_t y; #endif
} __attribute__((packed)) Point;
typedef struct rgb_led { #ifndef RGB_MATRIX_LED_PROCESS_LIMIT
union { #define RGB_MATRIX_LED_PROCESS_LIMIT (DRIVER_LED_TOTAL + 4) / 5
uint8_t raw; #endif
struct {
uint8_t row:4; // 16 max
uint8_t col:4; // 16 max
};
} matrix_co;
Point point;
uint8_t modifier:1;
} __attribute__((packed)) rgb_led;
#if defined(RGB_MATRIX_LED_PROCESS_LIMIT) && RGB_MATRIX_LED_PROCESS_LIMIT > 0 && RGB_MATRIX_LED_PROCESS_LIMIT < DRIVER_LED_TOTAL
#define RGB_MATRIX_USE_LIMITS(min, max) uint8_t min = RGB_MATRIX_LED_PROCESS_LIMIT * params->iter; \
uint8_t max = min + RGB_MATRIX_LED_PROCESS_LIMIT; \
if (max > DRIVER_LED_TOTAL) \
max = DRIVER_LED_TOTAL;
#else
#define RGB_MATRIX_USE_LIMITS(min, max) uint8_t min = 0; \
uint8_t max = DRIVER_LED_TOTAL;
#endif
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL]; extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
@ -56,79 +57,73 @@ typedef struct
uint8_t index; uint8_t index;
} rgb_indicator; } rgb_indicator;
typedef union {
uint32_t raw;
struct {
bool enable :1;
uint8_t mode :6;
uint16_t hue :9;
uint8_t sat :8;
uint8_t val :8;
uint8_t speed :8;//EECONFIG needs to be increased to support this
};
} rgb_config_t;
enum rgb_matrix_effects { enum rgb_matrix_effects {
RGB_MATRIX_NONE = 0,
RGB_MATRIX_SOLID_COLOR = 1, RGB_MATRIX_SOLID_COLOR = 1,
#ifndef DISABLE_RGB_MATRIX_ALPHAS_MODS #ifndef DISABLE_RGB_MATRIX_ALPHAS_MODS
RGB_MATRIX_ALPHAS_MODS, RGB_MATRIX_ALPHAS_MODS,
#endif #endif // DISABLE_RGB_MATRIX_ALPHAS_MODS
#ifndef DISABLE_RGB_MATRIX_DUAL_BEACON
RGB_MATRIX_DUAL_BEACON,
#endif
#ifndef DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN #ifndef DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
RGB_MATRIX_GRADIENT_UP_DOWN, RGB_MATRIX_GRADIENT_UP_DOWN,
#endif #endif // DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
#ifndef DISABLE_RGB_MATRIX_RAINDROPS #ifndef DISABLE_RGB_MATRIX_BREATHING
RGB_MATRIX_RAINDROPS, RGB_MATRIX_BREATHING,
#endif #endif // DISABLE_RGB_MATRIX_BREATHING
#ifndef DISABLE_RGB_MATRIX_CYCLE_ALL #ifndef DISABLE_RGB_MATRIX_CYCLE_ALL
RGB_MATRIX_CYCLE_ALL, RGB_MATRIX_CYCLE_ALL,
#endif #endif // DISABLE_RGB_MATRIX_CYCLE_ALL
#ifndef DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT #ifndef DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
RGB_MATRIX_CYCLE_LEFT_RIGHT, RGB_MATRIX_CYCLE_LEFT_RIGHT,
#endif #endif // DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
#ifndef DISABLE_RGB_MATRIX_CYCLE_UP_DOWN #ifndef DISABLE_RGB_MATRIX_CYCLE_UP_DOWN
RGB_MATRIX_CYCLE_UP_DOWN, RGB_MATRIX_CYCLE_UP_DOWN,
#endif #endif // DISABLE_RGB_MATRIX_CYCLE_UP_DOWN
#ifndef DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
RGB_MATRIX_RAINBOW_MOVING_CHEVRON,
#endif // DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
#ifndef DISABLE_RGB_MATRIX_DUAL_BEACON
RGB_MATRIX_DUAL_BEACON,
#endif // DISABLE_RGB_MATRIX_DUAL_BEACON
#ifndef DISABLE_RGB_MATRIX_RAINBOW_BEACON #ifndef DISABLE_RGB_MATRIX_RAINBOW_BEACON
RGB_MATRIX_RAINBOW_BEACON, RGB_MATRIX_RAINBOW_BEACON,
#endif #endif // DISABLE_RGB_MATRIX_RAINBOW_BEACON
#ifndef DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS #ifndef DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS
RGB_MATRIX_RAINBOW_PINWHEELS, RGB_MATRIX_RAINBOW_PINWHEELS,
#endif #endif // DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS
#ifndef DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON #ifndef DISABLE_RGB_MATRIX_RAINDROPS
RGB_MATRIX_RAINBOW_MOVING_CHEVRON, RGB_MATRIX_RAINDROPS,
#endif #endif // DISABLE_RGB_MATRIX_RAINDROPS
#ifndef DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS #ifndef DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
RGB_MATRIX_JELLYBEAN_RAINDROPS, RGB_MATRIX_JELLYBEAN_RAINDROPS,
#endif #endif // DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
#ifndef DISABLE_RGB_MATRIX_DIGITAL_RAIN #ifndef DISABLE_RGB_MATRIX_DIGITAL_RAIN
RGB_MATRIX_DIGITAL_RAIN, RGB_MATRIX_DIGITAL_RAIN,
#endif #endif // DISABLE_RGB_MATRIX_DIGITAL_RAIN
#ifdef RGB_MATRIX_KEYPRESSES #ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE #ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
RGB_MATRIX_SOLID_REACTIVE,
#endif
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
RGB_MATRIX_SOLID_REACTIVE_SIMPLE, RGB_MATRIX_SOLID_REACTIVE_SIMPLE,
#endif #endif // DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
#ifndef DISABLE_RGB_MATRIX_SPLASH #ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE
RGB_MATRIX_SOLID_REACTIVE,
#endif // DISABLE_RGB_MATRIX_SOLID_REACTIVE
#ifndef DISABLE_RGB_MATRIX_SPLASH
RGB_MATRIX_SPLASH, RGB_MATRIX_SPLASH,
#endif #endif // DISABLE_RGB_MATRIX_SPLASH
#ifndef DISABLE_RGB_MATRIX_MULTISPLASH #ifndef DISABLE_RGB_MATRIX_MULTISPLASH
RGB_MATRIX_MULTISPLASH, RGB_MATRIX_MULTISPLASH,
#endif #endif // DISABLE_RGB_MATRIX_MULTISPLASH
#ifndef DISABLE_RGB_MATRIX_SOLID_SPLASH #ifndef DISABLE_RGB_MATRIX_SOLID_SPLASH
RGB_MATRIX_SOLID_SPLASH, RGB_MATRIX_SOLID_SPLASH,
#endif #endif // DISABLE_RGB_MATRIX_SOLID_SPLASH
#ifndef DISABLE_RGB_MATRIX_SOLID_MULTISPLASH #ifndef DISABLE_RGB_MATRIX_SOLID_MULTISPLASH
RGB_MATRIX_SOLID_MULTISPLASH, RGB_MATRIX_SOLID_MULTISPLASH,
#endif #endif // DISABLE_RGB_MATRIX_SOLID_MULTISPLASH
#endif #endif // RGB_MATRIX_KEYREACTIVE_ENABLED
RGB_MATRIX_EFFECT_MAX RGB_MATRIX_EFFECT_MAX
}; };
uint8_t rgb_matrix_map_row_column_to_led( uint8_t row, uint8_t column, uint8_t *led_i);
void rgb_matrix_set_color( int index, uint8_t red, uint8_t green, uint8_t blue ); void rgb_matrix_set_color( int index, uint8_t red, uint8_t green, uint8_t blue );
void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue ); void rgb_matrix_set_color_all( uint8_t red, uint8_t green, uint8_t blue );
@ -162,8 +157,6 @@ void rgb_matrix_decrease(void);
// void backlight_get_key_color( uint8_t led, HSV *hsv ); // void backlight_get_key_color( uint8_t led, HSV *hsv );
// void backlight_set_key_color( uint8_t row, uint8_t column, HSV hsv ); // void backlight_set_key_color( uint8_t row, uint8_t column, HSV hsv );
uint32_t rgb_matrix_get_tick(void);
void rgb_matrix_toggle(void); void rgb_matrix_toggle(void);
void rgb_matrix_enable(void); void rgb_matrix_enable(void);
void rgb_matrix_enable_noeeprom(void); void rgb_matrix_enable_noeeprom(void);
@ -212,7 +205,6 @@ uint8_t rgb_matrix_get_mode(void);
typedef struct { typedef struct {
/* Perform any initialisation required for the other driver functions to work. */ /* Perform any initialisation required for the other driver functions to work. */
void (*init)(void); void (*init)(void);
/* Set the colour of a single LED in the buffer. */ /* Set the colour of a single LED in the buffer. */
void (*set_color)(int index, uint8_t r, uint8_t g, uint8_t b); void (*set_color)(int index, uint8_t r, uint8_t g, uint8_t b);
/* Set the colour of all LEDS on the keyboard in the buffer. */ /* Set the colour of all LEDS on the keyboard in the buffer. */

@ -0,0 +1,26 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_ALPHAS_MODS
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
// alphas = color1, mods = color2
bool rgb_matrix_alphas_mods(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { rgb_matrix_config.hue, rgb_matrix_config.sat, rgb_matrix_config.val };
RGB rgb1 = hsv_to_rgb(hsv);
hsv.h += rgb_matrix_config.speed;
RGB rgb2 = hsv_to_rgb(hsv);
for (uint8_t i = led_min; i < led_max; i++) {
if (g_rgb_leds[i].modifier) {
rgb_matrix_set_color(i, rgb2.r, rgb2.g, rgb2.b);
} else {
rgb_matrix_set_color(i, rgb1.r, rgb1.g, rgb1.b);
}
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_ALPHAS_MODS

@ -0,0 +1,19 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_BREATHING
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_breathing(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
uint16_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 8);
uint8_t val = scale8(abs8(sin8(time) - 128) * 2, rgb_matrix_config.val);
HSV hsv = { rgb_matrix_config.hue, rgb_matrix_config.sat, val };
RGB rgb = hsv_to_rgb(hsv);
for (uint8_t i = led_min; i < led_max; i++) {
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_BREATHING

@ -0,0 +1,21 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_CYCLE_ALL
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_cycle_all(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint8_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
for (uint8_t i = led_min; i < led_max; i++) {
hsv.h = time;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_CYCLE_ALL

@ -0,0 +1,22 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_cycle_left_right(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint8_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = point.x - time;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT

@ -0,0 +1,22 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_CYCLE_UP_DOWN
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_cycle_up_down(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint8_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = point.y - time;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_CYCLE_UP_DOWN

@ -0,0 +1,74 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_DIGITAL_RAIN
#ifndef RGB_DIGITAL_RAIN_DROPS
// lower the number for denser effect/wider keyboard
#define RGB_DIGITAL_RAIN_DROPS 24
#endif
bool rgb_matrix_digital_rain(effect_params_t* params) {
// algorithm ported from https://github.com/tremby/Kaleidoscope-LEDEffect-DigitalRain
const uint8_t drop_ticks = 28;
const uint8_t pure_green_intensity = 0xd0;
const uint8_t max_brightness_boost = 0xc0;
const uint8_t max_intensity = 0xff;
static uint8_t map[MATRIX_COLS][MATRIX_ROWS] = {{0}};
static uint8_t drop = 0;
if (params->init) {
rgb_matrix_set_color_all(0, 0, 0);
memset(map, 0, sizeof map);
drop = 0;
}
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
if (row == 0 && drop == 0 && rand() < RAND_MAX / RGB_DIGITAL_RAIN_DROPS) {
// top row, pixels have just fallen and we're
// making a new rain drop in this column
map[col][row] = max_intensity;
}
else if (map[col][row] > 0 && map[col][row] < max_intensity) {
// neither fully bright nor dark, decay it
map[col][row]--;
}
// set the pixel colour
uint8_t led[LED_HITS_TO_REMEMBER];
uint8_t led_count = rgb_matrix_map_row_column_to_led(row, col, led);
// TODO: multiple leds are supported mapped to the same row/column
if (led_count > 0) {
if (map[col][row] > pure_green_intensity) {
const uint8_t boost = (uint8_t) ((uint16_t) max_brightness_boost * (map[col][row] - pure_green_intensity) / (max_intensity - pure_green_intensity));
rgb_matrix_set_color(led[0], boost, max_intensity, boost);
}
else {
const uint8_t green = (uint8_t) ((uint16_t) max_intensity * map[col][row] / pure_green_intensity);
rgb_matrix_set_color(led[0], 0, green, 0);
}
}
}
}
if (++drop > drop_ticks) {
// reset drop timer
drop = 0;
for (uint8_t row = MATRIX_ROWS - 1; row > 0; row--) {
for (uint8_t col = 0; col < MATRIX_COLS; col++) {
// if ths is on the bottom row and bright allow decay
if (row == MATRIX_ROWS - 1 && map[col][row] == max_intensity) {
map[col][row]--;
}
// check if the pixel above is bright
if (map[col][row - 1] == max_intensity) {
// allow old bright pixel to decay
map[col][row - 1]--;
// make this pixel bright
map[col][row] = max_intensity;
}
}
}
}
return false;
}
#endif // DISABLE_RGB_MATRIX_DIGITAL_RAIN

@ -0,0 +1,24 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_DUAL_BEACON
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_dual_beacon(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint16_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
int8_t cos_value = cos8(time) - 128;
int8_t sin_value = sin8(time) - 128;
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = ((point.y - 32) * cos_value + (point.x - 112) * sin_value) / 128 + rgb_matrix_config.hue;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_DUAL_BEACON

@ -0,0 +1,22 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_gradient_up_down(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint8_t scale = scale8(64, rgb_matrix_config.speed);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
// The y range will be 0..64, map this to 0..4
// Relies on hue being 8-bit and wrapping
hsv.h = rgb_matrix_config.hue + scale * (point.y >> 4);
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN

@ -0,0 +1,30 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
static void jellybean_raindrops_set_color(int i) {
HSV hsv = { rand() & 0xFF , rand() & 0xFF, rgb_matrix_config.val };
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
bool rgb_matrix_jellybean_raindrops(effect_params_t* params) {
if (!params->init) {
// Change one LED every tick, make sure speed is not 0
if (scale16by8(g_rgb_counters.tick, qadd8(rgb_matrix_config.speed, 16)) % 5 == 0) {
jellybean_raindrops_set_color(rand() % DRIVER_LED_TOTAL);
}
return false;
}
RGB_MATRIX_USE_LIMITS(led_min, led_max);
for (int i = led_min; i < led_max; i++) {
jellybean_raindrops_set_color(i);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS

@ -0,0 +1,24 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_RAINBOW_BEACON
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_rainbow_beacon(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint16_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
int16_t cos_value = 2 * (cos8(time) - 128);
int16_t sin_value = 2 * (sin8(time) - 128);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = ((point.y - 32) * cos_value + (point.x - 112) * sin_value) / 128 + rgb_matrix_config.hue;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_RAINBOW_BEACON

@ -0,0 +1,22 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_rainbow_moving_chevron(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint8_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = abs8(point.y - 32) + (point.x - time) + rgb_matrix_config.hue;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON

@ -0,0 +1,24 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS
extern rgb_counters_t g_rgb_counters;
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_rainbow_pinwheels(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, rgb_matrix_config.val };
uint16_t time = scale16by8(g_rgb_counters.tick, rgb_matrix_config.speed / 4);
int16_t cos_value = 3 * (cos8(time) - 128);
int16_t sin_value = 3 * (sin8(time) - 128);
for (uint8_t i = led_min; i < led_max; i++) {
point_t point = g_rgb_leds[i].point;
hsv.h = ((point.y - 32) * cos_value + (56 - abs8(point.x - 112)) * sin_value) / 128 + rgb_matrix_config.hue;
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS

@ -0,0 +1,40 @@
#pragma once
#ifndef DISABLE_RGB_MATRIX_RAINDROPS
#include "rgb_matrix_types.h"
extern rgb_counters_t g_rgb_counters;
extern rgb_config_t rgb_matrix_config;
static void raindrops_set_color(int i) {
HSV hsv = { 0 , rgb_matrix_config.sat, rgb_matrix_config.val };
// Take the shortest path between hues
int16_t deltaH = ((rgb_matrix_config.hue + 180) % 360 - rgb_matrix_config.hue) / 4;
if (deltaH > 127) {
deltaH -= 256;
} else if (deltaH < -127) {
deltaH += 256;
}
hsv.h = rgb_matrix_config.hue + (deltaH * (rand() & 0x03));
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
bool rgb_matrix_raindrops(effect_params_t* params) {
if (!params->init) {
// Change one LED every tick, make sure speed is not 0
if (scale16by8(g_rgb_counters.tick, qadd8(rgb_matrix_config.speed, 16)) % 10 == 0) {
raindrops_set_color(rand() % DRIVER_LED_TOTAL);
}
return false;
}
RGB_MATRIX_USE_LIMITS(led_min, led_max);
for (int i = led_min; i < led_max; i++) {
raindrops_set_color(i);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_RAINDROPS

@ -0,0 +1,14 @@
#pragma once
extern rgb_config_t rgb_matrix_config;
bool rgb_matrix_solid_color(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { rgb_matrix_config.hue, rgb_matrix_config.sat, rgb_matrix_config.val };
RGB rgb = hsv_to_rgb(hsv);
for (uint8_t i = led_min; i < led_max; i++) {
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}

@ -0,0 +1,33 @@
#pragma once
#if defined(RGB_MATRIX_KEYREACTIVE_ENABLED)
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE
extern rgb_config_t rgb_matrix_config;
extern last_hit_t g_last_hit_tracker;
bool rgb_matrix_solid_reactive(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { rgb_matrix_config.hue, 255, rgb_matrix_config.val };
// Max tick based on speed scale ensures results from scale16by8 with rgb_matrix_config.speed are no greater than 255
uint16_t max_tick = 65535 / rgb_matrix_config.speed;
// Relies on hue being 8-bit and wrapping
for (uint8_t i = led_min; i < led_max; i++) {
uint16_t tick = max_tick;
for(uint8_t j = 0; j < g_last_hit_tracker.count; j++) {
if (g_last_hit_tracker.index[j] == i && g_last_hit_tracker.tick[j] < tick) {
tick = g_last_hit_tracker.tick[j];
break;
}
}
uint16_t offset = scale16by8(tick, rgb_matrix_config.speed);
hsv.h = rgb_matrix_config.hue + qsub8(130, offset);
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
#endif // defined(RGB_MATRIX_KEYREACTIVE_ENABLED)

@ -0,0 +1,32 @@
#pragma once
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
#ifndef DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
extern rgb_config_t rgb_matrix_config;
extern last_hit_t g_last_hit_tracker;
bool rgb_matrix_solid_reactive_simple(effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { rgb_matrix_config.hue, rgb_matrix_config.sat, 0 };
// Max tick based on speed scale ensures results from scale16by8 with rgb_matrix_config.speed are no greater than 255
uint16_t max_tick = 65535 / rgb_matrix_config.speed;
for (uint8_t i = led_min; i < led_max; i++) {
uint16_t tick = max_tick;
for(uint8_t j = 0; j < g_last_hit_tracker.count; j++) {
if (g_last_hit_tracker.index[j] == i && g_last_hit_tracker.tick[j] < tick) {
tick = g_last_hit_tracker.tick[j];
break;
}
}
uint16_t offset = scale16by8(tick, rgb_matrix_config.speed);
hsv.v = scale8(255 - offset, rgb_matrix_config.val);
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
#endif // DISABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED

@ -0,0 +1,42 @@
#pragma once
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
#if !defined(DISABLE_RGB_MATRIX_SOLID_SPLASH) || !defined(DISABLE_RGB_MATRIX_SOLID_MULTISPLASH)
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
extern last_hit_t g_last_hit_tracker;
static bool rgb_matrix_solid_multisplash_range(uint8_t start, effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { rgb_matrix_config.hue, rgb_matrix_config.sat, 0 };
uint8_t count = g_last_hit_tracker.count;
for (uint8_t i = led_min; i < led_max; i++) {
hsv.v = 0;
point_t point = g_rgb_leds[i].point;
for (uint8_t j = start; j < count; j++) {
int16_t dx = point.x - g_last_hit_tracker.x[j];
int16_t dy = point.y - g_last_hit_tracker.y[j];
uint8_t dist = sqrt16(dx * dx + dy * dy);
uint16_t effect = scale16by8(g_last_hit_tracker.tick[j], rgb_matrix_config.speed) - dist;
if (effect > 255)
effect = 255;
hsv.v = qadd8(hsv.v, 255 - effect);
}
hsv.v = scale8(hsv.v, rgb_matrix_config.val);
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
bool rgb_matrix_solid_multisplash(effect_params_t* params) {
return rgb_matrix_solid_multisplash_range(0, params);
}
bool rgb_matrix_solid_splash(effect_params_t* params) {
return rgb_matrix_solid_multisplash_range(qsub8(g_last_hit_tracker.count, 1), params);
}
#endif // !defined(DISABLE_RGB_MATRIX_SPLASH) && !defined(DISABLE_RGB_MATRIX_MULTISPLASH)
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED

@ -0,0 +1,44 @@
#pragma once
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
#if !defined(DISABLE_RGB_MATRIX_SPLASH) || !defined(DISABLE_RGB_MATRIX_MULTISPLASH)
extern const rgb_led g_rgb_leds[DRIVER_LED_TOTAL];
extern rgb_config_t rgb_matrix_config;
extern last_hit_t g_last_hit_tracker;
static bool rgb_matrix_multisplash_range(uint8_t start, effect_params_t* params) {
RGB_MATRIX_USE_LIMITS(led_min, led_max);
HSV hsv = { 0, rgb_matrix_config.sat, 0 };
uint8_t count = g_last_hit_tracker.count;
for (uint8_t i = led_min; i < led_max; i++) {
hsv.h = rgb_matrix_config.hue;
hsv.v = 0;
point_t point = g_rgb_leds[i].point;
for (uint8_t j = start; j < count; j++) {
int16_t dx = point.x - g_last_hit_tracker.x[j];
int16_t dy = point.y - g_last_hit_tracker.y[j];
uint8_t dist = sqrt16(dx * dx + dy * dy);
uint16_t effect = scale16by8(g_last_hit_tracker.tick[j], rgb_matrix_config.speed) - dist;
if (effect > 255)
effect = 255;
hsv.h += effect;
hsv.v = qadd8(hsv.v, 255 - effect);
}
hsv.v = scale8(hsv.v, rgb_matrix_config.val);
RGB rgb = hsv_to_rgb(hsv);
rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
}
return led_max < DRIVER_LED_TOTAL;
}
bool rgb_matrix_multisplash(effect_params_t* params) {
return rgb_matrix_multisplash_range(0, params);
}
bool rgb_matrix_splash(effect_params_t* params) {
return rgb_matrix_multisplash_range(qsub8(g_last_hit_tracker.count, 1), params);
}
#endif // !defined(DISABLE_RGB_MATRIX_SPLASH) || !defined(DISABLE_RGB_MATRIX_MULTISPLASH)
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED

@ -0,0 +1,90 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#if defined(__GNUC__)
#define PACKED __attribute__ ((__packed__))
#else
#define PACKED
#endif
#if defined(_MSC_VER)
#pragma pack( push, 1 )
#endif
#if defined(RGB_MATRIX_KEYPRESSES) || defined(RGB_MATRIX_KEYRELEASES)
#define RGB_MATRIX_KEYREACTIVE_ENABLED
#endif
// Last led hit
#ifndef LED_HITS_TO_REMEMBER
#define LED_HITS_TO_REMEMBER 8
#endif // LED_HITS_TO_REMEMBER
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
typedef struct PACKED {
uint8_t count;
uint8_t x[LED_HITS_TO_REMEMBER];
uint8_t y[LED_HITS_TO_REMEMBER];
uint8_t index[LED_HITS_TO_REMEMBER];
uint16_t tick[LED_HITS_TO_REMEMBER];
} last_hit_t;
#endif // RGB_MATRIX_KEYREACTIVE_ENABLED
typedef enum rgb_task_states {
STARTING,
RENDERING,
FLUSHING,
SYNCING
} rgb_task_states;
typedef uint8_t led_flags_t;
typedef struct PACKED {
uint8_t iter;
led_flags_t flags;
bool init;
} effect_params_t;
typedef struct PACKED {
// Global tick at 20 Hz
uint32_t tick;
// Ticks since this key was last hit.
uint32_t any_key_hit;
} rgb_counters_t;
typedef struct PACKED {
uint8_t x;
uint8_t y;
} point_t;
typedef union {
uint8_t raw;
struct {
uint8_t row:4; // 16 max
uint8_t col:4; // 16 max
};
} matrix_co_t;
typedef struct PACKED {
matrix_co_t matrix_co;
point_t point;
uint8_t modifier:1;
} rgb_led;
typedef union {
uint32_t raw;
struct PACKED {
bool enable :1;
uint8_t mode :7;
uint8_t hue :8;
uint8_t sat :8;
uint8_t val :8;
uint8_t speed :8;//EECONFIG needs to be increased to support this
};
} rgb_config_t;
#if defined(_MSC_VER)
#pragma pack( pop )
#endif
Loading…
Cancel
Save