moving standalone mode to alsa rust direct binding crate & moving led_driver to a specific package

This commit is contained in:
2025-08-04 16:37:40 +00:00
parent 66c4aeffa6
commit 57ace1383b
52 changed files with 859 additions and 577 deletions
-4
View File
@@ -1,4 +0,0 @@
fn main() {
println!("cargo::rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib/");
// println!("cargo:rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib");
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "led_driver"
version = "0.1.0"
edition = "2024"
[dependencies]
[build-dependencies]
bindgen = "0.72.0"
+26
View File
@@ -0,0 +1,26 @@
use std::{env, path::PathBuf};
fn main() {
println!("cargo::rustc-link-search=/workspaces/LightSabre/lightsabre_backend/led_driver/lib");
// Tell cargo to tell rustc to link the RPiLedBars_drivers and logc libraries.
println!("cargo:rustc-link-lib=RpiLedBars_drivers_8ch");
println!("cargo:rustc-link-lib=logc");
// The bindgen::Builder is the main entry point to bindgen, and lets you build up options for the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate bindings for.
.header("lib/wrapper.h")
// Tell cargo to invalidate the built crate whenever any of the included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
+13
View File
@@ -0,0 +1,13 @@
#! /usr/bin/env bash
dest_path=${0%/*}/lib
source_host=${1:-raspberrypi.local}
scp \
raspberrypi.local:RpiLedBars/backend/.build/src/drivers/libRpiLedBars_drivers.a \
${dest_path}/bin/libRpiLedBars_drivers_8ch.a
scp \
raspberrypi.local:RpiLedBars/backend/.build/_deps/logc-build/liblogc.a \
${dest_path}/bin/liblogc.a
@@ -0,0 +1,34 @@
#include "include/log.h"
#include "src/leddriver/rpi_leddriver.h"
#include "src/common.h"
#define CHAN_MAXLEDS 60 // Maximum number of LEDs per channel
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
#define TX_TEST 0 // If non-zero, use dummy Tx data
#define LED_NBITS 24 // Number of data bits per LED
#define LED_PREBITS 4 // Number of zero bits before LED data
#define LED_POSTBITS 4 // Number of zero bits after LED data
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
// Length of data for 1 row (1 LED on each channel)
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
// Transmit data type, 8 or 16 bits
#if LED_NCHANS > 8
typedef uint16_t TXDATA_T;
#else
typedef uint8_t TXDATA_T;
#endif
// Ofset into Tx data buffer, given LED number in chan
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
// Size of data buffers & NV memory, given number of LEDs per chan
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
extern TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
extern TXDATA_T *txdata;
extern MEM_MAP vc_mem;
+159
View File
@@ -0,0 +1,159 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub struct LedDriver {
led_per_strip_count: usize,
strip_count: usize,
}
// Offset into Tx data buffer, given LED number in chan
macro_rules! LED_TX_OSET {
($n:expr) => {
LED_PREBITS + (LED_DLEN * $n)
};
}
impl LedDriver {
const LED_NCHANS: usize = LED_NCHANS as usize; // Number of channels (strips)
const TX_TEST: usize = TX_TEST as usize; // If non-zero, use dummy Tx data
const LED_NBITS: usize = LED_NBITS as usize; // Number of data bits per LED
pub const LED_PREBITS: usize = LED_PREBITS as usize; // Number of zero bits before LED data
const LED_POSTBITS: usize = LED_POSTBITS as usize; // Number of zero bits after LED data
const BIT_NPULSES: usize = BIT_NPULSES as usize; // Number of O/P pulses per LED bit
// Length of data for 1 row (1 LED on each channel)
pub const LED_DLEN: usize = Self::LED_NBITS * Self::BIT_NPULSES;
pub fn new(led_per_strip_count: usize, strip_count: usize) -> LedDriver {
unsafe { leddriver_setup() };
LedDriver {
led_per_strip_count,
strip_count,
}
}
pub fn get_led_per_strip_count(&self) -> usize {
self.led_per_strip_count
}
pub fn get_strip_count(&self) -> usize {
self.strip_count
}
pub fn set_color(&self, rgb: u32, index: usize) {
unsafe { set_color(rgb, index as i32) };
}
pub fn set_color_till_led(&self, color: u32, led_num: usize) {
let led_num = led_num.min(self.get_led_per_strip_count());
// Set color for all strips
for led_index in 0..=led_num {
self.direct_set_color(color, led_index);
}
for led in led_num + 1..self.get_led_per_strip_count() {
self.direct_set_color(0x000000, led);
}
}
pub fn direct_set_color(&self, rgb: u32, index: usize) {
let mut mask = 0_u32;
let mut offset = LED_TX_OSET!(index as u32) as usize;
// For each bit of the 24-bit RGB values..
for n in 0..Self::LED_NBITS {
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
// Mask to convert RGB to GRB, M.S bit first
Self::compute_mask(&mut mask, n);
// 1st byte or word is a high pulse on all lines
tx_data[0] = 0xff;
// 2nd has high or low bits from data
tx_data[1] = if rgb & mask != 0_u32 { 0xff } else { 0x00 };
// 3rd is a low pulse
tx_data[2] = 0x00;
offset += Self::BIT_NPULSES;
}
}
pub fn set_led_color(&self, rgb: u32, led_index: usize, strip_index: usize) {
let mut mask = 0_u32;
let mut offset = LED_TX_OSET!(led_index as u32) as usize;
let strip_mask = !(0b1_u8 << strip_index); // Mask to clear the led bit of desired strip
// For each bit of the 24-bit RGB values..
for n in 0..Self::LED_NBITS {
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
// Mask to convert RGB to GRB, M.S bit first
Self::compute_mask(&mut mask, n);
// 1st byte or word is a high pulse on all lines
tx_data[0] = 0xff;
// 2nd has high or low bits from data
tx_data[1] = tx_data[1] & strip_mask
| if rgb & mask != 0_u32 {
0b1 << strip_index
} else {
0x00
};
// 3rd is a low pulse
tx_data[2] = 0x00;
offset += Self::BIT_NPULSES;
}
}
pub fn set_strip_index_colors(&self, rgb: [u32; Self::LED_NCHANS], led_index: usize) {
let mut mask = 0_u32;
let mut offset = LED_TX_OSET!(led_index as u32) as usize;
// For each bit of the 24-bit RGB values..
for n in 0..Self::LED_NBITS {
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
// Mask to convert RGB to GRB, M.S bit first
Self::compute_mask(&mut mask, n);
// 1st byte or word is a high pulse on all lines
tx_data[offset] = 0xff;
tx_data[offset + 1] = 0x00; // Clear the strip bits first
// 2nd has high or low bits from data
for (strip_index, rgb) in rgb.iter().enumerate() {
if rgb & mask != 0_u32 {
// Set the bit for this strip
tx_data[offset + 1] |= 0b1 << strip_index;
}
}
// 3rd is a low pulse
tx_data[offset + 2] = 0x00;
offset += Self::BIT_NPULSES;
}
}
pub fn refresh(&self) {
unsafe { leddriver_refresh() };
}
// pub fn color_hsv(&self, hue: u16, sat: u8, val: u8) -> u32 {
// unsafe { ColorHSV(hue, sat, val) }
// }
fn compute_mask(mask: &mut u32, n: usize) {
*mask = if n == 0 {
0x800000
} else if n == 8 {
0x8000
} else if n == 16 {
0x80
} else {
*mask >> 1
}
}
}
impl Drop for LedDriver {
fn drop(&mut self) {
unsafe { leddriver_close() };
}
}
@@ -1,59 +0,0 @@
use cpal::traits::{DeviceTrait, HostTrait};
use rppal::gpio::Gpio;
use crate::cputasks::modes::AppModeHandler;
use crate::devices::led_driver::LedDriver;
pub struct StandaloneMode {
_i2s_mic_pin: Vec<rppal::gpio::IoPin>,
}
impl StandaloneMode {
const I2S_PINS: [u8; 3] = [20, 19, 18];
pub fn new() -> Self {
let gpio = Gpio::new().expect("Failed to initialize GPIO");
let i2s_mic_pin: Vec<rppal::gpio::IoPin> = Self::I2S_PINS
.iter()
.map(|&pin| {
gpio.get(pin)
.expect(&format!("Failed to get GPIO pin {}", pin))
.into_io(rppal::gpio::Mode::Alt0)
})
.collect();
StandaloneMode {
_i2s_mic_pin: i2s_mic_pin,
}
}
}
impl AppModeHandler for StandaloneMode {
fn enter(&mut self) {
log::debug!("[Standalone] Entering Standalone Mode");
let host = cpal::default_host();
// default_input_device()
host.devices()
.unwrap()
.for_each(|device| log::info!("{:?}", device.name()));
if let Some(device) = host
.input_devices()
.unwrap()
.find(|device| device.name() == Ok(String::from("snd_rpi_googlevoicehat_soundcar")))
{
log::info!("Found record device {:?}", device.name());
log::info!("Default config : {:?}", device.default_input_config())
} else {
log::error!("Record device not found");
}
}
fn run(&mut self, _: &mut LedDriver) {
log::trace!("[Standalone] Running...");
}
fn exit(&mut self) {
log::debug!("[Standalone] Exiting Standalone Mode");
}
}
@@ -1,61 +0,0 @@
#[link(name = "logc", kind = "static")]
unsafe extern "C" {
unsafe fn log_log(level: u32, file: *const u8, line: i32, fmt: *const u8, ...);
}
#[link(name = "RpiLedBars_drivers", kind = "static")]
unsafe extern "C" {
unsafe fn leddriver_setup();
unsafe fn leddriver_close();
unsafe fn set_color(rgb: u32, index: usize);
// unsafe fn rgb_txdata(rgbs: *mut u32, index: usize);
unsafe fn leddriver_refresh();
// unsafe fn ColorHSV(hue: u16, sat: u8, val: u8) -> u32;
}
pub struct LedDriver {
led_per_strips: usize,
}
impl LedDriver {
pub fn new(led_per_strips: usize) -> LedDriver {
unsafe {
log_log(
0,
"coucou".as_bytes().as_ptr(),
13,
"hello".as_bytes().as_ptr(),
)
};
unsafe { leddriver_setup() };
LedDriver { led_per_strips }
}
pub fn get_led_per_strips(&self) -> usize {
self.led_per_strips
}
pub fn set_color(&self, rgb: u32, index: usize) {
unsafe { set_color(rgb, index) };
}
pub fn refresh(&self) {
unsafe { leddriver_refresh() };
}
// pub fn color_hsv(&self, hue: u16, sat: u8, val: u8) -> u32 {
// unsafe { ColorHSV(hue, sat, val) }
// }
}
impl Drop for LedDriver {
fn drop(&mut self) {
unsafe { leddriver_close() };
}
}
@@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
alsa = "0.9.1"
artnet_protocol = "0.4.3"
cpal = "0.16.0"
crossbeam = "0.8.4"
ctrlc = { version = "3.4.7", features = ["termination"] }
env_logger = "0.11.8"
@@ -13,6 +13,12 @@ log = "0.4.27"
nix = "0.30.1"
rpi-mailbox = "0.3.0"
rppal = "0.22.1"
spectrum-analyzer = "1.7.0"
textplots = "0.8.7"
led_driver = { path = "../led_driver" }
[build-dependencies]
bindgen = "0.71.0"
[features]
default = ["rpizero2", "16channel"]
@@ -14,9 +14,9 @@ use crate::channels::Message;
use crate::devices::led_driver::LedDriver;
pub trait AppModeHandler {
fn enter(&mut self);
fn run(&mut self, led_driver: &mut LedDriver);
fn exit(&mut self);
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>>;
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>>;
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -20,7 +20,7 @@ impl ArtNetMode {
}
impl AppModeHandler for ArtNetMode {
fn enter(&mut self) {
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[ArtNet] Entering ArtNet Mode");
let mut attempts = 0_usize;
@@ -41,9 +41,10 @@ impl AppModeHandler for ArtNetMode {
socket.set_nonblocking(true).unwrap();
self.socket = Some(socket);
log::debug!("[ArtNet] ArtNet Mode initialized and listening on port 6454");
Ok(())
}
fn run(&mut self, led_driver: &mut LedDriver) {
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
log::trace!("[ArtNet] Running...");
let buf = &mut [0; 530];
let mut is_first_data_frame = true;
@@ -109,8 +110,9 @@ impl AppModeHandler for ArtNetMode {
is_first_data_frame = false;
}
let led_strip = (u16::from(output.port_address) & 0b0111_u16) as usize;
//output.port_address
for i in 0..led_driver.get_led_per_strips() {
for i in 0..led_driver.get_led_per_strip_count() {
let data_index = i * 3;
let g = *output.data.as_ref().get(data_index).unwrap_or(&0);
let r = *output.data.as_ref().get(data_index + 1).unwrap_or(&0);
@@ -118,11 +120,9 @@ impl AppModeHandler for ArtNetMode {
// let color = (r as u32) << 16 + (g as u32) << 8 + b;
let color: u32 =
((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
led_driver.set_color(color, i);
led_driver.direct_set_color(color, i);
}
led_driver.refresh();
// Here you would typically handle the output data, e.g., send it to the LED driver
// For now, we just log it
}
ArtCommand::PollReply(_) => {
log::trace!("[ArtNet] Received PollReply command, ignoring");
@@ -139,14 +139,16 @@ impl AppModeHandler for ArtNetMode {
Err(e) => panic!("encountered IO error: {e}"),
}
}
Ok(())
}
fn exit(&mut self) {
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[ArtNet] Exiting ArtNet Mode");
log::info!("[ArtNet] Statistics: {}", self.statistics);
self.socket = None;
self.last_frame_time = None;
self.statistics = ArtNetModeStatistics::default();
Ok(())
}
}
@@ -30,22 +30,24 @@ impl DiagnosticsMode {
fn init_led_iterator(led_driver: &LedDriver) -> Range<usize> {
// Initialize the LED iterator to cover all LEDs
0..led_driver.get_led_per_strips()
0..led_driver.get_led_per_strip_count()
}
}
impl AppModeHandler for DiagnosticsMode {
fn enter(&mut self) {
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[Diagnostics] Entering Diagnostics Mode");
self.cycle_count = 0;
Ok(())
}
fn run(&mut self, led_driver: &mut LedDriver) {
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
if self.cycle_count % 50 == 0 {
log::trace!("[Diagnostics] Running...");
let (led, color) = match self.led_iterator.next() {
Some(led) => {
led_driver.set_color(**self.color_iterator.peek().unwrap(), led);
// led_driver.direct_set_color(**self.color_iterator.peek().unwrap(), led);
led_driver.set_led_color(**self.color_iterator.peek().unwrap(), led, 0);
(led, *self.color_iterator.peek().unwrap())
}
None => {
@@ -57,14 +59,16 @@ impl AppModeHandler for DiagnosticsMode {
led,
match self.color_iterator.peek() {
Some(color) => {
led_driver.set_color(**color, led);
// led_driver.direct_set_color(**color, led);
led_driver.set_led_color(**color, led, 0);
*color
}
None => {
// Reset the color iterator if it reaches the end
self.color_iterator = DiagnosticsMode::color_iterator();
let color = self.color_iterator.peek().unwrap();
led_driver.set_color(**color, led);
// led_driver.direct_set_color(**color, led);
led_driver.set_led_color(**color, led, 0);
*color
}
},
@@ -75,9 +79,11 @@ impl AppModeHandler for DiagnosticsMode {
led_driver.refresh();
}
self.cycle_count += 1;
Ok(())
}
fn exit(&mut self) {
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[Diagnostics] Exiting Diagnostics Mode");
Ok(())
}
}
@@ -10,15 +10,21 @@ impl ManualMode {
}
impl AppModeHandler for ManualMode {
fn enter(&mut self) {
fn enter(&mut self) -> std::result::Result<(), Box<(dyn std::error::Error + 'static)>> {
log::debug!("[Manual] Entering Manual Mode");
Ok(())
}
fn run(&mut self, _: &mut LedDriver) {
fn run(
&mut self,
_: &mut LedDriver,
) -> std::result::Result<(), Box<(dyn std::error::Error + 'static)>> {
log::trace!("[Manual] Running...");
Ok(())
}
fn exit(&mut self) {
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[Manual] Exiting Manual Mode");
Ok(())
}
}
@@ -0,0 +1,328 @@
use core::f32;
use crossbeam::channel;
use spectrum_analyzer::windows::hann_window;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Instant;
use rppal::gpio::Gpio;
use spectrum_analyzer::scaling::divide_by_N_sqrt;
use spectrum_analyzer::{FrequencyLimit, FrequencyValue, samples_fft_to_spectrum};
use crate::cputasks::modes::AppModeHandler;
use crate::devices::led_driver::LedDriver;
pub struct StandaloneMode {
_i2s_mic_pin: Vec<rppal::gpio::IoPin>,
audio_processor: Option<AudioProcessorControl>,
}
impl StandaloneMode {
const I2S_PINS: [u8; 3] = [20, 19, 18];
pub fn new() -> Self {
let gpio = Gpio::new().expect("Failed to initialize GPIO");
let i2s_mic_pin: Vec<rppal::gpio::IoPin> = Self::I2S_PINS
.iter()
.map(|&pin| {
gpio.get(pin)
.expect(&format!("Failed to get GPIO pin {}", pin))
.into_io(rppal::gpio::Mode::Alt0)
})
.collect();
StandaloneMode {
_i2s_mic_pin: i2s_mic_pin,
audio_processor: None,
}
}
fn compute_log_spectrum(
fft_data: &[(f32, f32)],
strip_count: usize,
f_min: f32,
f_max: f32,
) -> Vec<f32> {
let mut bands = vec![0.0; strip_count];
let mut counts = vec![0usize; strip_count];
// Precompute band edges logarithmically
let mut edges = Vec::with_capacity(strip_count + 1);
for i in 0..=strip_count {
let fraction = i as f32 / strip_count as f32;
let edge = f_min * (f_max / f_min).powf(fraction);
edges.push(edge);
}
// Assign each frequency to a band
for &(freq, amp) in fft_data {
if freq < f_min || freq > f_max {
continue;
}
if let Some(idx) = edges.windows(2).position(|w| freq >= w[0] && freq < w[1]) {
bands[idx] += amp;
counts[idx] += 1;
}
}
// Normalize
for (b, c) in bands.iter_mut().zip(counts) {
if c > 0 {
*b /= c as f32;
}
}
bands
}
}
impl AppModeHandler for StandaloneMode {
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[Standalone] Entering Standalone Mode");
let audio_processor = AudioProcessorControl::new()?;
self.audio_processor = Some(audio_processor);
Ok(())
}
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
log::trace!("[Standalone] Running...");
if let Some(audio_processor) = &self.audio_processor {
match audio_processor.get_data() {
Some(data) => {
log::trace!("[Standalone] Received audio data: {:?}", data.len());
log::info!(
"Frequency count: {} MAX([|{}; {}|]) -> {:?}",
data.len(),
data[0].0,
data[data.len() - 1].0,
data.iter()
.fold((0_f32, 0_f32), |(freq_max, val_max), (freq, val)| {
if *val >= val_max {
(*freq, *val)
} else {
(freq_max, val_max)
}
})
);
let spectrum = StandaloneMode::compute_log_spectrum(
&data,
led_driver.get_strip_count(),
data[0].0,
data[data.len() - 1].0,
);
log::debug!(
"[Standalone] Computed spectrum: {:?}",
spectrum
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
);
let avg_intensity = spectrum.iter().fold(0_f32, |x_max, x| x_max.max(*x));
let g = avg_intensity.min(255.0) as u8;
let r = 0_u8;
let b = 255 - avg_intensity.min(255.0) as u8;
let color: u32 = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
let light_number =
(avg_intensity as usize / 50).min(led_driver.get_led_per_strip_count());
log::debug!(
"[Standalone] Average intensity: {}, Color: {:06x}, Light number: {}",
avg_intensity,
color,
light_number
);
for i in 0..light_number {
// Set the color for the first `light_number` LEDs
led_driver.direct_set_color(color, i);
}
for i in light_number..led_driver.get_led_per_strip_count() {
// Set the remaining LEDs to black
led_driver.direct_set_color(0x000000, i);
}
led_driver.refresh();
}
None => {
log::trace!("[Standalone] No audio data received");
}
}
} else {
log::warn!("[Standalone] Audio processor is not initialized");
}
Ok(())
}
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::debug!("[Standalone] Exiting Standalone Mode");
self.audio_processor = None;
Ok(())
}
}
struct AudioProcessorControl {
rx: channel::Receiver<Vec<(f32, f32)>>,
stop_flag: Arc<AtomicBool>,
handle: Option<std::thread::JoinHandle<()>>,
}
impl AudioProcessorControl {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let stop_flag = Arc::new(AtomicBool::new(false));
let flag_clone = Arc::clone(&stop_flag);
let (tx, rx): (
channel::Sender<Vec<(f32, f32)>>,
channel::Receiver<Vec<(f32, f32)>>,
) = crossbeam::channel::bounded(1);
let mut audio_processor = AudioProcessor::new(tx)?;
let join_handle = thread::spawn(move || {
log::info!("Starting audio processing thread");
while !flag_clone.load(Ordering::Relaxed) {
audio_processor.process_audio();
}
});
Ok(AudioProcessorControl {
rx,
stop_flag,
handle: Some(join_handle),
})
}
pub fn get_data(&self) -> Option<Vec<(f32, f32)>> {
match self.rx.try_recv() {
Ok(data) => Some(data),
Err(crossbeam::channel::TryRecvError::Empty) => None,
Err(e) => {
log::error!("Failed to receive data from channel: {:?}", e);
None
}
}
}
}
impl Drop for AudioProcessorControl {
fn drop(&mut self) {
log::info!("Stopping audio processing thread");
self.stop_flag.store(true, Ordering::Relaxed);
let _ = self.handle.take().unwrap().join().map_err(|e| {
log::error!("Failed to stop audio processing thread: {:?}", e);
});
}
}
struct AudioProcessor {
pcm: alsa::pcm::PCM,
spectrum: Vec<(f32, f32)>,
tx: channel::Sender<Vec<(f32, f32)>>,
}
impl AudioProcessor {
pub fn new(tx: channel::Sender<Vec<(f32, f32)>>) -> Result<Self, Box<dyn std::error::Error>> {
let hints = alsa::device_name::HintIter::new_str(None, "pcm").unwrap();
for hint in hints {
// When Direction is None it means that both the PCM supports both playback and capture
if hint.name.is_some()
&& hint.desc.is_some()
&& (hint.direction.is_none()
|| hint
.direction
.map(|dir| dir == alsa::Direction::Capture)
.unwrap_or_default())
{
log::debug!(
"pcm: {:<35} desc: {:?}",
hint.name.unwrap(),
hint.desc.unwrap()
);
}
}
let pcm = alsa::PCM::new(
"plughw:CARD=sndrpigooglevoi,DEV=0",
alsa::Direction::Capture,
false,
)
.unwrap();
{
// For this example, we assume 44100Hz, one channel, 16 bit audio.
let hwp = alsa::pcm::HwParams::any(&pcm).unwrap();
hwp.set_channels_near(1).unwrap();
hwp.set_rate_near(44100, alsa::ValueOr::Nearest).unwrap();
hwp.set_format(alsa::pcm::Format::float()).unwrap();
hwp.set_access(alsa::pcm::Access::RWInterleaved).unwrap();
pcm.hw_params(&hwp).unwrap();
}
pcm.start()?;
Ok(AudioProcessor {
pcm,
spectrum: Vec::new(),
tx,
})
}
fn process_audio(&mut self) {
// Placeholder for audio processing logic
// This should return a vector of tuples representing frequency and amplitude
let start = Instant::now();
let binding = &self.pcm;
let io = binding.io_f32().unwrap();
let mut buf = [0f32; 2048];
// Block while waiting for 2048 samples to be read from the device.
let nb_samples = io.readi(&mut buf).unwrap();
let elapsed_1 = start.elapsed();
log::trace!(
"[Standalone] Read {} samples in {:?}",
nb_samples,
elapsed_1
);
let start = Instant::now();
if nb_samples >= 2048 {
let data = buf[..2048].to_vec();
let res: spectrum_analyzer::FrequencySpectrum = samples_fft_to_spectrum(
&hann_window(&data),
44100,
FrequencyLimit::Range(20_f32, 20000_f32),
// FrequencyLimit::All,
Some(&divide_by_N_sqrt),
)
.unwrap();
if self.spectrum.is_empty() {
self.spectrum = Vec::from_iter(
res.data()
.iter()
.map(|(fr, fr_val)| (fr.val(), fr_val.val() * 5000.0_f32)),
);
} else {
res.data().iter().zip(self.spectrum.iter_mut()).for_each(
|((fr, fr_val), (fr_old, fr_old_val))| {
*fr_old = fr.val();
let old_val = *fr_old_val * 0.84;
let max = (*fr_val * 5000.0_f32.into()).max(FrequencyValue::from(old_val));
*fr_old_val = max.val();
},
);
}
self.tx
.try_send(self.spectrum.clone())
.expect("Failed to send audio data");
let elapsed_2 = start.elapsed();
log::trace!("{elapsed_1:?} {elapsed_2:?}");
} else {
log::trace!("{elapsed_1:?}");
}
}
}
@@ -0,0 +1 @@
pub use led_driver::LedDriver;
@@ -32,7 +32,7 @@ fn setup() -> GlobalContext {
std::process::exit(0);
})
.expect("Error setting SIGINT/SIGTERM/SIGHUP handler");
let led_driver = LedDriver::new(5);
let led_driver = LedDriver::new(5, 3);
let (tx, rx) = unbounded();
let mode_manager = ModeManager::new(rx);
@@ -57,7 +57,7 @@ fn run(ctx: &mut GlobalContext) {
if elapsed > period {
log::warn!(
"Mode {:?} execution took too long: {:?}/{:?}ms",
"Mode {:?} execution took too long: {:?}/{:?}",
ctx.mode_manager.get_current_mode(),
elapsed,
period
@@ -70,7 +70,6 @@ fn run(ctx: &mut GlobalContext) {
fn cleanup() {
log::info!("Cleaning up before quitting...");
// Perform cleanup here
}
impl LightSabre {