Browse Source

Improve Ndsp API and docs

pull/104/head
Andrea Ciliberti 2 years ago
parent
commit
7c2e944867
  1. 19
      ctru-rs/examples/audio-filters.rs
  2. 130
      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; @@ -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() { @@ -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() { @@ -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() { @@ -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]);

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

@ -1,12 +1,13 @@ @@ -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,18 @@ pub enum AudioFormat { @@ -30,6 +31,18 @@ pub enum AudioFormat {
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
}
/// Representation of volume mix for a channel.
/// Each member is made up of 2 values, the first is for the "left" channel, while the second is for the "right" channel.
#[derive(Copy, Clone, Debug)]
pub struct AudioMix {
pub front: (f32, f32),
pub back: (f32, f32),
pub aux1_front: (f32, f32),
pub aux1_back: (f32, f32),
pub aux2_front: (f32, f32),
pub aux2_back: (f32, f32),
}
#[derive(Copy, Clone, Debug)]
#[repr(u32)]
pub enum InterpolationType {
@ -47,7 +60,7 @@ pub enum NdspError { @@ -47,7 +60,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 +154,19 @@ impl Channel<'_> { @@ -141,17 +154,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 +187,8 @@ impl Channel<'_> { @@ -172,19 +187,8 @@ impl Channel<'_> {
}
/// Set the channel's volume mix.
///
/// # Notes
///
/// 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()) }
pub fn set_mix(&self, mix: &AudioMix) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.to_raw().as_mut_ptr()) }
}
/// Set the channel's rate of sampling.
@ -206,10 +210,10 @@ impl Channel<'_> { @@ -206,10 +210,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,14 +306,70 @@ impl Channel<'_> { @@ -302,14 +306,70 @@ 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 {
pub fn zeroed() -> Self {
Self {
front: (0., 0.),
back: (0., 0.),
aux1_front: (0., 0.),
aux1_back: (0., 0.),
aux2_front: (0., 0.),
aux2_back: (0., 0.),
}
}
pub fn from_raw(data: [f32; 12]) -> Self {
Self {
front: (data[0], data[1]),
back: (data[2], data[3]),
aux1_front: (data[4], data[5]),
aux1_back: (data[6], data[7]),
aux2_front: (data[8], data[9]),
aux2_back: (data[10], data[11]),
}
}
pub fn to_raw(&self) -> [f32; 12] {
[
self.front.0,
self.front.1,
self.back.0,
self.back.1,
self.aux1_front.0,
self.aux1_front.1,
self.aux1_back.0,
self.aux1_back.1,
self.aux2_front.0,
self.aux2_front.1,
self.aux2_back.0,
self.aux2_back.1,
]
}
}
/// Returns an [AudioMix] object with front left and front right volumes set to max, and all other volumes set to 0.
impl Default for AudioMix {
fn default() -> Self {
Self {
front: (1., 1.),
back: (0., 0.),
aux1_front: (0., 0.),
aux1_back: (0., 0.),
aux2_front: (0., 0.),
aux2_back: (0., 0.),
}
}
}
@ -319,7 +379,7 @@ impl fmt::Display for NdspError { @@ -319,7 +379,7 @@ impl fmt::Display for NdspError {
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 +387,10 @@ impl fmt::Display for NdspError { @@ -327,16 +387,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();
}
}
}

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

@ -1,8 +1,8 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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<u8> for WaveStatus { @@ -150,16 +147,16 @@ impl TryFrom<u8> 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"
_ => {

Loading…
Cancel
Save