Compare commits

...

9 Commits

Author SHA1 Message Date
Thomas Kolb fc43035f9b Removing last remains of the Lua interface 8 months ago
Thomas Kolb d01c5b459c New animation: sparkles 8 months ago
Thomas Kolb c1202bb41c Work around panic in time::Instant subtraction 8 months ago
Thomas Kolb c08f24f8dc Reset send timing if lag becomes too high 8 months ago
Thomas Kolb 898ebbecc7 UDPProto: Fixed index-out-of-bounds bug 9 months ago
Thomas Kolb b683c8a391 main: handle “Connection refused” UDP errors 9 months ago
Thomas Kolb 24850f1a1b particles animation: config tuning 9 months ago
Thomas Kolb 35c5c33689 Disabled userscript module 9 months ago
Thomas Kolb 7929861ad4 Implement "particles" animation in Rust 9 months ago
  1. 99
      Cargo.lock
  2. 2
      Cargo.toml
  3. 554
      src/animation.rs
  4. 3
      src/config.rs
  5. 84
      src/main.rs
  6. 2
      src/udpproto.rs
  7. 158
      src/userscript.rs
  8. 31
      test.lua

99
Cargo.lock

@ -41,27 +41,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bstr"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
dependencies = [
"memchr",
]
[[package]]
name = "byteorder"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -121,6 +106,17 @@ dependencies = [
"num-complex",
]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.23.0"
@ -157,12 +153,6 @@ dependencies = [
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "miniz_oxide"
version = "0.4.3"
@ -173,26 +163,13 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mlua"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bd179c80340f87911c17eb365ab9f890cf745ac722860d1465be5889be2853a"
dependencies = [
"bstr",
"cc",
"lazy_static",
"num-traits",
"pkg-config",
]
[[package]]
name = "musiclight"
version = "0.1.0"
dependencies = [
"byteorder",
"fftw",
"mlua",
"rand",
]
[[package]]
@ -233,10 +210,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
[[package]]
name = "pkg-config"
version = "0.3.19"
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro2"
@ -256,6 +233,46 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core",
]
[[package]]
name = "rawpointer"
version = "0.1.0"
@ -296,3 +313,9 @@ name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"

2
Cargo.toml

@ -9,4 +9,4 @@ edition = "2018"
[dependencies]
byteorder = "1.4"
fftw = { version = "0.6", default-features = false, features = ["system"] }
mlua = { version = "0.5", features = ["lua53"] }
rand = "0.8"

554
src/animation.rs

@ -0,0 +1,554 @@
// vim: noet
use std::fmt;
use std::error::Error as StdError;
use std::rc::Rc;
use std::cell::RefCell;
use crate::config;
use crate::signal_processing::SignalProcessing;
type Result<T> = std::result::Result<T, AnimationError>;
/////////// Error Type and Implementation ////////////
#[derive(Debug)]
pub enum AnimationError
{
ErrorMessage(std::string::String),
}
impl fmt::Display for AnimationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AnimationError::ErrorMessage(s) => f.write_fmt(format_args!("Message({})", s))?,
};
Ok(())
}
}
impl StdError for AnimationError {
fn description(&self) -> &str {
match *self {
AnimationError::ErrorMessage(_) => "Error Message",
}
}
}
/////////// Helper Structs ////////////
#[derive(Copy, Clone)]
pub struct Color
{
pub r: f32,
pub g: f32,
pub b: f32,
pub w: f32
}
impl Color
{
pub fn scale(&mut self, factor: f32)
{
self.r *= factor;
self.g *= factor;
self.b *= factor;
self.w *= factor;
}
pub fn scaled_copy(&self, factor: f32) -> Color
{
let mut c = *self;
c.scale(factor);
c
}
pub fn add(&mut self, other: &Color)
{
self.r += other.r;
self.g += other.g;
self.b += other.b;
self.w += other.w;
}
fn _limit_component(c: &mut f32)
{
if *c > 1.0 {
*c = 1.0;
} else if *c < 0.0 {
*c = 0.0;
}
}
pub fn limit(&mut self)
{
Color::_limit_component(&mut self.r);
Color::_limit_component(&mut self.g);
Color::_limit_component(&mut self.b);
Color::_limit_component(&mut self.w);
}
pub fn ref_by_index_mut(&mut self, i: usize) -> Option<&mut f32>
{
match i {
0 => Some(&mut self.r),
1 => Some(&mut self.g),
2 => Some(&mut self.b),
3 => Some(&mut self.w),
_ => None
}
}
pub fn ref_by_index(&self, i: usize) -> Option<&f32>
{
match i {
0 => Some(&self.r),
1 => Some(&self.g),
2 => Some(&self.b),
3 => Some(&self.w),
_ => None
}
}
}
/////////// Animation Trait ////////////
pub trait Animation {
fn new(sigproc: Rc<RefCell<SignalProcessing>>) -> Self;
fn init(&mut self) -> Result<()>;
fn periodic(&mut self) -> Result<()>;
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;
}
}
}

