Browse Source

Merge pull request #83 from rust3ds/feature/ndsp

Addition of the NDSP module and a related example
pull/87/head
Meziu 2 years ago committed by GitHub
parent
commit
6cbfbbea4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      ctru-rs/Cargo.toml
  2. 166
      ctru-rs/examples/audio-filters.rs
  3. 6
      ctru-rs/src/error.rs
  4. 4
      ctru-rs/src/gfx.rs
  5. 1
      ctru-rs/src/linear.rs
  6. 4
      ctru-rs/src/romfs.rs
  7. 9
      ctru-rs/src/services/mod.rs
  8. 342
      ctru-rs/src/services/ndsp/mod.rs
  9. 180
      ctru-rs/src/services/ndsp/wave.rs
  10. 4
      ctru-rs/src/services/soc.rs

3
ctru-rs/Cargo.toml

@ -20,7 +20,6 @@ pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121" libc = "0.2.121"
bitflags = "1.0.0" bitflags = "1.0.0"
widestring = "0.2.2" widestring = "0.2.2"
once_cell = "1.10.0"
[build-dependencies] [build-dependencies]
toml = "0.5" toml = "0.5"
@ -31,6 +30,8 @@ futures = "0.3"
time = "0.3.7" time = "0.3.7"
tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] }
cfg-if = "1.0.0" cfg-if = "1.0.0"
bytemuck = "1.12.3"
lewton = "0.10.2"
[features] [features]
default = ["romfs", "big-stack"] default = ["romfs", "big-stack"]

166
ctru-rs/examples/audio-filters.rs

