rog-aura: Add flicker effect

This commit is contained in:
Luke D. Jones
2022-08-27 20:52:43 +12:00
parent 414d610bd2
commit ebbfa58a76
8 changed files with 537 additions and 312 deletions

View File

@@ -1,262 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use crate::{
keys::Key, layouts::KeyLayout, Colour, KeyColourArray, PerKeyRaw, PerZone, Speed,
ZonedColourArray,
};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum LedType {
Key(Key),
Zone(PerZone),
}
impl Default for LedType {
fn default() -> Self {
Self::Zone(PerZone::None)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(super) enum Action {
Static(Colour),
Breathe {
/// The starting colour
colour1: Colour,
/// The secondary starting colour
colour2: Colour,
/// The speed at which to cycle between the colours
speed: Speed,
/// Temporary data to help keep state
#[serde(skip)]
colour_actual: Colour,
#[serde(skip)]
count_flipped: bool,
#[serde(skip)]
use_colour1: bool,
},
}
impl Default for Action {
fn default() -> Self {
Self::Static(Colour::default())
}
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct ActionData {
led_type: LedType,
action: Action,
// TODO: time
/// The end resulting colour after stepping through effect
#[serde(skip)]
colour: Colour,
}
impl ActionData {
pub fn set_led_type(&mut self, led_type: LedType) {
self.led_type = led_type
}
pub fn new_static(led_type: LedType, colour: Colour) -> Self {
Self {
led_type,
action: Action::Static(colour),
colour: Default::default(),
}
}
pub fn new_breathe(led_type: LedType, colour1: Colour, colour2: Colour, speed: Speed) -> Self {
Self {
led_type,
action: Action::Breathe {
colour1,
colour2,
speed,
colour_actual: colour1,
count_flipped: false,
use_colour1: true,
},
colour: Default::default(),
}
}
pub fn next_state(&mut self, _layout: &KeyLayout) {
match &mut self.action {
Action::Static(c) => self.colour = *c,
Action::Breathe {
colour1,
colour2,
speed,
colour_actual,
count_flipped: flipped,
use_colour1,
} => {
let speed = 4 - <u8>::from(*speed);
let colour: &mut Colour;
if *colour_actual == Colour(0, 0, 0) {
*use_colour1 = !*use_colour1;
}
if !*use_colour1 {
colour = colour2;
} else {
colour = colour1;
}
let r1_scale = colour.0 / speed / 2;
let g1_scale = colour.1 / speed / 2;
let b1_scale = colour.2 / speed / 2;
if *colour_actual == Colour(0, 0, 0) {
*flipped = true;
} else if colour_actual >= colour {
*flipped = false;
}
if !*flipped {
colour_actual.0 = colour_actual.0.saturating_sub(r1_scale);
colour_actual.1 = colour_actual.1.saturating_sub(g1_scale);
colour_actual.2 = colour_actual.2.saturating_sub(b1_scale);
} else {
colour_actual.0 = colour_actual.0.saturating_add(r1_scale);
colour_actual.1 = colour_actual.1.saturating_add(g1_scale);
colour_actual.2 = colour_actual.2.saturating_add(b1_scale);
}
self.colour = *colour_actual;
}
}
}
}
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<ActionData>);
impl Sequences {
#[inline]
pub fn new() -> Self {
Self(Vec::new())
}
#[inline]
pub fn push(&mut self, action: ActionData) {
self.0.push(action);
}
#[inline]
pub fn insert(&mut self, index: usize, action: ActionData) {
self.0.insert(index, action);
}
/// Remove an item at this position from the run buffer. If the `index` supplied
/// is not in range then `None` is returned, otherwise the `ActionData` at that location
/// is yeeted and returned.
#[inline]
pub fn remove_item(&mut self, index: usize) -> Option<ActionData> {
if index < self.0.len() {
return Some(self.0.remove(index));
}
None
}
pub fn next_state(&mut self, layout: &KeyLayout) {
for effect in self.0.iter_mut() {
effect.next_state(layout);
}
}
pub fn create_packets(&self) -> PerKeyRaw {
let mut keys = KeyColourArray::new();
let mut zones = ZonedColourArray::new();
let mut is_per_key = false;
for effect in self.0.iter() {
match effect.led_type {
LedType::Key(key) => {
is_per_key = true;
if let Some(rgb) = keys.rgb_for_key(key) {
rgb[0] = effect.colour.0;
rgb[1] = effect.colour.1;
rgb[2] = effect.colour.2;
}
}
LedType::Zone(z) => {
let rgb = zones.rgb_for_zone(z);
rgb[0] = effect.colour.0;
rgb[1] = effect.colour.1;
rgb[2] = effect.colour.2;
}
}
}
if is_per_key {
keys.into()
} else {
vec![zones.into()]
}
}
}
#[cfg(test)]
mod tests {
use crate::{
keys::Key, layouts::KeyLayout, Action, ActionData, Colour, LedType, Sequences, Speed,
};
#[test]
fn single_key_next_state_then_create() {
let layout = KeyLayout::gx502_layout();
let mut seq = Sequences::new();
seq.0.push(ActionData {
led_type: LedType::Key(Key::F),
action: Action::Static(Colour(255, 127, 0)),
colour: Default::default(),
});
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 255);
assert_eq!(packets[5][34], 127);
assert_eq!(packets[5][35], 0);
}
#[test]
fn cycle_breathe() {
let layout = KeyLayout::gx502_layout();
let mut seq = Sequences::new();
seq.0.push(ActionData {
led_type: LedType::Key(Key::F),
action: Action::Breathe {
colour1: Colour(255, 127, 0),
colour2: Colour(127, 0, 255),
speed: Speed::Med,
colour_actual: Colour(255, 127, 0),
count_flipped: false,
use_colour1: true,
},
colour: Default::default(),
});
let s = serde_json::to_string_pretty(&seq).unwrap();
println!("{s}");
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 213);
assert_eq!(packets[5][34], 106);
assert_eq!(packets[5][35], 0);
// dbg!(&packets[5][33..=35]);
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 171);
assert_eq!(packets[5][34], 85);
assert_eq!(packets[5][35], 0);
}
}

