This commit is contained in:
Kai Vogelgesang 2021-10-28 03:07:03 +02:00
parent 5a71aa2cf0
commit 763aa23ca8
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
9 changed files with 302 additions and 35 deletions

View File

@ -14,3 +14,4 @@ ringbuffer = "0.8.3"
num = "0.4.0"
serialport = "4.0.1"
palette = "0.6.0"
rand = "0.8.4"

View File

@ -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)

View File

@ -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);

View 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;
}
}
}

View File

@ -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;

View File

@ -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]);
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@ -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 {