@ -0,0 +1,166 @@
#![feature(allocator_api)]
use std::f32::consts::PI;
use ctru::linear::LinearAllocator;
use ctru::prelude::*;
use ctru::services::ndsp::{
wave::{WaveInfo, WaveStatus},
AudioFormat, InterpolationType, Ndsp, OutputMode,
};
const SAMPLE_RATE: usize = 22050;
const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205
const BYTES_PER_SAMPLE: usize = 4;
const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE;
// Note Frequencies
const NOTEFREQ: [f32; 7] = [220., 440., 880., 1760., 3520., 7040., 14080.];
// The audio format is Stereo PCM16
// As such, a sample is made up of 2 "Mono" samples (2 * i16 = u32), one for each channel (left and right)
fn fill_buffer(audio_data: &mut [u8], frequency: f32) {
let formatted_data = bytemuck::cast_slice_mut::<_, [i16; 2]>(audio_data);
for (i, chunk) in formatted_data.iter_mut().enumerate() {
// This is a simple sine wave, with a frequency of `frequency` Hz, and an amplitude 30% of maximum.
let sample: f32 = (frequency * (i as f32 / SAMPLE_RATE as f32) * 2. * PI).sin();
let amplitude = 0.3 * i16::MAX as f32;
let result = (sample * amplitude) as i16;
// Stereo samples are interleaved: left and right channels.
*chunk = [result, result];
}
}
fn main() {
ctru::init();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut());
let mut note: usize = 4;
// Filters
let filter_names = [
"None",
"Low-Pass",
"High-Pass",
"Band-Pass",
"Notch",
"Peaking",
];
let mut filter: i32 = 0;
// We set up two wave buffers and alternate between the two,
// effectively streaming an infinitely long sine wave.
let mut audio_data1 = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator);
fill_buffer(audio_data1.as_mut_slice(), NOTEFREQ[4]);
let audio_data2 = audio_data1.clone();
let mut wave_info1 = WaveInfo::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = WaveInfo::new(audio_data2, AudioFormat::PCM16Stereo, false);
let mut 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).unwrap();
channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_sample_rate(SAMPLE_RATE as f32);
channel_zero.set_format(AudioFormat::PCM16Stereo);
// Output at 100% on the first pair of left and right channels.
let mut mix: [f32; 12] = [0f32; 12];
mix[0] = 1.0;
mix[1] = 1.0;
channel_zero.set_mix(&mix);
channel_zero.queue_wave(&mut wave_info1).unwrap();
channel_zero.queue_wave(&mut wave_info2).unwrap();
println!("\x1b[1;1HPress up/down to change tone frequency");
println!("\x1b[2;1HPress left/right to change filter");
println!("\x1b[4;1Hnote = {} Hz ", NOTEFREQ[note]);
println!(
"\x1b[5;1Hfilter = {} ",
filter_names[filter as usize]
);
let mut altern = true; // true is wave_info1, false is wave_info2
while apt.main_loop() {
hid.scan_input();
let keys_down = hid.keys_down();
if keys_down.contains(KeyPad::KEY_START) {
break;
} // break in order to return to hbmenu
if keys_down.intersects(KeyPad::KEY_DOWN) {
note = note.saturating_sub(1);
} else if keys_down.intersects(KeyPad::KEY_UP) {
note = std::cmp::min(note + 1, NOTEFREQ.len() - 1);
}
let mut update_params = false;
if keys_down.intersects(KeyPad::KEY_LEFT) {
filter -= 1;
filter = filter.rem_euclid(filter_names.len() as _);
update_params = true;
} else if keys_down.intersects(KeyPad::KEY_RIGHT) {
filter += 1;
filter = filter.rem_euclid(filter_names.len() as _);
update_params = true;
}
println!("\x1b[4;1Hnote = {} Hz ", NOTEFREQ[note]);
println!(
"\x1b[5;1Hfilter = {} ",
filter_names[filter as usize]
);
if update_params {
match filter {
1 => channel_zero.iir_biquad_set_params_low_pass_filter(1760., 0.707),
2 => channel_zero.iir_biquad_set_params_high_pass_filter(1760., 0.707),
3 => channel_zero.iir_biquad_set_params_band_pass_filter(1760., 0.707),
4 => channel_zero.iir_biquad_set_params_notch_filter(1760., 0.707),
5 => channel_zero.iir_biquad_set_params_peaking_equalizer(1760., 0.707, 3.),
_ => channel_zero.iir_biquad_set_enabled(false),
}
}
let current: &mut WaveInfo = if altern {
&mut wave_info1
} else {
&mut wave_info2
};
let status = current.get_status();
if let WaveStatus::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
channel_zero.queue_wave(current).unwrap();
altern = !altern;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

6
ctru-rs/src/error.rs

@ -117,11 +117,7 @@ impl fmt::Display for Error {
} }
} }
impl error::Error for Error { impl error::Error for Error {}
fn description(&self) -> &str {
"error originating from a libctru function"
}
}
fn result_code_level_str(result: ctru_sys::Result) -> Cow<'static, str> { fn result_code_level_str(result: ctru_sys::Result) -> Cow<'static, str> {
use ctru_sys::{ use ctru_sys::{

4
ctru-rs/src/gfx.rs

@ -1,6 +1,5 @@
//! LCD screens manipulation helper //! LCD screens manipulation helper
use once_cell::sync::Lazy;
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Mutex; use std::sync::Mutex;
@ -119,14 +118,13 @@ pub enum Side {
/// provides helper functions and utilities for software rendering. /// provides helper functions and utilities for software rendering.
/// ///
/// The service exits when this struct is dropped. /// The service exits when this struct is dropped.
#[non_exhaustive]
pub struct Gfx { pub struct Gfx {
pub top_screen: RefCell<TopScreen>, pub top_screen: RefCell<TopScreen>,
pub bottom_screen: RefCell<BottomScreen>, pub bottom_screen: RefCell<BottomScreen>,
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static GFX_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0)); static GFX_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Gfx { impl Gfx {
/// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom

1
ctru-rs/src/linear.rs

@ -17,6 +17,7 @@ use std::ptr::NonNull;
/// [`std::alloc::Allocator`] struct for LINEAR memory /// [`std::alloc::Allocator`] struct for LINEAR memory
/// To use this struct the main crate must activate the `allocator_api` unstable feature. /// To use this struct the main crate must activate the `allocator_api` unstable feature.
#[derive(Copy, Clone, Default, Debug)]
pub struct LinearAllocator; pub struct LinearAllocator;
impl LinearAllocator { impl LinearAllocator {

4
ctru-rs/src/romfs.rs

@ -11,18 +11,16 @@
//! ``` //! ```
use crate::error::ResultCode; use crate::error::ResultCode;
use once_cell::sync::Lazy;
use std::ffi::CStr; use std::ffi::CStr;
use std::sync::Mutex; use std::sync::Mutex;
use crate::services::ServiceReference; use crate::services::ServiceReference;
#[non_exhaustive]
pub struct RomFS { pub struct RomFS {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static ROMFS_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0)); static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0);
impl RomFS { impl RomFS {
pub fn init() -> crate::Result<Self> { pub fn init() -> crate::Result<Self> {

9
ctru-rs/src/services/mod.rs

@ -1,9 +1,17 @@
//! System services used to handle system-specific functionalities.
//!
//! Most of the 3DS console's functionalities (when writing homebrew) are locked behind services,
//! which need to be initialized before accessing any particular feature.
//!
//! Some include: button input, audio playback, graphics rendering, built-in cameras, etc.
pub mod apt; pub mod apt;
pub mod cam; pub mod cam;
pub mod cfgu; pub mod cfgu;
pub mod fs; pub mod fs;
pub mod gspgpu; pub mod gspgpu;
pub mod hid; pub mod hid;
pub mod ndsp;
pub mod ps; pub mod ps;
mod reference; mod reference;
pub mod soc; pub mod soc;
@ -11,6 +19,5 @@ pub mod sslc;
pub use self::apt::Apt; pub use self::apt::Apt;
pub use self::hid::Hid; pub use self::hid::Hid;
pub use self::sslc::SslC;
pub(crate) use self::reference::ServiceReference; pub(crate) use self::reference::ServiceReference;

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

@ -0,0 +1,342 @@
//! NDSP (Audio) service
pub mod wave;
use wave::{WaveInfo, WaveStatus};
use crate::error::ResultCode;
use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut};
use std::error;
use std::fmt;
use std::sync::Mutex;
const NUMBER_OF_CHANNELS: u8 = 24;
#[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,
PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
}
#[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,
}
#[derive(Copy, Clone, Debug)]
pub enum NdspError {
/// Channel ID
InvalidChannel(u8),
/// Channel ID
ChannelAlreadyInUse(u8),
/// Channel ID
WaveBusy(u8),
/// Sample amount requested, Max sample amount
SampleCountOutOfBounds(u32, u32),
}
pub struct Channel<'ndsp> {
id: u8,
_rf: RefMut<'ndsp, ()>, // we don't need to hold any data
}
static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0);
/// Handler of the DSP service and DSP processor.
///
/// This is the main struct to handle audio playback using the 3DS' speakers and headphone jack.
/// Only one "instance" of this struct can exist at a time.
pub struct Ndsp {
_service_handler: ServiceReference,
channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize],
}
impl Ndsp {
/// Initialize the DSP service and audio units.
///
/// # Errors
///
/// This function will return an error if an instance of the `Ndsp` struct already exists
/// or if there are any issues during initialization.
pub fn init() -> crate::Result<Self> {
let _service_handler = ServiceReference::new(
&NDSP_ACTIVE,
false,
|| {
ResultCode(unsafe { ctru_sys::ndspInit() })?;
Ok(())
},
|| unsafe {
ctru_sys::ndspExit();
},
)?;
Ok(Self {
_service_handler,
channel_flags: Default::default(),
})
}
/// Return a representation of the specified channel.
///
/// # Errors
///
/// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used.
pub fn channel(&self, id: u8) -> std::result::Result<Channel, NdspError> {
let in_bounds = self.channel_flags.get(id as usize);
match in_bounds {
Some(ref_cell) => {
let flag = ref_cell.try_borrow_mut();
match flag {
Ok(_rf) => Ok(Channel { id, _rf }),
Err(_) => Err(NdspError::ChannelAlreadyInUse(id)),
}
}
None => Err(NdspError::InvalidChannel(id)),
}
}
/// 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) };
}
}
impl Channel<'_> {
/// Reset the channel
pub fn reset(&self) {
unsafe { ctru_sys::ndspChnReset(self.id.into()) };
}
/// Initialize the channel's parameters
pub fn init_parameters(&self) {
unsafe { ctru_sys::ndspChnInitParams(self.id.into()) };
}
/// Returns whether the channel is playing any audio.
pub fn is_playing(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) }
}
/// Returns whether the channel's playback is currently paused.
pub fn is_paused(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPaused(self.id.into()) }
}
// Returns the channel's id
pub fn get_id(&self) -> u8 {
self.id
}
/// Returns the channel's current sample's position.
pub fn get_sample_position(&self) -> u32 {
unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }
}
/// Returns the channel's current wave sequence's id.
pub fn get_wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
}
/// Pause or un-pause the channel's playback.
pub fn set_paused(&self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), 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.into(), format as u16) };
}
/// Set the channel's interpolation mode.
pub fn set_interpolation(&self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type as u32) };
}
/// Set the channel's volume mix.
///
/// # Notes
///
/// The buffer's format is read as:
///
/// Index 0: Front left volume <br>
/// Index 1: Front right volume <br>
/// Index 2: Back left volume <br>
/// Index 3: Back right volume <br>
/// Index 4..7: Same as 0..3 but for auxiliary output 0 <br>
/// Index 8..11: Same as 0..3 but for auxiliary output 1 <br>
pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), 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.into(), rate) };
}
// `ndspChnSetAdpcmCoefs` isn't wrapped on purpose.
// DSPADPCM is a proprietary format used by Nintendo, unavailable by "normal" means.
// We suggest using other wave formats when developing homebrew applications.
/// Clear the wave buffer queue and stop playback.
pub fn clear_queue(&self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
}
/// Add a wave buffer to the channel's queue.
/// If there are no other buffers in queue, playback for this buffer will start.
///
/// # Warning
///
/// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback.
/// To ensure safety, checks within [WaveInfo] will clear the whole channel queue if any queued [WaveInfo] is dropped prematurely.
pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> {
match wave.get_status() {
WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)),
_ => (),
}
wave.set_channel(self.id);
unsafe { ctru_sys::ndspChnWaveBufAdd(self.id.into(), &mut wave.raw_data) };
Ok(())
}
}
/// Functions to handle audio filtering.
///
/// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
impl Channel<'_> {
/// Enables/disables monopole filters.
pub fn iir_mono_set_enabled(&self, enable: bool) {
unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) };
}
/// Sets the monopole to be a high pass filter.
///
/// # Notes
///
/// This is a lower quality filter than the Biquad alternative.
pub fn iir_mono_set_params_high_pass_filter(&self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) };
}
/// Sets the monopole to be a low pass filter.
///
/// # Notes
///
/// This is a lower quality filter than the Biquad alternative.
pub fn iir_mono_set_params_low_pass_filter(&self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
}
/// Enables/disables biquad filters.
pub fn iir_biquad_set_enabled(&self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) };
}
/// Sets the biquad to be a high pass filter.
pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// Sets the biquad to be a low pass filter.
pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// Sets the biquad to be a notch filter.
pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
};
}
/// Sets the biquad to be a band pass filter.
pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
};
}
/// Sets the biquad to be a peaking equalizer.
pub fn iir_biquad_set_params_peaking_equalizer(
&self,
central_freq: f32,
quality: f32,
gain: f32,
) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer(
self.id.into(),
central_freq,
quality,
gain,
)
};
}
}
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::PCM8Mono => 1,
AudioFormat::PCM16Mono | AudioFormat::PCM8Stereo => 2,
AudioFormat::PCM16Stereo => 4,
}
}
}
impl fmt::Display for NdspError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidChannel(id) => write!(f, "Audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23."),
Self::ChannelAlreadyInUse(id) => write!(f, "Audio Channel with ID {id} is already being used. Drop the other instance if you want to use it here."),
Self::WaveBusy(id) => write!(f, "The selected WaveInfo is busy playing on channel {id}."),
Self::SampleCountOutOfBounds(samples_requested, max_samples) => write!(f, "The sample count requested is too big. Requested amount was {samples_requested} while the maximum sample count is {max_samples}."),
}
}
}
impl error::Error for NdspError {}
impl<'ndsp> Drop for Channel<'ndsp> {
fn drop(&mut self) {
self.reset();
}
}
impl Drop for Ndsp {
fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS {
self.channel(i).unwrap().clear_queue();
}
}
}

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

