diff --git a/src/audio_input.rs b/src/audio_input.rs new file mode 100644 index 0000000..f006f78 --- /dev/null +++ b/src/audio_input.rs @@ -0,0 +1,73 @@ +use bytemuck::bytes_of_mut; +use embassy_time::Duration; +use esp_idf_svc::hal::{ + gpio::{AnyIOPin, Gpio4, Gpio5, Gpio6}, + i2s::{self, I2sRx, I2S0}, +}; + +use crate::{audio_process::AudioBuffer, config::AUDIO_SAMPLES_PER_BUF}; + +#[embassy_executor::task] +pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) { + let std_cfg = i2s::config::StdConfig::new( + i2s::config::Config::new().role(i2s::config::Role::Controller), + i2s::config::StdClkConfig::new( + 24000, + i2s::config::ClockSource::default(), + i2s::config::MclkMultiple::M256, + ), + i2s::config::StdSlotConfig::msb_slot_default( + i2s::config::DataBitWidth::Bits32, + i2s::config::SlotMode::Stereo, + ) + .data_bit_width(i2s::config::DataBitWidth::Bits32) + .slot_bit_width(i2s::config::SlotBitWidth::Bits32) + .slot_mode_mask( + i2s::config::SlotMode::Stereo, + i2s::config::StdSlotMask::Both, + ) + .ws_width(32) + .ws_polarity(false) + .bit_shift(true) + .left_align(true) + .big_endian(false) + .bit_order_lsb(false), + i2s::config::StdGpioConfig::new(false, false, false), + ); + let mut mic_drv = + i2s::I2sDriver::new_std_rx(i2s_per, &std_cfg, sclk, sd, AnyIOPin::none(), ws).unwrap(); + + mic_drv.rx_enable().expect("rx not enabled"); + + async fn ignore_mic_startup(mic: &mut i2s::I2sDriver<'_, I2sRx>) { + let mut tmp_buf: [u8; 128] = [0; 128]; + loop { + mic.read_async(tmp_buf.as_mut_slice()).await.unwrap(); + } + } + + embassy_time::with_timeout(Duration::from_millis(100), ignore_mic_startup(&mut mic_drv)) + .await + .expect_err("ignore died early"); + + loop { + let audio_in_buf: &mut AudioBuffer = audio_sender.send().await; + + let buffer = bytes_of_mut(audio_in_buf); + let mut total_bytes_read: usize = 0; + let mut remaining_bytes = AUDIO_SAMPLES_PER_BUF * 4; + + while total_bytes_read < AUDIO_SAMPLES_PER_BUF * 4 { + let chunk = &mut buffer[total_bytes_read..total_bytes_read + remaining_bytes]; + let num_bytes_read = mic_drv.read_async(chunk).await.unwrap(); + total_bytes_read += num_bytes_read; + remaining_bytes -= num_bytes_read; + } + + if total_bytes_read != AUDIO_SAMPLES_PER_BUF * 4 { + log::warn!("buffer underflow: {}", total_bytes_read); + } + + audio_sender.send_done(); + } +} diff --git a/src/audio.rs b/src/audio_process.rs similarity index 100% rename from src/audio.rs rename to src/audio_process.rs diff --git a/src/effects/bass_sparks.rs b/src/effects/bass_sparks.rs index 157e8de..c6e2374 100644 --- a/src/effects/bass_sparks.rs +++ b/src/effects/bass_sparks.rs @@ -3,7 +3,7 @@ use embassy_sync::zerocopy_channel::Sender; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver}; use embassy_time::{Duration, Ticker}; -use crate::audio::{AudioStats, DspBuffer}; +use crate::audio_process::{AudioStats, DspBuffer}; use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv}; use crate::helpers::random_at_most; use crate::LED_COUNT; diff --git a/src/effects/led_effect.rs b/src/effects/led_effect.rs index ea51d04..03204cf 100644 --- a/src/effects/led_effect.rs +++ b/src/effects/led_effect.rs @@ -6,7 +6,7 @@ use embassy_sync::{ }; use embassy_time::Duration; -use crate::{audio::DspBuffer, config::LED_COUNT, AudioStats}; +use crate::{audio_process::DspBuffer, config::LED_COUNT, AudioStats}; pub trait LedEffect { fn render(&mut self, fft: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> Duration; diff --git a/src/led_output.rs b/src/led_output.rs new file mode 100644 index 0000000..6406fbf --- /dev/null +++ b/src/led_output.rs @@ -0,0 +1,37 @@ +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver}; +use esp_idf_svc::hal::{gpio::{Gpio11, Gpio12}, spi::SPI2}; + +use crate::effects::led_effect::LedData; + + +#[embassy_executor::task] +pub async fn output_leds( + spi_per: SPI2, + sdo: Gpio11, + sck: Gpio12, + mut receiver: Receiver<'static, NoopRawMutex, LedData>, +) { + let mut led_drv = spi::SpiDeviceDriver::new_single( + spi_per, + sck, + sdo, + AnyIOPin::none(), + AnyIOPin::none(), + &spi::config::DriverConfig::new(), + &spi::config::Config::new() + .baudrate(1.MHz().into()) + .data_mode(spi::config::MODE_3), + ) + .unwrap(); + + loop { + let led_data = receiver.receive().await; + + let output_buffer = bytes_of(led_data); + led_drv + .write_async(output_buffer) + .await + .expect("spi write failed"); + receiver.receive_done(); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 36d211a..0a47f88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,16 @@ -pub mod audio; +pub mod audio_input; +pub mod audio_process; pub mod config; pub mod effects; pub mod helpers; +pub mod led_output; +pub mod triple_buffer; -use embassy_time::Duration; use esp_idf_svc::{ - hal::{ - gpio::{AnyIOPin, Gpio11, Gpio12}, - i2s::{self, I2sRx}, - peripherals::Peripherals, - spi::{self, SPI2}, - units::FromValueType, - }, + hal::peripherals::Peripherals, sys::{esp_dsp, esp_nofail}, }; -use bytemuck::{bytes_of, bytes_of_mut}; use embassy_executor::Spawner; use embassy_sync::{ blocking_mutex::raw::NoopRawMutex, @@ -23,12 +18,14 @@ use embassy_sync::{ }; use static_cell::StaticCell; -use audio::{process_audio, AudioBuffer, AudioStats, DspBuffer}; +use audio_input::mic_input_task; +use audio_process::{process_audio, AudioBuffer, AudioStats, DspBuffer}; use config::{AUDIO_BUFFERS, AUDIO_SAMPLES_PER_BUF, LED_COUNT}; use effects::{ bass_sparks::LedEffectBassSparks, led_effect::{LedData, LedEffect}, }; +use led_output::output_leds; #[embassy_executor::task] async fn effect_task( @@ -39,38 +36,6 @@ async fn effect_task( led_effect.process_led_effect(input, output).await; } -#[embassy_executor::task] -async fn output_leds( - spi_per: SPI2, - sdo: Gpio11, - sck: Gpio12, - mut receiver: Receiver<'static, NoopRawMutex, LedData>, -) { - let mut led_drv = spi::SpiDeviceDriver::new_single( - spi_per, - sck, - sdo, - AnyIOPin::none(), - AnyIOPin::none(), - &spi::config::DriverConfig::new(), - &spi::config::Config::new() - .baudrate(1.MHz().into()) - .data_mode(spi::config::MODE_3), - ) - .unwrap(); - - loop { - let led_data = receiver.receive().await; - - let output_buffer = bytes_of(led_data); - led_drv - .write_async(output_buffer) - .await - .expect("spi write failed"); - receiver.receive_done(); - } -} - #[embassy_executor::main] async fn main(spawner: Spawner) { // It is necessary to call this function once. Otherwise some patches to the runtime @@ -111,47 +76,6 @@ async fn main(spawner: Spawner) { let led_channel = LED_CHANNEL.init(Channel::new(leds)); let (led_sender, led_receiver) = led_channel.split(); - // i2s config - let mic_i2s_per = peripherals.i2s0; - let mic_i2s_sd = peripherals.pins.gpio5; - let mic_i2s_sclk = peripherals.pins.gpio4; - let mic_i2s_ws = peripherals.pins.gpio6; - - let mic_i2s_std_cfg = i2s::config::StdConfig::new( - i2s::config::Config::new().role(i2s::config::Role::Controller), - i2s::config::StdClkConfig::new( - 24000, - i2s::config::ClockSource::default(), - i2s::config::MclkMultiple::M256, - ), - i2s::config::StdSlotConfig::msb_slot_default( - i2s::config::DataBitWidth::Bits32, - i2s::config::SlotMode::Stereo, - ) - .data_bit_width(i2s::config::DataBitWidth::Bits32) - .slot_bit_width(i2s::config::SlotBitWidth::Bits32) - .slot_mode_mask( - i2s::config::SlotMode::Stereo, - i2s::config::StdSlotMask::Both, - ) - .ws_width(32) - .ws_polarity(false) - .bit_shift(true) - .left_align(true) - .big_endian(false) - .bit_order_lsb(false), - i2s::config::StdGpioConfig::new(false, false, false), - ); - let mut mic_drv = i2s::I2sDriver::new_std_rx( - mic_i2s_per, - &mic_i2s_std_cfg, - mic_i2s_sclk, - mic_i2s_sd, - AnyIOPin::none(), - mic_i2s_ws, - ) - .unwrap(); - unsafe { esp_nofail!(esp_dsp::dsps_fft2r_init_fc32( std::ptr::null_mut(), @@ -163,6 +87,15 @@ async fn main(spawner: Spawner) { )); } + spawner + .spawn(mic_input_task( + peripherals.i2s0, + peripherals.pins.gpio5, + peripherals.pins.gpio4, + peripherals.pins.gpio6, + )) + .expect("spawn failed"); + spawner .spawn(process_audio(audio_receiver, fft_sender)) .expect("spawn failed"); @@ -183,38 +116,4 @@ async fn main(spawner: Spawner) { led_receiver, )) .expect("spawn failed"); - - mic_drv.rx_enable().expect("rx not enabled"); - - async fn ignore_mic_startup(mic: &mut i2s::I2sDriver<'_, I2sRx>) { - let mut tmp_buf: [u8; 128] = [0; 128]; - loop { - mic.read_async(tmp_buf.as_mut_slice()).await.unwrap(); - } - } - - embassy_time::with_timeout(Duration::from_millis(100), ignore_mic_startup(&mut mic_drv)) - .await - .expect_err("ignore died early"); - - loop { - let audio_in_buf: &mut AudioBuffer = audio_sender.send().await; - - let buffer = bytes_of_mut(audio_in_buf); - let mut total_bytes_read: usize = 0; - let mut remaining_bytes = AUDIO_SAMPLES_PER_BUF * 4; - - while total_bytes_read < AUDIO_SAMPLES_PER_BUF * 4 { - let chunk = &mut buffer[total_bytes_read..total_bytes_read + remaining_bytes]; - let num_bytes_read = mic_drv.read_async(chunk).await.unwrap(); - total_bytes_read += num_bytes_read; - remaining_bytes -= num_bytes_read; - } - - if total_bytes_read != AUDIO_SAMPLES_PER_BUF * 4 { - log::warn!("buffer underflow: {}", total_bytes_read); - } - - audio_sender.send_done(); - } } diff --git a/src/triple_buffer.rs b/src/triple_buffer.rs new file mode 100644 index 0000000..295c64d --- /dev/null +++ b/src/triple_buffer.rs @@ -0,0 +1,149 @@ +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_sync::blocking_mutex::{raw::RawMutex, Mutex}; +use embassy_sync::waitqueue::WakerRegistration; + +/// Triple-buffered zero-copy Channel. +/// +/// Sender will flip the input buffer when done, which will in turn +/// flip the output buffer at the next Receiver call. +pub struct TripleBuffer<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex<M, RefCell<State>>, +} + +impl<'a, M: RawMutex, T> TripleBuffer<'a, M, T> { + pub fn new(buffers: &'a mut [T; 3]) -> Self { + Self { + buf: buffers.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + input: 0, + internal: 1, + output: None, + new_data: false, + send_waker: WakerRegistration::new(), + })), + } + } + + /// Creates a [`Sender`] and [`Receiver`] from an existing buffer. + /// + // / Further Senders and Receivers can be created through [`Sender::borrow`] and + // / [`Receiver::borrow`] respectively. + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { buffer: self }, Receiver { buffer: self }) + } +} + +/// Send-only access to a [`TripleBuffer`]. +pub struct Sender<'a, M: RawMutex, T> { + buffer: &'a TripleBuffer<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Sender<'a, M, T> { + /// Asynchronously send a value into the buffer. + pub fn send(&mut self) -> impl Future<Output = &mut T> { + poll_fn(|_| { + self.buffer.state.lock(|s| { + let s = &mut *s.borrow_mut(); + let r = unsafe { &mut *self.buffer.buf.add(s.input) }; + Poll::Ready(r) + }) + }) + } + + /// Notify the buffer that the sending of the value has been finalized. + pub fn send_done(&mut self) { + self.buffer.state.lock(|s| s.borrow_mut().flip_input()); + } +} + +/// Receive-only access to a [`Channel`]. +pub struct Receiver<'a, M: RawMutex, T> { + buffer: &'a TripleBuffer<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + /// Checks if the buffer has data available + pub fn is_ready(&self) -> bool { + self.buffer.state.lock(|s| s.borrow_mut().output.is_some()) + } + + /// Asynchronously receive, wait for a new value. + pub fn receive_new(&mut self) -> impl Future<Output = &mut T> { + poll_fn(|cx| { + self.buffer.state.lock(|s| { + let s = &mut *s.borrow_mut(); + + if s.output.is_some() && s.new_data { + s.flip_output(); + let r = unsafe { &mut *self.buffer.buf.add(s.output.unwrap()) }; + Poll::Ready(r) + } else { + s.send_waker.register(cx.waker()); + Poll::Pending + } + }) + }) + } + + /// Asynchronously receive, return with old data if possible. + pub fn receive_cached(&mut self) -> impl Future<Output = (&mut T, bool)> { + poll_fn(|cx| { + self.buffer.state.lock(|s| { + let s = &mut *s.borrow_mut(); + + if s.output.is_some() { + let had_new_data = s.new_data; + if s.new_data { + s.flip_output(); + } + let r = unsafe { &mut *self.buffer.buf.add(s.output.unwrap()) }; + Poll::Ready((r, had_new_data)) + } else { + s.send_waker.register(cx.waker()); + Poll::Pending + } + }) + }) + } +} + +struct State { + input: usize, + internal: usize, + output: Option<usize>, + new_data: bool, + send_waker: WakerRegistration, // notifies when data was flipped from input to internal +} + +impl State { + fn flip_input(&mut self) { + (self.input, self.internal) = (self.internal, self.input); + self.new_data = true; + if self.output.is_none() { + self.output = match (self.input, self.internal) { + (0, 1) => Some(2), + (1, 1) => Some(2), + (0, 2) => Some(1), + (2, 0) => Some(1), + (_, _) => None, + } + } + self.send_waker.wake(); + } + fn flip_output(&mut self) { + match self.output { + Some(o) => { + (self.output, self.internal) = (Some(self.internal), o); + self.new_data = false; + } + None => {} + } + } +}