From afd3c89f6877ad367d37b7acc1ee86983c65995b Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Mon, 2 Jan 2023 17:43:21 +0100 Subject: [PATCH] Sort ndsp module in 2 files --- ctru-rs/src/services/ndsp/mod.rs | 181 ++++++++++++++++++++++++++++++ ctru-rs/src/services/ndsp/wave.rs | 144 ++++++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 ctru-rs/src/services/ndsp/mod.rs create mode 100644 ctru-rs/src/services/ndsp/wave.rs diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs new file mode 100644 index 0000000..01947f2 --- /dev/null +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -0,0 +1,181 @@ +pub mod wave; + +use crate::error::ResultCode; +use wave::WaveInfo; + +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum OutputMode { + Mono = ctru_sys::NDSP_OUTPUT_MONO, + Stereo = ctru_sys::NDSP_OUTPUT_STEREO, + Surround = ctru_sys::NDSP_OUTPUT_SURROUND, +} + +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum AudioFormat { + PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, + PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16, + ADPCMMono = ctru_sys::NDSP_FORMAT_MONO_ADPCM, + PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8, + PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, + FrontBypass = ctru_sys::NDSP_FRONT_BYPASS, + SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED, +} + +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum InterpolationType { + Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE, + Linear = ctru_sys::NDSP_INTERP_LINEAR, + None = ctru_sys::NDSP_INTERP_NONE, +} + +pub struct Channel { + id: i32, +} + +#[non_exhaustive] +pub struct Ndsp(()); + +impl Ndsp { + pub fn init() -> crate::Result { + ResultCode(unsafe { ctru_sys::ndspInit() })?; + + Ok(Self(())) + } + + /// Return the representation of the specified channel. + /// + /// # Errors + /// + /// An error will be returned if the channel id is not between 0 and 23. + pub fn channel(&self, id: u8) -> crate::Result { + if id > 23 { + return Err(crate::Error::InvalidChannel(id.into())); + } + + Ok(Channel { id: id.into() }) + } + + /// Set the audio output mode. Defaults to `OutputMode::Stereo`. + pub fn set_output_mode(&mut self, mode: OutputMode) { + unsafe { ctru_sys::ndspSetOutputMode(mode as u32) }; + } +} + +// All channel operations are thread-safe thanks to `libctru`'s use of thread locks. +// As such, there is no need to hold channels to ensure correct mutability. +// With this prospective in mind, this struct looks more like a dummy than an actually functional block. +impl Channel { + /// Reset the channel + pub fn reset(&self) { + unsafe { ctru_sys::ndspChnReset(self.id) }; + } + + /// Initialize the channel's parameters + pub fn init_parameters(&self) { + unsafe { ctru_sys::ndspChnInitParams(self.id) }; + } + + /// Returns whether the channel is playing any audio. + pub fn is_playing(&self) -> bool { + unsafe { ctru_sys::ndspChnIsPlaying(self.id) } + } + + /// Returns whether the channel's playback is currently paused. + pub fn is_paused(&self) -> bool { + unsafe { ctru_sys::ndspChnIsPaused(self.id) } + } + + /// Returns the channel's current sample's position. + pub fn get_sample_position(&self) -> u32 { + unsafe { ctru_sys::ndspChnGetSamplePos(self.id) } + } + + /// Returns the channel's current wave sequence's id. + pub fn get_wave_sequence_id(&self) -> u16 { + unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id) } + } + + /// Pause or un-pause the channel's playback. + pub fn set_paused(&self, state: bool) { + unsafe { ctru_sys::ndspChnSetPaused(self.id, state) }; + } + + /// Set the channel's output format. + /// Change this setting based on the used sample's format. + pub fn set_format(&self, format: AudioFormat) { + unsafe { ctru_sys::ndspChnSetFormat(self.id, format as u16) }; + } + + /// Set the channel's interpolation mode. + pub fn set_interpolation(&self, interp_type: InterpolationType) { + unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type as u32) }; + } + + /// Set the channel's volume mix. + /// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444 + pub fn set_mix(&self, mix: &[f32; 12]) { + unsafe { ctru_sys::ndspChnSetMix(self.id, mix.as_ptr().cast_mut()) } + } + + /// Set the channel's rate of sampling. + pub fn set_sample_rate(&self, rate: f32) { + unsafe { ctru_sys::ndspChnSetRate(self.id, rate) }; + } + + // TODO: find a way to wrap `ndspChnSetAdpcmCoefs` + + /// Clear the wave buffer queue and stop playback. + pub fn clear_queue(&self) { + unsafe { ctru_sys::ndspChnWaveBufClear(self.id) }; + } + + /// Add a wave buffer to the channel's queue. + /// Note: if there are no other buffers in queue, playback for this buffer will start. + /// + /// # Beware + /// + /// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback. + /// Furthermore, there are no checks to see if the used memory is still valid. All responsibility of handling this data is left to the user. + pub fn queue_wave(&self, buffer: &mut WaveInfo) { + unsafe { ctru_sys::ndspChnWaveBufAdd(self.id, &mut buffer.raw_data) }; + } + + // FILTERS + + // TODO: Add Mono filters (and maybe setup the filter functions in a better way) + + pub fn iir_biquad_set_enabled(&self, enable: bool) { + unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id, enable) }; + } + + pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) { + unsafe { ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id, cut_off_freq, quality) }; + } + + pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) { + unsafe { ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id, cut_off_freq, quality) }; + } + + pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) { + unsafe { ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id, notch_freq, quality) }; + } + + pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) { + unsafe { ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id, mid_freq, quality) }; + } + + pub fn iir_biquad_set_params_peaking_equalizer(&self, central_freq: f32, quality: f32, gain: f32) { + unsafe { ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer(self.id, central_freq, quality, gain) }; + } +} + +impl Drop for Ndsp { + fn drop(&mut self) { + unsafe { + ctru_sys::ndspExit(); + } + } +} diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs new file mode 100644 index 0000000..9c5c9ff --- /dev/null +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -0,0 +1,144 @@ +use super::AudioFormat; +use crate::error::ResultCode; +use crate::linear::LinearAllocator; + +/// Base struct to represent audio wave data. This requires audio format information. +#[derive(Debug, Clone)] +pub struct WaveBuffer { + /// Buffer data. This data must be allocated on the LINEAR memory. + data: Box<[u8], LinearAllocator>, + audio_format: AudioFormat, + nsamples: usize, // We don't use the slice's length here because depending on the format it may vary + // adpcm_data: AdpcmData, TODO: Requires research on how this format is handled. +} + +/// Informational struct holding the raw audio data and playaback info. This corresponds to [ctru_sys::ndspWaveBuf] +pub struct WaveInfo<'b> { + /// Data block of the audio wave (plus its format information). + buffer: &'b mut WaveBuffer, + // Holding the data with the raw format is necessary since `libctru` will access it. + pub(crate) raw_data: ctru_sys::ndspWaveBuf, +} + +#[derive(Copy, Clone, Debug)] +#[repr(u8)] +pub enum WaveStatus { + Free = ctru_sys::NDSP_WBUF_FREE as u8, + Queued = ctru_sys::NDSP_WBUF_QUEUED as u8, + Playing = ctru_sys::NDSP_WBUF_PLAYING as u8, + Done = ctru_sys::NDSP_WBUF_DONE as u8, +} + +impl AudioFormat { + /// Returns the amount of bytes needed to store one sample + /// Eg. + /// 8 bit formats return 1 (byte) + /// 16 bit formats return 2 (bytes) + pub fn sample_size(self) -> u8 { + match self { + AudioFormat::PCM16Mono | AudioFormat::PCM16Stereo => 2, + AudioFormat::SurroundPreprocessed => { + panic!("Can't find size for Sourround Preprocessed audio: format is under research") + } + _ => 1, + } + } +} + +impl WaveBuffer { + pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> crate::Result { + let nsamples: usize = data.len() / (audio_format.sample_size() as usize); + + unsafe { + ResultCode(ctru_sys::DSP_FlushDataCache( + data.as_ptr().cast(), + data.len().try_into().unwrap(), + ))?; + } + + Ok(WaveBuffer { + data, + audio_format, + nsamples, + }) + } + + pub fn get_mut_data(&mut self) -> &mut Box<[u8], LinearAllocator> { + &mut self.data + } + + pub fn get_format(&self) -> AudioFormat { + self.audio_format + } + + pub fn get_sample_amount(&self) -> usize { + self.nsamples + } +} + +impl<'b> WaveInfo<'b> { + pub fn new(buffer: &'b mut WaveBuffer, looping: bool) -> Self { + let address = ctru_sys::tag_ndspWaveBuf__bindgen_ty_1 { + data_vaddr: buffer.data.as_ptr().cast(), + }; + + let raw_data = ctru_sys::ndspWaveBuf { + __bindgen_anon_1: address, // Buffer data virtual address + nsamples: buffer.nsamples.try_into().unwrap(), + adpcm_data: std::ptr::null_mut(), + offset: 0, + looping, + // The ones after this point aren't supposed to be setup by the user + status: 0, + sequence_id: 0, + next: std::ptr::null_mut(), + }; + + Self { buffer, raw_data } + } + + pub fn get_mut_wavebuffer(&'b mut self) -> &'b mut WaveBuffer { + &mut self.buffer + } + + pub fn get_status(&self) -> WaveStatus { + self.raw_data.status.try_into().unwrap() + } +} + +impl TryFrom for WaveStatus { + type Error = String; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Free), + 1 => Ok(Self::Queued), + 2 => Ok(Self::Playing), + 3 => Ok(Self::Done), + _ => Err(String::from("Invalid WaveInfo Status code")), + } + } +} + +impl Drop for WaveBuffer { + fn drop(&mut self) { + unsafe { + // Result can't be used in any way, let's just shrug it off + let _r = ctru_sys::DSP_InvalidateDataCache( + self.data.as_ptr().cast(), + self.data.len().try_into().unwrap(), + ); + } + } +} + +impl<'b> Drop for WaveInfo<'b> { + fn drop(&mut self) { + // This was the only way I found I could check for improper drops of WaveInfos. + // It should be enough to warn users of the danger. + match self.get_status() { + WaveStatus::Free | WaveStatus::Done => (), + _ => panic!("WaveInfo struct has been dropped before finishing use. This could cause Undefined Behaviour."), + } + } +}