Update
This commit is contained in:
parent
5a71aa2cf0
commit
763aa23ca8
@ -14,3 +14,4 @@ ringbuffer = "0.8.3"
|
||||
num = "0.4.0"
|
||||
serialport = "4.0.1"
|
||||
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 serialport::SerialPort;
|
||||
@ -11,7 +16,9 @@ enum MCUResponse {
|
||||
Sync,
|
||||
Ack,
|
||||
#[allow(dead_code)]
|
||||
Info { num_pkts: u32 },
|
||||
Info {
|
||||
num_pkts: u32,
|
||||
},
|
||||
}
|
||||
|
||||
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 mut dmx_buffer = [0u8; 512];
|
||||
|
||||
let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
|
||||
|
@ -45,20 +45,7 @@ impl DMXFixture for MovingHead {
|
||||
let offset = self.start_addr - 1;
|
||||
|
||||
let channels = [
|
||||
pan,
|
||||
pan_fine,
|
||||
tilt,
|
||||
tilt_fine,
|
||||
self.speed,
|
||||
dimmer,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
w,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
pan, 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);
|
||||
|
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,
|
||||
)
|
||||
.into_color();
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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]) {
|
||||
let [r, g, b] = self.rgb;
|
||||
|
@ -1,5 +1,8 @@
|
||||
pub mod hsl_cycle;
|
||||
pub mod beat_detector;
|
||||
pub mod constant_brightness;
|
||||
pub mod hsl_cycle;
|
||||
pub mod movement;
|
||||
pub mod tap;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
@ -11,8 +14,8 @@ pub trait OutputLayer {
|
||||
fn tick(&mut self, dt: Duration);
|
||||
|
||||
fn draw_sdl(&self, canvas: &mut Canvas<Window>, texture_size: u32);
|
||||
|
||||
|
||||
fn on_sdl_event(&mut self, event: &Event);
|
||||
|
||||
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 util;
|
||||
|
||||
use std::{sync::{Arc, Mutex}, thread, time::Duration};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
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 crate::dmx_controller::controller_thread;
|
||||
@ -16,6 +23,12 @@ use crate::fixtures::MovingHead;
|
||||
|
||||
const FPS: usize = 50;
|
||||
|
||||
enum BrightnessControl {
|
||||
BeatDetection,
|
||||
TapMetronome,
|
||||
ConstantBrightness,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// dmx thread
|
||||
|
||||
@ -36,7 +49,13 @@ fn main() -> Result<()> {
|
||||
// output layers
|
||||
|
||||
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 head_mover = HeadMover::new(Duration::from_secs_f32(0.5));
|
||||
|
||||
// sdl
|
||||
|
||||
@ -55,7 +74,7 @@ fn main() -> Result<()> {
|
||||
texture_size,
|
||||
texture_size,
|
||||
)?;
|
||||
|
||||
|
||||
let frame_duration = Duration::from_secs_f64(1.0 / FPS as f64);
|
||||
|
||||
let mut event_pump = sdl.event_pump().unwrap();
|
||||
@ -69,32 +88,77 @@ fn main() -> Result<()> {
|
||||
..
|
||||
} => 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 => {
|
||||
// println!("{:?}", event);
|
||||
beat_detector.on_sdl_event(&event);
|
||||
match active_brightness_control {
|
||||
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);
|
||||
head_mover.on_sdl_event(&event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
beat_detector.tick(frame_duration);
|
||||
tap_metronome.tick(frame_duration);
|
||||
hsl_cycle.tick(frame_duration);
|
||||
|
||||
head_mover.tick(frame_duration);
|
||||
|
||||
{
|
||||
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);
|
||||
head_mover.update_dmx(&mut *output);
|
||||
}
|
||||
|
||||
canvas.set_draw_color(Color::RGB(0, 0, 0));
|
||||
canvas.clear();
|
||||
|
||||
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);
|
||||
head_mover.draw_sdl(canvas, texture_size);
|
||||
})?;
|
||||
|
||||
|
||||
let (w, h) = canvas.window().drawable_size();
|
||||
|
||||
let target_rect = if w > h {
|
||||
|
Loading…
Reference in New Issue
Block a user