Browse Source

Sort ndsp module in 2 files

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
afd3c89f68
  1. 181
      ctru-rs/src/services/ndsp/mod.rs
  2. 144
      ctru-rs/src/services/ndsp/wave.rs

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

@ -0,0 +1,181 @@
pub mod wave;
use crate::error::ResultCode;
use wave::WaveInfo;
#[derive(Copy, Clone, Debug)]
#[repr(u32)]
pub enum OutputMode {
Mono = ctru_sys::NDSP_OUTPUT_MONO,
Stereo = ctru_sys::NDSP_OUTPUT_STEREO,
Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
}
#[derive(Copy, Clone, Debug)]
#[repr(u32)]
pub enum AudioFormat {
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
ADPCMMono = ctru_sys::NDSP_FORMAT_MONO_ADPCM,
PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
FrontBypass = ctru_sys::NDSP_FRONT_BYPASS,
SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED,
}
#[derive(Copy, Clone, Debug)]
#[repr(u32)]
pub enum InterpolationType {
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
Linear = ctru_sys::NDSP_INTERP_LINEAR,
None = ctru_sys::NDSP_INTERP_NONE,
}
pub struct Channel {
id: i32,
}
#[non_exhaustive]
pub struct Ndsp(());
impl Ndsp {
pub fn init() -> crate::Result<Self> {
ResultCode(unsafe { ctru_sys::ndspInit() })?;
Ok(Self(()))
}
/// Return the representation of the specified channel.
///
/// # Errors
///
/// An error will be returned if the channel id is not between 0 and 23.
pub fn channel(&self, id: u8) -> crate::Result<Channel> {
if id > 23 {
return Err(crate::Error::InvalidChannel(id.into()));
}
Ok(Channel { id: id.into() })
}
/// Set the audio output mode. Defaults to `OutputMode::Stereo`.
pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ctru_sys::ndspSetOutputMode(mode as u32) };
}
}
// All channel operations are thread-safe thanks to `libctru`'s use of thread locks.
// As such, there is no need to hold channels to ensure correct mutability.
// With this prospective in mind, this struct looks more like a dummy than an actually functional block.
impl Channel {
/// Reset the channel
pub fn reset(&self) {
unsafe { ctru_sys::ndspChnReset(self.id) };
}
/// Initialize the channel's parameters
pub fn init_parameters(&self) {
unsafe { ctru_sys::ndspChnInitParams(self.id) };
}
/// Returns whether the channel is playing any audio.
pub fn is_playing(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPlaying(self.id) }
}
/// Returns whether the channel's playback is currently paused.
pub fn is_paused(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPaused(self.id) }
}
/// Returns the channel's current sample's position.
pub fn get_sample_position(&self) -> u32 {
unsafe { ctru_sys::ndspChnGetSamplePos(self.id) }
}
/// Returns the channel's current wave sequence's id.
pub fn get_wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id) }
}
/// Pause or un-pause the channel's playback.
pub fn set_paused(&self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.id, state) };
}
/// Set the channel's output format.
/// Change this setting based on the used sample's format.
pub fn set_format(&self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id, format as u16) };
}
/// Set the channel's interpolation mode.
pub fn set_interpolation(&self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type as u32) };
}
/// Set the channel's volume mix.
/// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444
pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.id, mix.as_ptr().cast_mut()) }
}
/// Set the channel's rate of sampling.
pub fn set_sample_rate(&self, rate: f32) {
unsafe { ctru_sys::ndspChnSetRate(self.id, rate) };
}
// TODO: find a way to wrap `ndspChnSetAdpcmCoefs`
/// Clear the wave buffer queue and stop playback.
pub fn clear_queue(&self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id) };
}
/// Add a wave buffer to the channel's queue.
/// Note: if there are no other buffers in queue, playback for this buffer will start.
///
/// # Beware
///
/// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback.
/// Furthermore, there are no checks to see if the used memory is still valid. All responsibility of handling this data is left to the user.
pub fn queue_wave(&self, buffer: &mut WaveInfo) {
unsafe { ctru_sys::ndspChnWaveBufAdd(self.id, &mut buffer.raw_data) };
}
// FILTERS
// TODO: Add Mono filters (and maybe setup the filter functions in a better way)
pub fn iir_biquad_set_enabled(&self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id, enable) };
}
pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe { ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id, cut_off_freq, quality) };
}
pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe { ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id, cut_off_freq, quality) };
}
pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) {
unsafe { ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id, notch_freq, quality) };
}
pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) {
unsafe { ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id, mid_freq, quality) };
}
pub fn iir_biquad_set_params_peaking_equalizer(&self, central_freq: f32, quality: f32, gain: f32) {
unsafe { ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer(self.id, central_freq, quality, gain) };
}
}
impl Drop for Ndsp {
fn drop(&mut self) {
unsafe {
ctru_sys::ndspExit();
}
}
}

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

