Advanced Aura feature

Groundwork for 'advanced' aura modes
Add single zone + Doom light flash
Fix mocking for ROGCC
Better prepare & change to mapping of keyboard layouts to models and functions
Refactor and begin using new key layout stuff
Enable first arg to rogcc to set layout in mocking feature mode
Complete refactor of key layouts, and to RON serde
This commit is contained in:
Luke D. Jones
2022-12-11 11:50:47 +13:00
parent e3ecaa92bd
commit 1cbffedaeb
134 changed files with 8249 additions and 4390 deletions

View File

@@ -0,0 +1,33 @@
use super::{EffectState, InputForEffect};
use crate::advanced::LedCode;
use crate::Colour;
pub struct InputBased {
address: LedCode,
colour: Colour,
/// - audio
/// - cpu freq
/// - temperature
/// - fan speed
/// - time
input: Box<dyn InputForEffect>,
}
impl EffectState for InputBased {
fn next_colour_state(&mut self, _layout: &crate::layouts::KeyLayout) {
self.input.next_colour_state();
self.colour = self.input.get_colour();
}
fn get_colour(&self) -> Colour {
self.colour
}
fn get_led(&self) -> LedCode {
self.address
}
fn set_led(&mut self, address: LedCode) {
self.address = address
}
}

View File

@@ -0,0 +1,82 @@
use serde::{Deserialize, Serialize};
use super::EffectState;
use crate::advanced::LedCode;
use crate::layouts::KeyLayout;
use crate::{effect_state_impl, Colour, Speed};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Breathe {
address: LedCode,
/// 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(address: LedCode, colour1: Colour, colour2: Colour, speed: Speed) -> Self {
Self {
address,
start_colour1: colour1,
start_colour2: colour2,
speed,
colour: colour1,
count_flipped: false,
use_colour1: true,
}
}
}
impl EffectState for Breathe {
effect_state_impl!();
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);
if *colour_actual == Colour(0, 0, 0) {
*use_colour1 = !*use_colour1;
}
let colour = if !*use_colour1 { colour2 } else { 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);
}
}
}

View File