View File

@@ -0,0 +1,249 @@
use crate::{layouts::KeyLayout, p_random, Colour, EffectState, LedType, Speed};
use serde_derive::{Deserialize, Serialize};
macro_rules! effect_state_impl {
() => {
fn get_colour(&self) -> Colour {
self.colour
}
fn get_led_type(&self) -> LedType {
self.led_type.clone()
}
/// Change the led type
fn set_led_type(&mut self, led_type: LedType) {
self.led_type = led_type;
}
};
}
macro_rules! effect_impl {
($($effect:ident),*) => {
impl Effect {
/// Get the type of LED set
pub fn get_led_type(&self) -> LedType {
match self {
$(Effect::$effect(c) => c.get_led_type(),)*
}
}
/// Change the led type
pub fn set_led_type(&mut self, led_type: LedType) {
match self {
$(Effect::$effect(c) => c.set_led_type(led_type),)*
}
}
/// Calculate the next state of the effect
pub fn next_state(&mut self, layout: &KeyLayout) {
match self {
$(Effect::$effect(c) => c.next_colour_state(layout),)*
}
}
/// Get the calculated colour
pub fn get_colour(&self) -> Colour {
match self {
$(Effect::$effect(c) => c.get_colour(),)*
}
}
}
};
}
/**************************************************************************************************/
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Effect {
Static(Static),
Breathe(Breathe),
Flicker(Flicker),
}
impl Default for Effect {
fn default() -> Self {
Self::Static(Static::new(LedType::default(), Colour::default()))
}
}
effect_impl!(Static, Breathe, Flicker);
/**************************************************************************************************/
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Static {
led_type: LedType,
/// The starting colour
colour: Colour,
}
impl Static {
pub fn new(led_type: LedType, colour: Colour) -> Self {
Self { led_type, colour }
}
}
impl EffectState for Static {
fn next_colour_state(&mut self, _layout: &KeyLayout) {}
effect_state_impl!();
}
/**************************************************************************************************/
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Breathe {
led_type: LedType,
/// The starting colour
start_colour1: Colour,
/// The secondary starting colour
start_colour2: Colour,
/// The speed at which to cycle between the colours
speed: Speed,
/// Temporary data to help keep state
#[serde(skip)]
colour: Colour,
#[serde(skip)]
count_flipped: bool,
#[serde(skip)]
use_colour1: bool,
}
impl Breathe {
pub fn new(led_type: LedType, colour1: Colour, colour2: Colour, speed: Speed) -> Self {
Self {
led_type,
start_colour1: colour1,
start_colour2: colour2,
speed,
colour: colour1,
count_flipped: false,
use_colour1: true,
}
}
}
impl EffectState for Breathe {
fn next_colour_state(&mut self, _layout: &KeyLayout) {
let Self {
start_colour1: colour1,
start_colour2: colour2,
speed,
colour: colour_actual,
count_flipped: flipped,
use_colour1,
..
} = self;
let speed = 4 - <u8>::from(*speed);
let colour: &mut Colour;
if *colour_actual == Colour(0, 0, 0) {
*use_colour1 = !*use_colour1;
}
if !*use_colour1 {
colour = colour2;
} else {
colour = colour1;
}
let r1_scale = colour.0 / speed / 2;
let g1_scale = colour.1 / speed / 2;
let b1_scale = colour.2 / speed / 2;
if *colour_actual == Colour(0, 0, 0) {
*flipped = true;
} else if colour_actual >= colour {
*flipped = false;
}
if !*flipped {
colour_actual.0 = colour_actual.0.saturating_sub(r1_scale);
colour_actual.1 = colour_actual.1.saturating_sub(g1_scale);
colour_actual.2 = colour_actual.2.saturating_sub(b1_scale);
} else {
colour_actual.0 = colour_actual.0.saturating_add(r1_scale);
colour_actual.1 = colour_actual.1.saturating_add(g1_scale);
colour_actual.2 = colour_actual.2.saturating_add(b1_scale);
}
}
effect_state_impl!();
}
/**************************************************************************************************/
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Flicker {
led_type: LedType,
/// The starting colour
colour: Colour,
max_percentage: u8,
min_percentage: u8,
#[serde(skip)]
count: u8,
}
impl Flicker {
pub fn new(led_type: LedType, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self {
Self {
led_type,
colour,
count: 4,
max_percentage,
min_percentage,
}
}
}
impl EffectState for Flicker {
fn next_colour_state(&mut self, _layout: &KeyLayout) {
let Self {
max_percentage,
min_percentage,
colour,
..
} = self;
self.count -= 1;
if self.count != 0 {
return;
}
// TODO: make a "percentage" method on Colour.
let max_light = Colour(
(colour.0 as f32 / 100.0 * *max_percentage as f32) as u8,
(colour.1 as f32 / 100.0 * *max_percentage as f32) as u8,
(colour.2 as f32 / 100.0 * *max_percentage as f32) as u8,
);
// min light is a percentage of the set colour
let min_light = Colour(
(colour.0 as f32 / 100.0 * *min_percentage as f32) as u8,
(colour.1 as f32 / 100.0 * *min_percentage as f32) as u8,
(colour.2 as f32 / 100.0 * *min_percentage as f32) as u8,
);
// Convert the 255 to percentage
let amount = (p_random() & 7) as f32 * 8.0;
let set_colour = |colour: &mut u8, max: f32, min: f32| {
let pc = amount / max * 100.0;
let min_amount = pc * min / 100.0; // percentage of min colour
let max_amount = pc * max / 100.0; // percentage of max colour
if *colour as f32 - min_amount < min {
*colour = min as u8;
} else {
*colour = (max - max_amount) as u8;
}
};
set_colour(&mut colour.0, max_light.0 as f32, min_light.0 as f32);
set_colour(&mut colour.1, max_light.1 as f32, min_light.1 as f32);
set_colour(&mut colour.2, max_light.2 as f32, min_light.2 as f32);
self.count = 4;
}
effect_state_impl!();
}

View File

@@ -0,0 +1,218 @@
mod effects;
pub use effects::*;
use crate::{
keys::Key, layouts::KeyLayout, Colour, KeyColourArray, PerKeyRaw, PerZone, ZonedColourArray,
};
use serde_derive::{Deserialize, Serialize};
// static mut RNDINDEX: usize = 0;
static mut PRNDINDEX: usize = 0;
pub const RNDTABLE: [i32; 256] = [
0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, 74, 21, 211, 47, 80, 242, 154,
27, 205, 128, 161, 89, 77, 36, 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188,
52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224, 149, 104, 25, 178, 252, 182,
202, 182, 141, 197, 4, 81, 181, 242, 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175,
249, 0, 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235, 25, 92, 20, 145, 138,
77, 69, 166, 78, 176, 173, 212, 166, 113, 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37,
171, 75, 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196, 135, 106, 63, 197, 195,
86, 96, 203, 113, 101, 170, 247, 181, 113, 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112,
166, 103, 241, 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224, 145, 224, 81,
206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95, 28, 139, 123, 98, 125, 196, 15, 70, 194, 253,
54, 14, 109, 226, 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36, 17, 46, 52,
231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106, 197, 242, 98, 43, 39, 175, 254, 145, 190,
84, 118, 222, 187, 136, 120, 163, 236, 249,
];
pub fn p_random() -> i32 {
unsafe {
PRNDINDEX = (PRNDINDEX + 1) & 0xFF;
RNDTABLE[PRNDINDEX]
}
}
pub(crate) trait EffectState {
/// Calculate the next colour state
fn next_colour_state(&mut self, _layout: &KeyLayout);
/// Return the resulting colour. Implementers should store the colour to return it.
fn get_colour(&self) -> Colour;
fn get_led_type(&self) -> LedType;
fn set_led_type(&mut self, led_type: LedType);
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum LedType {
Key(Key),
Zone(PerZone),
}
impl Default for LedType {
fn default() -> Self {
Self::Zone(PerZone::None)
}
}
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct Sequences(Vec<Effect>);
impl Sequences {
#[inline]
pub fn new() -> Self {
Self(Vec::new())
}
#[inline]
pub fn push(&mut self, action: Effect) {
self.0.push(action);
}
#[inline]
pub fn insert(&mut self, index: usize, action: Effect) {
self.0.insert(index, action);
}
/// Remove an item at this position from the run buffer. If the `index` supplied
/// is not in range then `None` is returned, otherwise the `ActionData` at that location
/// is yeeted and returned.
#[inline]
pub fn remove_item(&mut self, index: usize) -> Option<Effect> {
if index < self.0.len() {
return Some(self.0.remove(index));
}
None
}
pub fn next_state(&mut self, layout: &KeyLayout) {
for effect in self.0.iter_mut() {
effect.next_state(layout);
}
}
pub fn create_packets(&self) -> PerKeyRaw {
let mut keys = KeyColourArray::new();
let mut zones = ZonedColourArray::new();
let mut is_per_key = false;
for effect in self.0.iter() {
match effect.get_led_type() {
LedType::Key(key) => {
is_per_key = true;
if let Some(rgb) = keys.rgb_for_key(key) {
let c = effect.get_colour();
rgb[0] = c.0;
rgb[1] = c.1;
rgb[2] = c.2;
}
}
LedType::Zone(z) => {
let rgb = zones.rgb_for_zone(z);
let c = effect.get_colour();
rgb[0] = c.0;
rgb[1] = c.1;
rgb[2] = c.2;
}
}
}
if is_per_key {
keys.into()
} else {
vec![zones.into()]
}
}
}
#[cfg(test)]
mod tests {
use crate::{
keys::Key, layouts::KeyLayout, Breathe, Colour, Effect, Flicker, LedType, Sequences, Speed,
Static,
};
#[test]
fn single_key_next_state_then_create() {
let layout = KeyLayout::gx502_layout();
let mut seq = Sequences::new();
seq.0.push(Effect::Static(Static::new(
LedType::Key(Key::F),
Colour(255, 127, 0),
)));
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 255);
assert_eq!(packets[5][34], 127);
assert_eq!(packets[5][35], 0);
}
#[test]
fn cycle_breathe() {
let layout = KeyLayout::gx502_layout();
let mut seq = Sequences::new();
seq.0.push(Effect::Breathe(Breathe::new(
LedType::Key(Key::F),
Colour(255, 127, 0),
Colour(127, 0, 255),
Speed::Med,
)));
let s = serde_json::to_string_pretty(&seq).unwrap();
println!("{s}");
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 213);
assert_eq!(packets[5][34], 106);
assert_eq!(packets[5][35], 0);
// dbg!(&packets[5][33..=35]);
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 171);
assert_eq!(packets[5][34], 85);
assert_eq!(packets[5][35], 0);
}
#[test]
fn cycle_flicker() {
let layout = KeyLayout::gx502_layout();
let mut seq = Sequences::new();
seq.0.push(Effect::Flicker(Flicker::new(
LedType::Key(Key::F),
Colour(255, 127, 80),
100,
10,
)));
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[0][0], 0x5d);
assert_eq!(packets[5][33], 255);
assert_eq!(packets[5][34], 127);
assert_eq!(packets[5][35], 80);
// The random is deterministic
seq.next_state(&layout);
seq.next_state(&layout);
seq.next_state(&layout);
seq.next_state(&layout);
seq.next_state(&layout);
seq.next_state(&layout);
seq.next_state(&layout);
let packets = seq.create_packets();
assert_eq!(packets[5][33], 215);
assert_eq!(packets[5][34], 87);
assert_eq!(packets[5][35], 40);
}
}