Update
This commit is contained in:
parent
5a71aa2cf0
commit
763aa23ca8
@ -14,3 +14,4 @@ ringbuffer = "0.8.3"
|
|||||||
num = "0.4.0"
|
num = "0.4.0"
|
||||||
serialport = "4.0.1"
|
serialport = "4.0.1"
|
||||||
palette = "0.6.0"
|
palette = "0.6.0"
|
||||||
|
rand = "0.8.4"
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
use std::{io, sync::{Arc, Mutex}, thread, time::{Duration, Instant}};
|
use std::{
|
||||||
|
io,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use serialport::SerialPort;
|
use serialport::SerialPort;
|
||||||
@ -11,7 +16,9 @@ enum MCUResponse {
|
|||||||
Sync,
|
Sync,
|
||||||
Ack,
|
Ack,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
Info { num_pkts: u32 },
|
Info {
|
||||||
|
num_pkts: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
|
fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
|
||||||
@ -38,9 +45,12 @@ fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn controller_thread(running: Arc<Mutex<bool>>, movingheads: Arc<Mutex<[MovingHead; 4]>>) -> Result<()> {
|
pub fn controller_thread(
|
||||||
|
running: Arc<Mutex<bool>>,
|
||||||
|
movingheads: Arc<Mutex<[MovingHead; 4]>>,
|
||||||
|
) -> Result<()> {
|
||||||
let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);
|
let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);
|
||||||
|
|
||||||
let mut dmx_buffer = [0u8; 512];
|
let mut dmx_buffer = [0u8; 512];
|
||||||
|
|
||||||
let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
|
let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
|
||||||
|
@ -45,20 +45,7 @@ impl DMXFixture for MovingHead {
|
|||||||
let offset = self.start_addr - 1;
|
let offset = self.start_addr - 1;
|
||||||
|
|
||||||
let channels = [
|
let channels = [
|
||||||
pan,
|
pan, pan_fine, tilt, tilt_fine, self.speed, dimmer, r, g, b, w, 0, 0, 0, 0,
|
||||||
pan_fine,
|
|
||||||
tilt,
|
|
||||||
tilt_fine,
|
|
||||||
self.speed,
|
|
||||||
dimmer,
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
w,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
dst[offset..offset + channels.len()].copy_from_slice(&channels);
|
dst[offset..offset + channels.len()].copy_from_slice(&channels);
|
||||||
|
24
beat_detection/src/layers/constant_brightness.rs
Normal file
24
beat_detection/src/layers/constant_brightness.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use super::OutputLayer;
|
||||||
|
|
||||||
|
pub struct ConstantBrightness {
|
||||||
|
pub brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputLayer for ConstantBrightness {
|
||||||
|
fn tick(&mut self, _dt: std::time::Duration) {}
|
||||||
|
|
||||||
|
fn draw_sdl(
|
||||||
|
&self,
|
||||||
|
_canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
|
||||||
|
_texture_size: u32,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_sdl_event(&mut self, _event: &sdl2::event::Event) {}
|
||||||
|
|
||||||
|
fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
|
||||||
|
for head in output.iter_mut() {
|
||||||
|
head.dimmer = self.brightness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ impl OutputLayer for HSLCycle {
|
|||||||
0.5,
|
0.5,
|
||||||
)
|
)
|
||||||
.into_color();
|
.into_color();
|
||||||
|
|
||||||
self.rgb = hsl.into_format().into_raw();
|
self.rgb = hsl.into_format().into_raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,9 +42,7 @@ impl OutputLayer for HSLCycle {
|
|||||||
canvas.fill_rect(Rect::new(0, 0, 5, 5)).unwrap();
|
canvas.fill_rect(Rect::new(0, 0, 5, 5)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_sdl_event(&mut self, _event: &sdl2::event::Event) {
|
fn on_sdl_event(&mut self, _event: &sdl2::event::Event) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
|
fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
|
||||||
let [r, g, b] = self.rgb;
|
let [r, g, b] = self.rgb;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
pub mod hsl_cycle;
|
|
||||||
pub mod beat_detector;
|
pub mod beat_detector;
|
||||||
|
pub mod constant_brightness;
|
||||||
|
pub mod hsl_cycle;
|
||||||
|
pub mod movement;
|
||||||
|
pub mod tap;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -11,8 +14,8 @@ pub trait OutputLayer {
|
|||||||
fn tick(&mut self, dt: Duration);
|
fn tick(&mut self, dt: Duration);
|
||||||
|
|
||||||
fn draw_sdl(&self, canvas: &mut Canvas<Window>, texture_size: u32);
|
fn draw_sdl(&self, canvas: &mut Canvas<Window>, texture_size: u32);
|
||||||
|
|
||||||
fn on_sdl_event(&mut self, event: &Event);
|
fn on_sdl_event(&mut self, event: &Event);
|
||||||
|
|
||||||
fn update_dmx(&self, output: &mut [MovingHead; 4]);
|
fn update_dmx(&self, output: &mut [MovingHead; 4]);
|
||||||
}
|
}
|
||||||
|
78
beat_detection/src/layers/movement.rs
Normal file
78
beat_detection/src/layers/movement.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use std::{
|
||||||
|
f32::consts::PI,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::random;
|
||||||
|
use sdl2::{event::Event, keyboard::Keycode};
|
||||||
|
|
||||||
|
use crate::util::rescale;
|
||||||
|
|
||||||
|
use super::OutputLayer;
|
||||||
|
|
||||||
|
pub struct HeadMover {
|
||||||
|
movement_duration: Duration,
|
||||||
|
last_timestamp: Instant,
|
||||||
|
targets: [(f32, f32); 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeadMover {
|
||||||
|
pub fn new(movement_duration: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
movement_duration,
|
||||||
|
last_timestamp: Instant::now() - movement_duration,
|
||||||
|
targets: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_random_point() -> (f32, f32) {
|
||||||
|
let pan = rescale(random::<f32>(), (0.0, 1.0), (-0.5 * PI, 0.5 * PI));
|
||||||
|
let tilt = random::<f32>().acos();
|
||||||
|
|
||||||
|
(pan, tilt)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputLayer for HeadMover {
|
||||||
|
fn tick(&mut self, _dt: Duration) {
|
||||||
|
let now = Instant::now();
|
||||||
|
if now - self.last_timestamp >= self.movement_duration {
|
||||||
|
for target in self.targets.iter_mut() {
|
||||||
|
*target = sample_random_point();
|
||||||
|
}
|
||||||
|
self.last_timestamp = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_sdl(
|
||||||
|
&self,
|
||||||
|
_canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
|
||||||
|
_texture_size: u32,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_sdl_event(&mut self, event: &sdl2::event::Event) {
|
||||||
|
match event {
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Return),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let now = Instant::now();
|
||||||
|
for target in self.targets.iter_mut() {
|
||||||
|
*target = sample_random_point();
|
||||||
|
}
|
||||||
|
self.last_timestamp = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
|
||||||
|
for (head, target) in output.iter_mut().zip(self.targets.iter()) {
|
||||||
|
let (pan, tilt) = target;
|
||||||
|
head.pan = *pan;
|
||||||
|
head.tilt = *tilt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
beat_detection/src/layers/tap.rs
Normal file
102
beat_detection/src/layers/tap.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use sdl2::{event::Event, keyboard::Keycode, pixels::Color};
|
||||||
|
|
||||||
|
use super::OutputLayer;
|
||||||
|
|
||||||
|
pub struct TapMetronome {
|
||||||
|
max_time: Duration,
|
||||||
|
timestamps: VecDeque<Instant>,
|
||||||
|
seconds_per_beat: Option<f32>,
|
||||||
|
brightness: f32,
|
||||||
|
decay: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TapMetronome {
|
||||||
|
pub fn new(max_time: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
max_time,
|
||||||
|
timestamps: VecDeque::new(),
|
||||||
|
seconds_per_beat: None,
|
||||||
|
brightness: 0.0,
|
||||||
|
decay: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputLayer for TapMetronome {
|
||||||
|
fn tick(&mut self, _dt: std::time::Duration) {
|
||||||
|
let now = Instant::now();
|
||||||
|
if let (Some(stamp), Some(spb)) = (self.timestamps.back(), self.seconds_per_beat) {
|
||||||
|
let dt = (now - *stamp).as_secs_f32();
|
||||||
|
|
||||||
|
let beat_offset = dt % spb;
|
||||||
|
|
||||||
|
self.brightness = (-1.0 * self.decay * beat_offset).exp()
|
||||||
|
} else {
|
||||||
|
self.brightness = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_sdl(&self, canvas: &mut sdl2::render::Canvas<sdl2::video::Window>, _texture_size: u32) {
|
||||||
|
let v = (255.0 * self.brightness) as u8;
|
||||||
|
canvas.set_draw_color(Color::RGB(v, v, v));
|
||||||
|
canvas.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_sdl_event(&mut self, event: &sdl2::event::Event) {
|
||||||
|
match event {
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Space),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let now = Instant::now();
|
||||||
|
while let Some(stamp) = self.timestamps.front() {
|
||||||
|
if now - *stamp < self.max_time {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.timestamps.pop_front();
|
||||||
|
}
|
||||||
|
self.timestamps.push_back(now);
|
||||||
|
|
||||||
|
if self.timestamps.len() >= 2 {
|
||||||
|
let dt = *self.timestamps.back().unwrap() - *self.timestamps.front().unwrap();
|
||||||
|
let spb = dt.as_secs_f32() / (self.timestamps.len() - 1) as f32;
|
||||||
|
|
||||||
|
println!("Detected BPM: {:.2}", 60.0 / spb);
|
||||||
|
|
||||||
|
self.seconds_per_beat = Some(spb);
|
||||||
|
} else {
|
||||||
|
self.seconds_per_beat = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Down),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.decay += 0.1;
|
||||||
|
println!("Decay: {:.3}", self.decay)
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Up),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.decay -= 0.1;
|
||||||
|
println!("Decay: {:.3}", self.decay)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) {
|
||||||
|
for head in output.iter_mut() {
|
||||||
|
head.dimmer = 0.2 + 0.8 * self.brightness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,17 @@ mod fixtures;
|
|||||||
mod layers;
|
mod layers;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use std::{sync::{Arc, Mutex}, thread, time::Duration};
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use layers::{OutputLayer, beat_detector::BeatDetectinator, hsl_cycle::HSLCycle};
|
use layers::{
|
||||||
|
beat_detector::BeatDetectinator, constant_brightness::ConstantBrightness, hsl_cycle::HSLCycle,
|
||||||
|
movement::HeadMover, tap::TapMetronome, OutputLayer,
|
||||||
|
};
|
||||||
use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect};
|
use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect};
|
||||||
|
|
||||||
use crate::dmx_controller::controller_thread;
|
use crate::dmx_controller::controller_thread;
|
||||||
@ -16,6 +23,12 @@ use crate::fixtures::MovingHead;
|
|||||||
|
|
||||||
const FPS: usize = 50;
|
const FPS: usize = 50;
|
||||||
|
|
||||||
|
enum BrightnessControl {
|
||||||
|
BeatDetection,
|
||||||
|
TapMetronome,
|
||||||
|
ConstantBrightness,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// dmx thread
|
// dmx thread
|
||||||
|
|
||||||
@ -36,7 +49,13 @@ fn main() -> Result<()> {
|
|||||||
// output layers
|
// output layers
|
||||||
|
|
||||||
let mut beat_detector = BeatDetectinator::new();
|
let mut beat_detector = BeatDetectinator::new();
|
||||||
|
let mut tap_metronome = TapMetronome::new(Duration::from_secs(2));
|
||||||
|
let mut constant_brightness = ConstantBrightness { brightness: 1.0 };
|
||||||
|
|
||||||
|
let mut active_brightness_control = BrightnessControl::BeatDetection;
|
||||||
|
|
||||||
let mut hsl_cycle = HSLCycle::new(Duration::from_secs(5));
|
let mut hsl_cycle = HSLCycle::new(Duration::from_secs(5));
|
||||||
|
let mut head_mover = HeadMover::new(Duration::from_secs_f32(0.5));
|
||||||
|
|
||||||
// sdl
|
// sdl
|
||||||
|
|
||||||
@ -55,7 +74,7 @@ fn main() -> Result<()> {
|
|||||||
texture_size,
|
texture_size,
|
||||||
texture_size,
|
texture_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let frame_duration = Duration::from_secs_f64(1.0 / FPS as f64);
|
let frame_duration = Duration::from_secs_f64(1.0 / FPS as f64);
|
||||||
|
|
||||||
let mut event_pump = sdl.event_pump().unwrap();
|
let mut event_pump = sdl.event_pump().unwrap();
|
||||||
@ -69,32 +88,77 @@ fn main() -> Result<()> {
|
|||||||
..
|
..
|
||||||
} => break 'running,
|
} => break 'running,
|
||||||
|
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::F1),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
active_brightness_control = BrightnessControl::BeatDetection;
|
||||||
|
println!("Using automatic beat detection.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::F2),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
active_brightness_control = BrightnessControl::TapMetronome;
|
||||||
|
println!("Using tap metronome.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::F3),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
active_brightness_control = BrightnessControl::ConstantBrightness;
|
||||||
|
println!("Using constant brightness.")
|
||||||
|
}
|
||||||
|
|
||||||
event => {
|
event => {
|
||||||
// println!("{:?}", event);
|
match active_brightness_control {
|
||||||
beat_detector.on_sdl_event(&event);
|
BrightnessControl::BeatDetection => beat_detector.on_sdl_event(&event),
|
||||||
|
BrightnessControl::TapMetronome => tap_metronome.on_sdl_event(&event),
|
||||||
|
BrightnessControl::ConstantBrightness => {
|
||||||
|
constant_brightness.on_sdl_event(&event)
|
||||||
|
}
|
||||||
|
}
|
||||||
hsl_cycle.on_sdl_event(&event);
|
hsl_cycle.on_sdl_event(&event);
|
||||||
|
head_mover.on_sdl_event(&event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beat_detector.tick(frame_duration);
|
beat_detector.tick(frame_duration);
|
||||||
|
tap_metronome.tick(frame_duration);
|
||||||
hsl_cycle.tick(frame_duration);
|
hsl_cycle.tick(frame_duration);
|
||||||
|
head_mover.tick(frame_duration);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut output = movingheads.lock().unwrap();
|
let mut output = movingheads.lock().unwrap();
|
||||||
beat_detector.update_dmx(&mut *output);
|
match active_brightness_control {
|
||||||
|
BrightnessControl::BeatDetection => beat_detector.update_dmx(&mut *output),
|
||||||
|
BrightnessControl::TapMetronome => tap_metronome.update_dmx(&mut *output),
|
||||||
|
BrightnessControl::ConstantBrightness => {
|
||||||
|
constant_brightness.update_dmx(&mut *output)
|
||||||
|
}
|
||||||
|
}
|
||||||
hsl_cycle.update_dmx(&mut *output);
|
hsl_cycle.update_dmx(&mut *output);
|
||||||
|
head_mover.update_dmx(&mut *output);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
|
|
||||||
canvas.with_texture_canvas(&mut texture, |canvas| {
|
canvas.with_texture_canvas(&mut texture, |canvas| {
|
||||||
beat_detector.draw_sdl(canvas, texture_size);
|
match active_brightness_control {
|
||||||
|
BrightnessControl::BeatDetection => beat_detector.draw_sdl(canvas, texture_size),
|
||||||
|
BrightnessControl::TapMetronome => tap_metronome.draw_sdl(canvas, texture_size),
|
||||||
|
BrightnessControl::ConstantBrightness => {
|
||||||
|
constant_brightness.draw_sdl(canvas, texture_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
hsl_cycle.draw_sdl(canvas, texture_size);
|
hsl_cycle.draw_sdl(canvas, texture_size);
|
||||||
|
head_mover.draw_sdl(canvas, texture_size);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
||||||
let (w, h) = canvas.window().drawable_size();
|
let (w, h) = canvas.window().drawable_size();
|
||||||
|
|
||||||
let target_rect = if w > h {
|
let target_rect = if w > h {
|
||||||
|
Loading…
Reference in New Issue
Block a user