Browse Source

Bind Channels to Ndsp lifetime and use exclusive mutability

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
143a27b0ed
  1. 94
      ctru-rs/src/services/ndsp/mod.rs

94
ctru-rs/src/services/ndsp/mod.rs

@ -4,6 +4,7 @@ use wave::{WaveInfo, WaveStatus};
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut};
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::sync::Mutex; use std::sync::Mutex;
@ -37,16 +38,21 @@ pub enum InterpolationType {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum NdspError { pub enum NdspError {
InvalidChannel(u8), // channel id InvalidChannel(u8), // channel id
WaveAlreadyQueued(u8), // channel id ChannelAlreadyInUse(u8), // channel id
WaveAlreadyQueued(u8), // channel id
} }
pub struct Channel(u8); pub struct Channel<'ndsp> {
id: u8,
_rf: RefMut<'ndsp, ()>, // we don't need to hold any data
}
static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0); static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0);
pub struct Ndsp { pub struct Ndsp {
_service_handler: ServiceReference, _service_handler: ServiceReference,
channel_flags: [RefCell<()>; 24],
} }
impl Ndsp { impl Ndsp {
@ -64,7 +70,10 @@ impl Ndsp {
}, },
)?; )?;
Ok(Self { _service_handler }) Ok(Self {
_service_handler,
channel_flags: Default::default(),
})
} }
/// Return the representation of the specified channel. /// Return the representation of the specified channel.
@ -73,10 +82,17 @@ impl Ndsp {
/// ///
/// An error will be returned if the channel id is not between 0 and 23. /// An error will be returned if the channel id is not between 0 and 23.
pub fn channel(&self, id: u8) -> std::result::Result<Channel, NdspError> { pub fn channel(&self, id: u8) -> std::result::Result<Channel, NdspError> {
if id >= NUMBER_OF_CHANNELS { let in_bounds = self.channel_flags.get(id as usize);
Err(NdspError::InvalidChannel(id))
} else { match in_bounds {
Ok(Channel(id)) Some(ref_cell) => {
let flag = ref_cell.try_borrow_mut();
match flag {
Ok(_rf) => Ok(Channel { id, _rf }),
Err(_) => Err(NdspError::ChannelAlreadyInUse(id)),
}
}
None => Err(NdspError::InvalidChannel(id)),
} }
} }
@ -86,70 +102,67 @@ impl Ndsp {
} }
} }
// All channel operations are thread-safe thanks to `libctru`'s use of thread locks. impl Channel<'_> {
// As such, there is no need to hold channels to ensure correct mutability.
// As such, this struct is more of a dummy than an actually functional block.
impl Channel {
/// Reset the channel /// Reset the channel
pub fn reset(&self) { pub fn reset(&self) {
unsafe { ctru_sys::ndspChnReset(self.0.into()) }; unsafe { ctru_sys::ndspChnReset(self.id.into()) };
} }
/// Initialize the channel's parameters /// Initialize the channel's parameters
pub fn init_parameters(&self) { pub fn init_parameters(&self) {
unsafe { ctru_sys::ndspChnInitParams(self.0.into()) }; unsafe { ctru_sys::ndspChnInitParams(self.id.into()) };
} }
/// Returns whether the channel is playing any audio. /// Returns whether the channel is playing any audio.
pub fn is_playing(&self) -> bool { pub fn is_playing(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPlaying(self.0.into()) } unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) }
} }
/// Returns whether the channel's playback is currently paused. /// Returns whether the channel's playback is currently paused.
pub fn is_paused(&self) -> bool { pub fn is_paused(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPaused(self.0.into()) } unsafe { ctru_sys::ndspChnIsPaused(self.id.into()) }
} }
// Returns the channel's id // Returns the channel's id
pub fn get_id(&self) -> u8 { pub fn get_id(&self) -> u8 {
self.0 self.id
} }
/// Returns the channel's current sample's position. /// Returns the channel's current sample's position.
pub fn get_sample_position(&self) -> u32 { pub fn get_sample_position(&self) -> u32 {
unsafe { ctru_sys::ndspChnGetSamplePos(self.0.into()) } unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }
} }
/// Returns the channel's current wave sequence's id. /// Returns the channel's current wave sequence's id.
pub fn get_wave_sequence_id(&self) -> u16 { pub fn get_wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.0.into()) } unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
} }
/// Pause or un-pause the channel's playback. /// Pause or un-pause the channel's playback.
pub fn set_paused(&self, state: bool) { pub fn set_paused(&self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.0.into(), state) }; unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) };
} }
/// Set the channel's output format. /// Set the channel's output format.
/// Change this setting based on the used sample's format. /// Change this setting based on the used sample's format.
pub fn set_format(&self, format: AudioFormat) { pub fn set_format(&self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.0.into(), format as u16) }; unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format as u16) };
} }
/// Set the channel's interpolation mode. /// Set the channel's interpolation mode.
pub fn set_interpolation(&self, interp_type: InterpolationType) { pub fn set_interpolation(&self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.0.into(), interp_type as u32) }; unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type as u32) };
} }
/// Set the channel's volume mix. /// Set the channel's volume mix.
/// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444 /// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444
pub fn set_mix(&self, mix: &[f32; 12]) { pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.0.into(), mix.as_ptr().cast_mut()) } unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_ptr().cast_mut()) }
} }
/// Set the channel's rate of sampling. /// Set the channel's rate of sampling.
pub fn set_sample_rate(&self, rate: f32) { pub fn set_sample_rate(&self, rate: f32) {
unsafe { ctru_sys::ndspChnSetRate(self.0.into(), rate) }; unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) };
} }
// `ndspChnSetAdpcmCoefs` isn't wrapped on purpose. // `ndspChnSetAdpcmCoefs` isn't wrapped on purpose.
@ -158,7 +171,7 @@ impl Channel {
/// Clear the wave buffer queue and stop playback. /// Clear the wave buffer queue and stop playback.
pub fn clear_queue(&self) { pub fn clear_queue(&self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.0.into()) }; unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
} }
/// Add a wave buffer to the channel's queue. /// Add a wave buffer to the channel's queue.
@ -171,14 +184,14 @@ impl Channel {
pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> { pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> {
match wave.get_status() { match wave.get_status() {
WaveStatus::Playing | WaveStatus::Queued => { WaveStatus::Playing | WaveStatus::Queued => {
return Err(NdspError::WaveAlreadyQueued(self.0)) return Err(NdspError::WaveAlreadyQueued(self.id))
} }
_ => (), _ => (),
} }
wave.set_channel(self.0); wave.set_channel(self.id);
unsafe { ctru_sys::ndspChnWaveBufAdd(self.0.into(), &mut wave.raw_data) }; unsafe { ctru_sys::ndspChnWaveBufAdd(self.id.into(), &mut wave.raw_data) };
Ok(()) Ok(())
} }
@ -186,42 +199,42 @@ impl Channel {
// FILTERS // FILTERS
pub fn iir_mono_set_enabled(&self, enable: bool) { pub fn iir_mono_set_enabled(&self, enable: bool) {
unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.0.into(), enable) }; unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) };
} }
pub fn iir_mono_set_params_high_pass_filter(&self, cut_off_freq: f32) { pub fn iir_mono_set_params_high_pass_filter(&self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.0.into(), cut_off_freq) }; unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) };
} }
pub fn iir_mono_set_params_low_pass_filter(&self, cut_off_freq: f32) { pub fn iir_mono_set_params_low_pass_filter(&self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.0.into(), cut_off_freq) }; unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
} }
pub fn iir_biquad_set_enabled(&self, enable: bool) { pub fn iir_biquad_set_enabled(&self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.0.into(), enable) }; unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) };
} }
pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) { pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.0.into(), cut_off_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
}; };
} }
pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) { pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.0.into(), cut_off_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
}; };
} }
pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) { pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.0.into(), notch_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
}; };
} }
pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) { pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.0.into(), mid_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
}; };
} }
@ -233,7 +246,7 @@ impl Channel {
) { ) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer( ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer(
self.0.into(), self.id.into(),
central_freq, central_freq,
quality, quality,
gain, gain,
@ -260,6 +273,7 @@ impl fmt::Display for NdspError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::InvalidChannel(id) => write!(f, "Audio Channel with id {id} doesn't exist. Valid channels have an id between 0 and 23."), Self::InvalidChannel(id) => write!(f, "Audio Channel with id {id} doesn't exist. Valid channels have an id between 0 and 23."),
Self::ChannelAlreadyInUse(id) => write!(f, "Audio Channel with id {id} is already being used. Drop the other instance if you want to use it here."),
Self::WaveAlreadyQueued(id) => write!(f, "The selected WaveInfo is already playing on channel {id}.") Self::WaveAlreadyQueued(id) => write!(f, "The selected WaveInfo is already playing on channel {id}.")
} }
} }
@ -271,6 +285,12 @@ impl error::Error for NdspError {
} }
} }
impl<'ndsp> Drop for Channel<'ndsp> {
fn drop(&mut self) {
self.reset();
}
}
impl Drop for Ndsp { impl Drop for Ndsp {
fn drop(&mut self) { fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS { for i in 0..NUMBER_OF_CHANNELS {

Loading…
Cancel
Save