Update
This commit is contained in:
61
rust_native_module/index.d.ts
vendored
61
rust_native_module/index.d.ts
vendored
@@ -1,31 +1,42 @@
|
||||
type Result<T> =
|
||||
{ type: "success" } & T
|
||||
| { type: "error", message: string };
|
||||
|
||||
type Brightness =
|
||||
{ type: "off" }
|
||||
| { type: "switch" }
|
||||
| { type: "dimmer", value: number }
|
||||
| { type: "strobe", value: number };
|
||||
|
||||
type MovingHeadState = {
|
||||
start_address: number, // [0, 512]
|
||||
|
||||
pan: number, // [-3pi/2, 3pi/2]
|
||||
tilt: number, // [-pi/2, pi/2]
|
||||
brightness: Brightness,
|
||||
rgbw: [number, number, number, number], // RGBW, [0, 255]
|
||||
speed: number, // [255, 0]
|
||||
}
|
||||
|
||||
type OutputHandle = {
|
||||
set: (heads: [MovingHeadState, MovingHeadState, MovingHeadState, MovingHeadState]) => Result<{}>,
|
||||
close: () => Result<{}>,
|
||||
}
|
||||
|
||||
declare module rust_native_module {
|
||||
type Result<T> =
|
||||
{ type: "success" } & T
|
||||
| { type: "error", message: string };
|
||||
|
||||
type Option<T> =
|
||||
{ type: "some", value: T }
|
||||
| { type: "none" }
|
||||
|
||||
type Brightness =
|
||||
{ type: "off" }
|
||||
| { type: "switch" }
|
||||
| { type: "dimmer", value: number }
|
||||
| { type: "strobe", value: number };
|
||||
|
||||
type MovingHeadState = {
|
||||
startAddress: number, // [0, 512]
|
||||
|
||||
pan: number, // [-3pi/2, 3pi/2]
|
||||
tilt: number, // [-pi/2, pi/2]
|
||||
brightness: Brightness,
|
||||
rgbw: [number, number, number, number], // RGBW, [0, 255]
|
||||
speed: number, // [255, 0]
|
||||
}
|
||||
|
||||
type OutputHandle = {
|
||||
set: (heads: Array<MovingHeadState>) => Result<{}>,
|
||||
close: () => Result<{}>,
|
||||
}
|
||||
|
||||
type BeatTrackerHandle = {
|
||||
tap: () => void,
|
||||
getProgress: () => Option<number>,
|
||||
}
|
||||
|
||||
function listPorts(): Array<string>;
|
||||
function openOutput(): Result<OutputHandle>;
|
||||
|
||||
function getBeatTracker(): Result<BeatTrackerHandle>;
|
||||
}
|
||||
|
||||
export = rust_native_module;
|
||||
47
rust_native_module/src/beat_tracking/metronome.rs
Normal file
47
rust_native_module/src/beat_tracking/metronome.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct Metronome {
|
||||
taps: Vec<Instant>,
|
||||
beat_interval: Option<u128>,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl Metronome {
|
||||
pub fn new(timeout: Duration) -> Self {
|
||||
Self {
|
||||
taps: Vec::new(),
|
||||
beat_interval: None,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tap(&mut self) {
|
||||
let now = Instant::now();
|
||||
if let Some(t) = self.taps.last() {
|
||||
if now - *t > self.timeout {
|
||||
self.taps.clear();
|
||||
self.beat_interval = None;
|
||||
}
|
||||
}
|
||||
|
||||
self.taps.push(now);
|
||||
|
||||
let n = self.taps.len();
|
||||
if n >= 2 {
|
||||
let dt = *self.taps.last().unwrap() - *self.taps.first().unwrap();
|
||||
let interval_millis = dt.as_millis() / (n - 1) as u128;
|
||||
self.beat_interval = Some(interval_millis);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_beat_progress(&self) -> Option<f64> {
|
||||
if self.beat_interval.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let relative_millis = (now - *self.taps.last().unwrap()).as_millis();
|
||||
|
||||
Some(relative_millis as f64 / self.beat_interval.unwrap() as f64)
|
||||
}
|
||||
}
|
||||
67
rust_native_module/src/beat_tracking/mod.rs
Normal file
67
rust_native_module/src/beat_tracking/mod.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
mod metronome;
|
||||
|
||||
use std::{cell::RefCell, time::Duration};
|
||||
|
||||
use metronome::Metronome;
|
||||
|
||||
use neon::prelude::*;
|
||||
|
||||
type BoxedTracker = JsBox<RefCell<Metronome>>;
|
||||
impl Finalize for Metronome {}
|
||||
|
||||
pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let success_string = cx.string("success".to_string());
|
||||
obj.set(&mut cx, "type", success_string)?;
|
||||
|
||||
let boxed_tracker = cx.boxed(RefCell::new(Metronome::new(Duration::from_secs(2))));
|
||||
obj.set(&mut cx, "_rust_ptr", boxed_tracker)?;
|
||||
|
||||
let tap_function = JsFunction::new(&mut cx, tap)?;
|
||||
obj.set(&mut cx, "tap", tap_function)?;
|
||||
|
||||
let get_progress_function = JsFunction::new(&mut cx, get_progress)?;
|
||||
obj.set(&mut cx, "getProgress", get_progress_function)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub fn tap(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let this = cx.this();
|
||||
|
||||
let boxed_tracker = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedTracker, _>(&mut cx)?;
|
||||
|
||||
boxed_tracker.borrow_mut().tap();
|
||||
|
||||
Ok(JsUndefined::new(&mut cx).upcast())
|
||||
}
|
||||
|
||||
pub fn get_progress(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let this = cx.this();
|
||||
|
||||
let boxed_tracker = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedTracker, _>(&mut cx)?;
|
||||
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let type_string;
|
||||
match boxed_tracker.borrow().current_beat_progress() {
|
||||
Some(progress) => {
|
||||
type_string = cx.string("some".to_string());
|
||||
|
||||
let progress = cx.number(progress);
|
||||
obj.set(&mut cx, "value", progress)?;
|
||||
}
|
||||
None => {
|
||||
type_string = cx.string("none".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
obj.set(&mut cx, "type", type_string)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
@@ -1,199 +1,13 @@
|
||||
pub mod output;
|
||||
use std::cell::RefCell;
|
||||
|
||||
use output::controller::Controller;
|
||||
use output::fixtures::{Brightness, DMXFixture, MovingHead};
|
||||
|
||||
type BoxedController = JsBox<RefCell<Controller>>;
|
||||
impl Finalize for Controller {}
|
||||
mod beat_tracking;
|
||||
mod output;
|
||||
|
||||
use neon::prelude::*;
|
||||
|
||||
fn list_ports(mut cx: FunctionContext) -> JsResult<JsArray> {
|
||||
let array = cx.empty_array();
|
||||
|
||||
if let Ok(ports) = serialport::available_ports() {
|
||||
for (i, port) in ports.iter().enumerate() {
|
||||
let port_name = cx.string(&port.port_name);
|
||||
array.set(&mut cx, i as u32, port_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
fn open_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let path = cx.argument::<JsString>(0)?;
|
||||
let path = path.value(&mut cx);
|
||||
|
||||
let obj = cx.empty_object();
|
||||
let success_string;
|
||||
|
||||
match Controller::new(path) {
|
||||
Ok(controller) => {
|
||||
success_string = cx.string("success");
|
||||
|
||||
let boxed_controller = cx.boxed(RefCell::new(controller));
|
||||
obj.set(&mut cx, "_rust_ptr", boxed_controller)?;
|
||||
|
||||
let set_output_function = JsFunction::new(&mut cx, set_output)?;
|
||||
obj.set(&mut cx, "set", set_output_function)?;
|
||||
|
||||
let close_function = JsFunction::new(&mut cx, close_output)?;
|
||||
obj.set(&mut cx, "close", close_function)?;
|
||||
}
|
||||
Err(e) => {
|
||||
success_string = cx.string("error");
|
||||
|
||||
let error_message = cx.string(e.to_string());
|
||||
obj.set(&mut cx, "message", error_message)?;
|
||||
}
|
||||
}
|
||||
|
||||
obj.set(&mut cx, "type", success_string)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn set_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let arg = cx.argument::<JsArray>(0)?;
|
||||
|
||||
let mut moving_heads = Vec::with_capacity(4);
|
||||
|
||||
for i in 0..4 {
|
||||
// why isn't neon-serde maintained anymore? T_T
|
||||
|
||||
let head = arg
|
||||
.get(&mut cx, i)?
|
||||
.downcast_or_throw::<JsObject, _>(&mut cx)?;
|
||||
|
||||
moving_heads.push(MovingHead {
|
||||
start_address: head
|
||||
.get(&mut cx, "startAddress")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as usize,
|
||||
|
||||
pan: head
|
||||
.get(&mut cx, "pan")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
|
||||
tilt: head
|
||||
.get(&mut cx, "tilt")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
|
||||
brightness: {
|
||||
let brightness = head
|
||||
.get(&mut cx, "brightness")?
|
||||
.downcast_or_throw::<JsObject, _>(&mut cx)?;
|
||||
|
||||
match brightness
|
||||
.get(&mut cx, "type")?
|
||||
.downcast_or_throw::<JsString, _>(&mut cx)?
|
||||
.value(&mut cx)
|
||||
.as_str()
|
||||
{
|
||||
"off" => Brightness::Off,
|
||||
"dimmer" => Brightness::Dimmer(
|
||||
brightness
|
||||
.get(&mut cx, "value")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
),
|
||||
"strobe" => Brightness::Strobe(
|
||||
brightness
|
||||
.get(&mut cx, "value")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
),
|
||||
"switch" => Brightness::Switch,
|
||||
s => {
|
||||
let err = cx.string(format!("Invalid brightness type: {}", s));
|
||||
return cx.throw(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
rgbw: {
|
||||
let rgbw = head
|
||||
.get(&mut cx, "rgbw")?
|
||||
.downcast_or_throw::<JsArray, _>(&mut cx)?;
|
||||
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for i in 0..4 {
|
||||
let v = rgbw
|
||||
.get(&mut cx, i)?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as u8;
|
||||
|
||||
vec.push(v);
|
||||
}
|
||||
|
||||
(vec[0], vec[1], vec[2], vec[3])
|
||||
},
|
||||
|
||||
speed: head
|
||||
.get(&mut cx, "speed")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
let this = cx.this();
|
||||
let boxed_controller = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
|
||||
|
||||
let boxed_controller = boxed_controller.borrow_mut();
|
||||
{
|
||||
let mut state = boxed_controller.state.lock().unwrap();
|
||||
|
||||
for head in moving_heads {
|
||||
head.render(&mut state.data);
|
||||
}
|
||||
}
|
||||
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let success_text = cx.string("success".to_string());
|
||||
obj.set(&mut cx, "type", success_text)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn close_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let this = cx.this();
|
||||
|
||||
let boxed_controller = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
|
||||
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let success_string;
|
||||
|
||||
match boxed_controller.borrow_mut().stop() {
|
||||
Ok(()) => {
|
||||
success_string = cx.string("success");
|
||||
}
|
||||
Err(e) => {
|
||||
success_string = cx.string("error");
|
||||
|
||||
let error_message = cx.string(e.to_string());
|
||||
obj.set(&mut cx, "message", error_message)?;
|
||||
}
|
||||
}
|
||||
|
||||
obj.set(&mut cx, "type", success_string)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
#[neon::main]
|
||||
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||
cx.export_function("listPorts", list_ports)?;
|
||||
cx.export_function("openOutput", open_output)?;
|
||||
cx.export_function("listPorts", output::list_ports)?;
|
||||
cx.export_function("openOutput", output::open_output)?;
|
||||
|
||||
cx.export_function("getBeatTracker", beat_tracking::get_beat_tracker)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,2 +1,194 @@
|
||||
pub mod fixtures;
|
||||
pub mod controller;
|
||||
mod controller;
|
||||
mod fixtures;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use controller::Controller;
|
||||
use fixtures::{Brightness, DMXFixture, MovingHead};
|
||||
|
||||
type BoxedController = JsBox<RefCell<Controller>>;
|
||||
impl Finalize for Controller {}
|
||||
|
||||
use neon::prelude::*;
|
||||
|
||||
pub fn list_ports(mut cx: FunctionContext) -> JsResult<JsArray> {
|
||||
let array = cx.empty_array();
|
||||
|
||||
if let Ok(ports) = serialport::available_ports() {
|
||||
for (i, port) in ports.iter().enumerate() {
|
||||
let port_name = cx.string(&port.port_name);
|
||||
array.set(&mut cx, i as u32, port_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
pub fn open_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let path = cx.argument::<JsString>(0)?;
|
||||
let path = path.value(&mut cx);
|
||||
|
||||
let obj = cx.empty_object();
|
||||
let success_string;
|
||||
|
||||
match Controller::new(path) {
|
||||
Ok(controller) => {
|
||||
success_string = cx.string("success");
|
||||
|
||||
let boxed_controller = cx.boxed(RefCell::new(controller));
|
||||
obj.set(&mut cx, "_rust_ptr", boxed_controller)?;
|
||||
|
||||
let set_output_function = JsFunction::new(&mut cx, set_output)?;
|
||||
obj.set(&mut cx, "set", set_output_function)?;
|
||||
|
||||
let close_function = JsFunction::new(&mut cx, close_output)?;
|
||||
obj.set(&mut cx, "close", close_function)?;
|
||||
}
|
||||
Err(e) => {
|
||||
success_string = cx.string("error");
|
||||
|
||||
let error_message = cx.string(e.to_string());
|
||||
obj.set(&mut cx, "message", error_message)?;
|
||||
}
|
||||
}
|
||||
|
||||
obj.set(&mut cx, "type", success_string)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn set_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let arg = cx.argument::<JsArray>(0)?;
|
||||
|
||||
let mut moving_heads = Vec::with_capacity(4);
|
||||
|
||||
for i in 0..arg.len(&mut cx) {
|
||||
// why isn't neon-serde maintained anymore? T_T
|
||||
|
||||
let head = arg
|
||||
.get(&mut cx, i)?
|
||||
.downcast_or_throw::<JsObject, _>(&mut cx)?;
|
||||
|
||||
moving_heads.push(MovingHead {
|
||||
start_address: head
|
||||
.get(&mut cx, "startAddress")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as usize,
|
||||
|
||||
pan: head
|
||||
.get(&mut cx, "pan")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
|
||||
tilt: head
|
||||
.get(&mut cx, "tilt")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
|
||||
brightness: {
|
||||
let brightness = head
|
||||
.get(&mut cx, "brightness")?
|
||||
.downcast_or_throw::<JsObject, _>(&mut cx)?;
|
||||
|
||||
match brightness
|
||||
.get(&mut cx, "type")?
|
||||
.downcast_or_throw::<JsString, _>(&mut cx)?
|
||||
.value(&mut cx)
|
||||
.as_str()
|
||||
{
|
||||
"off" => Brightness::Off,
|
||||
"dimmer" => Brightness::Dimmer(
|
||||
brightness
|
||||
.get(&mut cx, "value")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
),
|
||||
"strobe" => Brightness::Strobe(
|
||||
brightness
|
||||
.get(&mut cx, "value")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx),
|
||||
),
|
||||
"switch" => Brightness::Switch,
|
||||
s => {
|
||||
let err = cx.string(format!("Invalid brightness type: {}", s));
|
||||
return cx.throw(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
rgbw: {
|
||||
let rgbw = head
|
||||
.get(&mut cx, "rgbw")?
|
||||
.downcast_or_throw::<JsArray, _>(&mut cx)?;
|
||||
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for i in 0..4 {
|
||||
let v = rgbw
|
||||
.get(&mut cx, i)?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as u8;
|
||||
|
||||
vec.push(v);
|
||||
}
|
||||
|
||||
(vec[0], vec[1], vec[2], vec[3])
|
||||
},
|
||||
|
||||
speed: head
|
||||
.get(&mut cx, "speed")?
|
||||
.downcast_or_throw::<JsNumber, _>(&mut cx)?
|
||||
.value(&mut cx) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
let this = cx.this();
|
||||
let boxed_controller = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
|
||||
|
||||
let boxed_controller = boxed_controller.borrow_mut();
|
||||
{
|
||||
let mut state = boxed_controller.state.lock().unwrap();
|
||||
|
||||
for head in moving_heads {
|
||||
head.render(&mut state.data);
|
||||
}
|
||||
}
|
||||
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let success_text = cx.string("success".to_string());
|
||||
obj.set(&mut cx, "type", success_text)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn close_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||
let this = cx.this();
|
||||
|
||||
let boxed_controller = this
|
||||
.get(&mut cx, "_rust_ptr")?
|
||||
.downcast_or_throw::<BoxedController, _>(&mut cx)?;
|
||||
|
||||
let obj = cx.empty_object();
|
||||
|
||||
let success_string;
|
||||
|
||||
match boxed_controller.borrow_mut().stop() {
|
||||
Ok(()) => {
|
||||
success_string = cx.string("success");
|
||||
}
|
||||
Err(e) => {
|
||||
success_string = cx.string("error");
|
||||
|
||||
let error_message = cx.string(e.to_string());
|
||||
obj.set(&mut cx, "message", error_message)?;
|
||||
}
|
||||
}
|
||||
|
||||
obj.set(&mut cx, "type", success_string)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user