@ -0,0 +1,144 @@
use super::AudioFormat;
use crate::error::ResultCode;
use crate::linear::LinearAllocator;
/// Base struct to represent audio wave data. This requires audio format information.
#[derive(Debug, Clone)]
pub struct WaveBuffer {
/// Buffer data. This data must be allocated on the LINEAR memory.
data: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
nsamples: usize, // We don't use the slice's length here because depending on the format it may vary
// adpcm_data: AdpcmData, TODO: Requires research on how this format is handled.
}
/// Informational struct holding the raw audio data and playaback info. This corresponds to [ctru_sys::ndspWaveBuf]
pub struct WaveInfo<'b> {
/// Data block of the audio wave (plus its format information).
buffer: &'b mut WaveBuffer,
// Holding the data with the raw format is necessary since `libctru` will access it.
pub(crate) raw_data: ctru_sys::ndspWaveBuf,
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum WaveStatus {
Free = ctru_sys::NDSP_WBUF_FREE as u8,
Queued = ctru_sys::NDSP_WBUF_QUEUED as u8,
Playing = ctru_sys::NDSP_WBUF_PLAYING as u8,
Done = ctru_sys::NDSP_WBUF_DONE as u8,
}
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 {
match self {
AudioFormat::PCM16Mono | AudioFormat::PCM16Stereo => 2,
AudioFormat::SurroundPreprocessed => {
panic!("Can't find size for Sourround Preprocessed audio: format is under research")
}
_ => 1,
}
}
}
impl WaveBuffer {
pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> crate::Result<Self> {
let nsamples: usize = data.len() / (audio_format.sample_size() as usize);
unsafe {
ResultCode(ctru_sys::DSP_FlushDataCache(
data.as_ptr().cast(),
data.len().try_into().unwrap(),
))?;
}
Ok(WaveBuffer {
data,
audio_format,
nsamples,
})
}
pub fn get_mut_data(&mut self) -> &mut Box<[u8], LinearAllocator> {
&mut self.data
}
pub fn get_format(&self) -> AudioFormat {
self.audio_format
}
pub fn get_sample_amount(&self) -> usize {
self.nsamples
}
}
impl<'b> WaveInfo<'b> {
pub fn new(buffer: &'b mut WaveBuffer, looping: bool) -> Self {
let address = ctru_sys::tag_ndspWaveBuf__bindgen_ty_1 {
data_vaddr: buffer.data.as_ptr().cast(),
};
let raw_data = ctru_sys::ndspWaveBuf {
__bindgen_anon_1: address, // Buffer data virtual address
nsamples: buffer.nsamples.try_into().unwrap(),
adpcm_data: std::ptr::null_mut(),
offset: 0,
looping,
// The ones after this point aren't supposed to be setup by the user
status: 0,
sequence_id: 0,
next: std::ptr::null_mut(),
};
Self { buffer, raw_data }
}
pub fn get_mut_wavebuffer(&'b mut self) -> &'b mut WaveBuffer {
&mut self.buffer
}
pub fn get_status(&self) -> WaveStatus {
self.raw_data.status.try_into().unwrap()
}
}
impl TryFrom<u8> for WaveStatus {
type Error = String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Free),
1 => Ok(Self::Queued),
2 => Ok(Self::Playing),
3 => Ok(Self::Done),
_ => Err(String::from("Invalid WaveInfo Status code")),
}
}
}
impl Drop for WaveBuffer {
fn drop(&mut self) {
unsafe {
// Result can't be used in any way, let's just shrug it off
let _r = ctru_sys::DSP_InvalidateDataCache(
self.data.as_ptr().cast(),
self.data.len().try_into().unwrap(),
);
}
}
}
impl<'b> Drop for WaveInfo<'b> {
fn drop(&mut self) {
// This was the only way I found I could check for improper drops of WaveInfos.
// It should be enough to warn users of the danger.
match self.get_status() {
WaveStatus::Free | WaveStatus::Done => (),
_ => panic!("WaveInfo struct has been dropped before finishing use. This could cause Undefined Behaviour."),
}
}
}
Loading…
Cancel
Save