From 50341507cd6b9a30f9a81945de7d8391a19a2ab1 Mon Sep 17 00:00:00 2001 From: sawyer bristol Date: Wed, 19 Nov 2025 11:02:44 -0700 Subject: [PATCH] allow userapps to change sample rate of audio --- kernel/src/audio.rs | 42 ++++++++++++++++++++++++++++++++++++------ kernel/src/elf.rs | 3 +++ kernel/src/syscalls.rs | 11 ++++++++--- userlib_sys/src/lib.rs | 18 +++++++++++++++--- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/kernel/src/audio.rs b/kernel/src/audio.rs index 7742b1c..088c272 100644 --- a/kernel/src/audio.rs +++ b/kernel/src/audio.rs @@ -1,5 +1,5 @@ use crate::Audio; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use embassy_futures::{join::join, yield_now}; use embassy_rp::{ Peri, @@ -23,9 +23,12 @@ const SILENCE: u8 = u8::MAX / 2; pub static mut AUDIO_BUFFER: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2]; static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2]; +// atomics for user applications to signal changes to audio buffers pub static AUDIO_BUFFER_READY: AtomicBool = AtomicBool::new(true); pub static AUDIO_BUFFER_WRITTEN: AtomicBool = AtomicBool::new(false); +pub static AUDIO_BUFFER_SAMPLE_RATE: AtomicU32 = AtomicU32::new(SAMPLE_RATE_HZ); +/// resets audio buffers after user applications are unloaded pub fn clear_audio_buffers() { unsafe { AUDIO_BUFFER.fill(SILENCE); @@ -41,8 +44,29 @@ pub async fn audio_handler(mut audio: Audio) { let mut pwm_pio_right = PioPwmAudio::new(audio.dma1, &mut audio.pio, audio.sm1, audio.right, &prg); + // enable sms at the same time to ensure they are synced + audio.pio.apply_sm_batch(|pio| { + pio.set_enable(&mut pwm_pio_right.sm, true); + pio.set_enable(&mut pwm_pio_left.sm, true); + }); + + let mut sample_rate = SAMPLE_RATE_HZ; + loop { unsafe { + let new_sample_rate = AUDIO_BUFFER_SAMPLE_RATE.load(Ordering::Acquire); + if new_sample_rate != sample_rate { + sample_rate = new_sample_rate; + pwm_pio_left.reconfigure(sample_rate); + pwm_pio_right.reconfigure(sample_rate); + + // restart sms at the same time to ensure they are synced + audio.pio.apply_sm_batch(|pio| { + pio.restart(&mut pwm_pio_right.sm); + pio.restart(&mut pwm_pio_left.sm); + }); + } + if AUDIO_BUFFER_WRITTEN.load(Ordering::Acquire) { write_samples(&mut pwm_pio_left, &mut pwm_pio_right, &AUDIO_BUFFER_1).await; AUDIO_BUFFER_1.fill(SILENCE); @@ -130,6 +154,11 @@ struct PioPwmAudio<'d, PIO: Instance, const SM: usize> { } impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> { + fn get_sm_divider(sample_rate: u32) -> u32 { + let target_clock = (u8::MAX as u32 + 1) * sample_rate; + clk_sys_freq() / target_clock + } + fn new( dma: Peri<'d, impl Channel>, pio: &mut Common<'d, PIO>, @@ -151,17 +180,18 @@ impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> { cfg.use_program(&prg.0, &[&pin]); sm.set_config(&cfg); - let target_clock = (u8::MAX as u32 + 1) * SAMPLE_RATE_HZ; - let divider = (clk_sys_freq() / target_clock).to_fixed(); - sm.set_clock_divider(divider); - - sm.set_enable(true); + sm.set_clock_divider(Self::get_sm_divider(SAMPLE_RATE_HZ).to_fixed()); Self { dma: dma.into(), sm, } } + + fn reconfigure(&mut self, sample_rate: u32) { + self.sm + .set_clock_divider(Self::get_sm_divider(sample_rate).to_fixed()); + } } /// packs two u8 samples into 32bit word diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs index 0ab37d2..680396a 100644 --- a/kernel/src/elf.rs +++ b/kernel/src/elf.rs @@ -204,6 +204,9 @@ fn patch_syscalls( SyscallTable::ReadFile => syscalls::read_file as usize, SyscallTable::WriteFile => syscalls::write_file as usize, SyscallTable::FileLen => syscalls::file_len as usize, + SyscallTable::ReconfigureAudioSampleRate => { + syscalls::reconfigure_audio_sample_rate as usize + } SyscallTable::AudioBufferReady => syscalls::audio_buffer_ready as usize, SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize, }; diff --git a/kernel/src/syscalls.rs b/kernel/src/syscalls.rs index 97b8397..357d7e6 100644 --- a/kernel/src/syscalls.rs +++ b/kernel/src/syscalls.rs @@ -7,8 +7,8 @@ use embedded_sdmmc::LfnBuffer; use heapless::spsc::Queue; use userlib_sys::{ AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen, - GenRand, GetMs, ListDir, Print, ReadFile, RngRequest, SendAudioBuffer, SleepMs, WriteFile, - keyboard::*, + GenRand, GetMs, ListDir, Print, ReadFile, ReconfigureAudioSampleRate, RngRequest, + SendAudioBuffer, SleepMs, WriteFile, keyboard::*, }; #[cfg(feature = "psram")] @@ -18,7 +18,7 @@ use crate::heap::HEAP; use core::alloc::GlobalAlloc; use crate::{ - audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_WRITTEN}, + audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_SAMPLE_RATE, AUDIO_BUFFER_WRITTEN}, display::FRAMEBUFFER, framebuffer::FB_PAUSED, storage::{Dir, File, SDCARD}, @@ -333,6 +333,11 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { len as usize } +const _: ReconfigureAudioSampleRate = reconfigure_audio_sample_rate; +pub extern "C" fn reconfigure_audio_sample_rate(sample_rate: u32) { + AUDIO_BUFFER_SAMPLE_RATE.store(sample_rate, Ordering::Release); +} + const _: AudioBufferReady = audio_buffer_ready; pub extern "C" fn audio_buffer_ready() -> bool { AUDIO_BUFFER_READY.load(Ordering::Acquire) diff --git a/userlib_sys/src/lib.rs b/userlib_sys/src/lib.rs index fb9e82a..97ab22d 100644 --- a/userlib_sys/src/lib.rs +++ b/userlib_sys/src/lib.rs @@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter}; pub type EntryFn = fn(); -pub const SYS_CALL_TABLE_COUNT: usize = 14; +pub const SYS_CALL_TABLE_COUNT: usize = 15; const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT); #[derive(Clone, Copy, EnumIter, EnumCount)] @@ -30,8 +30,9 @@ pub enum SyscallTable { ReadFile = 9, WriteFile = 10, FileLen = 11, - AudioBufferReady = 12, - SendAudioBuffer = 13, + ReconfigureAudioSampleRate = 12, + AudioBufferReady = 13, + SendAudioBuffer = 14, } #[unsafe(no_mangle)] @@ -473,6 +474,17 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { } } +pub type ReconfigureAudioSampleRate = extern "C" fn(sample_rate: u32); + +#[allow(unused)] +pub fn reconfigure_audio_sample_rate(sample_rate: u32) { + unsafe { + let ptr = SYS_CALL_TABLE[SyscallTable::ReconfigureAudioSampleRate as usize]; + let f: ReconfigureAudioSampleRate = core::mem::transmute(ptr); + f(sample_rate) + } +} + pub type AudioBufferReady = extern "C" fn() -> bool; #[allow(unused)]