@@ -0,0 +1,164 @@
use serde::{Deserialize, Serialize};
use crate::advanced::LedCode;
use crate::effects::{p_random, EffectState};
use crate::layouts::KeyLayout;
use crate::{effect_state_impl, Colour};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DoomFlicker {
address: LedCode,
start_colour: Colour,
max_percentage: u8,
min_percentage: u8,
#[serde(skip)]
count: u8,
#[serde(skip)]
colour: Colour,
}
impl DoomFlicker {
pub fn new(address: LedCode, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self {
Self {
address,
colour,
count: 4,
max_percentage,
min_percentage,
start_colour: colour,
}
}
}
impl EffectState for DoomFlicker {
effect_state_impl!();
fn next_colour_state(&mut self, _layout: &KeyLayout) {
let Self {
max_percentage,
min_percentage,
colour,
start_colour,
..
} = self;
if self.count == 0 {
self.count = 4;
}
self.count -= 1;
if self.count != 0 {
return;
}
// TODO: make a "percentage" method on Colour.
let max_light = Colour(
(start_colour.0 as f32 / 100.0 * *max_percentage as f32) as u8,
(start_colour.1 as f32 / 100.0 * *max_percentage as f32) as u8,
(start_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(
(start_colour.0 as f32 / 100.0 * *min_percentage as f32) as u8,
(start_colour.1 as f32 / 100.0 * *min_percentage as f32) as u8,
(start_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;
}
}
pub struct LightFlash {
pub count: i32,
pub max_light: i32,
pub min_light: i32,
pub max_time: i32,
pub min_time: i32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DoomLightFlash {
address: LedCode,
start_colour: Colour,
max_percentage: u8,
min_percentage: u8,
#[serde(skip)]
max_time: i32,
#[serde(skip)]
min_time: i32,
#[serde(skip)]
count: u8,
#[serde(skip)]
colour: Colour,
}
impl DoomLightFlash {
pub fn new(address: LedCode, colour: Colour, max_percentage: u8, min_percentage: u8) -> Self {
Self {
address,
colour,
count: 4,
max_percentage,
min_percentage,
start_colour: colour,
max_time: 32,
min_time: 7,
}
}
}
impl EffectState for DoomLightFlash {
effect_state_impl!();
fn next_colour_state(&mut self, _layout: &KeyLayout) {
let Self {
max_percentage,
min_percentage,
colour,
start_colour,
..
} = self;
self.count -= 1;
if self.count != 0 {
return;
}
// TODO: make a "percentage" method on Colour.
let max_light = Colour(
(start_colour.0 as f32 / 100.0 * *max_percentage as f32) as u8,
(start_colour.1 as f32 / 100.0 * *max_percentage as f32) as u8,
(start_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(
(start_colour.0 as f32 / 100.0 * *min_percentage as f32) as u8,
(start_colour.1 as f32 / 100.0 * *min_percentage as f32) as u8,
(start_colour.2 as f32 / 100.0 * *min_percentage as f32) as u8,
);
if *colour == max_light {
*colour = min_light;
self.count = ((p_random() & self.min_time) + 1) as u8;
} else {
*colour = max_light;
self.count = ((p_random() & self.max_time) + 1) as u8;
}
}
}

291
rog-aura/src/effects/mod.rs Normal file
View File

@@ -0,0 +1,291 @@
use serde_derive::{Deserialize, Serialize};
mod doom;
pub use doom::*;
mod base;
pub use base::*;
mod breathe;
pub use breathe::*;
mod static_;
pub use static_::*;
use crate::advanced::{LedCode, LedUsbPackets, UsbPackets};
use crate::layouts::KeyLayout;
use crate::Colour;
// static mut RNDINDEX: usize = 0;
static mut PRNDINDEX: usize = 0;
/// Pseudo random table ripped straight out of room4doom
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 trait InputForEffect {
/// Calculate the next colour state
fn next_colour_state(&mut self);
/// Return the resulting colour. Implementers should store the colour to
/// return it.
fn get_colour(&self) -> Colour;
}
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(&self) -> LedCode;
fn set_led(&mut self, address: LedCode);
}
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct AdvancedEffects {
effects: Vec<Effect>,
zoned: bool,
}
impl AdvancedEffects {
#[inline]
pub fn new(zoned: bool) -> Self {
Self {
effects: Default::default(),
zoned,
}
}
#[inline]
pub fn push(&mut self, action: Effect) {
self.effects.push(action);
}
#[inline]
pub fn insert(&mut self, index: usize, action: Effect) {
self.effects.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.effects.len() {
return Some(self.effects.remove(index));
}
None
}
pub fn next_state(&mut self, layout: &KeyLayout) {
for effect in &mut self.effects {
effect.next_state(layout);
}
}
pub fn create_packets(&self) -> UsbPackets {
let mut usb_packets = if self.zoned {
// TODO: figure out if that single byte difference for multizone actually
// matters
LedUsbPackets::new_zoned(true)
} else {
LedUsbPackets::new_per_key()
};
for effect in &self.effects {
let c = effect.colour();
usb_packets.set(effect.led(), c.0, c.1, c.2);
}
usb_packets.into()
}
}
// how to be lazy
#[macro_export]
macro_rules! effect_state_impl {
() => {
fn get_colour(&self) -> $crate::Colour {
self.colour
}
fn get_led(&self) -> $crate::advanced::LedCode {
self.address.clone()
}
/// Change the led type
fn set_led(&mut self, address: $crate::advanced::LedCode) {
self.address = address;
}
};
}
/// A helper macro to quickly add new effects to the matching on `Effect`
macro_rules! effect_impl {
($($effect:ident),*) => {
impl Effect {
/// Get the type of LED set
pub fn led(&self) -> $crate::advanced::LedCode {
match self {
$(Effect::$effect(c) => c.get_led(),)*
}
}
/// Change the led type (can be used to change location of the effect)
pub fn set_led(&mut self, address: $crate::advanced::LedCode) {
match self {
$(Effect::$effect(c) => c.set_led(address),)*
}
}
/// 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 colour(&self) -> $crate::Colour {
match self {
$(Effect::$effect(c) => c.get_colour(),)*
}
}
}
};
}
/// The main effect container, a sequencer will call various methods on this to
/// update states and get colours
///
/// Every effect is added here to quickly and easily match within `Effect`
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Effect {
Static(Static),
Breathe(Breathe),
DoomFlicker(DoomFlicker),
DoomLightFlash(DoomLightFlash),
}
impl Default for Effect {
fn default() -> Self {
Self::Static(Static::new(LedCode::default(), Colour::default()))
}
}
effect_impl!(Static, Breathe, DoomFlicker, DoomLightFlash);
#[cfg(test)]
mod tests {
use crate::advanced::LedCode;
use crate::effects::{AdvancedEffects, Breathe, DoomFlicker, Effect, Static};
use crate::layouts::KeyLayout;
use crate::{Colour, Speed};
#[test]
fn single_key_next_state_then_create() {
let layout = KeyLayout::default_layout();
let mut seq = AdvancedEffects::new(false);
seq.effects
.push(Effect::Static(Static::new(LedCode::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::default_layout();
let mut seq = AdvancedEffects::new(false);
seq.effects.push(Effect::Breathe(Breathe::new(
LedCode::F,
Colour(255, 127, 0),
Colour(127, 0, 255),
Speed::Med,
)));
let s =
ron::ser::to_string_pretty(&seq, ron::ser::PrettyConfig::new().depth_limit(4)).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::default_layout();
let mut seq = AdvancedEffects::new(false);
seq.effects.push(Effect::DoomFlicker(DoomFlicker::new(
LedCode::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);
}
}

View File

@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use super::EffectState;
use crate::advanced::LedCode;
use crate::layouts::KeyLayout;
use crate::{effect_state_impl, Colour};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Static {
address: LedCode,
/// The starting colour
colour: Colour,
}
impl Static {
pub fn new(address: LedCode, colour: Colour) -> Self {
Self { address, colour }
}
}
impl EffectState for Static {
effect_state_impl!();
fn next_colour_state(&mut self, _layout: &KeyLayout) {}
}