// 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>, user_script_path: &str) -> std::result::Result { 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::>(); // 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>, } 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 ) }); } }