|
|
@ -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(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|