diff --git a/ctru-rs/examples/audio-filters.rs b/ctru-rs/examples/audio-filters.rs index 616b8d4..48b65a7 100644 --- a/ctru-rs/examples/audio-filters.rs +++ b/ctru-rs/examples/audio-filters.rs @@ -5,13 +5,13 @@ use std::f32::consts::PI; use ctru::linear::LinearAllocator; use ctru::prelude::*; use ctru::services::ndsp::{ - wave::{WaveInfo, WaveStatus}, - AudioFormat, InterpolationType, Ndsp, OutputMode, + wave::{Wave, WaveStatus}, + AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode, }; const SAMPLE_RATE: usize = 22050; const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 -const BYTES_PER_SAMPLE: usize = 4; +const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size(); const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE; // Note Frequencies @@ -65,8 +65,8 @@ fn main() { let audio_data2 = audio_data1.clone(); - let mut wave_info1 = WaveInfo::new(audio_data1, AudioFormat::PCM16Stereo, false); - let mut wave_info2 = WaveInfo::new(audio_data2, AudioFormat::PCM16Stereo, false); + let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false); + let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false); let mut ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller"); @@ -79,10 +79,7 @@ fn main() { channel_zero.set_format(AudioFormat::PCM16Stereo); // Output at 100% on the first pair of left and right channels. - - let mut mix: [f32; 12] = [0f32; 12]; - mix[0] = 1.0; - mix[1] = 1.0; + let mix = AudioMix::default(); channel_zero.set_mix(&mix); channel_zero.queue_wave(&mut wave_info1).unwrap(); @@ -142,13 +139,13 @@ fn main() { } } - let current: &mut WaveInfo = if altern { + let current: &mut Wave = if altern { &mut wave_info1 } else { &mut wave_info2 }; - let status = current.get_status(); + let status = current.status(); if let WaveStatus::Done = status { fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index ee366d4..cd45f7b 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -1,12 +1,13 @@ //! NDSP (Audio) service pub mod wave; -use wave::{WaveInfo, WaveStatus}; +use wave::{Wave, WaveStatus}; use crate::error::ResultCode; use crate::services::ServiceReference; use std::cell::{RefCell, RefMut}; +use std::default::Default; use std::error; use std::fmt; use std::sync::Mutex; @@ -30,6 +31,12 @@ pub enum AudioFormat { PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, } +/// Representation of volume mix for a channel. +#[derive(Copy, Clone, Debug)] +pub struct AudioMix { + raw: [f32; 12], +} + #[derive(Copy, Clone, Debug)] #[repr(u32)] pub enum InterpolationType { @@ -47,7 +54,7 @@ pub enum NdspError { /// Channel ID WaveBusy(u8), /// Sample amount requested, Max sample amount - SampleCountOutOfBounds(u32, u32), + SampleCountOutOfBounds(usize, usize), } pub struct Channel<'ndsp> { @@ -141,17 +148,19 @@ impl Channel<'_> { } // Returns the channel's id - pub fn get_id(&self) -> u8 { + pub fn id(&self) -> u8 { self.id } - /// Returns the channel's current sample's position. - pub fn get_sample_position(&self) -> u32 { - unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) } + /// Returns the index of the currently played sample. + /// + /// Because of how fast this value changes, it should only be used as a rough estimate of the current progress. + pub fn sample_position(&self) -> usize { + (unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize } /// Returns the channel's current wave sequence's id. - pub fn get_wave_sequence_id(&self) -> u16 { + pub fn wave_sequence_id(&self) -> u16 { unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) } } @@ -172,19 +181,8 @@ impl Channel<'_> { } /// Set the channel's volume mix. - /// - /// # Notes - /// - /// The buffer's format is read as: - /// - /// Index 0: Front left volume
- /// Index 1: Front right volume
- /// Index 2: Back left volume
- /// Index 3: Back right volume
- /// Index 4..7: Same as 0..3 but for auxiliary output 0
- /// Index 8..11: Same as 0..3 but for auxiliary output 1
- pub fn set_mix(&self, mix: &[f32; 12]) { - unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_ptr().cast_mut()) } + pub fn set_mix(&self, mix: &AudioMix) { + unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) } } /// Set the channel's rate of sampling. @@ -206,10 +204,10 @@ impl Channel<'_> { /// /// # Warning /// - /// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback. - /// To ensure safety, checks within [WaveInfo] will clear the whole channel queue if any queued [WaveInfo] is dropped prematurely. - pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> { - match wave.get_status() { + /// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback. + /// To ensure safety, checks within [Wave] will clear the whole channel queue if any queued [Wave] is dropped prematurely. + pub fn queue_wave(&self, wave: &mut Wave) -> std::result::Result<(), NdspError> { + match wave.status() { WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)), _ => (), } @@ -302,24 +300,146 @@ impl Channel<'_> { 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 { + /// 8 bit mono formats return 1 (byte) + /// 16 bit stereo (dual-channel) formats return 4 (bytes) + pub const fn size(self) -> usize { match self { - AudioFormat::PCM8Mono => 1, - AudioFormat::PCM16Mono | AudioFormat::PCM8Stereo => 2, - AudioFormat::PCM16Stereo => 4, + Self::PCM8Mono => 1, + Self::PCM16Mono | Self::PCM8Stereo => 2, + Self::PCM16Stereo => 4, } } } +impl AudioMix { + /// Creates a new [AudioMix] with all volumes set to 0. + pub fn zeroed() -> Self { + Self { raw: [0.; 12] } + } + + /// Returns a reference to the raw data. + pub fn as_raw(&self) -> &[f32; 12] { + &self.raw + } + + /// Returns a mutable reference to the raw data. + pub fn as_raw_mut(&mut self) -> &mut [f32; 12] { + &mut self.raw + } + + /// Returns the values set for the "front" volume mix (left and right channel). + pub fn front(&self) -> (f32, f32) { + (self.raw[0], self.raw[1]) + } + + /// Returns the values set for the "back" volume mix (left and right channel). + pub fn back(&self) -> (f32, f32) { + (self.raw[2], self.raw[3]) + } + + /// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + pub fn aux_front(&self, id: usize) -> (f32, f32) { + if id > 1 { + panic!("invalid auxiliary output device index") + } + + let index = 4 + id * 4; + + (self.raw[index], self.raw[index + 1]) + } + + /// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + pub fn aux_back(&self, id: usize) -> (f32, f32) { + if id > 1 { + panic!("invalid auxiliary output device index") + } + + let index = 6 + id * 4; + + (self.raw[index], self.raw[index + 1]) + } + + /// Sets the values for the "front" volume mix (left and right channel). + /// + /// # Notes + /// + /// [Channel] will normalize the mix values to be within 0 and 1. + /// However, an [AudioMix] instance with larger/smaller values is valid. + pub fn set_front(&mut self, left: f32, right: f32) { + self.raw[0] = left; + self.raw[1] = right; + } + + /// Sets the values for the "back" volume mix (left and right channel). + /// + /// # Notes + /// + /// [Channel] will normalize the mix values to be within 0 and 1. + /// However, an [AudioMix] instance with larger/smaller values is valid. + pub fn set_back(&mut self, left: f32, right: f32) { + self.raw[2] = left; + self.raw[3] = right; + } + + /// Sets the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + /// + /// # Notes + /// + /// [Channel] will normalize the mix values to be within 0 and 1. + /// However, an [AudioMix] instance with larger/smaller values is valid. + pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { + if id > 1 { + panic!("invalid auxiliary output device index") + } + + let index = 4 + id * 4; + + self.raw[index] = left; + self.raw[index + 1] = right; + } + + /// Sets the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + /// + /// # Notes + /// + /// [Channel] will normalize the mix values to be within 0 and 1. + /// However, an [AudioMix] instance with larger/smaller values is valid. + pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { + if id > 1 { + panic!("invalid auxiliary output device index") + } + + let index = 6 + id * 4; + + self.raw[index] = left; + self.raw[index + 1] = right; + } +} + +/// Returns an [AudioMix] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%. +impl Default for AudioMix { + fn default() -> Self { + let mut mix = AudioMix::zeroed(); + mix.set_front(1.0, 1.0); + + mix + } +} + +impl From<[f32; 12]> for AudioMix { + fn from(value: [f32; 12]) -> Self { + Self { raw: value } + } +} + impl fmt::Display for NdspError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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::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::WaveBusy(id) => write!(f, "the selected WaveInfo is busy playing on channel {id}"), + Self::WaveBusy(id) => write!(f, "the selected Wave is busy playing on channel {id}"), Self::SampleCountOutOfBounds(samples_requested, max_samples) => write!(f, "the sample count requested is too big (requested = {samples_requested}, maximum = {max_samples})"), } } @@ -327,16 +447,10 @@ impl fmt::Display for NdspError { impl error::Error for NdspError {} -impl<'ndsp> Drop for Channel<'ndsp> { - fn drop(&mut self) { - self.reset(); - } -} - impl Drop for Ndsp { fn drop(&mut self) { for i in 0..NUMBER_OF_CHANNELS { - self.channel(i).unwrap().clear_queue(); + self.channel(i).unwrap().reset(); } } } diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index 4a5de98..a47c113 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -1,8 +1,8 @@ use super::{AudioFormat, NdspError}; use crate::linear::LinearAllocator; -/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf] -pub struct WaveInfo { +/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf]. +pub struct Wave { /// Data block of the audio wave (and its format information). buffer: Box<[u8], LinearAllocator>, audio_format: AudioFormat, @@ -13,7 +13,7 @@ pub struct WaveInfo { #[derive(Copy, Clone, Debug)] #[repr(u8)] -/// Enum representing the playback status of a [WaveInfo]. +/// Enum representing the playback status of a [Wave]. pub enum WaveStatus { Free = ctru_sys::NDSP_WBUF_FREE as u8, Queued = ctru_sys::NDSP_WBUF_QUEUED as u8, @@ -21,14 +21,14 @@ pub enum WaveStatus { Done = ctru_sys::NDSP_WBUF_DONE as u8, } -impl WaveInfo { +impl Wave { /// Build a new playable wave object from a raw buffer on LINEAR memory and a some info. pub fn new( buffer: Box<[u8], LinearAllocator>, audio_format: AudioFormat, looping: bool, ) -> Self { - let sample_count: usize = buffer.len() / (audio_format.sample_size() as usize); + let sample_count = buffer.len() / audio_format.size(); // Signal to the DSP processor the buffer's RAM sector. // This step may seem delicate, but testing reports failure most of the time, while still having no repercussions on the resulting audio. @@ -69,10 +69,10 @@ impl WaveInfo { /// /// # Errors /// - /// This function will return an error if the [WaveInfo] is currently busy, + /// This function will return an error if the [Wave] is currently busy, /// with the id to the channel in which it's queued. pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { - match self.get_status() { + match self.status() { WaveStatus::Playing | WaveStatus::Queued => { Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) } @@ -81,7 +81,7 @@ impl WaveInfo { } /// Return this wave's playback status. - pub fn get_status(&self) -> WaveStatus { + pub fn status(&self) -> WaveStatus { self.raw_data.status.try_into().unwrap() } @@ -90,12 +90,12 @@ impl WaveInfo { /// # Notes /// /// This value varies depending on [Self::set_sample_count]. - pub fn get_sample_count(&self) -> u32 { - self.raw_data.nsamples + pub fn sample_count(&self) -> usize { + self.raw_data.nsamples as usize } /// Get the format of the audio data. - pub fn get_format(&self) -> AudioFormat { + pub fn format(&self) -> AudioFormat { self.audio_format } @@ -117,25 +117,22 @@ impl WaveInfo { /// # Errors /// /// This function will return an error if the sample size exceeds the buffer's capacity - /// or if the WaveInfo is currently queued. - pub fn set_sample_count(&mut self, sample_count: u32) -> Result<(), NdspError> { - match self.get_status() { + /// or if the [Wave] is currently queued. + pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> { + match self.status() { WaveStatus::Playing | WaveStatus::Queued => { return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); } _ => (), } - let max_count: usize = self.buffer.len() / (self.audio_format.sample_size() as usize); + let max_count = self.buffer.len() / self.audio_format.size(); - if sample_count > max_count as u32 { - return Err(NdspError::SampleCountOutOfBounds( - sample_count, - max_count as u32, - )); + if sample_count > max_count { + return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count)); } - self.raw_data.nsamples = sample_count; + self.raw_data.nsamples = sample_count as u32; Ok(()) } @@ -150,16 +147,16 @@ impl TryFrom for WaveStatus { 1 => Ok(Self::Queued), 2 => Ok(Self::Playing), 3 => Ok(Self::Done), - _ => Err("Invalid WaveInfo Status code"), + _ => Err("Invalid Wave Status code"), } } } -impl Drop for WaveInfo { +impl Drop for Wave { fn drop(&mut self) { - // This was the only way I found I could check for improper drops of `WaveInfos`. + // This was the only way I found I could check for improper drops of `Wave`. // A panic was considered, but it would cause issues with drop order against `Ndsp`. - match self.get_status() { + match self.status() { WaveStatus::Free | WaveStatus::Done => (), // If the status flag is "unfinished" _ => {