async
This commit is contained in:
parent
bd18905a96
commit
385bd830ad
5 changed files with 203 additions and 85 deletions
|
@ -24,9 +24,14 @@ experimental = ["esp-idf-svc/experimental"]
|
|||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] }
|
||||
esp-idf-svc = { version = "0.51", features = ["embassy-time-driver", "embassy-sync"] }
|
||||
anyhow = "1.0.97"
|
||||
bytemuck = { version="1.22.0", features = ["derive", "min_const_generics"] }
|
||||
embassy-executor = {version ="0.7.0" , features = ["arch-std", "executor-thread", "log"] }
|
||||
embassy-sync = "0.6.2"
|
||||
static_cell = "2.1.0"
|
||||
embassy-time = "0.4.0"
|
||||
assign-resources = "0.4.1"
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.33"
|
||||
|
|
106
src/audio.rs
106
src/audio.rs
|
@ -1,41 +1,51 @@
|
|||
use esp_idf_svc::sys::{esp_dsp, esp_nofail};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
|
||||
use crate::config::{AUDIO_BANDS, AUDIO_BUFFERS, AUDIO_SAMPLES_PER_BUF};
|
||||
use embassy_sync::zerocopy_channel::{Receiver, Sender};
|
||||
use esp_idf_svc::sys::{esp_dsp, esp_nofail};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::config::{AUDIO_BANDS, AUDIO_SAMPLES_PER_BUF};
|
||||
use crate::helpers::{falloff, falloff_f};
|
||||
|
||||
pub type AudioBuffer = [i32; AUDIO_SAMPLES_PER_BUF];
|
||||
pub type DspBuffer = [f32; AUDIO_SAMPLES_PER_BUF];
|
||||
|
||||
pub struct AudioProcessor {
|
||||
static FFT_WINDOW_CELL: StaticCell<DspBuffer> = StaticCell::new();
|
||||
static FFT_WINDOW: Option<&DspBuffer> = None;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct AudioStats {
|
||||
pub floating_max: i32,
|
||||
pub current_powers: [f32; AUDIO_BANDS],
|
||||
pub avg_powers: [f32; AUDIO_BANDS],
|
||||
pub fft_buffer: [DspBuffer; AUDIO_BUFFERS],
|
||||
pub next_fft_buf: usize,
|
||||
fft_window: DspBuffer,
|
||||
}
|
||||
|
||||
impl AudioProcessor {
|
||||
pub fn new() -> Self {
|
||||
pub struct AudioProcessor {
|
||||
pub stats: AudioStats,
|
||||
}
|
||||
|
||||
impl Default for AudioProcessor {
|
||||
fn default() -> Self {
|
||||
let mut buf: DspBuffer = [0f32; AUDIO_SAMPLES_PER_BUF];
|
||||
|
||||
unsafe {
|
||||
esp_dsp::dsps_wind_hann_f32(buf.as_mut_ptr(), AUDIO_SAMPLES_PER_BUF as i32);
|
||||
}
|
||||
|
||||
FFT_WINDOW_CELL.init(buf);
|
||||
|
||||
AudioProcessor {
|
||||
floating_max: 0i32,
|
||||
current_powers: [0f32; AUDIO_BANDS],
|
||||
avg_powers: [0f32; AUDIO_BANDS],
|
||||
fft_buffer: [[0f32; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS],
|
||||
next_fft_buf: 0,
|
||||
fft_window: buf,
|
||||
stats: AudioStats {
|
||||
floating_max: 0i32,
|
||||
current_powers: [0f32; AUDIO_BANDS],
|
||||
avg_powers: [0f32; AUDIO_BANDS],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, audio: &AudioBuffer) -> usize {
|
||||
let &(mut proc_fft_buffer) = &self.fft_buffer[self.next_fft_buf];
|
||||
|
||||
impl AudioProcessor {
|
||||
pub fn process(&mut self, audio: &AudioBuffer, fft: &mut DspBuffer) {
|
||||
/* calculate floating max */
|
||||
let mut new_max = 0i32;
|
||||
for value in audio {
|
||||
|
@ -43,17 +53,20 @@ impl AudioProcessor {
|
|||
}
|
||||
|
||||
/* get maximum */
|
||||
self.floating_max = std::cmp::max(
|
||||
self.stats.floating_max = std::cmp::max(
|
||||
10000000,
|
||||
if new_max > self.floating_max {
|
||||
if new_max > self.stats.floating_max {
|
||||
new_max
|
||||
} else {
|
||||
falloff(self.floating_max, falloff(self.floating_max, new_max))
|
||||
falloff(
|
||||
self.stats.floating_max,
|
||||
falloff(self.stats.floating_max, new_max),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
/* convert to floats for input to fft */
|
||||
for it in audio.iter().zip(proc_fft_buffer.iter_mut()) {
|
||||
for it in audio.iter().zip(fft.iter_mut()) {
|
||||
let (audio_it, fft_it) = it;
|
||||
*fft_it = (*audio_it as f32) / (i32::MAX as f32);
|
||||
}
|
||||
|
@ -62,9 +75,9 @@ impl AudioProcessor {
|
|||
let half_sample_count = (AUDIO_SAMPLES_PER_BUF / 2) as i32;
|
||||
unsafe {
|
||||
esp_nofail!(esp_dsp::dsps_mul_f32_ae32(
|
||||
proc_fft_buffer.as_ptr(),
|
||||
self.fft_window.as_ptr(),
|
||||
proc_fft_buffer.as_mut_ptr(),
|
||||
fft.as_ptr(),
|
||||
FFT_WINDOW.expect("windows not initialized!").as_ptr(),
|
||||
fft.as_mut_ptr(),
|
||||
AUDIO_SAMPLES_PER_BUF as i32,
|
||||
1,
|
||||
1,
|
||||
|
@ -72,40 +85,57 @@ impl AudioProcessor {
|
|||
));
|
||||
|
||||
esp_nofail!(esp_dsp::dsps_fft2r_fc32_aes3_(
|
||||
proc_fft_buffer.as_mut_ptr(),
|
||||
fft.as_mut_ptr(),
|
||||
half_sample_count,
|
||||
esp_dsp::dsps_fft_w_table_fc32,
|
||||
)); // operating on half length but complex
|
||||
esp_nofail!(esp_dsp::dsps_bit_rev2r_fc32(
|
||||
proc_fft_buffer.as_mut_ptr(),
|
||||
fft.as_mut_ptr(),
|
||||
half_sample_count
|
||||
)); // operating on half length but complex
|
||||
esp_nofail!(esp_dsp::dsps_cplx2real_fc32_ae32_(
|
||||
proc_fft_buffer.as_mut_ptr(),
|
||||
fft.as_mut_ptr(),
|
||||
half_sample_count,
|
||||
esp_dsp::dsps_fft_w_table_fc32,
|
||||
esp_dsp::dsps_fft_w_table_size,
|
||||
)); // operating on half length but complex
|
||||
|
||||
for i in 0..half_sample_count as usize {
|
||||
proc_fft_buffer[i] = (proc_fft_buffer[i * 2] * proc_fft_buffer[i * 2]
|
||||
+ proc_fft_buffer[i * 2 + 1] * proc_fft_buffer[i * 2 + 1])
|
||||
.sqrt();
|
||||
fft[i] = (fft[i * 2] * fft[i * 2] + fft[i * 2 + 1] * fft[i * 2 + 1]).sqrt();
|
||||
}
|
||||
}
|
||||
|
||||
/* do band stats */
|
||||
self.current_powers[0] = proc_fft_buffer[1..8].iter().sum::<f32>() / 8f32;
|
||||
self.current_powers[1] = proc_fft_buffer[9..86].iter().sum::<f32>() / 78f32;
|
||||
self.current_powers[2] = proc_fft_buffer[87..470].iter().sum::<f32>() / 384f32;
|
||||
self.stats.current_powers[0] = fft[1..8].iter().sum::<f32>() / 8f32;
|
||||
self.stats.current_powers[1] = fft[9..86].iter().sum::<f32>() / 78f32;
|
||||
self.stats.current_powers[2] = fft[87..470].iter().sum::<f32>() / 384f32;
|
||||
|
||||
for it in self.current_powers.iter().zip(self.avg_powers.iter_mut()) {
|
||||
for it in self
|
||||
.stats
|
||||
.current_powers
|
||||
.iter()
|
||||
.zip(self.stats.avg_powers.iter_mut())
|
||||
{
|
||||
let (current, avg) = it;
|
||||
*avg = falloff_f(*avg, *current);
|
||||
}
|
||||
|
||||
let last_fft_buf = self.next_fft_buf;
|
||||
self.next_fft_buf = (self.next_fft_buf + 1) % AUDIO_BUFFERS;
|
||||
last_fft_buf
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn process_audio(
|
||||
mut input: Receiver<'static, NoopRawMutex, AudioBuffer>,
|
||||
mut output: Sender<'static, NoopRawMutex, (DspBuffer, AudioStats)>,
|
||||
) {
|
||||
let mut processor = AudioProcessor::default();
|
||||
|
||||
loop {
|
||||
let in_buf = input.receive().await;
|
||||
let (out_buf, stats) = output.send().await;
|
||||
|
||||
processor.process(in_buf, out_buf);
|
||||
*stats = processor.stats;
|
||||
|
||||
output.send_done();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
use esp_idf_svc::hal::units::{MilliSeconds,FromValueType};
|
||||
use embassy_sync::zerocopy_channel::Sender;
|
||||
use esp_idf_svc::hal::units::{FromValueType, MilliSeconds};
|
||||
|
||||
use crate::audio::AudioProcessor;
|
||||
use crate::effects::led_effect::{LedColors, LedEffect, Rgbv};
|
||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver};
|
||||
use embassy_time::{Duration, Ticker};
|
||||
|
||||
use crate::audio::{AudioStats, DspBuffer};
|
||||
use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv};
|
||||
use crate::helpers::random_at_most;
|
||||
use crate::LED_COUNT;
|
||||
|
||||
pub struct LedEffectBassSparks {
|
||||
bass_color: Rgbv,
|
||||
}
|
||||
impl LedEffectBassSparks {
|
||||
pub fn new() -> Self {
|
||||
impl Default for LedEffectBassSparks {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bass_color: Rgbv::black(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl LedEffect for LedEffectBassSparks {
|
||||
fn render(&mut self, processed: &AudioProcessor, _fft_buf: usize, leds: &mut LedColors) -> MilliSeconds {
|
||||
if processed.floating_max > 10100000
|
||||
&& (processed.current_powers[0] > 1.25 * processed.avg_powers[0])
|
||||
{
|
||||
fn render(&mut self, _: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> MilliSeconds {
|
||||
if stats.floating_max > 10100000 && (stats.current_powers[0] > 1.25 * stats.avg_powers[0]) {
|
||||
self.bass_color = Rgbv::new(127, 0, 255, 4)
|
||||
}
|
||||
|
||||
|
@ -27,9 +29,9 @@ impl LedEffect for LedEffectBassSparks {
|
|||
|
||||
self.bass_color.decrease(3, 5, 5, 0);
|
||||
|
||||
if processed.floating_max > 10100000
|
||||
&& (processed.current_powers[1] > 1.35 * processed.avg_powers[1])
|
||||
&& (processed.current_powers[2] > 1.35 * processed.avg_powers[2])
|
||||
if stats.floating_max > 10100000
|
||||
&& (stats.current_powers[1] > 1.35 * stats.avg_powers[1])
|
||||
&& (stats.current_powers[2] > 1.35 * stats.avg_powers[2])
|
||||
{
|
||||
for _ in 0..10 {
|
||||
let led_index = random_at_most(LED_COUNT as u32 - 1) as usize;
|
||||
|
@ -39,4 +41,19 @@ impl LedEffect for LedEffectBassSparks {
|
|||
|
||||
10.ms()
|
||||
}
|
||||
|
||||
async fn process_led_effect(
|
||||
&mut self,
|
||||
mut input: Receiver<'static, NoopRawMutex, (DspBuffer, AudioStats)>,
|
||||
mut output: Sender<'static, NoopRawMutex, LedData>,
|
||||
) {
|
||||
let mut ticker = Ticker::every(Duration::from_hz(20));
|
||||
loop {
|
||||
let (fft, stats) = input.receive().await;
|
||||
let leds = output.send().await;
|
||||
self.render(fft, stats, &mut leds.leds);
|
||||
output.send_done();
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
use anyhow::{bail, Result};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use embassy_sync::{
|
||||
blocking_mutex::raw::NoopRawMutex,
|
||||
zerocopy_channel::{Receiver, Sender},
|
||||
};
|
||||
use esp_idf_svc::hal::units::MilliSeconds;
|
||||
|
||||
use crate::{config::LED_COUNT, AudioProcessor};
|
||||
use crate::{audio::DspBuffer, config::LED_COUNT, AudioStats};
|
||||
|
||||
pub trait LedEffect {
|
||||
fn render(&mut self, processed: &AudioProcessor, fft_buf: usize, leds: &mut LedColors) -> MilliSeconds;
|
||||
fn render(&mut self, fft: &DspBuffer, stats: &AudioStats, leds: &mut LedColors)
|
||||
-> MilliSeconds;
|
||||
|
||||
async fn process_led_effect(
|
||||
&mut self,
|
||||
input: Receiver<'static, NoopRawMutex, (DspBuffer, AudioStats)>,
|
||||
output: Sender<'static, NoopRawMutex, LedData>,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Pod, Zeroable)]
|
||||
|
@ -112,8 +123,8 @@ pub struct LedData {
|
|||
ones: u32,
|
||||
}
|
||||
|
||||
impl LedData {
|
||||
pub fn new() -> Self {
|
||||
impl Default for LedData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
zeros: 0,
|
||||
leds: [Rgbv::new(0, 0, 0, 0); LED_COUNT],
|
||||
|
|
115
src/main.rs
115
src/main.rs
|
@ -3,27 +3,31 @@ pub mod config;
|
|||
pub mod effects;
|
||||
pub mod helpers;
|
||||
|
||||
use audio::{AudioBuffer, AudioProcessor};
|
||||
use bytemuck::{bytes_of, bytes_of_mut};
|
||||
use esp_idf_svc::{
|
||||
hal::{
|
||||
delay::FreeRtos,
|
||||
gpio::AnyIOPin,
|
||||
i2s,
|
||||
peripherals::Peripherals,
|
||||
spi,
|
||||
units::{FromValueType, MilliSeconds},
|
||||
},
|
||||
hal::{gpio::AnyIOPin, i2s, peripherals::Peripherals, spi, units::FromValueType},
|
||||
sys::{esp_dsp, esp_nofail, TickType_t},
|
||||
};
|
||||
|
||||
use bytemuck::{bytes_of, bytes_of_mut};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::{
|
||||
blocking_mutex::raw::NoopRawMutex,
|
||||
zerocopy_channel::{Channel, Receiver, Sender},
|
||||
};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use audio::{process_audio, AudioBuffer, AudioStats, DspBuffer};
|
||||
use config::{AUDIO_BUFFERS, AUDIO_SAMPLES_PER_BUF, LED_COUNT};
|
||||
use effects::{
|
||||
bass_sparks::LedEffectBassSparks,
|
||||
led_effect::{LedData, LedEffect},
|
||||
};
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
#[embassy_executor::task]
|
||||
async fn output_leds() {}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
// It is necessary to call this function once. Otherwise some patches to the runtime
|
||||
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
||||
esp_idf_svc::sys::link_patches();
|
||||
|
@ -31,15 +35,36 @@ fn main() -> anyhow::Result<()> {
|
|||
// Bind the log crate to the ESP Logging facilities
|
||||
esp_idf_svc::log::EspLogger::initialize_default();
|
||||
|
||||
// static PERIPHERALS: StaticCell<Peripherals> = StaticCell::new();
|
||||
// let peripherals = PERIPHERALS.init(Peripherals::take().unwrap());
|
||||
let peripherals = Peripherals::take().unwrap();
|
||||
|
||||
// mic buffers
|
||||
static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; AUDIO_BUFFERS]> = StaticCell::new();
|
||||
let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS]);
|
||||
|
||||
static AUDIO_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, AudioBuffer>> =
|
||||
StaticCell::new();
|
||||
let audio_channel = AUDIO_CHANNEL_CELL.init(Channel::new(audio_buffer));
|
||||
let (mut audio_sender, audio_receiver) = audio_channel.split();
|
||||
|
||||
// fft buffers
|
||||
static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); AUDIO_BUFFERS]> =
|
||||
StaticCell::new();
|
||||
let fft_buffer = FFT_BUFFER_CELL
|
||||
.init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); AUDIO_BUFFERS]);
|
||||
|
||||
static FFT_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, (DspBuffer, AudioStats)>> =
|
||||
StaticCell::new();
|
||||
let fft_channel = FFT_CHANNEL_CELL.init(Channel::new(fft_buffer));
|
||||
let (fft_sender, fft_receiver) = fft_channel.split();
|
||||
|
||||
// leds
|
||||
let mut leds = LedData::new();
|
||||
|
||||
// audio buffers
|
||||
let mut audio: [AudioBuffer; AUDIO_BUFFERS] = [[0; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS];
|
||||
let mut next_audio_buf: usize = 0;
|
||||
|
||||
static LED_DATA: StaticCell<[LedData; 1]> = StaticCell::new();
|
||||
let leds = LED_DATA.init([LedData::default(); 1]);
|
||||
static LED_CHANNEL: StaticCell<Channel<'_, NoopRawMutex, LedData>> = StaticCell::new();
|
||||
let led_channel = LED_CHANNEL.init(Channel::new(leds));
|
||||
let (led_sender, mut led_receiver) = led_channel.split();
|
||||
|
||||
// i2s config
|
||||
let mic_i2s_per = peripherals.i2s0;
|
||||
|
@ -79,7 +104,8 @@ fn main() -> anyhow::Result<()> {
|
|||
mic_i2s_sd,
|
||||
AnyIOPin::none(),
|
||||
mic_i2s_ws,
|
||||
)?;
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// spi config
|
||||
let led_spi_per = peripherals.spi2;
|
||||
|
@ -96,7 +122,8 @@ fn main() -> anyhow::Result<()> {
|
|||
&spi::config::Config::new()
|
||||
.baudrate(1.MHz().into())
|
||||
.data_mode(spi::config::MODE_3),
|
||||
)?;
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
esp_nofail!(esp_dsp::dsps_fft2r_init_fc32(
|
||||
|
@ -109,28 +136,56 @@ fn main() -> anyhow::Result<()> {
|
|||
));
|
||||
}
|
||||
|
||||
let mut processor = AudioProcessor::new();
|
||||
let mut effect = LedEffectBassSparks::new();
|
||||
spawner
|
||||
.spawn(process_audio(audio_receiver, fft_sender))
|
||||
.expect("spawn failed");
|
||||
|
||||
mic_drv.rx_enable()?;
|
||||
#[embassy_executor::task]
|
||||
async fn effect_task(
|
||||
mut led_effect: LedEffectBassSparks,
|
||||
input: Receiver<'static, NoopRawMutex, (DspBuffer, AudioStats)>,
|
||||
output: Sender<'static, NoopRawMutex, LedData>,
|
||||
) {
|
||||
led_effect.process_led_effect(input, output).await;
|
||||
}
|
||||
|
||||
spawner
|
||||
.spawn(effect_task(
|
||||
LedEffectBassSparks::default(),
|
||||
fft_receiver,
|
||||
led_sender,
|
||||
))
|
||||
.expect("spawn failed");
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn output_leds() {}
|
||||
|
||||
mic_drv.rx_enable().expect("rx not enabled");
|
||||
loop {
|
||||
let buffer = bytes_of_mut(&mut audio[next_audio_buf]);
|
||||
let num_bytes_read = mic_drv.read(buffer, TickType_t::MAX)?;
|
||||
let audio_in_buf: &mut AudioBuffer = audio_sender.send().await;
|
||||
|
||||
let buffer = bytes_of_mut(audio_in_buf);
|
||||
let num_bytes_read = mic_drv.read(buffer, TickType_t::MAX).unwrap();
|
||||
|
||||
if num_bytes_read != AUDIO_SAMPLES_PER_BUF * 4 {
|
||||
log::error!("buffer underflow");
|
||||
}
|
||||
audio_sender.send_done();
|
||||
|
||||
let current_fft_buf = processor.process(&audio[next_audio_buf]);
|
||||
let led_data = led_receiver.receive().await;
|
||||
|
||||
let delay_ms: MilliSeconds = effect.render(&processor, current_fft_buf, &mut leds.leds);
|
||||
let output_buffer = bytes_of(&led_data.leds);
|
||||
led_drv.write(output_buffer).expect("spi write failed");
|
||||
|
||||
let output_buffer = bytes_of(&leds);
|
||||
led_drv.write(output_buffer)?;
|
||||
// let current_fft_buf = processor.process(&audio[next_audio_buf]);
|
||||
|
||||
next_audio_buf = (next_audio_buf + 1) % AUDIO_BUFFERS;
|
||||
// let delay_ms: MilliSeconds = effect.render(&processor, current_fft_buf, &mut leds.leds);
|
||||
|
||||
FreeRtos::delay_ms(delay_ms.into());
|
||||
// let output_buffer = bytes_of(&leds);
|
||||
// led_drv.write(output_buffer)?;
|
||||
|
||||
// next_audio_buf = (next_audio_buf + 1) % AUDIO_BUFFERS;
|
||||
|
||||
// FreeRtos::delay_ms(delay_ms.into());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue