Browse Source

Added explanatory comments and small updates

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
a911e9c0e3
  1. 35
      ctru-rs/examples/audio_filters.rs
  2. 168
      ctru-rs/src/services/ndsp.rs

35
ctru-rs/examples/audio_filters.rs

@ -1,5 +1,8 @@
#![feature(allocator_api)]
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::ndsp::{Ndsp, OutputMode, InterpolationType}; use ctru::services::ndsp::{Ndsp, OutputMode, InterpolationType};
use ctru::linear::LinearAllocator;
const SAMPLERATE: u32 = 22050; const SAMPLERATE: u32 = 22050;
const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735 const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735
@ -33,11 +36,13 @@ fn main() {
println!("libctru filtered streamed audio\n"); println!("libctru filtered streamed audio\n");
let audioBuffer = [0u32; (SAMPLESPERBUF * BYTESPERSAMPLE * 2)]; let audioBuffer = Box::new_in([0u32; (SAMPLESPERBUF * BYTESPERSAMPLE * 2)], LinearAllocator);
let fillBlock = false; let fillBlock = false;
let ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller"); let ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller");
// This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo`
ndsp.set_output_mode(OutputMode::Stereo); ndsp.set_output_mode(OutputMode::Stereo);
let channel_zero = ndsp.channel(0); let channel_zero = ndsp.channel(0);
@ -48,34 +53,33 @@ fn main() {
// 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 = [0f32; 12]; let mix = [0f32; 12];
memset(mix, 0, sizeof(mix));
mix[0] = 1.0; mix[0] = 1.0;
mix[1] = 1.0; mix[1] = 1.0;
ndspChnSetMix(0, mix); channel_zero.set_mix(mix);
// Note Frequencies // Note Frequencies
int notefreq[] = { let notefreq = [
220, 220,
440, 880, 1760, 3520, 7040, 440, 880, 1760, 3520, 7040,
14080, 14080,
7040, 3520, 1760, 880, 440 7040, 3520, 1760, 880, 440
}; ];
int note = 4; let note: i32 = 4;
// Filters // Filters
const char* filter_names[] = { let filter_names = [
"None", "None",
"Low-Pass", "Low-Pass",
"High-Pass", "High-Pass",
"Band-Pass", "Band-Pass",
"Notch", "Notch",
"Peaking" "Peaking"
}; ];
int filter = 0; let filter = 0;
// We set up two wave buffers and alternate between the two, // We set up two wave buffers and alternate between the two,
// effectively streaming an infinitely long sine wave. // effectively streaming an infinitely long sine wave.
@ -87,14 +91,14 @@ fn main() {
waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF]; waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF];
waveBuf[1].nsamples = SAMPLESPERBUF; waveBuf[1].nsamples = SAMPLESPERBUF;
size_t stream_offset = 0; let stream_offset = 0;
fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2, notefreq[note]); fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2, notefreq[note]);
stream_offset += SAMPLESPERBUF; stream_offset += SAMPLESPERBUF;
ndspChnWaveBufAdd(0, &waveBuf[0]); channel_zero.add_wave_buffer(&waveBuf[0]);
ndspChnWaveBufAdd(0, &waveBuf[1]); channel_zero.add_wave_buffer(&waveBuf[1]);
println!("Press up/down to change tone frequency\n"); println!("Press up/down to change tone frequency\n");
println!("Press left/right to change filter\n"); println!("Press left/right to change filter\n");
@ -173,11 +177,4 @@ fn main() {
fillBlock = !fillBlock; fillBlock = !fillBlock;
} }
} }
ndspExit();
linearFree(audioBuffer);
gfxExit();
return 0;
} }

168
ctru-rs/src/services/ndsp.rs

@ -1,5 +1,7 @@
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::linear::LinearAllocator;
use arr_macro::arr;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(u32)] #[repr(u32)]
@ -18,7 +20,7 @@ pub enum InterpolationType {
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(u16)] #[repr(u32)]
pub enum AudioFormat { pub enum AudioFormat {
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16, PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
@ -29,75 +31,64 @@ pub enum AudioFormat {
SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED, SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED,
} }
pub struct Wave { /// Base struct to represent audio wave data. This requires audio format information.
/// Buffer data with specified type pub struct WaveBuffer {
buffer: Vector<i8>, /// Buffer data. This data must be allocated on the LINEAR memory.
// adpcm_data: AdpcmData, TODO: Requires research on how this WaveBuffer type is handled data: Box<[u8], LinearAllocator>,
/// Whether to loop the track or not audio_format: AudioFormat,
looping: bool, length: usize,
// adpcm_data: AdpcmData, TODO: Requires research on how this format is handled.
} }
#[non_exhaustive] pub struct WaveInfo {
pub struct Ndsp { /// Data block of the audio wave (plus its format information).
// To ensure safe tracking of the different channels, there must be only ONE buffer: WaveBuffer,
// existing instance of the service at a time. // Holding the data with the raw format is necessary since `libctru` will access it.
_service_handler: ServiceReference, raw_data: ctru_sys::ndspWaveBuf,
channel_mask: u32,
} }
#[non_exhaustive] #[non_exhaustive]
pub struct Channel { pub struct Ndsp(());
id: u8, // From 0 to 23.
}
static NDSP_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0));
impl Ndsp { impl Ndsp {
pub fn init() -> crate::Result<Self> { pub fn init() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( ResultCode(unsafe { ctru_sys::ndspInit() })?;
&NDSP_ACTIVE,
false,
|| unsafe {
ctru_sys::ndspInit();
Ok(()) let mut i = 0;
},
|| unsafe { ctru_sys::ndspExit() },
)?;
Ok(Self { Ok(Self(()))
_service_handler,
channel_mask: 0,
})
} }
/// Return the representation of the specified channel. /// Return the representation of the specified channel.
/// ///
/// # Errors /// # Errors
/// ///
/// An error will be returned if the channel id is greater than 23. /// An error will be returned if the channel id is not between 0 and 23.
pub fn channel(&self, id: u8) -> crate::Result<Channel> { pub fn channel(&self, id: usize) -> crate::Result<Channel> {
if channel > 23 { if id > 23 {
return Err(crate::Error::InvalidChannel); return Err(crate::Error::InvalidChannel);
} }
Ok(Channel(id as i32)) Ok(self.channels[id])
} }
/// Set the audio output mode. Defaults to `OutputMode::Stereo`. /// Set the audio output mode. Defaults to `OutputMode::Stereo`.
pub fn set_output_mode(&mut self, mode: OutputMode) { pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ndspSetOutputMode(mode) }; 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 { impl Channel {
/// Reset the channel /// Reset the channel
pub fn reset(&mut self) { pub fn reset(&self) {
unsafe { ctru_sys::ndspChnReset(self.id) }; unsafe { ctru_sys::ndspChnReset(self.id) };
} }
/// Initialize the channel's parameters /// Initialize the channel's parameters
pub fn init_parameters(&mut self) { pub fn init_parameters(&self) {
unsafe { ctru_sys::ndspChnInitParams(self.id) }; unsafe { ctru_sys::ndspChnInitParams(self.id) };
} }
@ -122,71 +113,110 @@ impl Channel {
} }
/// Pause or un-pause the channel's playback. /// Pause or un-pause the channel's playback.
pub fn set_paused(&mut self, state: bool) { pub fn set_paused(&self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.id, state) }; unsafe { ctru_sys::ndspChnSetPaused(self.id, 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 sample's format.
pub fn set_format(&mut self, format: AudioFormat) { pub fn set_format(&self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id, format) }; unsafe { ctru_sys::ndspChnSetFormat(self.id, format as u16) };
} }
/// Set the channel's interpolation mode. /// Set the channel's interpolation mode.
pub fn set_interpolation(&mut self, interp_type: InterpolationType) { pub fn set_interpolation(&self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type) }; unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type as u32) };
} }
/// Set the channel's volume mix. /// Set the channel's volume mix.
/// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444 /// Docs about the buffer usage: https://libctru.devkitpro.org/channel_8h.html#a30eb26f1972cc3ec28370263796c0444
pub fn set_mix(&mut self, mix: [f32; 12]) { pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.id, mix) } unsafe { ctru_sys::ndspChnSetMix(self.id, mix.as_mut_ptr()) }
} }
/// Set the channel's rate of sampling. /// Set the channel's rate of sampling.
pub fn set_sample_rate(&mut self, rate: f32) { pub fn set_sample_rate(&self, rate: f32) {
unsafe { ctru_sys::ndspChnSetRate(self.id, rate) }; unsafe { ctru_sys::ndspChnSetRate(self.id, rate) };
} }
// TODO: find a way to wrap `ndspChnSetAdpcmCoefs` // TODO: find a way to wrap `ndspChnSetAdpcmCoefs`
/// Clear the wave buffer's queue and stop playback. /// Clear the wave buffer queue and stop playback.
pub fn clear_wave_buffer(&mut self) { pub fn clear_queue(&self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id) }; unsafe { ctru_sys::ndspChnWaveBufClear(self.id) };
} }
/// Add a wave buffer to the channel's queue. /// Add a wave buffer to the channel's queue.
/// Note: if there are no other buffers in queue, playback for this buffer will start. /// Note: if there are no other buffers in queue, playback for this buffer will start.
pub fn add_wave_buffer(&mut self, buffer: buf) { ///
unsafe { ctru_sys::ndspChnWaveBufAdd(self.id, buffer) }; /// # Unsafe
///
/// This function is unsafe due to how the buffer is handled internally.
/// `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.
// INTERNAL NOTE: After extensive research to make a Rust checker for these cases,
// I came to the conclusion that leaving the responsibility to the user is (as of now) the only "good" way to handle this.
// Sadly `libctru` lacks the infrastructure to make runtime checks on the queued objects, like callbacks and iterators.
// Also, in most cases the memory is still accessible even after a `free`, so it may not be a problem to the average user.
// This is still a big "hole" in the Rust wrapper. Upstream changes to `libctru` would be my go-to way to solve this issue.
pub unsafe fn queue_wave(&self, buffer: WaveInfo) {
unsafe { ctru_sys::ndspChnWaveBufAdd(self.id, &mut buffer.raw_data) };
} }
} }
impl Drop for Ndsp { impl AudioFormat {
fn drop(&mut self) { pub fn size(self) -> u8 {
unsafe { match self {
ctru_sys::ndspExit(); AudioFormat::PCM16Mono | AudioFormat::PCM16Stereo => 2,
AudioFormat::SurroundPreprocessed => {
panic!("Can't find size for Sourround Preprocessed audio: format is under research")
}
_ => 1,
} }
} }
} }
impl From<Wave> for ctru_sys::ndspWaveBuf { impl WaveBuffer {
fn from(wave: Wave) -> ctru_sys::ndspWaveBuf { pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> Self {
let WaveBuffer(vector) = wave.buffer; WaveBuffer {
let length = wave.buffer.len(); data,
let buffer_data = ctru_sys::tag_ndspWaveBuf__bindgen_ty_1 { audio_format,
data_vaddr: vector.as_ptr(), length: data.len() / format.size(),
}; }
}
ctru_sys::ndspWaveBuf { pub fn format(&self) -> AudioFormat {
__bindgen_anon_1: buffer_data, self.audio_format
looping: wave.looping, }
next: 0 as *const _,
sequence_id: 0, pub fn len(&self) -> usize {
nsamples: lenght, self.length
adpcm_data: 0 as *const _, }
}
impl WaveInfo {
pub fn new(buffer: WaveBuffer, looping: bool) -> Self {
let raw_data = ctru_sys::ndspWaveBuf {
__bindgen_anon_1: buffer.data.as_ptr(), // Buffer data virtual address
nsamples: buffer.length,
adpcm_data: std::ptr::null(),
offset: 0, offset: 0,
looping,
// The ones after this point aren't supposed to be setup by the user
status: 0, status: 0,
sequence_id: 0,
next: std::ptr::null(),
};
Self { buffer, raw_data }
}
}
impl Drop for Ndsp {
fn drop(&mut self) {
unsafe {
ctru_sys::ndspExit();
} }
} }
} }

Loading…
Cancel
Save