mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 07:45:28 +00:00
allow userapps to change sample rate of audio
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
use crate::Audio;
|
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_futures::{join::join, yield_now};
|
||||||
use embassy_rp::{
|
use embassy_rp::{
|
||||||
Peri,
|
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];
|
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];
|
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_READY: AtomicBool = AtomicBool::new(true);
|
||||||
pub static AUDIO_BUFFER_WRITTEN: AtomicBool = AtomicBool::new(false);
|
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() {
|
pub fn clear_audio_buffers() {
|
||||||
unsafe {
|
unsafe {
|
||||||
AUDIO_BUFFER.fill(SILENCE);
|
AUDIO_BUFFER.fill(SILENCE);
|
||||||
@@ -41,8 +44,29 @@ pub async fn audio_handler(mut audio: Audio) {
|
|||||||
let mut pwm_pio_right =
|
let mut pwm_pio_right =
|
||||||
PioPwmAudio::new(audio.dma1, &mut audio.pio, audio.sm1, audio.right, &prg);
|
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 {
|
loop {
|
||||||
unsafe {
|
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) {
|
if AUDIO_BUFFER_WRITTEN.load(Ordering::Acquire) {
|
||||||
write_samples(&mut pwm_pio_left, &mut pwm_pio_right, &AUDIO_BUFFER_1).await;
|
write_samples(&mut pwm_pio_left, &mut pwm_pio_right, &AUDIO_BUFFER_1).await;
|
||||||
AUDIO_BUFFER_1.fill(SILENCE);
|
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> {
|
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(
|
fn new(
|
||||||
dma: Peri<'d, impl Channel>,
|
dma: Peri<'d, impl Channel>,
|
||||||
pio: &mut Common<'d, PIO>,
|
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]);
|
cfg.use_program(&prg.0, &[&pin]);
|
||||||
sm.set_config(&cfg);
|
sm.set_config(&cfg);
|
||||||
|
|
||||||
let target_clock = (u8::MAX as u32 + 1) * SAMPLE_RATE_HZ;
|
sm.set_clock_divider(Self::get_sm_divider(SAMPLE_RATE_HZ).to_fixed());
|
||||||
let divider = (clk_sys_freq() / target_clock).to_fixed();
|
|
||||||
sm.set_clock_divider(divider);
|
|
||||||
|
|
||||||
sm.set_enable(true);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
dma: dma.into(),
|
dma: dma.into(),
|
||||||
sm,
|
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
|
/// packs two u8 samples into 32bit word
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ fn patch_syscalls(
|
|||||||
SyscallTable::ReadFile => syscalls::read_file as usize,
|
SyscallTable::ReadFile => syscalls::read_file as usize,
|
||||||
SyscallTable::WriteFile => syscalls::write_file as usize,
|
SyscallTable::WriteFile => syscalls::write_file as usize,
|
||||||
SyscallTable::FileLen => syscalls::file_len 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::AudioBufferReady => syscalls::audio_buffer_ready as usize,
|
||||||
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
|
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use embedded_sdmmc::LfnBuffer;
|
|||||||
use heapless::spsc::Queue;
|
use heapless::spsc::Queue;
|
||||||
use userlib_sys::{
|
use userlib_sys::{
|
||||||
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen,
|
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen,
|
||||||
GenRand, GetMs, ListDir, Print, ReadFile, RngRequest, SendAudioBuffer, SleepMs, WriteFile,
|
GenRand, GetMs, ListDir, Print, ReadFile, ReconfigureAudioSampleRate, RngRequest,
|
||||||
keyboard::*,
|
SendAudioBuffer, SleepMs, WriteFile, keyboard::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "psram")]
|
#[cfg(feature = "psram")]
|
||||||
@@ -18,7 +18,7 @@ use crate::heap::HEAP;
|
|||||||
use core::alloc::GlobalAlloc;
|
use core::alloc::GlobalAlloc;
|
||||||
|
|
||||||
use crate::{
|
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,
|
display::FRAMEBUFFER,
|
||||||
framebuffer::FB_PAUSED,
|
framebuffer::FB_PAUSED,
|
||||||
storage::{Dir, File, SDCARD},
|
storage::{Dir, File, SDCARD},
|
||||||
@@ -333,6 +333,11 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
|
|||||||
len as 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;
|
const _: AudioBufferReady = audio_buffer_ready;
|
||||||
pub extern "C" fn audio_buffer_ready() -> bool {
|
pub extern "C" fn audio_buffer_ready() -> bool {
|
||||||
AUDIO_BUFFER_READY.load(Ordering::Acquire)
|
AUDIO_BUFFER_READY.load(Ordering::Acquire)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter};
|
|||||||
|
|
||||||
pub type EntryFn = fn();
|
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);
|
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT);
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
||||||
@@ -30,8 +30,9 @@ pub enum SyscallTable {
|
|||||||
ReadFile = 9,
|
ReadFile = 9,
|
||||||
WriteFile = 10,
|
WriteFile = 10,
|
||||||
FileLen = 11,
|
FileLen = 11,
|
||||||
AudioBufferReady = 12,
|
ReconfigureAudioSampleRate = 12,
|
||||||
SendAudioBuffer = 13,
|
AudioBufferReady = 13,
|
||||||
|
SendAudioBuffer = 14,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[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;
|
pub type AudioBufferReady = extern "C" fn() -> bool;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|||||||
Reference in New Issue
Block a user