@ -1,7 +1,11 @@
//! NDSP (Audio) service.
//! NDSP (Audio) service.
//!
//! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard.
//! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device
//! connected via the audio jack.
pub mod wave ;
pub mod wave ;
use wave ::{ Wave , WaveStatus } ;
use wave ::{ Status , Wave } ;
use crate ::error ::ResultCode ;
use crate ::error ::ResultCode ;
use crate ::services ::ServiceReference ;
use crate ::services ::ServiceReference ;
@ -27,7 +31,7 @@ pub enum OutputMode {
Surround = ctru_sys ::NDSP_OUTPUT_SURROUND ,
Surround = ctru_sys ::NDSP_OUTPUT_SURROUND ,
}
}
/// Audio PCM format.
/// PCM formats supported by the audio engine .
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
#[ repr(u32) ]
#[ repr(u32) ]
pub enum AudioFormat {
pub enum AudioFormat {
@ -35,13 +39,13 @@ pub enum AudioFormat {
PCM8Mono = ctru_sys ::NDSP_FORMAT_MONO_PCM8 ,
PCM8Mono = ctru_sys ::NDSP_FORMAT_MONO_PCM8 ,
/// PCM 16bit single-channel.
/// PCM 16bit single-channel.
PCM16Mono = ctru_sys ::NDSP_FORMAT_MONO_PCM16 ,
PCM16Mono = ctru_sys ::NDSP_FORMAT_MONO_PCM16 ,
/// PCM 8bit dual-channel.
/// PCM 8bit interleaved dual-channel.
PCM8Stereo = ctru_sys ::NDSP_FORMAT_STEREO_PCM8 ,
PCM8Stereo = ctru_sys ::NDSP_FORMAT_STEREO_PCM8 ,
/// PCM 16bit dual-channel.
/// PCM 16bit interleaved dual-channel.
PCM16Stereo = ctru_sys ::NDSP_FORMAT_STEREO_PCM16 ,
PCM16Stereo = ctru_sys ::NDSP_FORMAT_STEREO_PCM16 ,
}
}
/// Representation of volume mix for a channel.
/// Representation of the volume mix for a channel.
#[ derive(Copy, Clone, Debug, PartialEq) ]
#[ derive(Copy, Clone, Debug, PartialEq) ]
pub struct AudioMix {
pub struct AudioMix {
raw : [ f32 ; 12 ] ,
raw : [ f32 ; 12 ] ,
@ -60,16 +64,16 @@ pub enum InterpolationType {
None = ctru_sys ::NDSP_INTERP_NONE ,
None = ctru_sys ::NDSP_INTERP_NONE ,
}
}
/// Error enum returned by NDSP method s.
/// Errors returned by [`ndsp`](self) function s.
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
pub enum NdspError {
pub enum NdspError {
/// Channel ID
/// Channel with the specified ID does not exist.
InvalidChannel ( u8 ) ,
InvalidChannel ( u8 ) ,
/// Channel ID
/// Channel with the specified ID is already being used.
ChannelAlreadyInUse ( u8 ) ,
ChannelAlreadyInUse ( u8 ) ,
/// Channel ID
/// The wave is already busy playing in the channel with the specified ID.
WaveBusy ( u8 ) ,
WaveBusy ( u8 ) ,
/// Sample amount requested, Max sample amount
/// The sample amount requested was larger than the maximum.
SampleCountOutOfBounds ( usize , usize ) ,
SampleCountOutOfBounds ( usize , usize ) ,
}
}
@ -79,13 +83,15 @@ pub enum NdspError {
///
///
/// # Default
/// # Default
///
///
/// NDSP initialises all channels with default values on cre ation, but the developer is supposed to change these values to correctly work with the service.
/// NDSP initialises all channels with default values on initializ ation, but the developer is supposed to change these values to correctly work with the service.
///
///
/// In particular:
/// In particular:
/// - Default audio format is set to [`AudioFormat::PCM16Mono`].
/// - Default audio format is set to [`AudioFormat::PCM16Mono`].
/// - Default sample rate is set to 1 Hz.
/// - Default sample rate is set to 1 Hz.
/// - Default interpolation type is set to [`InterpolationType::Polyphase`].
/// - Default interpolation type is set to [`InterpolationType::Polyphase`].
/// - Default mix is set to [`AudioMix::default()`]
/// - Default mix is set to [`AudioMix::default()`]
///
/// The handle to a channel can be retrieved with [`Ndsp::channel()`]
pub struct Channel < ' ndsp > {
pub struct Channel < ' ndsp > {
id : u8 ,
id : u8 ,
_rf : RefMut < ' ndsp , ( ) > , // we don't need to hold any data
_rf : RefMut < ' ndsp , ( ) > , // we don't need to hold any data
@ -93,10 +99,9 @@ pub struct Channel<'ndsp> {
static NDSP_ACTIVE : Mutex < usize > = Mutex ::new ( 0 ) ;
static NDSP_ACTIVE : Mutex < usize > = Mutex ::new ( 0 ) ;
/// Handler of the DSP service and DSP processor .
/// Handle t o the DSP service.
///
///
/// This is the main struct to handle audio playback using the 3DS' speakers and headphone jack.
/// Only one handle for this service can exist at a time.
/// Only one "instance" of this struct can exist at a time.
pub struct Ndsp {
pub struct Ndsp {
_service_handler : ServiceReference ,
_service_handler : ServiceReference ,
channel_flags : [ RefCell < ( ) > ; NUMBER_OF_CHANNELS as usize ] ,
channel_flags : [ RefCell < ( ) > ; NUMBER_OF_CHANNELS as usize ] ,
@ -107,8 +112,22 @@ impl Ndsp {
///
///
/// # Errors
/// # Errors
///
///
/// This function will return an error if an instance of the `Ndsp` struct already exists
/// This function will return an error if an instance of the [ `Ndsp`] struct already exists
/// or if there are any issues during initialization.
/// or if there are any issues during initialization.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
///
/// let ndsp = Ndsp::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspInit " ) ]
#[ doc(alias = " ndspInit " ) ]
pub fn new ( ) -> crate ::Result < Self > {
pub fn new ( ) -> crate ::Result < Self > {
let _service_handler = ServiceReference ::new (
let _service_handler = ServiceReference ::new (
@ -135,6 +154,21 @@ impl Ndsp {
/// # Errors
/// # Errors
///
///
/// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used.
/// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
///
/// let channel_0 = ndsp.channel(0)?;
/// #
/// # Ok(())
/// # }
/// ```
pub fn channel ( & self , id : u8 ) -> std ::result ::Result < Channel , NdspError > {
pub fn channel ( & self , id : u8 ) -> std ::result ::Result < Channel , NdspError > {
let in_bounds = self . channel_flags . get ( id as usize ) ;
let in_bounds = self . channel_flags . get ( id as usize ) ;
@ -150,7 +184,23 @@ impl Ndsp {
}
}
}
}
/// Set the audio output mode. Defaults to `OutputMode::Stereo`.
/// Set the audio output mode. Defaults to [`OutputMode::Stereo`].
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::{Ndsp, OutputMode};
/// let mut ndsp = Ndsp::new()?;
///
/// // Use dual-channel output.
/// ndsp.set_output_mode(OutputMode::Stereo);
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspSetOutputMode " ) ]
#[ doc(alias = " ndspSetOutputMode " ) ]
pub fn set_output_mode ( & mut self , mode : OutputMode ) {
pub fn set_output_mode ( & mut self , mode : OutputMode ) {
unsafe { ctru_sys ::ndspSetOutputMode ( mode . into ( ) ) } ;
unsafe { ctru_sys ::ndspSetOutputMode ( mode . into ( ) ) } ;
@ -158,31 +208,114 @@ impl Ndsp {
}
}
impl Channel < ' _ > {
impl Channel < ' _ > {
/// Reset the channel
/// Reset the channel (clear the queue and reset parameters).
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// channel_0.reset();
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnReset " ) ]
#[ doc(alias = " ndspChnReset " ) ]
pub fn reset ( & mut self ) {
pub fn reset ( & mut self ) {
unsafe { ctru_sys ::ndspChnReset ( self . id . into ( ) ) } ;
unsafe { ctru_sys ::ndspChnReset ( self . id . into ( ) ) } ;
}
}
/// Initialize the channel's parameters
/// Initialize the channel's parameters with default values.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// channel_0.init_parameters();
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnInitParams " ) ]
#[ doc(alias = " ndspChnInitParams " ) ]
pub fn init_parameters ( & self ) {
pub fn init_parameters ( & mut self ) {
unsafe { ctru_sys ::ndspChnInitParams ( self . id . into ( ) ) } ;
unsafe { ctru_sys ::ndspChnInitParams ( self . id . into ( ) ) } ;
}
}
/// Returns whether the channel is playing any audio.
/// Returns whether the channel is playing any audio.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // The channel is not playing any audio.
/// assert!(!channel_0.is_playing());
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnIsPlaying " ) ]
#[ doc(alias = " ndspChnIsPlaying " ) ]
pub fn is_playing ( & self ) -> bool {
pub fn is_playing ( & self ) -> bool {
unsafe { ctru_sys ::ndspChnIsPlaying ( self . id . 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.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // The channel is not paused.
/// assert!(!channel_0.is_paused());
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnIsPaused " ) ]
#[ doc(alias = " ndspChnIsPaused " ) ]
pub fn is_paused ( & self ) -> bool {
pub fn is_paused ( & self ) -> bool {
unsafe { ctru_sys ::ndspChnIsPaused ( self . id . into ( ) ) }
unsafe { ctru_sys ::ndspChnIsPaused ( self . id . into ( ) ) }
}
}
/// Returns the channel's index.
/// Returns the channel's index.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // The channel's index is 0.
/// assert_eq!(channel_0.id(), 0);
/// #
/// # Ok(())
/// # }
/// ```
pub fn id ( & self ) -> u8 {
pub fn id ( & self ) -> u8 {
self . id
self . id
}
}
@ -202,41 +335,148 @@ impl Channel<'_> {
}
}
/// Pause or un-pause the channel's playback.
/// Pause or un-pause the channel's playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// channel_0.set_paused(true);
///
/// // The channel is paused.
/// assert!(channel_0.is_paused());
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnSetPaused " ) ]
#[ doc(alias = " ndspChnSetPaused " ) ]
pub fn set_paused ( & mut self , state : bool ) {
pub fn set_paused ( & mut self , state : bool ) {
unsafe { ctru_sys ::ndspChnSetPaused ( self . id . 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 wave's format.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::{AudioFormat, Ndsp};
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // Use the PCM16 interleaved dual-channel audio format.
/// channel_0.set_format(AudioFormat::PCM16Stereo);
/// #
/// # Ok(())
/// # }
/// ```
// TODO: Channels treat all waves as equal and do not read their format when playing them. Another good reason to re-write the service.
#[ doc(alias = " ndspChnSetFormat " ) ]
#[ doc(alias = " ndspChnSetFormat " ) ]
pub fn set_format ( & mut self , format : AudioFormat ) {
pub fn set_format ( & mut self , format : AudioFormat ) {
unsafe { ctru_sys ::ndspChnSetFormat ( self . id . into ( ) , format . into ( ) ) } ;
unsafe { ctru_sys ::ndspChnSetFormat ( self . id . into ( ) , format . into ( ) ) } ;
}
}
/// Set the channel's interpolation mode.
/// Set the channel's interpolation mode.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::{InterpolationType, Ndsp};
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // Use linear interpolation within frames.
/// channel_0.set_interpolation(InterpolationType::Linear);
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnSetInterp " ) ]
#[ doc(alias = " ndspChnSetInterp " ) ]
pub fn set_interpolation ( & mut self , interp_type : InterpolationType ) {
pub fn set_interpolation ( & mut self , interp_type : InterpolationType ) {
unsafe { ctru_sys ::ndspChnSetInterp ( self . id . into ( ) , interp_type . into ( ) ) } ;
unsafe { ctru_sys ::ndspChnSetInterp ( self . id . into ( ) , interp_type . into ( ) ) } ;
}
}
/// Set the channel's volume mix.
/// Set the channel's volume mix.
///
/// Look at [`AudioMix`] for more information on the volume mix.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # use std::default::Default;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::{AudioMix, Ndsp};
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // Front-left and front-right channel maxed.
/// channel_0.set_mix(&AudioMix::default());
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnSetMix " ) ]
#[ doc(alias = " ndspChnSetMix " ) ]
pub fn set_mix ( & mut self , mix : & AudioMix ) {
pub fn set_mix ( & mut self , mix : & AudioMix ) {
unsafe { ctru_sys ::ndspChnSetMix ( self . id . into ( ) , mix . as_raw ( ) . as_ptr ( ) . cast_mut ( ) ) }
unsafe { ctru_sys ::ndspChnSetMix ( self . id . into ( ) , mix . as_raw ( ) . as_ptr ( ) . cast_mut ( ) ) }
}
}
/// Set the channel's rate of sampling.
/// Set the channel's rate of sampling in hertz.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // Standard CD sample rate. (44100 Hz)
/// channel_0.set_sample_rate(44100.);
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnSetRate " ) ]
#[ doc(alias = " ndspChnSetRate " ) ]
pub fn set_sample_rate ( & mut self , rate : f32 ) {
pub fn set_sample_rate ( & mut self , rate : f32 ) {
unsafe { ctru_sys ::ndspChnSetRate ( self . id . into ( ) , rate ) } ;
unsafe { ctru_sys ::ndspChnSetRate ( self . id . into ( ) , rate ) } ;
}
}
// `ndspChnSetAdpcmCoefs` isn't wrapped on purpose.
// TODO: wrap ADPCM format helpers.
// DSPADPCM is a proprietary format used by Nintendo, unavailable by "normal" means.
// We suggest using other wave formats when developing homebrew applications.
/// Clear the wave buffer queue and stop playback.
/// Clear the wave buffer queue and stop playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// // Clear the audio queue and stop playback.
/// channel_0.clear_queue();
/// #
/// # Ok(())
/// # }
/// ```
#[ doc(alias = " ndspChnWaveBufClear " ) ]
#[ doc(alias = " ndspChnWaveBufClear " ) ]
pub fn clear_queue ( & mut self ) {
pub fn clear_queue ( & mut self ) {
unsafe { ctru_sys ::ndspChnWaveBufClear ( self . id . into ( ) ) } ;
unsafe { ctru_sys ::ndspChnWaveBufClear ( self . id . into ( ) ) } ;
@ -249,10 +489,36 @@ impl Channel<'_> {
///
///
/// `libctru` expects the user to manually keep the info data (in this case [`Wave`]) alive during playback.
/// `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.
/// To ensure safety, checks within [`Wave`] will clear the whole channel queue if any queued [`Wave`] is dropped prematurely.
///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # use ctru::linear::LinearAllocator;
/// use ctru::services::ndsp::{AudioFormat, Ndsp, wave::Wave};
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator);
///
/// // Provide your own audio data.
/// let mut wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false);
///
/// // Clear the audio queue and stop playback.
/// channel_0.queue_wave(&mut wave);
/// #
/// # Ok(())
/// # }
/// ```
// TODO: Find a better way to handle the wave lifetime problem.
// These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust.
#[ doc(alias = " ndspChnWaveBufAdd " ) ]
#[ doc(alias = " ndspChnWaveBufAdd " ) ]
pub fn queue_wave ( & mut self , wave : & mut Wave ) -> std ::result ::Result < ( ) , NdspError > {
pub fn queue_wave ( & mut self , wave : & mut Wave ) -> std ::result ::Result < ( ) , NdspError > {
match wave . status ( ) {
match wave . status ( ) {
WaveStatus ::Playing | WaveStatus ::Queued = > return Err ( NdspError ::WaveBusy ( self . id ) ) ,
Status ::Playing | Status ::Queued = > return Err ( NdspError ::WaveBusy ( self . id ) ) ,
_ = > ( ) ,
_ = > ( ) ,
}
}
@ -354,9 +620,10 @@ impl Channel<'_> {
impl AudioFormat {
impl AudioFormat {
/// Returns the amount of bytes needed to store one sample
/// Returns the amount of bytes needed to store one sample
///
///
/// Eg.
/// # Example
/// 8 bit mono formats return 1 (byte)
///
/// 16 bit stereo (dual-channel) formats return 4 (bytes)
/// - 8 bit mono formats return 1 (byte)
/// - 16 bit stereo (dual-channel) formats return 4 (bytes)
pub const fn size ( self ) -> usize {
pub const fn size ( self ) -> usize {
match self {
match self {
Self ::PCM8Mono = > 1 ,
Self ::PCM8Mono = > 1 ,