Compare commits
No commits in common. "master" and "standby_mode" have entirely different histories.
master
...
standby_mo
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
parec --raw --rate 48000 --format=s16ne --channels=1 | target/release/musiclight
|
|
433
src/animation.rs
433
src/animation.rs
|
@ -11,11 +11,6 @@ use crate::signal_processing::SignalProcessing;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, AnimationError>;
|
type Result<T> = std::result::Result<T, AnimationError>;
|
||||||
|
|
||||||
pub mod particles;
|
|
||||||
pub mod sparkles;
|
|
||||||
pub mod racers;
|
|
||||||
pub mod fire;
|
|
||||||
|
|
||||||
/////////// Error Type and Implementation ////////////
|
/////////// Error Type and Implementation ////////////
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -129,3 +124,431 @@ pub trait Animation {
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS];
|
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////// Animation implementations ////////////
|
||||||
|
|
||||||
|
pub mod particles
|
||||||
|
{
|
||||||
|
use crate::animation::{Color, Animation, Result};
|
||||||
|
use crate::signal_processing::SignalProcessing;
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
const COOLDOWN_FACTOR : f32 = 0.99995;
|
||||||
|
const RGB_EXPONENT : f32 = 1.8;
|
||||||
|
const W_EXPONENT : f32 = 2.2;
|
||||||
|
const FADE_FACTOR : f32 = 0.98;
|
||||||
|
const AVG_LEDS_ACTIVATED : f32 = 0.02;
|
||||||
|
const WHITE_EXTRA_SCALE : f32 = 0.5;
|
||||||
|
const CONDENSATION_FACTOR : f32 = 5.0;
|
||||||
|
|
||||||
|
pub struct Particles
|
||||||
|
{
|
||||||
|
energy : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
max_energy : Color,
|
||||||
|
|
||||||
|
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
|
||||||
|
sigproc: Rc<RefCell<SignalProcessing>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Animation for Particles
|
||||||
|
{
|
||||||
|
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Particles
|
||||||
|
{
|
||||||
|
Particles {
|
||||||
|
energy: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
||||||
|
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
sigproc: sigproc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) -> Result<()>
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn periodic(&mut self) -> Result<()>
|
||||||
|
{
|
||||||
|
let sigproc = self.sigproc.borrow();
|
||||||
|
|
||||||
|
// extract frequency band energies
|
||||||
|
let cur_energy = Color{
|
||||||
|
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
||||||
|
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
||||||
|
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
||||||
|
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
||||||
|
|
||||||
|
// track the maximum energy with cooldown
|
||||||
|
self.max_energy.r *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.r > self.max_energy.r {
|
||||||
|
self.max_energy.r = cur_energy.r;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.g *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.g > self.max_energy.g {
|
||||||
|
self.max_energy.g = cur_energy.g;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.b *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.b > self.max_energy.b {
|
||||||
|
self.max_energy.b = cur_energy.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.w *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.w > self.max_energy.w {
|
||||||
|
self.max_energy.w = cur_energy.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fade all LEDs towards black
|
||||||
|
for strip in 0..config::NUM_STRIPS {
|
||||||
|
for led in 0..config::NUM_LEDS_PER_STRIP {
|
||||||
|
self.energy[strip][led].scale(FADE_FACTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// distribute the energy for each color
|
||||||
|
let new_energy = Color{
|
||||||
|
r: (cur_energy.r / self.max_energy.r).powf(RGB_EXPONENT),
|
||||||
|
g: (cur_energy.g / self.max_energy.g).powf(RGB_EXPONENT),
|
||||||
|
b: (cur_energy.b / self.max_energy.b).powf(RGB_EXPONENT),
|
||||||
|
w: (cur_energy.w / self.max_energy.w).powf(W_EXPONENT),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remaining_energy = new_energy;
|
||||||
|
remaining_energy.scale(AVG_LEDS_ACTIVATED * config::NUM_LEDS_TOTAL as f32);
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
for coloridx in 0..=3 {
|
||||||
|
let new_energy_ref = new_energy.ref_by_index(coloridx).unwrap();
|
||||||
|
let rem_energy_ref = remaining_energy.ref_by_index_mut(coloridx).unwrap();
|
||||||
|
|
||||||
|
while *rem_energy_ref > 0.0 {
|
||||||
|
let mut rnd_energy = rng.gen::<f32>() * (*new_energy_ref) * CONDENSATION_FACTOR;
|
||||||
|
|
||||||
|
let rnd_strip = rng.gen_range(0..config::NUM_STRIPS);
|
||||||
|
let rnd_led = rng.gen_range(0..config::NUM_LEDS_PER_STRIP);
|
||||||
|
|
||||||
|
if rnd_energy > *rem_energy_ref {
|
||||||
|
rnd_energy = *rem_energy_ref;
|
||||||
|
*rem_energy_ref = 0.0;
|
||||||
|
} else {
|
||||||
|
*rem_energy_ref -= rnd_energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
let led_ref = self.energy[rnd_strip][rnd_led].ref_by_index_mut(coloridx).unwrap();
|
||||||
|
*led_ref += rnd_energy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// color post-processing
|
||||||
|
self.colorlists = self.energy;
|
||||||
|
|
||||||
|
for strip in 0..config::NUM_STRIPS {
|
||||||
|
for led in 0..config::NUM_LEDS_PER_STRIP {
|
||||||
|
self.colorlists[strip][led].w *= WHITE_EXTRA_SCALE;
|
||||||
|
|
||||||
|
self.colorlists[strip][led].limit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
||||||
|
{
|
||||||
|
return &self.colorlists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod sparkles
|
||||||
|
{
|
||||||
|
use crate::animation::{Color, Animation, Result};
|
||||||
|
use crate::signal_processing::SignalProcessing;
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
const COOLDOWN_FACTOR : f32 = 0.99995;
|
||||||
|
const RGB_EXPONENT : f32 = 1.5;
|
||||||
|
const W_EXPONENT : f32 = 2.2;
|
||||||
|
const FADE_FACTOR : f32 = 0.97;
|
||||||
|
const AVG_LEDS_ACTIVATED : f32 = 0.03;
|
||||||
|
const WHITE_EXTRA_SCALE : f32 = 0.3;
|
||||||
|
const CONDENSATION_FACTOR : f32 = 5.0;
|
||||||
|
|
||||||
|
const SPARK_FADE_STEP : f32 = 2.500 / config::FPS_ANIMATION;
|
||||||
|
const SPARK_VSPEED_MIDS : f32 = 1.000 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
||||||
|
const SPARK_VSPEED_HIGHS : f32 = 0.800 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
||||||
|
const SPARK_VSPEED_XHIGHS : f32 = 0.500 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A spark is a point of light that can move vertically along the LED strips.
|
||||||
|
*/
|
||||||
|
struct Spark
|
||||||
|
{
|
||||||
|
pub vspeed: f32, // LEDs per frame
|
||||||
|
pub brightness: f32,
|
||||||
|
pub color: Color,
|
||||||
|
|
||||||
|
strip: u16,
|
||||||
|
led: f32,
|
||||||
|
|
||||||
|
has_expired: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spark
|
||||||
|
{
|
||||||
|
pub fn new(vspeed: f32, brightness: f32, color: Color, strip: u16, led: f32) -> Spark
|
||||||
|
{
|
||||||
|
Spark {
|
||||||
|
vspeed: vspeed,
|
||||||
|
brightness: brightness,
|
||||||
|
color: color,
|
||||||
|
strip: strip,
|
||||||
|
led: led,
|
||||||
|
has_expired: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self)
|
||||||
|
{
|
||||||
|
if self.has_expired {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.led += self.vspeed;
|
||||||
|
self.brightness -= SPARK_FADE_STEP;
|
||||||
|
|
||||||
|
if (self.led >= config::NUM_LEDS_PER_STRIP as f32) || (self.led <= -1.0) {
|
||||||
|
// moved outside of the LED array -> no need to update this any more
|
||||||
|
self.has_expired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.brightness <= 0.0 {
|
||||||
|
// moved outside of the LED array -> no need to update this any more
|
||||||
|
self.has_expired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_expired(&self) -> bool
|
||||||
|
{
|
||||||
|
self.has_expired
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&self, colorlists: &mut [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS])
|
||||||
|
{
|
||||||
|
if self.has_expired {
|
||||||
|
// do not render if this Spark has expired
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fract_led = self.led - self.led.floor();
|
||||||
|
|
||||||
|
let led1_idx = self.led.floor() as i32;
|
||||||
|
let led2_idx = self.led.ceil() as usize;
|
||||||
|
|
||||||
|
let led1_color = self.color.scaled_copy(fract_led * self.brightness);
|
||||||
|
let led2_color = self.color.scaled_copy((1.0 - fract_led) * self.brightness);
|
||||||
|
|
||||||
|
if led1_idx >= 0 {
|
||||||
|
colorlists[self.strip as usize][led1_idx as usize].add(&led1_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if led2_idx < config::NUM_LEDS_PER_STRIP {
|
||||||
|
colorlists[self.strip as usize][led2_idx as usize].add(&led2_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sparkles
|
||||||
|
{
|
||||||
|
max_energy : Color,
|
||||||
|
|
||||||
|
sparks : VecDeque<Spark>,
|
||||||
|
|
||||||
|
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
|
||||||
|
sigproc: Rc<RefCell<SignalProcessing>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Animation for Sparkles
|
||||||
|
{
|
||||||
|
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Sparkles
|
||||||
|
{
|
||||||
|
Sparkles {
|
||||||
|
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
||||||
|
sparks: VecDeque::with_capacity(1024),
|
||||||
|
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
||||||
|
sigproc: sigproc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) -> Result<()>
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn periodic(&mut self) -> Result<()>
|
||||||
|
{
|
||||||
|
let sigproc = self.sigproc.borrow();
|
||||||
|
|
||||||
|
// extract frequency band energies
|
||||||
|
let cur_energy = Color{
|
||||||
|
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
||||||
|
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
||||||
|
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
||||||
|
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
||||||
|
|
||||||
|
// track the maximum energy with cooldown
|
||||||
|
self.max_energy.r *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.r > self.max_energy.r {
|
||||||
|
self.max_energy.r = cur_energy.r;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.g *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.g > self.max_energy.g {
|
||||||
|
self.max_energy.g = cur_energy.g;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.b *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.b > self.max_energy.b {
|
||||||
|
self.max_energy.b = cur_energy.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.max_energy.w *= COOLDOWN_FACTOR;
|
||||||
|
if cur_energy.w > self.max_energy.w {
|
||||||
|
self.max_energy.w = cur_energy.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fade all LEDs towards black
|
||||||
|
for strip in 0..config::NUM_STRIPS {
|
||||||
|
for led in 0..config::NUM_LEDS_PER_STRIP {
|
||||||
|
self.colorlists[strip][led].scale(FADE_FACTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// distribute the energy for each color
|
||||||
|
let new_energy = Color{
|
||||||
|
r: (cur_energy.r / self.max_energy.r).powf(RGB_EXPONENT),
|
||||||
|
g: (cur_energy.g / self.max_energy.g).powf(RGB_EXPONENT),
|
||||||
|
b: (cur_energy.b / self.max_energy.b).powf(RGB_EXPONENT),
|
||||||
|
w: (cur_energy.w / self.max_energy.w).powf(W_EXPONENT),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remaining_energy = new_energy.r;
|
||||||
|
remaining_energy *= AVG_LEDS_ACTIVATED * config::NUM_LEDS_TOTAL as f32;
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// Red (bass) uses exactly the same algorithm as for the “Particles” animation.
|
||||||
|
while remaining_energy > 0.0 {
|
||||||
|
let mut rnd_energy = rng.gen::<f32>() * new_energy.r * CONDENSATION_FACTOR;
|
||||||
|
|
||||||
|
let rnd_strip = rng.gen_range(0..config::NUM_STRIPS);
|
||||||
|
let rnd_led = rng.gen_range(0..config::NUM_LEDS_PER_STRIP);
|
||||||
|
|
||||||
|
if rnd_energy > remaining_energy {
|
||||||
|
rnd_energy = remaining_energy;
|
||||||
|
remaining_energy = 0.0;
|
||||||
|
} else {
|
||||||
|
remaining_energy -= rnd_energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.colorlists[rnd_strip][rnd_led].r += rnd_energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update all existing sparks
|
||||||
|
self.sparks.iter_mut().for_each(|x| x.update());
|
||||||
|
|
||||||
|
// Create green sparks for middle frequencies.
|
||||||
|
// They originate in the center and can go both up and down from there.
|
||||||
|
self.sparks.push_back(Spark::new(
|
||||||
|
match rng.gen::<bool>() {
|
||||||
|
true => SPARK_VSPEED_MIDS,
|
||||||
|
false => -SPARK_VSPEED_MIDS,
|
||||||
|
},
|
||||||
|
new_energy.g,
|
||||||
|
Color{r: 0.0, g: 1.0, b: 0.0, w: 0.0},
|
||||||
|
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
||||||
|
(config::NUM_LEDS_PER_STRIP as f32 / 2.0) - 0.5));
|
||||||
|
|
||||||
|
// Create blue sparks for high frequencies.
|
||||||
|
// They originate either in the top, moving down, or in the bottom, moving up
|
||||||
|
{
|
||||||
|
let start_from_top = rng.gen::<bool>();
|
||||||
|
|
||||||
|
let start_led = match start_from_top {
|
||||||
|
true => config::NUM_LEDS_PER_STRIP-1,
|
||||||
|
false => 0} as f32;
|
||||||
|
|
||||||
|
let vspeed = match start_from_top {
|
||||||
|
true => -SPARK_VSPEED_HIGHS,
|
||||||
|
false => SPARK_VSPEED_HIGHS};
|
||||||
|
|
||||||
|
self.sparks.push_back(Spark::new(
|
||||||
|
vspeed,
|
||||||
|
new_energy.b,
|
||||||
|
Color{r: 0.0, g: 0.0, b: 1.0, w: 0.0},
|
||||||
|
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
||||||
|
start_led));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create white sparks for very high frequencies.
|
||||||
|
// They originate either in the top, moving down, or in the bottom, moving up
|
||||||
|
{
|
||||||
|
let start_from_top = rng.gen::<bool>();
|
||||||
|
|
||||||
|
let start_led = match start_from_top {
|
||||||
|
true => config::NUM_LEDS_PER_STRIP-1,
|
||||||
|
false => 0} as f32;
|
||||||
|
|
||||||
|
let vspeed = match start_from_top {
|
||||||
|
true => -SPARK_VSPEED_XHIGHS,
|
||||||
|
false => SPARK_VSPEED_XHIGHS};
|
||||||
|
|
||||||
|
self.sparks.push_back(Spark::new(
|
||||||
|
vspeed,
|
||||||
|
new_energy.w * WHITE_EXTRA_SCALE,
|
||||||
|
Color{r: 0.0, g: 0.0, b: 0.0, w: 1.0},
|
||||||
|
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
||||||
|
start_led));
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove expired sparks in the beginning of the deque
|
||||||
|
while self.sparks.front().map_or(false, |s| s.has_expired()) {
|
||||||
|
self.sparks.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// render all remaining sparks
|
||||||
|
for spark in self.sparks.iter() {
|
||||||
|
spark.render(&mut self.colorlists);
|
||||||
|
}
|
||||||
|
|
||||||
|
// color post-processing
|
||||||
|
for strip in 0..config::NUM_STRIPS {
|
||||||
|
for led in 0..config::NUM_LEDS_PER_STRIP {
|
||||||
|
self.colorlists[strip][led].limit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
||||||
|
{
|
||||||
|
return &self.colorlists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
// vim: noet
|
|
||||||
|
|
||||||
use crate::animation::{Color, Animation, Result};
|
|
||||||
use crate::signal_processing::SignalProcessing;
|
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const COOLDOWN_FACTOR : f32 = 0.99995;
|
|
||||||
const MAX_ENERGY_PROPAGATION : f32 = 0.4;
|
|
||||||
const RM_ENERGY_SUB : f32 = 0.011;
|
|
||||||
const RM_ENERGY_MULT : f32 = 0.995;
|
|
||||||
const EXPONENT : f32 = 1.50;
|
|
||||||
const W_EXPONENT : f32 = 2.20;
|
|
||||||
const W_SCALE : f32 = 0.3;
|
|
||||||
const OVERDRIVE : f32 = 0.3;
|
|
||||||
|
|
||||||
// A single-color flame.
|
|
||||||
// This struct contains the energy generation and propagation algorithm.
|
|
||||||
pub struct Flame
|
|
||||||
{
|
|
||||||
energy: [ [f32; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Flame
|
|
||||||
{
|
|
||||||
fn new() -> Flame
|
|
||||||
{
|
|
||||||
Flame {
|
|
||||||
energy: [ [0.0; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, new_energy: f32)
|
|
||||||
{
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
// Add new energy in the bottom row
|
|
||||||
self.energy[strip][0] += rng.gen::<f32>() * new_energy;
|
|
||||||
|
|
||||||
// Remove energy at the top
|
|
||||||
self.energy[strip][config::NUM_LEDS_PER_STRIP-1] *= 1.0 - rng.gen::<f32>() * MAX_ENERGY_PROPAGATION;
|
|
||||||
|
|
||||||
// move energy upwards
|
|
||||||
for led_out in (1..config::NUM_LEDS_PER_STRIP).rev() {
|
|
||||||
let led_in = led_out - 1;
|
|
||||||
let energy_moved = self.energy[strip][led_in] * rng.gen::<f32>() * MAX_ENERGY_PROPAGATION;
|
|
||||||
|
|
||||||
self.energy[strip][led_in] -= energy_moved;
|
|
||||||
self.energy[strip][led_out] += energy_moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
// globally remove energy
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.energy[strip][led] *= RM_ENERGY_MULT;
|
|
||||||
if self.energy[strip][led] > RM_ENERGY_SUB {
|
|
||||||
self.energy[strip][led] -= RM_ENERGY_SUB;
|
|
||||||
} else {
|
|
||||||
self.energy[strip][led] = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print(&self)
|
|
||||||
{
|
|
||||||
println!("-----");
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
print!("{:8.3} ", self.energy[strip][led]);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
println!("-END-");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_energy(&self, strip: usize, led: usize) -> f32
|
|
||||||
{
|
|
||||||
self.energy[strip][led]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Merges multiple single-colored flames in a multi-colored one.
|
|
||||||
pub struct Fire
|
|
||||||
{
|
|
||||||
r_flame: Flame,
|
|
||||||
g_flame: Flame,
|
|
||||||
b_flame: Flame,
|
|
||||||
w_flame: Flame,
|
|
||||||
|
|
||||||
max_energy: Color,
|
|
||||||
|
|
||||||
colorlists: [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
|
|
||||||
sigproc: Rc<RefCell<SignalProcessing>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation for Fire
|
|
||||||
{
|
|
||||||
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Fire
|
|
||||||
{
|
|
||||||
Fire {
|
|
||||||
r_flame: Flame::new(),
|
|
||||||
g_flame: Flame::new(),
|
|
||||||
b_flame: Flame::new(),
|
|
||||||
w_flame: Flame::new(),
|
|
||||||
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
|
||||||
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
sigproc: sigproc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn periodic(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let sigproc = self.sigproc.borrow();
|
|
||||||
|
|
||||||
// extract frequency band energies
|
|
||||||
let cur_energy = Color{
|
|
||||||
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
|
||||||
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
|
||||||
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
|
||||||
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
|
||||||
|
|
||||||
// track the maximum energy with cooldown
|
|
||||||
self.max_energy.r *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.r > self.max_energy.r {
|
|
||||||
self.max_energy.r = cur_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.g *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.g > self.max_energy.g {
|
|
||||||
self.max_energy.g = cur_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.b *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.b > self.max_energy.b {
|
|
||||||
self.max_energy.b = cur_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.w *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.w > self.max_energy.w {
|
|
||||||
self.max_energy.w = cur_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the flames
|
|
||||||
self.r_flame.update((cur_energy.r / self.max_energy.r).powf(EXPONENT));
|
|
||||||
self.g_flame.update((cur_energy.g / self.max_energy.g).powf(EXPONENT));
|
|
||||||
self.b_flame.update((cur_energy.b / self.max_energy.b).powf(EXPONENT));
|
|
||||||
self.w_flame.update((cur_energy.w / self.max_energy.w).powf(W_EXPONENT));
|
|
||||||
|
|
||||||
//self.r_flame.print();
|
|
||||||
|
|
||||||
// color rendering and post-processing
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led] = Color {
|
|
||||||
r: self.r_flame.get_energy(strip, led),
|
|
||||||
g: self.g_flame.get_energy(strip, led),
|
|
||||||
b: self.b_flame.get_energy(strip, led),
|
|
||||||
w: self.w_flame.get_energy(strip, led) * W_SCALE
|
|
||||||
};
|
|
||||||
|
|
||||||
self.colorlists[strip][led].limit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
{
|
|
||||||
return &self.colorlists;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,139 +0,0 @@
|
||||||
// vim: noet
|
|
||||||
|
|
||||||
use crate::animation::{Color, Animation, Result};
|
|
||||||
use crate::signal_processing::SignalProcessing;
|
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const COOLDOWN_FACTOR : f32 = 0.99980;
|
|
||||||
const RGB_EXPONENT : f32 = 1.8;
|
|
||||||
const W_EXPONENT : f32 = 2.2;
|
|
||||||
const FADE_FACTOR : f32 = 0.98;
|
|
||||||
const AVG_LEDS_ACTIVATED : f32 = 0.02;
|
|
||||||
const WHITE_EXTRA_SCALE : f32 = 0.5;
|
|
||||||
const CONDENSATION_FACTOR : f32 = 5.0;
|
|
||||||
|
|
||||||
pub struct Particles
|
|
||||||
{
|
|
||||||
energy : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
max_energy : Color,
|
|
||||||
|
|
||||||
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
|
|
||||||
sigproc: Rc<RefCell<SignalProcessing>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation for Particles
|
|
||||||
{
|
|
||||||
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Particles
|
|
||||||
{
|
|
||||||
Particles {
|
|
||||||
energy: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
|
||||||
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
sigproc: sigproc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn periodic(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let sigproc = self.sigproc.borrow();
|
|
||||||
|
|
||||||
// extract frequency band energies
|
|
||||||
let cur_energy = Color{
|
|
||||||
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
|
||||||
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
|
||||||
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
|
||||||
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
|
||||||
|
|
||||||
// track the maximum energy with cooldown
|
|
||||||
self.max_energy.r *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.r > self.max_energy.r {
|
|
||||||
self.max_energy.r = cur_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.g *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.g > self.max_energy.g {
|
|
||||||
self.max_energy.g = cur_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.b *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.b > self.max_energy.b {
|
|
||||||
self.max_energy.b = cur_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.w *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.w > self.max_energy.w {
|
|
||||||
self.max_energy.w = cur_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fade all LEDs towards black
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.energy[strip][led].scale(FADE_FACTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// distribute the energy for each color
|
|
||||||
let new_energy = Color{
|
|
||||||
r: (cur_energy.r / self.max_energy.r).powf(RGB_EXPONENT),
|
|
||||||
g: (cur_energy.g / self.max_energy.g).powf(RGB_EXPONENT),
|
|
||||||
b: (cur_energy.b / self.max_energy.b).powf(RGB_EXPONENT),
|
|
||||||
w: (cur_energy.w / self.max_energy.w).powf(W_EXPONENT),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut remaining_energy = new_energy;
|
|
||||||
remaining_energy.scale(AVG_LEDS_ACTIVATED * config::NUM_LEDS_TOTAL as f32);
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
for coloridx in 0..=3 {
|
|
||||||
let new_energy_ref = new_energy.ref_by_index(coloridx).unwrap();
|
|
||||||
let rem_energy_ref = remaining_energy.ref_by_index_mut(coloridx).unwrap();
|
|
||||||
|
|
||||||
while *rem_energy_ref > 0.0 {
|
|
||||||
let mut rnd_energy = rng.gen::<f32>() * (*new_energy_ref) * CONDENSATION_FACTOR;
|
|
||||||
|
|
||||||
let rnd_strip = rng.gen_range(0..config::NUM_STRIPS);
|
|
||||||
let rnd_led = rng.gen_range(0..config::NUM_LEDS_PER_STRIP);
|
|
||||||
|
|
||||||
if rnd_energy > *rem_energy_ref {
|
|
||||||
rnd_energy = *rem_energy_ref;
|
|
||||||
*rem_energy_ref = 0.0;
|
|
||||||
} else {
|
|
||||||
*rem_energy_ref -= rnd_energy;
|
|
||||||
}
|
|
||||||
|
|
||||||
let led_ref = self.energy[rnd_strip][rnd_led].ref_by_index_mut(coloridx).unwrap();
|
|
||||||
*led_ref += rnd_energy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// color post-processing
|
|
||||||
self.colorlists = self.energy;
|
|
||||||
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led].w *= WHITE_EXTRA_SCALE;
|
|
||||||
|
|
||||||
self.colorlists[strip][led].limit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
{
|
|
||||||
return &self.colorlists;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,391 +0,0 @@
|
||||||
// vim: noet
|
|
||||||
|
|
||||||
use crate::animation::{Color, Animation, Result};
|
|
||||||
use crate::signal_processing::SignalProcessing;
|
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const COOLDOWN_FACTOR : f32 = 0.99980;
|
|
||||||
const RGB_EXPONENT : f32 = 1.5;
|
|
||||||
const W_EXPONENT : f32 = 2.2;
|
|
||||||
const W_SCALE : f32 = 0.3;
|
|
||||||
const ENERGY_FILTER_ALPHA : f32 = 0.20;
|
|
||||||
const BRIGHTNESS_FILTER_ALPHA : f32 = 0.002;
|
|
||||||
|
|
||||||
const NUM_RACERS_R : usize = 10 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
const NUM_RACERS_G : usize = 10 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
const NUM_RACERS_B : usize = 10 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_R : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_R : f32 = 80.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_R : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_R : f32 = 1.00;
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_G : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_G : f32 = 80.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_G : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_G : f32 = 1.00;
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_B : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_B : f32 = 80.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_B : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_B : f32 = 1.00;
|
|
||||||
|
|
||||||
const SPEED_SCALE_RANGE : f32 = 0.10;
|
|
||||||
|
|
||||||
fn dbg_bar(min: f32, current: f32, max: f32)
|
|
||||||
{
|
|
||||||
const LEN: usize = 60;
|
|
||||||
let mut bar = ['.'; LEN];
|
|
||||||
|
|
||||||
let minpos = ((LEN as f32) * min / max) as usize;
|
|
||||||
let curpos = ((LEN as f32) * current / max) as usize;
|
|
||||||
|
|
||||||
for idx in minpos..LEN {
|
|
||||||
bar[idx] = '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
if curpos < LEN {
|
|
||||||
bar[curpos] = '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("{}", bar.iter().collect::<String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A racer is a point of light that can move along the LED strips.
|
|
||||||
*/
|
|
||||||
struct Racer
|
|
||||||
{
|
|
||||||
min_speed: f32, // LEDs per frame
|
|
||||||
max_speed: f32, // LEDs per frame
|
|
||||||
direction: i8, // either +1 or -1
|
|
||||||
|
|
||||||
min_brightness: f32,
|
|
||||||
max_brightness: f32,
|
|
||||||
|
|
||||||
color: Color,
|
|
||||||
|
|
||||||
pos: f32,
|
|
||||||
|
|
||||||
brightness: f32,
|
|
||||||
flare_brightness: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Racer
|
|
||||||
{
|
|
||||||
pub fn new(min_speed: f32, max_speed: f32, min_brightness: f32, max_brightness: f32, color: Color, start_pos: f32, direction: i8) -> Racer
|
|
||||||
{
|
|
||||||
Racer {
|
|
||||||
min_speed: min_speed,
|
|
||||||
max_speed: max_speed,
|
|
||||||
min_brightness: min_brightness,
|
|
||||||
max_brightness: max_brightness,
|
|
||||||
direction: direction,
|
|
||||||
color: color,
|
|
||||||
pos: start_pos,
|
|
||||||
brightness: min_brightness,
|
|
||||||
flare_brightness: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _pos2ledstrip(pos: i32) -> (i32, i32)
|
|
||||||
{
|
|
||||||
let strip = pos / (config::NUM_LEDS_PER_STRIP as i32);
|
|
||||||
|
|
||||||
let mut led = pos % (config::NUM_LEDS_PER_STRIP as i32);
|
|
||||||
|
|
||||||
if (strip % 2) == 1 {
|
|
||||||
led = (config::NUM_LEDS_PER_STRIP as i32) - led - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
(strip, led)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, speed: f32, brightness: f32, flare_brightness: f32)
|
|
||||||
{
|
|
||||||
// move along the strip
|
|
||||||
let cur_speed = self.min_speed + speed * (self.max_speed - self.min_speed);
|
|
||||||
|
|
||||||
self.pos += (self.direction as f32) * cur_speed;
|
|
||||||
|
|
||||||
let maxpos = config::NUM_LEDS_TOTAL as f32;
|
|
||||||
|
|
||||||
// if the end is reached, reverse the direction
|
|
||||||
if self.pos >= maxpos {
|
|
||||||
self.direction = -1;
|
|
||||||
self.pos = 2.0 * maxpos - self.pos;
|
|
||||||
} else if self.pos <= 0.0 {
|
|
||||||
self.direction = 1;
|
|
||||||
self.pos = -self.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.brightness = brightness;
|
|
||||||
self.flare_brightness = flare_brightness;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&self, colorlists: &mut [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS])
|
|
||||||
{
|
|
||||||
let brightness = self.min_brightness + self.brightness * (self.max_brightness - self.min_brightness);
|
|
||||||
|
|
||||||
let fract_led = self.pos - self.pos.floor();
|
|
||||||
|
|
||||||
let led1_idx = self.pos.floor() as i32;
|
|
||||||
let led2_idx = self.pos.ceil() as i32;
|
|
||||||
|
|
||||||
let mut color = self.color;
|
|
||||||
color.w += self.flare_brightness;
|
|
||||||
|
|
||||||
let led1_color = color.scaled_copy((1.0 - fract_led) * brightness);
|
|
||||||
let led2_color = color.scaled_copy(fract_led * brightness);
|
|
||||||
|
|
||||||
if led1_idx >= 0 && led1_idx < (config::NUM_LEDS_TOTAL as i32) {
|
|
||||||
let (strip, led) = Racer::_pos2ledstrip(led1_idx);
|
|
||||||
|
|
||||||
colorlists[strip as usize][led as usize].add(&led1_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if led2_idx >= 0 && led2_idx < (config::NUM_LEDS_TOTAL as i32) {
|
|
||||||
let (strip, led) = Racer::_pos2ledstrip(led2_idx);
|
|
||||||
|
|
||||||
colorlists[strip as usize][led as usize].add(&led2_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Racers
|
|
||||||
{
|
|
||||||
max_energy : Color,
|
|
||||||
min_energy : Color,
|
|
||||||
filtered_energy : Color,
|
|
||||||
filtered_brightness : Color,
|
|
||||||
|
|
||||||
racers_r : Vec<Racer>,
|
|
||||||
racers_g : Vec<Racer>,
|
|
||||||
racers_b : Vec<Racer>,
|
|
||||||
|
|
||||||
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
|
|
||||||
frame_count: usize,
|
|
||||||
|
|
||||||
sigproc: Rc<RefCell<SignalProcessing>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation for Racers
|
|
||||||
{
|
|
||||||
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Racers
|
|
||||||
{
|
|
||||||
Racers {
|
|
||||||
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
|
||||||
min_energy: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
filtered_energy: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
filtered_brightness: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
racers_r: Vec::with_capacity(NUM_RACERS_R),
|
|
||||||
racers_g: Vec::with_capacity(NUM_RACERS_G),
|
|
||||||
racers_b: Vec::with_capacity(NUM_RACERS_B),
|
|
||||||
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
sigproc: sigproc,
|
|
||||||
frame_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_R {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_r.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_R * speed_scale,
|
|
||||||
RACER_MAX_SPEED_R * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_R,
|
|
||||||
RACER_MAX_BRIGHTNESS_R,
|
|
||||||
Color{r: 1.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_G {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_g.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_G * speed_scale,
|
|
||||||
RACER_MAX_SPEED_G * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_G,
|
|
||||||
RACER_MAX_BRIGHTNESS_G,
|
|
||||||
Color{r: 0.0, g: 1.0, b: 0.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_B {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_b.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_B * speed_scale,
|
|
||||||
RACER_MAX_SPEED_B * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_B,
|
|
||||||
RACER_MAX_BRIGHTNESS_B,
|
|
||||||
Color{r: 0.0, g: 0.0, b: 1.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn periodic(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let sigproc = self.sigproc.borrow();
|
|
||||||
|
|
||||||
// extract frequency band energies
|
|
||||||
let cur_energy = Color{
|
|
||||||
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
|
||||||
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
|
||||||
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
|
||||||
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
|
||||||
|
|
||||||
for i in 0..4 {
|
|
||||||
let f = self.filtered_energy.ref_by_index_mut(i).unwrap();
|
|
||||||
let n = cur_energy.ref_by_index(i).unwrap();
|
|
||||||
|
|
||||||
*f = (1.0 - ENERGY_FILTER_ALPHA) * (*f) + ENERGY_FILTER_ALPHA * (*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// track the maximum energy with cooldown
|
|
||||||
self.max_energy.r *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.r > self.max_energy.r {
|
|
||||||
self.max_energy.r = self.filtered_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.g *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.g > self.max_energy.g {
|
|
||||||
self.max_energy.g = self.filtered_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.b *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.b > self.max_energy.b {
|
|
||||||
self.max_energy.b = self.filtered_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.w *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.w > self.max_energy.w {
|
|
||||||
self.max_energy.w = self.filtered_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// track the minimum energy with warmup
|
|
||||||
self.min_energy.r += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.r * 0.5 - self.min_energy.r);
|
|
||||||
if self.filtered_energy.r < self.min_energy.r {
|
|
||||||
self.min_energy.r = self.filtered_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.g += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.g * 0.5 - self.min_energy.g);
|
|
||||||
if self.filtered_energy.g < self.min_energy.g {
|
|
||||||
self.min_energy.g = self.filtered_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.b += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.b * 0.5 - self.min_energy.b);
|
|
||||||
if self.filtered_energy.b < self.min_energy.b {
|
|
||||||
self.min_energy.b = self.filtered_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.w += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.w * 0.5 - self.min_energy.w);
|
|
||||||
if self.filtered_energy.w < self.min_energy.w {
|
|
||||||
self.min_energy.w = self.filtered_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set all LEDs initially to black
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
//self.colorlists[strip][led].scale(FADE_FACTOR);
|
|
||||||
self.colorlists[strip][led] = Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rescaling and normalization of the energies
|
|
||||||
let brightness = Color{
|
|
||||||
r: ((self.filtered_energy.r - self.min_energy.r) / (self.max_energy.r - self.min_energy.r)).powf(RGB_EXPONENT),
|
|
||||||
g: ((self.filtered_energy.g - self.min_energy.g) / (self.max_energy.g - self.min_energy.g)).powf(RGB_EXPONENT),
|
|
||||||
b: ((self.filtered_energy.b - self.min_energy.b) / (self.max_energy.b - self.min_energy.b)).powf(RGB_EXPONENT),
|
|
||||||
w: ((self.filtered_energy.w - self.min_energy.w) / (self.max_energy.w - self.min_energy.w)).powf(W_EXPONENT) * W_SCALE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// lowpass-filter brightness to reduce intensive fast flashing
|
|
||||||
for i in 0..4 {
|
|
||||||
let f = self.filtered_brightness.ref_by_index_mut(i).unwrap();
|
|
||||||
let n = brightness.ref_by_index(i).unwrap();
|
|
||||||
|
|
||||||
*f = (1.0 - BRIGHTNESS_FILTER_ALPHA) * (*f) + BRIGHTNESS_FILTER_ALPHA * (*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update all racers
|
|
||||||
let f = &self.filtered_brightness;
|
|
||||||
let speed = &brightness;
|
|
||||||
self.racers_r.iter_mut().for_each(|x| x.update(speed.r, f.r, f.w));
|
|
||||||
self.racers_g.iter_mut().for_each(|x| x.update(speed.g, f.g, f.w));
|
|
||||||
self.racers_b.iter_mut().for_each(|x| x.update(speed.b, f.b, f.w));
|
|
||||||
|
|
||||||
// render all racers
|
|
||||||
for racer in self.racers_r.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
for racer in self.racers_g.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
for racer in self.racers_b.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// color post-processing
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led].limit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// debug stuff
|
|
||||||
self.frame_count += 1;
|
|
||||||
if self.frame_count % 100 == 0 {
|
|
||||||
println!("---");
|
|
||||||
print!("Red "); dbg_bar(self.min_energy.r, self.filtered_brightness.r, self.max_energy.r); println!("{:11.2}", self.max_energy.r);
|
|
||||||
print!("Green "); dbg_bar(self.min_energy.g, self.filtered_brightness.g, self.max_energy.g); println!("{:11.2}", self.max_energy.g);
|
|
||||||
print!("Blue "); dbg_bar(self.min_energy.b, self.filtered_brightness.b, self.max_energy.b); println!("{:11.2}", self.max_energy.b);
|
|
||||||
print!("White "); dbg_bar(self.min_energy.w, self.filtered_brightness.w, self.max_energy.w); println!("{:11.2}", self.max_energy.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
{
|
|
||||||
return &self.colorlists;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,396 +0,0 @@
|
||||||
// vim: noet
|
|
||||||
|
|
||||||
use crate::animation::{Color, Animation, Result};
|
|
||||||
use crate::signal_processing::SignalProcessing;
|
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const COOLDOWN_FACTOR : f32 = 0.99980;
|
|
||||||
const RGB_EXPONENT : f32 = 1.5;
|
|
||||||
const W_EXPONENT : f32 = 2.2;
|
|
||||||
const W_SCALE : f32 = 0.3;
|
|
||||||
const ENERGY_FILTER_ALPHA : f32 = 0.20;
|
|
||||||
const BRIGHTNESS_FILTER_ALPHA : f32 = 0.10;
|
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
const NUM_RACERS_R : usize = 20 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
const NUM_RACERS_G : usize = 20 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
const NUM_RACERS_B : usize = 20 * config::NUM_LEDS_TOTAL / 300;
|
|
||||||
=======
|
|
||||||
const NUM_RACERS_R : usize = 20;
|
|
||||||
const NUM_RACERS_G : usize = 20;
|
|
||||||
const NUM_RACERS_B : usize = 20;
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_R : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_R : f32 = 60.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_R : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_R : f32 = 1.00;
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_G : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_G : f32 = 60.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_G : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_G : f32 = 1.00;
|
|
||||||
|
|
||||||
const RACER_MIN_SPEED_B : f32 = 0.5 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MAX_SPEED_B : f32 = 60.0 / config::FPS_ANIMATION;
|
|
||||||
const RACER_MIN_BRIGHTNESS_B : f32 = 0.01;
|
|
||||||
const RACER_MAX_BRIGHTNESS_B : f32 = 1.00;
|
|
||||||
|
|
||||||
const SPEED_SCALE_RANGE : f32 = 0.10;
|
|
||||||
|
|
||||||
fn dbg_bar(min: f32, current: f32, max: f32)
|
|
||||||
{
|
|
||||||
const LEN: usize = 60;
|
|
||||||
let mut bar = ['.'; LEN];
|
|
||||||
|
|
||||||
let minpos = ((LEN as f32) * min / max) as usize;
|
|
||||||
let curpos = ((LEN as f32) * current / max) as usize;
|
|
||||||
|
|
||||||
for idx in minpos..LEN {
|
|
||||||
bar[idx] = '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
if curpos < LEN {
|
|
||||||
bar[curpos] = '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("{}", bar.iter().collect::<String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A racer is a point of light that can move along the LED strips.
|
|
||||||
*/
|
|
||||||
struct Racer
|
|
||||||
{
|
|
||||||
min_speed: f32, // LEDs per frame
|
|
||||||
max_speed: f32, // LEDs per frame
|
|
||||||
direction: i8, // either +1 or -1
|
|
||||||
|
|
||||||
min_brightness: f32,
|
|
||||||
max_brightness: f32,
|
|
||||||
|
|
||||||
color: Color,
|
|
||||||
|
|
||||||
pos: f32,
|
|
||||||
|
|
||||||
brightness: f32,
|
|
||||||
flare_brightness: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Racer
|
|
||||||
{
|
|
||||||
pub fn new(min_speed: f32, max_speed: f32, min_brightness: f32, max_brightness: f32, color: Color, start_pos: f32, direction: i8) -> Racer
|
|
||||||
{
|
|
||||||
Racer {
|
|
||||||
min_speed: min_speed,
|
|
||||||
max_speed: max_speed,
|
|
||||||
min_brightness: min_brightness,
|
|
||||||
max_brightness: max_brightness,
|
|
||||||
direction: direction,
|
|
||||||
color: color,
|
|
||||||
pos: start_pos,
|
|
||||||
brightness: min_brightness,
|
|
||||||
flare_brightness: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _pos2ledstrip(pos: i32) -> (i32, i32)
|
|
||||||
{
|
|
||||||
let strip = pos / (config::NUM_LEDS_PER_STRIP as i32);
|
|
||||||
|
|
||||||
let mut led = pos % (config::NUM_LEDS_PER_STRIP as i32);
|
|
||||||
|
|
||||||
if (strip % 2) == 1 {
|
|
||||||
led = (config::NUM_LEDS_PER_STRIP as i32) - led - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
(strip, led)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, brightness: f32, flare_brightness: f32)
|
|
||||||
{
|
|
||||||
// move along the strip
|
|
||||||
let cur_speed = self.min_speed + brightness * (self.max_speed - self.min_speed);
|
|
||||||
|
|
||||||
self.pos += (self.direction as f32) * cur_speed;
|
|
||||||
|
|
||||||
let maxpos = config::NUM_LEDS_TOTAL as f32;
|
|
||||||
|
|
||||||
// if the end is reached, reverse the direction
|
|
||||||
if self.pos >= maxpos {
|
|
||||||
self.direction = -1;
|
|
||||||
self.pos = 2.0 * maxpos - self.pos;
|
|
||||||
} else if self.pos <= 0.0 {
|
|
||||||
self.direction = 1;
|
|
||||||
self.pos = -self.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.brightness = brightness;
|
|
||||||
self.flare_brightness = flare_brightness;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&self, colorlists: &mut [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS])
|
|
||||||
{
|
|
||||||
let brightness = self.min_brightness + self.brightness * (self.max_brightness - self.min_brightness);
|
|
||||||
|
|
||||||
let fract_led = self.pos - self.pos.floor();
|
|
||||||
|
|
||||||
let led1_idx = self.pos.floor() as i32;
|
|
||||||
let led2_idx = self.pos.ceil() as i32;
|
|
||||||
|
|
||||||
let mut color = self.color;
|
|
||||||
color.w += self.flare_brightness;
|
|
||||||
|
|
||||||
let led1_color = color.scaled_copy((1.0 - fract_led) * brightness);
|
|
||||||
let led2_color = color.scaled_copy(fract_led * brightness);
|
|
||||||
|
|
||||||
if led1_idx >= 0 && led1_idx < (config::NUM_LEDS_TOTAL as i32) {
|
|
||||||
let (strip, led) = Racer::_pos2ledstrip(led1_idx);
|
|
||||||
|
|
||||||
colorlists[strip as usize][led as usize].add(&led1_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if led2_idx >= 0 && led2_idx < (config::NUM_LEDS_TOTAL as i32) {
|
|
||||||
let (strip, led) = Racer::_pos2ledstrip(led2_idx);
|
|
||||||
|
|
||||||
colorlists[strip as usize][led as usize].add(&led2_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Racers
|
|
||||||
{
|
|
||||||
max_energy : Color,
|
|
||||||
min_energy : Color,
|
|
||||||
filtered_energy : Color,
|
|
||||||
filtered_brightness : Color,
|
|
||||||
|
|
||||||
racers_r : Vec<Racer>,
|
|
||||||
racers_g : Vec<Racer>,
|
|
||||||
racers_b : Vec<Racer>,
|
|
||||||
|
|
||||||
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
|
|
||||||
frame_count: usize,
|
|
||||||
|
|
||||||
sigproc: Rc<RefCell<SignalProcessing>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation for Racers
|
|
||||||
{
|
|
||||||
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Racers
|
|
||||||
{
|
|
||||||
Racers {
|
|
||||||
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
|
||||||
min_energy: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
filtered_energy: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
filtered_brightness: Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
racers_r: Vec::with_capacity(NUM_RACERS_R),
|
|
||||||
racers_g: Vec::with_capacity(NUM_RACERS_G),
|
|
||||||
racers_b: Vec::with_capacity(NUM_RACERS_B),
|
|
||||||
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
sigproc: sigproc,
|
|
||||||
frame_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_R {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_r.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_R * speed_scale,
|
|
||||||
RACER_MAX_SPEED_R * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_R,
|
|
||||||
RACER_MAX_BRIGHTNESS_R,
|
|
||||||
Color{r: 1.0, g: 0.0, b: 0.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_G {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_g.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_G * speed_scale,
|
|
||||||
RACER_MAX_SPEED_G * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_G,
|
|
||||||
RACER_MAX_BRIGHTNESS_G,
|
|
||||||
Color{r: 0.0, g: 1.0, b: 0.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _i in 0 .. NUM_RACERS_B {
|
|
||||||
let start_pos = rng.gen::<f32>() * (config::NUM_LEDS_TOTAL as f32);
|
|
||||||
let speed_scale = 1.0 + SPEED_SCALE_RANGE * (rng.gen::<f32>() - 0.5);
|
|
||||||
let mut dir = rng.gen::<i8>();
|
|
||||||
if dir > 0 {
|
|
||||||
dir = 1;
|
|
||||||
} else {
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.racers_b.push(Racer::new(
|
|
||||||
RACER_MIN_SPEED_B * speed_scale,
|
|
||||||
RACER_MAX_SPEED_B * speed_scale,
|
|
||||||
RACER_MIN_BRIGHTNESS_B,
|
|
||||||
RACER_MAX_BRIGHTNESS_B,
|
|
||||||
Color{r: 0.0, g: 0.0, b: 1.0, w: 0.0},
|
|
||||||
start_pos,
|
|
||||||
dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn periodic(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let sigproc = self.sigproc.borrow();
|
|
||||||
|
|
||||||
// extract frequency band energies
|
|
||||||
let cur_energy = Color{
|
|
||||||
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
|
||||||
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
|
||||||
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
|
||||||
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
|
||||||
|
|
||||||
for i in 0..4 {
|
|
||||||
let f = self.filtered_energy.ref_by_index_mut(i).unwrap();
|
|
||||||
let n = cur_energy.ref_by_index(i).unwrap();
|
|
||||||
|
|
||||||
*f = (1.0 - ENERGY_FILTER_ALPHA) * (*f) + ENERGY_FILTER_ALPHA * (*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// track the maximum energy with cooldown
|
|
||||||
self.max_energy.r *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.r > self.max_energy.r {
|
|
||||||
self.max_energy.r = self.filtered_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.g *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.g > self.max_energy.g {
|
|
||||||
self.max_energy.g = self.filtered_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.b *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.b > self.max_energy.b {
|
|
||||||
self.max_energy.b = self.filtered_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.w *= COOLDOWN_FACTOR;
|
|
||||||
if self.filtered_energy.w > self.max_energy.w {
|
|
||||||
self.max_energy.w = self.filtered_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// track the minimum energy with warmup
|
|
||||||
self.min_energy.r += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.r * 0.5 - self.min_energy.r);
|
|
||||||
if self.filtered_energy.r < self.min_energy.r {
|
|
||||||
self.min_energy.r = self.filtered_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.g += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.g * 0.5 - self.min_energy.g);
|
|
||||||
if self.filtered_energy.g < self.min_energy.g {
|
|
||||||
self.min_energy.g = self.filtered_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.b += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.b * 0.5 - self.min_energy.b);
|
|
||||||
if self.filtered_energy.b < self.min_energy.b {
|
|
||||||
self.min_energy.b = self.filtered_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_energy.w += (1.0 - COOLDOWN_FACTOR) * (self.max_energy.w * 0.5 - self.min_energy.w);
|
|
||||||
if self.filtered_energy.w < self.min_energy.w {
|
|
||||||
self.min_energy.w = self.filtered_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set all LEDs initially to black
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
//self.colorlists[strip][led].scale(FADE_FACTOR);
|
|
||||||
self.colorlists[strip][led] = Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rescaling and normalization of the energies
|
|
||||||
let brightness = Color{
|
|
||||||
r: ((self.filtered_energy.r - self.min_energy.r) / (self.max_energy.r - self.min_energy.r)).powf(RGB_EXPONENT),
|
|
||||||
g: ((self.filtered_energy.g - self.min_energy.g) / (self.max_energy.g - self.min_energy.g)).powf(RGB_EXPONENT),
|
|
||||||
b: ((self.filtered_energy.b - self.min_energy.b) / (self.max_energy.b - self.min_energy.b)).powf(RGB_EXPONENT),
|
|
||||||
w: ((self.filtered_energy.w - self.min_energy.w) / (self.max_energy.w - self.min_energy.w)).powf(W_EXPONENT) * W_SCALE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// lowpass-filter brightness to reduce intensive fast flashing
|
|
||||||
for i in 0..4 {
|
|
||||||
let f = self.filtered_brightness.ref_by_index_mut(i).unwrap();
|
|
||||||
let n = brightness.ref_by_index(i).unwrap();
|
|
||||||
|
|
||||||
*f = (1.0 - BRIGHTNESS_FILTER_ALPHA) * (*f) + BRIGHTNESS_FILTER_ALPHA * (*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update all racers
|
|
||||||
let f = &self.filtered_brightness;
|
|
||||||
self.racers_r.iter_mut().for_each(|x| x.update(f.r, f.w));
|
|
||||||
self.racers_g.iter_mut().for_each(|x| x.update(f.g, f.w));
|
|
||||||
self.racers_b.iter_mut().for_each(|x| x.update(f.b, f.w));
|
|
||||||
|
|
||||||
// render all racers
|
|
||||||
for racer in self.racers_r.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
for racer in self.racers_g.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
for racer in self.racers_b.iter() {
|
|
||||||
racer.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// color post-processing
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led].limit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// debug stuff
|
|
||||||
self.frame_count += 1;
|
|
||||||
if self.frame_count % 100 == 0 {
|
|
||||||
println!("---");
|
|
||||||
print!("Red "); dbg_bar(self.min_energy.r, self.filtered_brightness.r, self.max_energy.r); println!("{:11.2}", self.max_energy.r);
|
|
||||||
print!("Green "); dbg_bar(self.min_energy.g, self.filtered_brightness.g, self.max_energy.g); println!("{:11.2}", self.max_energy.g);
|
|
||||||
print!("Blue "); dbg_bar(self.min_energy.b, self.filtered_brightness.b, self.max_energy.b); println!("{:11.2}", self.max_energy.b);
|
|
||||||
print!("White "); dbg_bar(self.min_energy.w, self.filtered_brightness.w, self.max_energy.w); println!("{:11.2}", self.max_energy.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
{
|
|
||||||
return &self.colorlists;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,283 +0,0 @@
|
||||||
// vim: noet
|
|
||||||
|
|
||||||
use crate::animation::{Color, Animation, Result};
|
|
||||||
use crate::signal_processing::SignalProcessing;
|
|
||||||
use crate::config;
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const COOLDOWN_FACTOR : f32 = 0.99995;
|
|
||||||
const RGB_EXPONENT : f32 = 1.5;
|
|
||||||
const W_EXPONENT : f32 = 2.2;
|
|
||||||
const FADE_FACTOR : f32 = 0.97;
|
|
||||||
const AVG_LEDS_ACTIVATED : f32 = 0.03;
|
|
||||||
const WHITE_EXTRA_SCALE : f32 = 0.3;
|
|
||||||
const CONDENSATION_FACTOR : f32 = 5.0;
|
|
||||||
|
|
||||||
const SPARK_FADE_STEP : f32 = 2.500 / config::FPS_ANIMATION;
|
|
||||||
const SPARK_VSPEED_MIDS : f32 = 1.000 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
|
||||||
const SPARK_VSPEED_HIGHS : f32 = 0.800 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
|
||||||
const SPARK_VSPEED_XHIGHS : f32 = 0.500 * config::NUM_LEDS_PER_STRIP as f32 / config::FPS_ANIMATION;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A spark is a point of light that can move vertically along the LED strips.
|
|
||||||
*/
|
|
||||||
struct Spark
|
|
||||||
{
|
|
||||||
pub vspeed: f32, // LEDs per frame
|
|
||||||
pub brightness: f32,
|
|
||||||
pub color: Color,
|
|
||||||
|
|
||||||
strip: u16,
|
|
||||||
led: f32,
|
|
||||||
|
|
||||||
has_expired: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Spark
|
|
||||||
{
|
|
||||||
pub fn new(vspeed: f32, brightness: f32, color: Color, strip: u16, led: f32) -> Spark
|
|
||||||
{
|
|
||||||
Spark {
|
|
||||||
vspeed: vspeed,
|
|
||||||
brightness: brightness,
|
|
||||||
color: color,
|
|
||||||
strip: strip,
|
|
||||||
led: led,
|
|
||||||
has_expired: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self)
|
|
||||||
{
|
|
||||||
if self.has_expired {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.led += self.vspeed;
|
|
||||||
self.brightness -= SPARK_FADE_STEP;
|
|
||||||
|
|
||||||
if (self.led >= config::NUM_LEDS_PER_STRIP as f32) || (self.led <= -1.0) {
|
|
||||||
// moved outside of the LED array -> no need to update this any more
|
|
||||||
self.has_expired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.brightness <= 0.0 {
|
|
||||||
// moved outside of the LED array -> no need to update this any more
|
|
||||||
self.has_expired = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_expired(&self) -> bool
|
|
||||||
{
|
|
||||||
self.has_expired
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&self, colorlists: &mut [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS])
|
|
||||||
{
|
|
||||||
if self.has_expired {
|
|
||||||
// do not render if this Spark has expired
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fract_led = self.led - self.led.floor();
|
|
||||||
|
|
||||||
let led1_idx = self.led.floor() as i32;
|
|
||||||
let led2_idx = self.led.ceil() as usize;
|
|
||||||
|
|
||||||
let led1_color = self.color.scaled_copy(fract_led * self.brightness);
|
|
||||||
let led2_color = self.color.scaled_copy((1.0 - fract_led) * self.brightness);
|
|
||||||
|
|
||||||
if led1_idx >= 0 {
|
|
||||||
colorlists[self.strip as usize][led1_idx as usize].add(&led1_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if led2_idx < config::NUM_LEDS_PER_STRIP {
|
|
||||||
colorlists[self.strip as usize][led2_idx as usize].add(&led2_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sparkles
|
|
||||||
{
|
|
||||||
max_energy : Color,
|
|
||||||
|
|
||||||
sparks : VecDeque<Spark>,
|
|
||||||
|
|
||||||
colorlists : [ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
|
|
||||||
sigproc: Rc<RefCell<SignalProcessing>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation for Sparkles
|
|
||||||
{
|
|
||||||
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Sparkles
|
|
||||||
{
|
|
||||||
Sparkles {
|
|
||||||
max_energy: Color{r: 1.0, g: 1.0, b: 1.0, w: 1.0},
|
|
||||||
sparks: VecDeque::with_capacity(1024),
|
|
||||||
colorlists: [ [Color{r: 0.0, g: 0.0, b: 0.0, w: 0.0}; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS],
|
|
||||||
sigproc: sigproc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn periodic(&mut self) -> Result<()>
|
|
||||||
{
|
|
||||||
let sigproc = self.sigproc.borrow();
|
|
||||||
|
|
||||||
// extract frequency band energies
|
|
||||||
let cur_energy = Color{
|
|
||||||
r: sigproc.get_energy_in_band( 0.0, 400.0),
|
|
||||||
g: sigproc.get_energy_in_band( 400.0, 4000.0),
|
|
||||||
b: sigproc.get_energy_in_band( 4000.0, 12000.0),
|
|
||||||
w: sigproc.get_energy_in_band(12000.0, 22000.0)};
|
|
||||||
|
|
||||||
// track the maximum energy with cooldown
|
|
||||||
self.max_energy.r *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.r > self.max_energy.r {
|
|
||||||
self.max_energy.r = cur_energy.r;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.g *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.g > self.max_energy.g {
|
|
||||||
self.max_energy.g = cur_energy.g;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.b *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.b > self.max_energy.b {
|
|
||||||
self.max_energy.b = cur_energy.b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.max_energy.w *= COOLDOWN_FACTOR;
|
|
||||||
if cur_energy.w > self.max_energy.w {
|
|
||||||
self.max_energy.w = cur_energy.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fade all LEDs towards black
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led].scale(FADE_FACTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// distribute the energy for each color
|
|
||||||
let new_energy = Color{
|
|
||||||
r: (cur_energy.r / self.max_energy.r).powf(RGB_EXPONENT),
|
|
||||||
g: (cur_energy.g / self.max_energy.g).powf(RGB_EXPONENT),
|
|
||||||
b: (cur_energy.b / self.max_energy.b).powf(RGB_EXPONENT),
|
|
||||||
w: (cur_energy.w / self.max_energy.w).powf(W_EXPONENT),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut remaining_energy = new_energy.r;
|
|
||||||
remaining_energy *= AVG_LEDS_ACTIVATED * config::NUM_LEDS_TOTAL as f32;
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
// Red (bass) uses exactly the same algorithm as for the “Particles” animation.
|
|
||||||
while remaining_energy > 0.0 {
|
|
||||||
let mut rnd_energy = rng.gen::<f32>() * new_energy.r * CONDENSATION_FACTOR;
|
|
||||||
|
|
||||||
let rnd_strip = rng.gen_range(0..config::NUM_STRIPS);
|
|
||||||
let rnd_led = rng.gen_range(0..config::NUM_LEDS_PER_STRIP);
|
|
||||||
|
|
||||||
if rnd_energy > remaining_energy {
|
|
||||||
rnd_energy = remaining_energy;
|
|
||||||
remaining_energy = 0.0;
|
|
||||||
} else {
|
|
||||||
remaining_energy -= rnd_energy;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.colorlists[rnd_strip][rnd_led].r += rnd_energy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update all existing sparks
|
|
||||||
self.sparks.iter_mut().for_each(|x| x.update());
|
|
||||||
|
|
||||||
// Create green sparks for middle frequencies.
|
|
||||||
// They originate in the center and can go both up and down from there.
|
|
||||||
self.sparks.push_back(Spark::new(
|
|
||||||
match rng.gen::<bool>() {
|
|
||||||
true => SPARK_VSPEED_MIDS,
|
|
||||||
false => -SPARK_VSPEED_MIDS,
|
|
||||||
},
|
|
||||||
new_energy.g,
|
|
||||||
Color{r: 0.0, g: 1.0, b: 0.0, w: 0.0},
|
|
||||||
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
|
||||||
(config::NUM_LEDS_PER_STRIP as f32 / 2.0) - 0.5));
|
|
||||||
|
|
||||||
// Create blue sparks for high frequencies.
|
|
||||||
// They originate either in the top, moving down, or in the bottom, moving up
|
|
||||||
{
|
|
||||||
let start_from_top = rng.gen::<bool>();
|
|
||||||
|
|
||||||
let start_led = match start_from_top {
|
|
||||||
true => config::NUM_LEDS_PER_STRIP-1,
|
|
||||||
false => 0} as f32;
|
|
||||||
|
|
||||||
let vspeed = match start_from_top {
|
|
||||||
true => -SPARK_VSPEED_HIGHS,
|
|
||||||
false => SPARK_VSPEED_HIGHS};
|
|
||||||
|
|
||||||
self.sparks.push_back(Spark::new(
|
|
||||||
vspeed,
|
|
||||||
new_energy.b,
|
|
||||||
Color{r: 0.0, g: 0.0, b: 1.0, w: 0.0},
|
|
||||||
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
|
||||||
start_led));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create white sparks for very high frequencies.
|
|
||||||
// They originate either in the top, moving down, or in the bottom, moving up
|
|
||||||
{
|
|
||||||
let start_from_top = rng.gen::<bool>();
|
|
||||||
|
|
||||||
let start_led = match start_from_top {
|
|
||||||
true => config::NUM_LEDS_PER_STRIP-1,
|
|
||||||
false => 0} as f32;
|
|
||||||
|
|
||||||
let vspeed = match start_from_top {
|
|
||||||
true => -SPARK_VSPEED_XHIGHS,
|
|
||||||
false => SPARK_VSPEED_XHIGHS};
|
|
||||||
|
|
||||||
self.sparks.push_back(Spark::new(
|
|
||||||
vspeed,
|
|
||||||
new_energy.w * WHITE_EXTRA_SCALE,
|
|
||||||
Color{r: 0.0, g: 0.0, b: 0.0, w: 1.0},
|
|
||||||
rng.gen_range(0..config::NUM_STRIPS) as u16,
|
|
||||||
start_led));
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove expired sparks in the beginning of the deque
|
|
||||||
while self.sparks.front().map_or(false, |s| s.has_expired()) {
|
|
||||||
self.sparks.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
// render all remaining sparks
|
|
||||||
for spark in self.sparks.iter() {
|
|
||||||
spark.render(&mut self.colorlists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// color post-processing
|
|
||||||
for strip in 0..config::NUM_STRIPS {
|
|
||||||
for led in 0..config::NUM_LEDS_PER_STRIP {
|
|
||||||
self.colorlists[strip][led].limit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_colorlist(&self) -> &[ [Color; config::NUM_LEDS_PER_STRIP]; config::NUM_STRIPS]
|
|
||||||
{
|
|
||||||
return &self.colorlists;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -44,9 +44,7 @@ fn main()
|
||||||
|
|
||||||
// TODO: let the user select via the command line
|
// TODO: let the user select via the command line
|
||||||
//let mut anim: animation::particles::Particles = animation::Animation::new(sigproc.clone());
|
//let mut anim: animation::particles::Particles = animation::Animation::new(sigproc.clone());
|
||||||
//let mut anim: animation::sparkles::Sparkles = animation::Animation::new(sigproc.clone());
|
let mut anim: animation::sparkles::Sparkles = animation::Animation::new(sigproc.clone());
|
||||||
//let mut anim: animation::racers::Racers = animation::Animation::new(sigproc.clone());
|
|
||||||
let mut anim: animation::fire::Fire = animation::Animation::new(sigproc.clone());
|
|
||||||
|
|
||||||
println!("Calling Animation::init()...");
|
println!("Calling Animation::init()...");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue