Browse Source

Merge pull request #104 from rust3ds/improve/ndsp

Improve Ndsp API
pull/112/head
Meziu 2 years ago committed by GitHub
parent
commit
0b295ce0f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      ctru-rs/examples/audio-filters.rs
  2. 190
      ctru-rs/src/services/ndsp/mod.rs
  3. 47
      ctru-rs/src/services/ndsp/wave.rs

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

@ -5,13 +5,13 @@ 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::{WaveInfo, WaveStatus}, wave::{Wave, WaveStatus},
AudioFormat, InterpolationType, Ndsp, OutputMode, AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
}; };
const SAMPLE_RATE: usize = 22050; const SAMPLE_RATE: usize = 22050;
const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 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; const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE;
// Note Frequencies // Note Frequencies
@ -65,8 +65,8 @@ fn main() {
let audio_data2 = audio_data1.clone(); let audio_data2 = audio_data1.clone();
let mut wave_info1 = WaveInfo::new(audio_data1, AudioFormat::PCM16Stereo, false); let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = WaveInfo::new(audio_data2, 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"); let mut ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller");
@ -79,10 +79,7 @@ fn main() {
channel_zero.set_format(AudioFormat::PCM16Stereo); channel_zero.set_format(AudioFormat::PCM16Stereo);
// Output at 100% on the first pair of left and right channels. // Output at 100% on the first pair of left and right channels.
let mix = AudioMix::default();
let mut mix: [f32; 12] = [0f32; 12];
mix[0] = 1.0;
mix[1] = 1.0;
channel_zero.set_mix(&mix); channel_zero.set_mix(&mix);
channel_zero.queue_wave(&mut wave_info1).unwrap(); 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 &mut wave_info1
} else { } else {
&mut wave_info2 &mut wave_info2
}; };
let status = current.get_status(); let status = current.status();
if let WaveStatus::Done = status { if let WaveStatus::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);

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

@ -1,12 +1,13 @@
//! NDSP (Audio) service //! NDSP (Audio) service
pub mod wave; pub mod wave;
use wave::{WaveInfo, WaveStatus}; use wave::{Wave, WaveStatus};
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::sync::Mutex; use std::sync::Mutex;
@ -30,6 +31,12 @@ pub enum AudioFormat {
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, 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)] #[derive(Copy, Clone, Debug)]
#[repr(u32)] #[repr(u32)]
pub enum InterpolationType { pub enum InterpolationType {
@ -47,7 +54,7 @@ pub enum NdspError {
/// Channel ID /// Channel ID
WaveBusy(u8), WaveBusy(u8),
/// Sample amount requested, Max sample amount /// Sample amount requested, Max sample amount
SampleCountOutOfBounds(u32, u32), SampleCountOutOfBounds(usize, usize),
} }
pub struct Channel<'ndsp> { pub struct Channel<'ndsp> {
@ -141,17 +148,19 @@ impl Channel<'_> {
} }
// Returns the channel's id // Returns the channel's id
pub fn get_id(&self) -> u8 { pub fn id(&self) -> u8 {
self.id self.id
} }
/// Returns the channel's current sample's position. /// Returns the index of the currently played sample.
pub fn get_sample_position(&self) -> u32 { ///
unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) } /// 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. /// 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()) } unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
} }
@ -172,19 +181,8 @@ impl Channel<'_> {
} }
/// Set the channel's volume mix. /// Set the channel's volume mix.
/// pub fn set_mix(&self, mix: &AudioMix) {
/// # Notes unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) }
///
/// The buffer's format is read as:
///
/// Index 0: Front left volume <br>
/// Index 1: Front right volume <br>
/// Index 2: Back left volume <br>
/// Index 3: Back right volume <br>
/// Index 4..7: Same as 0..3 but for auxiliary output 0 <br>
/// Index 8..11: Same as 0..3 but for auxiliary output 1 <br>
pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_ptr().cast_mut()) }
} }
/// Set the channel's rate of sampling. /// Set the channel's rate of sampling.
@ -206,10 +204,10 @@ impl Channel<'_> {
/// ///
/// # Warning /// # Warning
/// ///
/// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) 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 [WaveInfo] will clear the whole channel queue if any queued [WaveInfo] is dropped prematurely. /// 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 WaveInfo) -> std::result::Result<(), NdspError> { pub fn queue_wave(&self, wave: &mut Wave) -> std::result::Result<(), NdspError> {
match wave.get_status() { match wave.status() {
WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)), WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)),
_ => (), _ => (),
} }
@ -302,24 +300,146 @@ 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. /// Eg.
/// 8 bit formats return 1 (byte) /// 8 bit mono formats return 1 (byte)
/// 16 bit formats return 2 (bytes) /// 16 bit stereo (dual-channel) formats return 4 (bytes)
pub fn sample_size(self) -> u8 { pub const fn size(self) -> usize {
match self { match self {
AudioFormat::PCM8Mono => 1, Self::PCM8Mono => 1,
AudioFormat::PCM16Mono | AudioFormat::PCM8Stereo => 2, Self::PCM16Mono | Self::PCM8Stereo => 2,
AudioFormat::PCM16Stereo => 4, 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 { impl fmt::Display for NdspError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { 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::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::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})"), 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 error::Error for NdspError {}
impl<'ndsp> Drop for Channel<'ndsp> {
fn drop(&mut self) {
self.reset();
}
}
impl Drop for Ndsp { impl Drop for Ndsp {
fn drop(&mut self) { fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS { for i in 0..NUMBER_OF_CHANNELS {
self.channel(i).unwrap().clear_queue(); self.channel(i).unwrap().reset();
} }
} }
} }

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

@ -1,8 +1,8 @@
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. This corresponds to [ctru_sys::ndspWaveBuf].
pub struct WaveInfo { 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>,
audio_format: AudioFormat, audio_format: AudioFormat,
@ -13,7 +13,7 @@ pub struct WaveInfo {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(u8)] #[repr(u8)]
/// Enum representing the playback status of a [WaveInfo]. /// Enum representing the playback status of a [Wave].
pub enum WaveStatus { pub enum WaveStatus {
Free = ctru_sys::NDSP_WBUF_FREE as u8, Free = ctru_sys::NDSP_WBUF_FREE as u8,
Queued = ctru_sys::NDSP_WBUF_QUEUED 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, 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. /// Build a new playable wave object from a raw buffer on LINEAR memory and a some info.
pub fn new( pub fn new(
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, audio_format: AudioFormat,
looping: bool, looping: bool,
) -> Self { ) -> 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. // 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. // 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 /// # 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. /// 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.get_status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { WaveStatus::Playing | WaveStatus::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
} }
@ -81,7 +81,7 @@ impl WaveInfo {
} }
/// Return this wave's playback status. /// Return this wave's playback status.
pub fn get_status(&self) -> WaveStatus { pub fn status(&self) -> WaveStatus {
self.raw_data.status.try_into().unwrap() self.raw_data.status.try_into().unwrap()
} }
@ -90,12 +90,12 @@ impl WaveInfo {
/// # Notes /// # Notes
/// ///
/// This value varies depending on [Self::set_sample_count]. /// This value varies depending on [Self::set_sample_count].
pub fn get_sample_count(&self) -> u32 { pub fn sample_count(&self) -> usize {
self.raw_data.nsamples self.raw_data.nsamples as usize
} }
/// Get the format of the audio data. /// Get the format of the audio data.
pub fn get_format(&self) -> AudioFormat { pub fn format(&self) -> AudioFormat {
self.audio_format self.audio_format
} }
@ -117,25 +117,22 @@ impl WaveInfo {
/// # Errors /// # Errors
/// ///
/// This function will return an error if the sample size exceeds the buffer's capacity /// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the WaveInfo is currently queued. /// or if the [Wave] is currently queued.
pub fn set_sample_count(&mut self, sample_count: u32) -> Result<(), NdspError> { pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.get_status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { WaveStatus::Playing | WaveStatus::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); 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 { if sample_count > max_count {
return Err(NdspError::SampleCountOutOfBounds( return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count));
sample_count,
max_count as u32,
));
} }
self.raw_data.nsamples = sample_count; self.raw_data.nsamples = sample_count as u32;
Ok(()) Ok(())
} }
@ -150,16 +147,16 @@ impl TryFrom<u8> for WaveStatus {
1 => Ok(Self::Queued), 1 => Ok(Self::Queued),
2 => Ok(Self::Playing), 2 => Ok(Self::Playing),
3 => Ok(Self::Done), 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) { 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`. // 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 => (), WaveStatus::Free | WaveStatus::Done => (),
// If the status flag is "unfinished" // If the status flag is "unfinished"
_ => { _ => {

Loading…
Cancel
Save