@ -0,0 +1,180 @@
use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf]
pub struct WaveInfo {
/// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
// Holding the data with the raw format is necessary since `libctru` will access it.
pub(crate) raw_data: ctru_sys::ndspWaveBuf,
played_on_channel: Option<u8>,
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
/// Enum representing the playback status of a [WaveInfo].
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 WaveInfo {
/// Build a new playable wave object from a raw buffer on LINEAR memory and a some info.
pub fn new(
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
looping: bool,
) -> Self {
let sample_count: usize = buffer.len() / (audio_format.sample_size() as usize);
// Signal to the DSP processor the buffer's RAM sector.
// This step may seem delicate, but testing reports failure most of the time, while still having no repercussions on the resulting audio.
unsafe {
let _r = ctru_sys::DSP_FlushDataCache(buffer.as_ptr().cast(), buffer.len() as u32);
}
let address = ctru_sys::tag_ndspWaveBuf__bindgen_ty_1 {
data_vaddr: buffer.as_ptr().cast(),
};
let raw_data = ctru_sys::ndspWaveBuf {
__bindgen_anon_1: address, // Buffer data virtual address
nsamples: sample_count as u32,
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,
audio_format,
raw_data,
played_on_channel: None,
}
}
/// Return a slice to the audio data (on the LINEAR memory).
pub fn get_buffer(&self) -> &[u8] {
&self.buffer
}
/// Return a mutable slice to the audio data (on the LINEAR memory).
///
/// # Errors
///
/// This function will return an error if the [WaveInfo] is currently busy,
/// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.get_status() {
WaveStatus::Playing | WaveStatus::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
}
_ => Ok(&mut self.buffer),
}
}
/// Return this wave's playback status.
pub fn get_status(&self) -> WaveStatus {
self.raw_data.status.try_into().unwrap()
}
/// Get the amounts of samples *read* by the NDSP process.
///
/// # Notes
///
/// This value varies depending on [Self::set_sample_count].
pub fn get_sample_count(&self) -> u32 {
self.raw_data.nsamples
}
/// Get the format of the audio data.
pub fn get_format(&self) -> AudioFormat {
self.audio_format
}
// Set the internal flag for the id of the channel playing this wave.
//
// Internal Use Only.
pub(crate) fn set_channel(&mut self, id: u8) {
self.played_on_channel = Some(id)
}
/// Set the amount of samples to be read.
/// This function doesn't resize the internal buffer.
///
/// # Note
///
/// Operations of this kind are particularly useful to allocate memory pools
/// for VBR (Variable BitRate) Formats, like OGG Vorbis.
///
/// # Errors
///
/// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the WaveInfo is currently queued.
pub fn set_sample_count(&mut self, sample_count: u32) -> Result<(), NdspError> {
match self.get_status() {
WaveStatus::Playing | WaveStatus::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap()));
}
_ => (),
}
let max_count: usize = self.buffer.len() / (self.audio_format.sample_size() as usize);
if sample_count > max_count as u32 {
return Err(NdspError::SampleCountOutOfBounds(
sample_count,
max_count as u32,
));
}
self.raw_data.nsamples = sample_count;
Ok(())
}
}
impl TryFrom<u8> for WaveStatus {
type Error = &'static str;
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("Invalid WaveInfo Status code"),
}
}
}
impl Drop for WaveInfo {
fn drop(&mut self) {
// This was the only way I found I could check for improper drops of `WaveInfos`.
// A panic was considered, but it would cause issues with drop order against `Ndsp`.
match self.get_status() {
WaveStatus::Free | WaveStatus::Done => (),
// If the status flag is "unfinished"
_ => {
// The unwrap is safe, since it must have a value in the case the status is "unfinished".
unsafe { ctru_sys::ndspChnWaveBufClear(self.played_on_channel.unwrap().into()) };
}
}
unsafe {
// Flag the buffer's RAM sector as unused
// This step has no real effect in normal applications and is skipped even by devkitPRO's own examples.
let _r = ctru_sys::DSP_InvalidateDataCache(
self.buffer.as_ptr().cast(),
self.buffer.len().try_into().unwrap(),
);
}
}
}

4
ctru-rs/src/services/soc.rs

@ -1,5 +1,4 @@
use libc::memalign; use libc::memalign;
use once_cell::sync::Lazy;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::Mutex; use std::sync::Mutex;
@ -9,13 +8,12 @@ use crate::Error;
/// Soc service. Initializing this service will enable the use of network sockets and utilities /// Soc service. Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will be closed when this struct is is dropped. /// such as those found in `std::net`. The service will be closed when this struct is is dropped.
#[non_exhaustive]
pub struct Soc { pub struct Soc {
_service_handler: ServiceReference, _service_handler: ServiceReference,
sock_3dslink: libc::c_int, sock_3dslink: libc::c_int,
} }
static SOC_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0)); static SOC_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Soc { impl Soc {
/// Initialize the Soc service with a default buffer size of 0x100000 bytes /// Initialize the Soc service with a default buffer size of 0x100000 bytes

Loading…
Cancel
Save