This commit is contained in:
Kai Vogelgesang 2021-10-28 01:31:19 +02:00
parent 9f07172f60
commit 30a4f83d32
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
5 changed files with 413 additions and 219 deletions

View File

@ -11,6 +11,7 @@ const FPS: u32 = 50;
enum MCUResponse { enum MCUResponse {
Sync, Sync,
Ack, Ack,
#[allow(dead_code)]
Info { num_pkts: u32 }, Info { num_pkts: u32 },
} }
@ -38,15 +39,13 @@ fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
} }
} }
pub fn controller_thread(running: Arc<Mutex<bool>>, brightness: Arc<Mutex<f32>>) -> 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 hsl_cycle = 12 * FPS; let hsl_cycle = 12 * FPS;
let mut dmx_buffer = [0u8; 512]; let mut dmx_buffer = [0u8; 512];
let mut movinghead = MovingHead::new(1);
let mut ser = serialport::new("/dev/ttyUSB0", 500_000) let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
.timeout(Duration::from_millis(10)) .timeout(Duration::from_millis(10))
.open()?; .open()?;
@ -72,18 +71,12 @@ pub fn controller_thread(running: Arc<Mutex<bool>>, brightness: Arc<Mutex<f32>>)
let loop_start = Instant::now(); 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 movingheads = movingheads.lock().unwrap();
let [r, g, b]: [u8; 3] = hsl.into_format().into_raw(); for head in movingheads.iter() {
movinghead.rgbw = (r, g, b, 0); head.render(&mut dmx_buffer);
}
movinghead.dimmer = { }
let brightness = brightness.lock().unwrap();
0.2 + 0.8 * *brightness
};
movinghead.render(&mut dmx_buffer);
t += 1; t += 1;
t %= hsl_cycle; t %= hsl_cycle;

View File

@ -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<JoinHandle<Result<()>>>,
shared_state: Arc<Mutex<BeatDetectinatorSharedState>>,
}
struct BeatDetectinatorSharedState {
running: bool,
brightness: f32,
threshold: f32,
point_buf: ConstGenericRingBuffer<f32, POINT_BUFFER_SIZE>,
}
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<Mutex<BeatDetectinatorSharedState>>) -> 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<sdl2::video::Window>, 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;
}
}
}

View File

@ -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<sdl2::video::Window>, _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);
}
}
}

View File

@ -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<Window>, texture_size: u32);
fn on_sdl_event(&mut self, event: &Event);
fn update_dmx(&self, output: &mut [MovingHead; 4]);
}

View File

@ -1,59 +1,42 @@
mod capture; mod capture;
mod dmx_controller;
mod dsp; mod dsp;
mod fixtures; mod fixtures;
mod layers;
mod util; mod util;
mod dmx_controller;
use std::{sync::{Arc, Mutex}, thread}; use std::{sync::{Arc, Mutex}, thread, time::Duration};
use anyhow::Result; use anyhow::Result;
use pulse::sample::{Format, Spec}; use layers::{OutputLayer, beat_detector::BeatDetectinator, hsl_cycle::HSLCycle};
use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferExt, RingBufferWrite}; use sdl2::{event::Event, keyboard::Keycode, pixels::Color, rect::Rect};
use sdl2::{
event::Event,
keyboard::Keycode,
pixels::Color,
rect::{Point, 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 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<()> { fn main() -> Result<()> {
let spec = Spec { // dmx thread
format: Format::F32le,
rate: SAMPLE_RATE as u32,
channels: 1,
};
assert!(spec.is_valid());
let dmx_running = Arc::new(Mutex::new(true)); 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_thread = {
let dmx_running = dmx_running.clone(); let dmx_running = dmx_running.clone();
let brightness = brightness.clone(); let movingheads = movingheads.clone();
thread::spawn(move || controller_thread(dmx_running, brightness)) thread::spawn(move || controller_thread(dmx_running, movingheads))
}; };
let reader = capture::get_audio_reader(&spec)?; // output layers
let mut buffer = [0u8; 4 * BUFFER_SIZE];
let mut bass_filter = dsp::BassFilter::default(); let mut beat_detector = BeatDetectinator::new();
let mut envelope_filter = dsp::EnvelopeFilter::default(); let mut hsl_cycle = HSLCycle::new(Duration::from_secs(5));
let mut beat_filter = dsp::BeatFilter::default();
let mut j = 0;
let mut threshold = 1.5f32;
// sdl // sdl
@ -66,15 +49,14 @@ fn main() -> Result<()> {
.build()?; .build()?;
let mut canvas = window.into_canvas().build()?; let mut canvas = window.into_canvas().build()?;
let texture_creator = canvas.texture_creator(); 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( let mut texture = texture_creator.create_texture_target(
canvas.default_pixel_format(), canvas.default_pixel_format(),
texture_size, texture_size,
texture_size, texture_size,
)?; )?;
let mut point_buf = ConstGenericRingBuffer::<f32, POINT_BUFFER_SIZE>::new(); let frame_duration = Duration::from_secs_f64(1.0 / FPS as f64);
point_buf.fill_default();
let mut event_pump = sdl.event_pump().unwrap(); let mut event_pump = sdl.event_pump().unwrap();
@ -86,176 +68,33 @@ fn main() -> Result<()> {
keycode: Some(Keycode::Escape), keycode: Some(Keycode::Escape),
.. ..
} => break 'running, } => break 'running,
Event::KeyDown {
keycode: Some(Keycode::Up), event => {
.. // println!("{:?}", event);
} => { beat_detector.on_sdl_event(&event);
threshold += 0.01f32; hsl_cycle.on_sdl_event(&event);
println!("threshold: {:.2}", threshold);
} }
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)?; beat_detector.tick(frame_duration);
hsl_cycle.tick(frame_duration);
for i in 0..BUFFER_SIZE { {
let mut float_bytes = [0u8; 4]; let mut output = movingheads.lock().unwrap();
float_bytes.copy_from_slice(&buffer[4 * i..4 * i + 4]); beat_detector.update_dmx(&mut *output);
hsl_cycle.update_dmx(&mut *output);
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;
}
} }
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| {
beat_detector.draw_sdl(canvas, texture_size);
hsl_cycle.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 {
@ -266,6 +105,7 @@ fn main() -> Result<()> {
canvas.copy(&texture, None, Some(target_rect)).unwrap(); canvas.copy(&texture, None, Some(target_rect)).unwrap();
canvas.present(); canvas.present();
thread::sleep(frame_duration);
} }
{ {