Browse Source

Finalize NDSP and Wave

pull/134/head
Andrea Ciliberti 1 year ago
parent
commit
ef4860cf69
  1. 4
      ctru-rs/examples/audio-filters.rs
  2. 323
      ctru-rs/src/services/ndsp/mod.rs
  3. 61
      ctru-rs/src/services/ndsp/wave.rs

4
ctru-rs/examples/audio-filters.rs

@ -5,7 +5,7 @@ use std::f32::consts::PI;
use ctru::linear::LinearAllocator; use ctru::linear::LinearAllocator;
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::ndsp::{ use ctru::services::ndsp::{
wave::{Wave, WaveStatus}, wave::{Status, Wave},
AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode, AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
}; };
@ -146,7 +146,7 @@ fn main() {
}; };
let status = current.status(); let status = current.status();
if let WaveStatus::Done = status { if let Status::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
channel_zero.queue_wave(current).unwrap(); channel_zero.queue_wave(current).unwrap();

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

@ -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 methods. /// Errors returned by [`ndsp`](self) functions.
#[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 creation, but the developer is supposed to change these values to correctly work with the service. /// NDSP initialises all channels with default values on initialization, 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 to 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,

61
ctru-rs/src/services/ndsp/wave.rs

@ -1,9 +1,13 @@
//! Audio wave representation. //! Audio wave.
//!
//! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service.
use super::{AudioFormat, NdspError}; use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator; use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [`ctru_sys::ndspWaveBuf`]. /// Informational struct holding the raw audio data and playback info.
///
/// You can play audio [`Wave`]s by using [`Channel::queue_wave()`](super::Channel::queue_wave).
pub struct Wave { pub struct Wave {
/// Data block of the audio wave (and its format information). /// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
@ -15,8 +19,8 @@ pub struct Wave {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
/// Enum representing the playback status of a [`Wave`]. /// Playback status of a [`Wave`].
pub enum WaveStatus { pub enum Status {
/// Wave has never been used. /// Wave has never been used.
Free = ctru_sys::NDSP_WBUF_FREE as u8, Free = ctru_sys::NDSP_WBUF_FREE as u8,
/// Wave is currently queued for usage. /// Wave is currently queued for usage.
@ -28,7 +32,23 @@ pub enum WaveStatus {
} }
impl Wave { impl Wave {
/// Build a new playable wave object from a raw buffer on LINEAR memory and a some info. /// Build a new playable wave object from a raw buffer on [LINEAR memory](`crate::linear`) and a some info.
///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # fn main() {
/// #
/// use ctru::linear::LinearAllocator;
/// use ctru::services::ndsp::{AudioFormat, wave::Wave};
///
/// // Zeroed box allocated in the LINEAR memory.
/// let audio_data = Box::new_in([0u8; 96], LinearAllocator);
///
/// let wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false);
/// # }
/// ```
pub fn new( pub fn new(
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, audio_format: AudioFormat,
@ -79,7 +99,7 @@ impl Wave {
/// with the id to the channel in which it's queued. /// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { Status::Playing | Status::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
} }
_ => Ok(&mut self.buffer), _ => Ok(&mut self.buffer),
@ -87,7 +107,26 @@ impl Wave {
} }
/// Return this wave's playback status. /// Return this wave's playback status.
pub fn status(&self) -> WaveStatus { ///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # fn main() {
/// #
/// # use ctru::linear::LinearAllocator;
/// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator);
/// #
/// use ctru::services::ndsp::{AudioFormat, wave::{Wave, Status}};
///
/// // Provide your own audio data.
/// let wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false);
///
/// // The `Wave` is free if never played before.
/// assert!(matches!(wave.status(), Status::Free));
/// # }
/// ```
pub fn status(&self) -> Status {
self.raw_data.status.try_into().unwrap() self.raw_data.status.try_into().unwrap()
} }
@ -118,7 +157,7 @@ impl Wave {
/// # Note /// # Note
/// ///
/// Operations of this kind are particularly useful to allocate memory pools /// Operations of this kind are particularly useful to allocate memory pools
/// for VBR (Variable BitRate) Formats, like OGG Vorbis. /// for VBR (Variable BitRate) formats, like OGG Vorbis.
/// ///
/// # Errors /// # Errors
/// ///
@ -126,7 +165,7 @@ impl Wave {
/// or if the [`Wave`] is currently queued. /// or if the [`Wave`] is currently queued.
pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> { pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { Status::Playing | Status::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); return Err(NdspError::WaveBusy(self.played_on_channel.unwrap()));
} }
_ => (), _ => (),
@ -144,7 +183,7 @@ impl Wave {
} }
} }
impl TryFrom<u8> for WaveStatus { impl TryFrom<u8> for Status {
type Error = &'static str; type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -163,7 +202,7 @@ impl Drop for Wave {
// This was the only way I found I could check for improper drops of `Wave`. // 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`. // A panic was considered, but it would cause issues with drop order against `Ndsp`.
match self.status() { match self.status() {
WaveStatus::Free | WaveStatus::Done => (), Status::Free | Status::Done => (),
// If the status flag is "unfinished" // If the status flag is "unfinished"
_ => { _ => {
// The unwrap is safe, since it must have a value in the case the status is "unfinished". // The unwrap is safe, since it must have a value in the case the status is "unfinished".

Loading…
Cancel
Save