diff --git a/ctru-rs/examples/audio_filters.rs b/ctru-rs/examples/audio_filters.rs index af46feb..a0fa71c 100644 --- a/ctru-rs/examples/audio_filters.rs +++ b/ctru-rs/examples/audio_filters.rs @@ -1,5 +1,8 @@ +#![feature(allocator_api)] + use ctru::prelude::*; use ctru::services::ndsp::{Ndsp, OutputMode, InterpolationType}; +use ctru::linear::LinearAllocator; const SAMPLERATE: u32 = 22050; const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735 @@ -33,11 +36,13 @@ fn main() { 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 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); let channel_zero = ndsp.channel(0); @@ -48,34 +53,33 @@ fn main() { // Output at 100% on the first pair of left and right channels. let mix = [0f32; 12]; - memset(mix, 0, sizeof(mix)); mix[0] = 1.0; mix[1] = 1.0; - ndspChnSetMix(0, mix); + channel_zero.set_mix(mix); // Note Frequencies - int notefreq[] = { + let notefreq = [ 220, 440, 880, 1760, 3520, 7040, 14080, 7040, 3520, 1760, 880, 440 - }; + ]; - int note = 4; + let note: i32 = 4; // Filters - const char* filter_names[] = { + let filter_names = [ "None", "Low-Pass", "High-Pass", "Band-Pass", "Notch", "Peaking" - }; + ]; - int filter = 0; + let filter = 0; // We set up two wave buffers and alternate between the two, // effectively streaming an infinitely long sine wave. @@ -87,14 +91,14 @@ fn main() { waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF]; waveBuf[1].nsamples = SAMPLESPERBUF; - size_t stream_offset = 0; + let stream_offset = 0; fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2, notefreq[note]); stream_offset += SAMPLESPERBUF; - ndspChnWaveBufAdd(0, &waveBuf[0]); - ndspChnWaveBufAdd(0, &waveBuf[1]); + channel_zero.add_wave_buffer(&waveBuf[0]); + channel_zero.add_wave_buffer(&waveBuf[1]); println!("Press up/down to change tone frequency\n"); println!("Press left/right to change filter\n"); @@ -173,11 +177,4 @@ fn main() { fillBlock = !fillBlock; } } - - ndspExit(); - - linearFree(audioBuffer); - - gfxExit(); - return 0; } diff --git a/ctru-rs/src/services/ndsp.rs b/ctru-rs/src/services/ndsp.rs index 3c03ab9..7397933 100644 --- a/ctru-rs/src/services/ndsp.rs +++ b/ctru-rs/src/services/ndsp.rs @@ -1,5 +1,7 @@ use crate::error::ResultCode; -use crate::services::ServiceReference; +use crate::linear::LinearAllocator; + +use arr_macro::arr; #[derive(Copy, Clone, Debug)] #[repr(u32)] @@ -18,7 +20,7 @@ pub enum InterpolationType { } #[derive(Copy, Clone, Debug)] -#[repr(u16)] +#[repr(u32)] pub enum AudioFormat { PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16, @@ -29,75 +31,64 @@ pub enum AudioFormat { SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED, } -pub struct Wave { - /// Buffer data with specified type - buffer: Vector, - // adpcm_data: AdpcmData, TODO: Requires research on how this WaveBuffer type is handled - /// Whether to loop the track or not - looping: bool, +/// Base struct to represent audio wave data. This requires audio format information. +pub struct WaveBuffer { + /// Buffer data. This data must be allocated on the LINEAR memory. + data: Box<[u8], LinearAllocator>, + audio_format: AudioFormat, + length: usize, + // adpcm_data: AdpcmData, TODO: Requires research on how this format is handled. } -#[non_exhaustive] -pub struct Ndsp { - // To ensure safe tracking of the different channels, there must be only ONE - // existing instance of the service at a time. - _service_handler: ServiceReference, - channel_mask: u32, +pub struct WaveInfo { + /// Data block of the audio wave (plus its format information). + buffer: WaveBuffer, + // Holding the data with the raw format is necessary since `libctru` will access it. + raw_data: ctru_sys::ndspWaveBuf, } #[non_exhaustive] -pub struct Channel { - id: u8, // From 0 to 23. -} - -static NDSP_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); +pub struct Ndsp(()); impl Ndsp { pub fn init() -> crate::Result { - let _service_handler = ServiceReference::new( - &NDSP_ACTIVE, - false, - || unsafe { - ctru_sys::ndspInit(); + ResultCode(unsafe { ctru_sys::ndspInit() })?; - Ok(()) - }, - || unsafe { ctru_sys::ndspExit() }, - )?; + let mut i = 0; - Ok(Self { - _service_handler, - channel_mask: 0, - }) + Ok(Self(())) } /// Return the representation of the specified channel. /// /// # Errors /// - /// An error will be returned if the channel id is greater than 23. - pub fn channel(&self, id: u8) -> crate::Result { - if channel > 23 { + /// An error will be returned if the channel id is not between 0 and 23. + pub fn channel(&self, id: usize) -> crate::Result { + if id > 23 { return Err(crate::Error::InvalidChannel); } - Ok(Channel(id as i32)) + Ok(self.channels[id]) } /// Set the audio output mode. Defaults to `OutputMode::Stereo`. 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 { /// Reset the channel - pub fn reset(&mut self) { + pub fn reset(&self) { unsafe { ctru_sys::ndspChnReset(self.id) }; } /// Initialize the channel's parameters - pub fn init_parameters(&mut self) { + pub fn init_parameters(&self) { unsafe { ctru_sys::ndspChnInitParams(self.id) }; } @@ -122,71 +113,110 @@ impl Channel { } /// 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) }; } /// Set the channel's output format. /// Change this setting based on the used sample's format. - pub fn set_format(&mut self, format: AudioFormat) { - unsafe { ctru_sys::ndspChnSetFormat(self.id, 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(&mut self, interp_type: InterpolationType) { - unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type) }; + 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(&mut self, mix: [f32; 12]) { - unsafe { ctru_sys::ndspChnSetMix(self.id, mix) } + pub fn set_mix(&self, mix: &[f32; 12]) { + unsafe { ctru_sys::ndspChnSetMix(self.id, mix.as_mut_ptr()) } } /// 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) }; } // TODO: find a way to wrap `ndspChnSetAdpcmCoefs` - /// Clear the wave buffer's queue and stop playback. - pub fn clear_wave_buffer(&mut self) { + /// 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. - 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 { - fn drop(&mut self) { - unsafe { - ctru_sys::ndspExit(); +impl AudioFormat { + pub fn 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 From for ctru_sys::ndspWaveBuf { - fn from(wave: Wave) -> ctru_sys::ndspWaveBuf { - let WaveBuffer(vector) = wave.buffer; - let length = wave.buffer.len(); - let buffer_data = ctru_sys::tag_ndspWaveBuf__bindgen_ty_1 { - data_vaddr: vector.as_ptr(), - }; +impl WaveBuffer { + pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> Self { + WaveBuffer { + data, + audio_format, + length: data.len() / format.size(), + } + } - ctru_sys::ndspWaveBuf { - __bindgen_anon_1: buffer_data, - looping: wave.looping, - next: 0 as *const _, - sequence_id: 0, - nsamples: lenght, - adpcm_data: 0 as *const _, + pub fn format(&self) -> AudioFormat { + self.audio_format + } + + pub fn len(&self) -> usize { + self.length + } +} + +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, + looping, + // The ones after this point aren't supposed to be setup by the user 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(); } } }