Implement beat detection -> dmx output

This commit is contained in:
Kai Vogelgesang 2021-10-27 22:44:51 +02:00
parent b0fecde639
commit 9f07172f60
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
5 changed files with 233 additions and 17 deletions

View File

@ -11,3 +11,6 @@ psimple = { version = "2.23.0", package = "libpulse-simple-binding" }
anyhow = "1.0.44" anyhow = "1.0.44"
sdl2 = "0.35.1" sdl2 = "0.35.1"
ringbuffer = "0.8.3" ringbuffer = "0.8.3"
num = "0.4.0"
serialport = "4.0.1"
palette = "0.6.0"

View File

@ -0,0 +1,118 @@
use std::{io, sync::{Arc, Mutex}, thread, time::{Duration, Instant}};
use anyhow::{anyhow, Result};
use palette::{Hsl, IntoColor, Pixel, Srgb};
use serialport::SerialPort;
use crate::fixtures::{DMXFixture, MovingHead};
const FPS: u32 = 50;
enum MCUResponse {
Sync,
Ack,
Info { num_pkts: u32 },
}
fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
let mut read_buffer = vec![0u8; 32];
let bytes_read;
loop {
match ser.read(read_buffer.as_mut_slice()) {
Ok(t) => {
bytes_read = t;
break;
}
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => continue,
Err(e) => Err(e),
}?
}
let response = std::str::from_utf8(&read_buffer[..bytes_read])?;
match response.trim() {
"Sync." => Ok(MCUResponse::Sync),
"Ack." => Ok(MCUResponse::Ack),
s if s.starts_with("Info") => Ok(MCUResponse::Info { num_pkts: 69 }),
s => Err(anyhow!("Unknown response: \"{}\"", s)),
}
}
pub fn controller_thread(running: Arc<Mutex<bool>>, brightness: Arc<Mutex<f32>>) -> Result<()> {
let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);
let hsl_cycle = 12 * FPS;
let mut dmx_buffer = [0u8; 512];
let mut movinghead = MovingHead::new(1);
let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
.timeout(Duration::from_millis(10))
.open()?;
// wait for initial sync
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Sync) => break,
_ => continue,
}
}
let mut t = 0;
'main: loop {
{
let running = running.lock().unwrap();
if !*running {
break Ok(());
}
}
let loop_start = Instant::now();
let hsl: Srgb = Hsl::new(360.0 * (t as f32 / hsl_cycle as f32), 1.0, 0.5).into_color();
let [r, g, b]: [u8; 3] = hsl.into_format().into_raw();
movinghead.rgbw = (r, g, b, 0);
movinghead.dimmer = {
let brightness = brightness.lock().unwrap();
0.2 + 0.8 * *brightness
};
movinghead.render(&mut dmx_buffer);
t += 1;
t %= hsl_cycle;
let write_result = ser.write(&dmx_buffer);
if write_result.is_err() {
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Sync) => continue 'main,
_ => continue,
}
}
}
loop {
match poll_response(&mut *ser) {
Ok(MCUResponse::Ack) => break,
Ok(MCUResponse::Info { .. }) | Err(_) => continue,
Ok(MCUResponse::Sync) => continue 'main,
}
}
let loop_time = loop_start.elapsed();
if loop_time < frame_time {
thread::sleep(frame_time - loop_time);
} else {
println!("loop took too long!");
}
}
}

View File

@ -0,0 +1,66 @@
use std::f32::consts::{FRAC_PI_2, PI};
use crate::util::rescale;
pub struct MovingHead {
start_addr: usize,
pub pan: f32, // -3pi/2 to 3pi/2
pub tilt: f32, // -pi/2 to pi/2
pub dimmer: f32, // 0 to 1
pub rgbw: (u8, u8, u8, u8),
pub speed: u8, // reversed
}
impl MovingHead {
pub fn new(start_addr: usize) -> Self {
Self {
start_addr,
pan: 0f32,
tilt: 0f32,
speed: 0u8,
dimmer: 0f32,
rgbw: (0u8, 0u8, 0u8, 0u8),
}
}
}
pub trait DMXFixture {
fn render(&self, dst: &mut [u8]);
}
impl DMXFixture for MovingHead {
fn render(&self, dst: &mut [u8]) {
let pan = rescale(self.pan, (-1.5 * PI, 1.5 * PI), (255.0, 0.0)) as u8;
let pan_fine = 0;
let tilt = rescale(self.tilt, (-1.0 * FRAC_PI_2, FRAC_PI_2), (0.0, 255.0)) as u8;
let tilt_fine = 0;
let dimmer = (7 + (127.0 * self.dimmer) as u8).clamp(7, 134);
let (r, g, b, w) = self.rgbw;
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,
];
dst[offset..offset + channels.len()].copy_from_slice(&channels);
}
}