3
src/config.rs

@ -13,3 +13,6 @@ pub const NUM_LEDS_TOTAL: usize = NUM_STRIPS * NUM_LEDS_PER_STRIP;
// network configuration
pub const UDP_SERVER_ADDR: &str = "192.168.23.118:2703";
pub const FPS_ANIMATION: f32 = SAMP_RATE / SAMPLES_PER_UPDATE as f32;
pub const FPS_LEDS: f32 = 60.0;

84
src/main.rs

@ -7,12 +7,12 @@ use byteorder::{NativeEndian, ReadBytesExt};
mod signal_processing;
mod config;
mod userscript;
mod udpproto;
mod animation;
use crate::signal_processing::SignalProcessing;
use crate::userscript::UserScript;
use crate::udpproto::UdpProto;
use crate::animation::Animation;
use std::rc::Rc;
use std::cell::RefCell;
@ -40,34 +40,24 @@ fn main()
let sigproc = Rc::new(RefCell::new(
SignalProcessing::new(config::BLOCK_LEN, config::SAMP_RATE).unwrap()));
// set up Lua environment
println!("Contructing Animation...");
println!("Loading user script...");
// TODO: let the user select via the command line
//let mut anim: animation::particles::Particles = animation::Animation::new(sigproc.clone());
let mut anim: animation::sparkles::Sparkles = animation::Animation::new(sigproc.clone());
let mut script = match UserScript::new(sigproc.clone(), "particles.lua") {
Ok(script) => script,
Err(e) => {
println!("=== Lua Error ===\n{}\n====> Terminating.", e);
exit(1);
}
};
println!("Calling Animation::init()...");
println!("Calling init()...");
match script.init() {
Ok(_) => (),
Err(e) => {
println!("=== Lua Error ===\n{}\n====> Terminating.", e);
exit(1);
}
};
anim.init().unwrap();
println!("Done! Starting main loop…");
// Timing setup
let block_period = Duration::from_nanos((0.95 * (config::SAMPLES_PER_UPDATE as f32) * 1e9 / config::SAMP_RATE) as u64);
let send_period = Duration::from_nanos(1000000000 / 60);
let send_period = Duration::from_nanos((1000000000.0 / config::FPS_LEDS) as u64);
let max_lag = 5*send_period;
let mut next_block_instant = Instant::now() + block_period;
let mut next_send_instant = Instant::now() + send_period;
@ -112,29 +102,55 @@ fn main()
}
// call the periodic function in the user script
match script.periodic() {
match anim.periodic() {
Ok(_) => (),
Err(e) => {
println!("=== Lua Error ===\n{}\n====> Terminating.", e);
println!("=== Animation Error ===\n{}\n====> Terminating.", e);
exit(1);
}
};
// FIXME: only send with 60 FPS!
if Instant::now() > next_send_instant {
for i in 0..script.colorlists[0].len() {
udpproto.set_color((i / config::NUM_LEDS_PER_STRIP) as u8,
(i % config::NUM_LEDS_PER_STRIP) as u8,
(script.colorlists[0][i] * 255.0) as u8,
(script.colorlists[1][i] * 255.0) as u8,
(script.colorlists[2][i] * 255.0) as u8,
(script.colorlists[3][i] * 255.0) as u8).unwrap();
let colorlists = anim.get_colorlist();
for i in 0..config::NUM_LEDS_TOTAL {
let strip = i / config::NUM_LEDS_PER_STRIP;
let led = i % config::NUM_LEDS_PER_STRIP;
let r = udpproto.set_color(strip as u8,
led as u8,
(colorlists[strip][led].r * 255.0) as u8,
(colorlists[strip][led].g * 255.0) as u8,
(colorlists[strip][led].b * 255.0) as u8,
(colorlists[strip][led].w * 255.0) as u8);
match r {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::ConnectionRefused => {
// try again in one second
next_send_instant += Duration::from_secs(1);
break;
}
Err(e) => panic!(e),
}
}
udpproto.commit().unwrap();
match udpproto.commit() {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::ConnectionRefused => {
// try again in one second
next_send_instant += Duration::from_secs(1);
}
Err(e) => panic!(e),
}
next_send_instant += send_period;
let now = Instant::now();
if now > (next_send_instant + max_lag) {
println!("Warning! Lag exceeds {:?}. Resetting sender timing.", max_lag);
next_send_instant = now + send_period;
} else {
next_send_instant += send_period;
}
}
let now = Instant::now();

2
src/udpproto.rs

@ -63,7 +63,7 @@ impl UdpProto
self.packet_offset += 7;
if self.packet_offset >= MAX_PACKET_LEN {
if self.packet_offset >= MAX_PACKET_LEN-7 {
self.send_packet()?;
}

158
src/userscript.rs

@ -1,158 +0,0 @@
// vim: noet
/*
* Module for the Lua scripting interface of Musiclight.
*/
/* Test code for reference
// test the mlua crate
let lua_state = Lua::new();
lua_state.globals().set("get_rust_value", lua_state.create_function(|_, ()| {
Ok(3)
}).unwrap()).unwrap();
let user_script = std::fs::read_to_string("test.lua").unwrap();
lua_state.load(&user_script).exec().unwrap();
let lua_func_test : mlua::Function = lua_state.globals().get("test").unwrap();
println!("{}", lua_func_test.call::<_, u32>(123).unwrap());
*/
use crate::config;
use crate::signal_processing::SignalProcessing;
use mlua::Lua;
use mlua::FromLua;
use mlua::Error;
use std::rc::Rc;
use std::cell::RefCell;
pub struct UserScript
{
lua_state: Lua,
pub colorlists: [ [f32; config::NUM_LEDS_TOTAL]; 4],
}
impl UserScript
{
pub fn new(sigproc: Rc<RefCell<SignalProcessing>>, user_script_path: &str) -> std::result::Result<UserScript, mlua::Error>
{
let s = UserScript {
lua_state: Lua::new(),
colorlists: [ [0f32; config::NUM_LEDS_TOTAL]; 4],
};
// provide some configuration constants to Lua via a table
let config_table = s.lua_state.create_table()?;
config_table.set("sampling_rate", config::SAMP_RATE)?;
config_table.set("block_length", config::BLOCK_LEN)?;
config_table.set("samples_per_update", config::SAMPLES_PER_UPDATE)?;
s.lua_state.globals().set("CONFIG", config_table)?;
// register the signal processing reference as Lua user data
s.lua_state.globals().set("sigproc", SignalProcessingWrapper{
signal_processing: sigproc
})?;
// register the LED interface
s.lua_state.globals().set("leds", LuaLED{})?;
// load the user script and execute it to make variables and functions available
let user_script = std::fs::read_to_string(user_script_path)?;
s.lua_state.load(&user_script).exec()?;
Ok(s)
}
pub fn init(&self) -> std::result::Result<(), mlua::Error>
{
// find the init functions
let lua_init_func: mlua::Function = self.lua_state.globals().get("init")?;
lua_init_func.call( (config::NUM_STRIPS, config::NUM_LEDS_PER_STRIP) )?;
Ok(())
}
pub fn periodic(&mut self) -> std::result::Result<(), mlua::Error>
{
// find the init functions
let lua_periodic_func: mlua::Function = self.lua_state.globals().get("periodic")?;
// call the script's periodic() function, which (hopefully) returns four Tables with color
// values
let rvals = lua_periodic_func.call::<_, mlua::MultiValue>( () )?;
// check the number of returned values
if rvals.len() != 4 {
return Err(Error::RuntimeError("Wrong number of return values from 'periodic'. Expected 4.".to_string()));
}
// convert the Lua Tables to normal vectors
let mut i = 0;
for rval in rvals {
let table = mlua::Table::from_lua(rval, &self.lua_state)?;
let v = table.sequence_values()
.map(|x| x.unwrap())
.collect::<Vec<f32>>();
// check the length of the color array
if v.len() != config::NUM_LEDS_TOTAL {
return Err(Error::RuntimeError("Number of color values returned from 'periodic' must match number of LEDs given in 'init'.".to_string()));
}
for j in 0 .. self.colorlists[i].len() {
self.colorlists[i][j] = v[j];
}
i += 1;
}
Ok(())
}
}
/*
* Wrap a SignalProcessing instance and provide a Lua interface for some of its methods.
*/
struct SignalProcessingWrapper
{
signal_processing: Rc<RefCell<SignalProcessing>>,
}
impl mlua::UserData for SignalProcessingWrapper
{
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M)
{
methods.add_method("get_energy_in_band", |_lua, this, (start_freq, end_freq): (f32, f32)| {
Ok(this.signal_processing.borrow().get_energy_in_band(start_freq, end_freq))
});
}
}
/*
* Lua interface for functions related to the LED setup
*/
struct LuaLED
{
}
impl mlua::UserData for LuaLED
{
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M)
{
methods.add_method("idx", |_lua, _this, (strip, led): (usize, usize)| {
Ok( (strip - 1) * config::NUM_LEDS_PER_STRIP + (led - 1) + 1 )
});
}
}

31
test.lua

@ -1,31 +0,0 @@
function init(nstrip, nmod)
print("Initializing with "..nstrip.." strips with "..nmod.." modules each.")
print("Sampling rate: "..CONFIG['sampling_rate'].." Hz")
local nled = nstrip * nmod
red = {}
green = {}
blue = {}
white = {}
for i = 1,nled do
red[i] = 0.8
green[i] = 0.1
blue[i] = 0.2
white[i] = 0.1
end
return 0
end
function periodic()
bass = sigproc:get_energy_in_band(0, 400)
mid = sigproc:get_energy_in_band(400, 4000)
treble = sigproc:get_energy_in_band(4000, 20000)
print("Bass: "..bass.." – Mid: "..mid.." – Treble: "..treble)
return red, green, blue, white
end
Loading…
Cancel
Save