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 @@ @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -173,11 +177,4 @@ fn main() {
fillBlock = !fillBlock;
}
}
ndspExit();
linearFree(audioBuffer);
gfxExit();
return 0;
}

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

@ -1,5 +1,7 @@ @@ -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 { @@ -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 { @@ -29,75 +31,64 @@ pub enum AudioFormat {
SurroundPreprocessed = ctru_sys::NDSP_3D_SURROUND_PREPROCESSED,
}
pub struct Wave {
/// Buffer data with specified type
buffer: Vector<i8>,
// 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<Mutex<usize>> = Lazy::new(|| Mutex::new(0));
pub struct Ndsp(());
impl Ndsp {
pub fn init() -> crate::Result<Self> {
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<Channel> {
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<Channel> {
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 { @@ -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<Wave> 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();
}
}
}

Loading…
Cancel
Save