From 30a4f83d32d5a72859c659c140384357c41ffddc Mon Sep 17 00:00:00 2001 From: Kai Vogelgesang Date: Thu, 28 Oct 2021 01:31:19 +0200 Subject: [PATCH] Refactor --- beat_detection/src/dmx_controller.rs | 23 +- beat_detection/src/layers/beat_detector.rs | 288 +++++++++++++++++++++ beat_detection/src/layers/hsl_cycle.rs | 55 ++++ beat_detection/src/layers/mod.rs | 18 ++ beat_detection/src/main.rs | 248 ++++-------------- 5 files changed, 413 insertions(+), 219 deletions(-) create mode 100644 beat_detection/src/layers/beat_detector.rs create mode 100644 beat_detection/src/layers/hsl_cycle.rs create mode 100644 beat_detection/src/layers/mod.rs diff --git a/beat_detection/src/dmx_controller.rs b/beat_detection/src/dmx_controller.rs index 569f22b..0a9532b 100644 --- a/beat_detection/src/dmx_controller.rs +++ b/beat_detection/src/dmx_controller.rs @@ -11,6 +11,7 @@ const FPS: u32 = 50; enum MCUResponse { Sync, Ack, + #[allow(dead_code)] Info { num_pkts: u32 }, } @@ -38,15 +39,13 @@ fn poll_response(ser: &mut dyn SerialPort) -> Result { } } -pub fn controller_thread(running: Arc>, brightness: Arc>) -> Result<()> { +pub fn controller_thread(running: Arc>, movingheads: Arc>) -> 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()?; @@ -72,18 +71,12 @@ pub fn controller_thread(running: Arc>, brightness: Arc>) 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); + { + let movingheads = movingheads.lock().unwrap(); + for head in movingheads.iter() { + head.render(&mut dmx_buffer); + } + } t += 1; t %= hsl_cycle; diff --git a/beat_detection/src/layers/beat_detector.rs b/beat_detection/src/layers/beat_detector.rs new file mode 100644 index 0000000..d3dd22b --- /dev/null +++ b/beat_detection/src/layers/beat_detector.rs @@ -0,0 +1,288 @@ +use std::{ + sync::{Arc, Mutex}, + thread::{self, JoinHandle}, +}; + +use anyhow::Result; +use pulse::sample::{Format, Spec}; +use ringbuffer::{ConstGenericRingBuffer, RingBufferExt, RingBufferWrite}; +use sdl2::{ + event::Event, + keyboard::Keycode, + pixels::Color, + rect::{Point, Rect}, +}; + +use crate::{ + capture, + dsp::{self, ZTransformFilter}, +}; + +use super::OutputLayer; + +const SAMPLE_RATE: usize = 5000; +const PULSE_UPDATES_PER_SECOND: usize = 50; +const BUFFER_SIZE: usize = SAMPLE_RATE / PULSE_UPDATES_PER_SECOND; + +const POINT_COUNT: usize = SAMPLE_RATE / 200; +const POINT_BUFFER_SIZE: usize = POINT_COUNT.next_power_of_two(); + +const MIN_MAX_SAMPLE_COUNT: usize = 10; + +pub struct BeatDetectinator { + join_handle: Option>>, + shared_state: Arc>, +} + +struct BeatDetectinatorSharedState { + running: bool, + brightness: f32, + threshold: f32, + point_buf: ConstGenericRingBuffer, +} + +impl BeatDetectinator { + pub fn new() -> Self { + let shared_state = Arc::new(Mutex::new(BeatDetectinatorSharedState { + running: true, + brightness: 0.0, + threshold: 1.5, + point_buf: Default::default(), + })); + + { + shared_state.lock().unwrap().point_buf.fill_default(); + } + + let join_handle = { + let shared_state = shared_state.clone(); + Some(thread::spawn(move || audio_loop(shared_state))) + }; + + println!("Audio thread started."); + + Self { + join_handle, + shared_state, + } + } +} + +impl Drop for BeatDetectinator { + fn drop(&mut self) { + { + self.shared_state.lock().unwrap().running = false; + } + + match self.join_handle.take().unwrap().join().unwrap() { + Ok(_) => println!("Audio thread stopped."), + Err(e) => println!("Audio thread died: {:?}", e), + } + } +} + +fn audio_loop(shared_state: Arc>) -> Result<()> { + let spec = Spec { + format: Format::F32le, + rate: SAMPLE_RATE as u32, + channels: 1, + }; + assert!(spec.is_valid()); + + let reader = capture::get_audio_reader(&spec)?; + let mut buffer = [0u8; 4 * BUFFER_SIZE]; + + let mut bass_filter = dsp::BassFilter::default(); + let mut envelope_filter = dsp::EnvelopeFilter::default(); + let mut beat_filter = dsp::BeatFilter::default(); + let mut j = 0; + + loop { + { + if shared_state.lock().unwrap().running == false { + break Ok(()); + } + } + + reader.read(&mut buffer)?; + + for i in 0..BUFFER_SIZE { + let mut float_bytes = [0u8; 4]; + float_bytes.copy_from_slice(&buffer[4 * i..4 * i + 4]); + + j += 1; + let sample = f32::from_le_bytes(float_bytes); + let mut value = bass_filter.process(sample); + + if value < 0f32 { + value = -value; + } + + let envelope = envelope_filter.process(value); + + if j == 200 { + let beat = beat_filter.process(envelope); + + shared_state.lock().unwrap().point_buf.push(beat); + + j = 0; + } + } + } +} + +impl OutputLayer for BeatDetectinator { + fn tick(&mut self, _dt: std::time::Duration) {} + + fn draw_sdl(&self, canvas: &mut sdl2::render::Canvas, texture_size: u32) { + let min_y = 0f32; + let max_y = 3f32; + + let beat = { + self.shared_state + .lock() + .unwrap() + .point_buf + .back() + .unwrap() + .clone() + }; + + let get_y = |y: &f32| { + let mut y = y.clone(); + + if y <= 0f32 { + y = 0f32; + } + + y = (1f32 + y).log2(); + + let y = (y - min_y) / (max_y - min_y); + + ((1f32 - y) * texture_size as f32) as u32 + }; + + // background + let v; + let (threshold, points) = { + let mut shared_state = self.shared_state.lock().unwrap(); + shared_state.brightness = if beat > shared_state.threshold { + 1f32 + } else { + 0.75f32 * shared_state.brightness + }; + + v = (255f32 * shared_state.brightness) as u8; + + ( + shared_state.threshold.clone(), + shared_state.point_buf.to_vec(), + ) + }; + + canvas.set_draw_color(Color::RGB(v, v, v)); + canvas.clear(); + + // zero + + canvas.set_draw_color(Color::RGB(0, 128, 0)); + let y = get_y(&0f32); + + canvas + .draw_line( + Point::new(0, y as i32), + Point::new(texture_size as i32, y as i32), + ) + .unwrap(); + + // min / max lines + + canvas.set_draw_color(Color::RGB(255, 0, 0)); + + let min_beat = points + .iter() + .skip(points.len() - MIN_MAX_SAMPLE_COUNT) + .reduce(|a, b| if a < b { a } else { b }) + .unwrap(); + + let x = texture_size - MIN_MAX_SAMPLE_COUNT as u32 * 10; + + let y = get_y(min_beat); + + canvas + .draw_line( + Point::new(x as i32, y as i32), + Point::new(texture_size as i32, y as i32), + ) + .unwrap(); + + let max_beat = points + .iter() + .skip(points.len() - MIN_MAX_SAMPLE_COUNT) + .reduce(|a, b| if a > b { a } else { b }) + .unwrap(); + + let y = get_y(max_beat); + + canvas + .draw_line( + Point::new(x as i32, y as i32), + Point::new(texture_size as i32, y as i32), + ) + .unwrap(); + + // threshhold line + + canvas.set_draw_color(Color::RGB(0, 0, 255)); + let y = get_y(&threshold); + + canvas + .draw_line( + Point::new(0, y as i32), + Point::new(texture_size as i32, y as i32), + ) + .unwrap(); + + // values + + canvas.set_draw_color(Color::RGB(0, 255, 0)); + + for (i, beat) in points.iter().skip(points.len() - POINT_COUNT).enumerate() { + let x = 10 * i; + let y = get_y(beat); + + canvas + .fill_rect(Rect::new((x + 1) as i32, (y - 1) as i32, 8, 3)) + .unwrap(); + } + } + + fn on_sdl_event(&mut self, event: &Event) { + match event { + Event::KeyDown { + keycode: Some(Keycode::Up), + .. + } => { + let mut shared_state = self.shared_state.lock().unwrap(); + shared_state.threshold += 0.01f32; + println!("threshold: {:.2}", shared_state.threshold); + } + Event::KeyDown { + keycode: Some(Keycode::Down), + .. + } => { + let mut shared_state = self.shared_state.lock().unwrap(); + shared_state.threshold -= 0.01f32; + println!("threshold: {:.2}", shared_state.threshold); + } + _ => {} + } + } + + fn update_dmx(&self, output: &mut [crate::fixtures::MovingHead; 4]) { + let shared_state = self.shared_state.lock().unwrap(); + for head in output.iter_mut() { + head.dimmer = 0.2 + 0.8 * shared_state.brightness; + } + } +} diff --git a/beat_detection/src/layers/hsl_cycle.rs b/beat_detection/src/layers/hsl_cycle.rs new file mode 100644 index 0000000..c61b112 --- /dev/null +++ b/beat_detection/src/layers/hsl_cycle.rs @@ -0,0 +1,55 @@ +use std::time::Duration; + +use palette::{Hsl, IntoColor, Pixel, Srgb}; +use sdl2::{pixels::Color, rect::Rect}; + +use crate::layers::OutputLayer; + +pub struct HSLCycle { + cycle_millis: usize, + time: usize, + rgb: [u8; 3], +} + +impl HSLCycle { + pub fn new(cycle_length: Duration) -> Self { + Self { + cycle_millis: cycle_length.as_millis() as usize, + time: 0, + rgb: [0, 0, 0], + } + } +} + +impl OutputLayer for HSLCycle { + fn tick(&mut self, dt: std::time::Duration) { + self.time += dt.as_millis() as usize; + self.time %= self.cycle_millis; + + let hsl: Srgb = Hsl::new( + 360.0 * (self.time as f32 / self.cycle_millis as f32), + 1.0, + 0.5, + ) + .into_color(); + + self.rgb = hsl.into_format().into_raw(); + } + + fn draw_sdl(&self, canvas: &mut sdl2::render::Canvas, _texture_size: u32) { + let [r, g, b] = self.rgb; + canvas.set_draw_color(Color::RGB(r, g, b)); + canvas.fill_rect(Rect::new(0, 0, 5, 5)).unwrap(); + } + + 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; + for head in output.iter_mut() { + head.rgbw = (r, g, b, 0); + } + } +} diff --git a/beat_detection/src/layers/mod.rs b/beat_detection/src/layers/mod.rs new file mode 100644 index 0000000..ff41c25 --- /dev/null +++ b/beat_detection/src/layers/mod.rs @@ -0,0 +1,18 @@ +pub mod hsl_cycle; +pub mod beat_detector; + +use std::time::Duration; + +use sdl2::{event::Event, render::Canvas, video::Window}; + +use crate::fixtures::MovingHead; + +pub trait OutputLayer { + fn tick(&mut self, dt: Duration); + + fn draw_sdl(&self, canvas: &mut Canvas, texture_size: u32); + + fn on_sdl_event(&mut self, event: &Event); + + fn update_dmx(&self, output: &mut [MovingHead; 4]); +} \ No newline at end of file diff --git a/beat_detection/src/main.rs b/beat_detection/src/main.rs index 5c39384..0433ead 100644 --- a/beat_detection/src/main.rs +++ b/beat_detection/src/main.rs @@ -1,59 +1,42 @@ mod capture; +mod dmx_controller; mod dsp; mod fixtures; +mod layers; mod util; -mod dmx_controller; -use std::{sync::{Arc, Mutex}, thread}; +use std::{sync::{Arc, Mutex}, thread, time::Duration}; use anyhow::Result; -use pulse::sample::{Format, Spec}; -use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferExt, RingBufferWrite}; -use sdl2::{ - event::Event, - keyboard::Keycode, - pixels::Color, - rect::{Point, Rect}, -}; +use layers::{OutputLayer, beat_detector::BeatDetectinator, hsl_cycle::HSLCycle}; +use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect}; -use crate::{dmx_controller::controller_thread, dsp::ZTransformFilter}; +use crate::dmx_controller::controller_thread; +use crate::fixtures::MovingHead; -const SAMPLE_RATE: usize = 5000; const FPS: usize = 50; -const BUFFER_SIZE: usize = SAMPLE_RATE / FPS; - -const POINT_COUNT: usize = SAMPLE_RATE / 200; -const POINT_BUFFER_SIZE: usize = POINT_COUNT.next_power_of_two(); - -const MIN_MAX_SAMPLE_COUNT: usize = 10; - fn main() -> Result<()> { - let spec = Spec { - format: Format::F32le, - rate: SAMPLE_RATE as u32, - channels: 1, - }; - assert!(spec.is_valid()); - + // dmx thread + let dmx_running = Arc::new(Mutex::new(true)); - let brightness = Arc::new(Mutex::new(0f32)); + let movingheads = Arc::new(Mutex::new([ + MovingHead::new(1), + MovingHead::new(15), + MovingHead::new(29), + MovingHead::new(43), + ])); let dmx_thread = { let dmx_running = dmx_running.clone(); - let brightness = brightness.clone(); - thread::spawn(move || controller_thread(dmx_running, brightness)) + let movingheads = movingheads.clone(); + thread::spawn(move || controller_thread(dmx_running, movingheads)) }; - let reader = capture::get_audio_reader(&spec)?; - let mut buffer = [0u8; 4 * BUFFER_SIZE]; + // output layers - let mut bass_filter = dsp::BassFilter::default(); - let mut envelope_filter = dsp::EnvelopeFilter::default(); - let mut beat_filter = dsp::BeatFilter::default(); - let mut j = 0; - - let mut threshold = 1.5f32; + let mut beat_detector = BeatDetectinator::new(); + let mut hsl_cycle = HSLCycle::new(Duration::from_secs(5)); // sdl @@ -66,15 +49,14 @@ fn main() -> Result<()> { .build()?; let mut canvas = window.into_canvas().build()?; let texture_creator = canvas.texture_creator(); - let texture_size = 10 * POINT_COUNT as u32; + let texture_size = 250; let mut texture = texture_creator.create_texture_target( canvas.default_pixel_format(), texture_size, texture_size, )?; - - let mut point_buf = ConstGenericRingBuffer::::new(); - point_buf.fill_default(); + + let frame_duration = Duration::from_secs_f64(1.0 / FPS as f64); let mut event_pump = sdl.event_pump().unwrap(); @@ -86,176 +68,33 @@ fn main() -> Result<()> { keycode: Some(Keycode::Escape), .. } => break 'running, - Event::KeyDown { - keycode: Some(Keycode::Up), - .. - } => { - threshold += 0.01f32; - println!("threshold: {:.2}", threshold); + + event => { + // println!("{:?}", event); + beat_detector.on_sdl_event(&event); + hsl_cycle.on_sdl_event(&event); } - Event::KeyDown { - keycode: Some(Keycode::Down), - .. - } => { - threshold -= 0.01f32; - println!("threshold: {:.2}", threshold); - } - Event::KeyDown { - keycode: Some(Keycode::Space), - .. - } => { - let mut brightness = brightness.lock().unwrap(); - *brightness = 1.0; - } - Event::KeyDown { - keycode: Some(k), .. - } => { - println!("{}", k) - } - _ => {} } } - - reader.read(&mut buffer)?; - - for i in 0..BUFFER_SIZE { - let mut float_bytes = [0u8; 4]; - float_bytes.copy_from_slice(&buffer[4 * i..4 * i + 4]); - - j += 1; - let sample = f32::from_le_bytes(float_bytes); - let mut value = bass_filter.process(sample); - - if value < 0f32 { - value = -value; - } - - let envelope = envelope_filter.process(value); - - if j == 200 { - let beat = beat_filter.process(envelope); - point_buf.push(beat); - - canvas.with_texture_canvas(&mut texture, |canvas| { - let min_y = 0f32; - let max_y = 3f32; - - let get_y = |y: &f32| { - - let mut y = y.clone(); - - if y <= 0f32 { - y = 0f32; - } - - y = (1f32 + y).log2(); - - let y = (y - min_y) / (max_y - min_y); - - ((1f32 - y) * texture_size as f32) as u32 - }; - - // background - let v; - { - let mut brightness = brightness.lock().unwrap(); - *brightness = if beat > threshold { - 1f32 - } else { - 0.75f32 * *brightness - }; - - v = (255f32 * *brightness) as u8; - } - - canvas.set_draw_color(Color::RGB(v, v, v)); - canvas.clear(); - - // zero - - canvas.set_draw_color(Color::RGB(0, 128, 0)); - let y = get_y(&0f32); - - canvas - .draw_line( - Point::new(0, y as i32), - Point::new(texture_size as i32, y as i32), - ) - .unwrap(); - - // min / max lines - - canvas.set_draw_color(Color::RGB(255, 0, 0)); - - let min_beat = point_buf - .iter() - .skip(point_buf.capacity() - MIN_MAX_SAMPLE_COUNT) - .reduce(|a, b| if a < b { a } else { b }) - .unwrap(); - - let x = texture_size - MIN_MAX_SAMPLE_COUNT as u32 * 10; - - let y = get_y(min_beat); - - canvas - .draw_line( - Point::new(x as i32, y as i32), - Point::new(texture_size as i32, y as i32), - ) - .unwrap(); - - let max_beat = point_buf - .iter() - .skip(point_buf.capacity() - MIN_MAX_SAMPLE_COUNT) - .reduce(|a, b| if a > b { a } else { b }) - .unwrap(); - - let y = get_y(max_beat); - - canvas - .draw_line( - Point::new(x as i32, y as i32), - Point::new(texture_size as i32, y as i32), - ) - .unwrap(); - - // threshhold line - - canvas.set_draw_color(Color::RGB(0, 0, 255)); - let y = get_y(&threshold); - - canvas - .draw_line( - Point::new(0, y as i32), - Point::new(texture_size as i32, y as i32), - ) - .unwrap(); - - // values - - canvas.set_draw_color(Color::RGB(0, 255, 0)); - - for (i, beat) in point_buf - .iter() - .skip(point_buf.capacity() - POINT_COUNT) - .enumerate() - { - let x = 10 * i; - let y = get_y(beat); - - canvas - .draw_rect(Rect::new((x + 1) as i32, (y - 1) as i32, 8, 3)) - .unwrap(); - } - })?; - - j = 0; - } + + beat_detector.tick(frame_duration); + hsl_cycle.tick(frame_duration); + + { + let mut output = movingheads.lock().unwrap(); + beat_detector.update_dmx(&mut *output); + hsl_cycle.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); + hsl_cycle.draw_sdl(canvas, texture_size); + })?; + + let (w, h) = canvas.window().drawable_size(); let target_rect = if w > h { @@ -266,8 +105,9 @@ fn main() -> Result<()> { canvas.copy(&texture, None, Some(target_rect)).unwrap(); canvas.present(); + thread::sleep(frame_duration); } - + { let mut dmx_running = dmx_running.lock().unwrap(); *dmx_running = false;