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"
_ => {