View File

@ -1,5 +1,10 @@
mod capture; mod capture;
mod dsp; mod dsp;
mod fixtures;
mod util;
mod dmx_controller;
use std::{sync::{Arc, Mutex}, thread};
use anyhow::Result; use anyhow::Result;
use pulse::sample::{Format, Spec}; use pulse::sample::{Format, Spec};
@ -11,7 +16,7 @@ use sdl2::{
rect::{Point, Rect}, rect::{Point, Rect},
}; };
use crate::dsp::ZTransformFilter; use crate::{dmx_controller::controller_thread, dsp::ZTransformFilter};
const SAMPLE_RATE: usize = 5000; const SAMPLE_RATE: usize = 5000;
const FPS: usize = 50; const FPS: usize = 50;
@ -31,6 +36,15 @@ fn main() -> Result<()> {
}; };
assert!(spec.is_valid()); assert!(spec.is_valid());
let dmx_running = Arc::new(Mutex::new(true));
let brightness = Arc::new(Mutex::new(0f32));
let dmx_thread = {
let dmx_running = dmx_running.clone();
let brightness = brightness.clone();
thread::spawn(move || controller_thread(dmx_running, brightness))
};
let reader = capture::get_audio_reader(&spec)?; let reader = capture::get_audio_reader(&spec)?;
let mut buffer = [0u8; 4 * BUFFER_SIZE]; let mut buffer = [0u8; 4 * BUFFER_SIZE];
@ -41,10 +55,6 @@ fn main() -> Result<()> {
let mut threshold = 1.5f32; let mut threshold = 1.5f32;
let mut brightness = 0f32;
let mut beat_toggle = false;
// sdl // sdl
let sdl = sdl2::init().unwrap(); let sdl = sdl2::init().unwrap();
@ -90,6 +100,13 @@ fn main() -> Result<()> {
threshold -= 0.01f32; threshold -= 0.01f32;
println!("threshold: {:.2}", threshold); println!("threshold: {:.2}", threshold);
} }
Event::KeyDown {
keycode: Some(Keycode::Space),
..
} => {
let mut brightness = brightness.lock().unwrap();
*brightness = 1.0;
}
Event::KeyDown { Event::KeyDown {
keycode: Some(k), .. keycode: Some(k), ..
} => { } => {
@ -139,21 +156,18 @@ fn main() -> Result<()> {
}; };
// background // background
let v;
let prev_brightness = brightness; {
brightness = if beat > threshold { let mut brightness = brightness.lock().unwrap();
*brightness = if beat > threshold {
1f32 1f32
} else { } else {
0.75f32 * brightness 0.75f32 * *brightness
}; };
if brightness - prev_brightness > 0.5 { v = (255f32 * *brightness) as u8;
println!("beat. {}", if beat_toggle {"-"} else {"|"});
beat_toggle ^= true;
} }
let v = (255f32 * brightness) as u8;
canvas.set_draw_color(Color::RGB(v, v, v)); canvas.set_draw_color(Color::RGB(v, v, v));
canvas.clear(); canvas.clear();
@ -254,5 +268,12 @@ fn main() -> Result<()> {
canvas.present(); canvas.present();
} }
{
let mut dmx_running = dmx_running.lock().unwrap();
*dmx_running = false;
}
dmx_thread.join().unwrap()?;
Ok(()) Ok(())
} }

View File

@ -0,0 +1,8 @@
use num::traits::float::Float;
pub fn rescale<T: Float>(x: T, from: (T, T), to: (T, T)) -> T {
let (a, b) = from;
let (c, d) = to;
c + (d - c) * (x - a) / (b - a)
}