diff --git a/ctru-rs/examples/audio-filters.rs b/ctru-rs/examples/audio-filters.rs index 747f935..d171dc0 100644 --- a/ctru-rs/examples/audio-filters.rs +++ b/ctru-rs/examples/audio-filters.rs @@ -149,7 +149,7 @@ fn main() { let status = current.get_status(); if let WaveStatus::Done = status { - fill_buffer(current.get_buffer_mut(), NOTEFREQ[note]); + fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); channel_zero.queue_wave(current).unwrap(); diff --git a/ctru-rs/examples/ogg-audio.rs b/ctru-rs/examples/ogg-audio.rs deleted file mode 100644 index ef6baf5..0000000 --- a/ctru-rs/examples/ogg-audio.rs +++ /dev/null @@ -1,138 +0,0 @@ -#![feature(allocator_api)] -use ctru::linear::LinearAllocator; - -use ctru::prelude::*; -use ctru::services::ndsp::{ - wave::{WaveInfo, WaveStatus}, - AudioFormat, Channel, InterpolationType, Ndsp, OutputMode, -}; - -use lewton::inside_ogg::OggStreamReader; -use lewton::samples::InterleavedSamples; - -use std::fs::File; - -const CHANNEL_COUNT: usize = 2; -// It should never get this big, but extra space is useful since we can't know the size beforehand -// Read -const AUDIO_WAVE_SIZE: usize = 4000 * CHANNEL_COUNT; - -fn setup_next_wave( - stream_reader: &mut OggStreamReader, - channel: &Channel, - wave_info: &mut WaveInfo, -) { - // Interleaved Dual Channel (Stereo PCM16) - match stream_reader - .read_dec_packet_generic::>() - .unwrap() - { - Some(pck) => { - // A good way to handle the data would be to allocate it on LINEAR memory directly within `lewton`, - // but since that API isn't exposed (for its instability) a memcopy is needed either way. - - let mut samples = pck.samples; - let raw_samples = bytemuck::cast_slice_mut::<_, u8>(samples.as_mut_slice()); - - // We need only the first part of the slice to clone the data over - let buf = wave_info.get_buffer_mut(); - let (buf, _) = buf.split_at_mut(raw_samples.len()); - - buf.copy_from_slice(raw_samples); - - // We change the sample_count of the WaveInfo so NDSP won't read the unused bytes - wave_info - .set_sample_count((samples.len() / pck.channel_count) as u32) - .unwrap(); - - channel.queue_wave(wave_info).unwrap(); - } - None => return, // do nothing when the audio ends - } -} - -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()); - - cfg_if::cfg_if! { - if #[cfg(not(all(feature = "romfs", romfs_exists)))] { - panic!("No RomFS was found, are you sure you included it?") - } - } - - let _romfs = ctru::romfs::RomFS::init().unwrap(); - let f = File::open("romfs:/music.ogg").unwrap(); - let mut srr = OggStreamReader::new(f).unwrap(); - - println!("\nPress START to exit"); - println!("Sample rate: {}", srr.ident_hdr.audio_sample_rate); - - // If the audio is Mono or it has more channels than 2, it's incompatibile with this current setup - if srr.ident_hdr.audio_channels != 2 { - panic!( - "Audio file improperly modified: Number of channels incompatible with Stereo output" - ); - } - - // We setup the alternating buffers - let audio_data1 = Box::new_in([0u8; AUDIO_WAVE_SIZE], LinearAllocator); - 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); - - // NDSP setup - let mut ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller"); - ndsp.set_output_mode(OutputMode::Stereo); - - let channel_zero = ndsp.channel(0).unwrap(); - channel_zero.set_interpolation(InterpolationType::Linear); - channel_zero.set_sample_rate(srr.ident_hdr.audio_sample_rate as f32); - channel_zero.set_format(AudioFormat::PCM16Stereo); - - setup_next_wave(&mut srr, &channel_zero, &mut wave_info1); - setup_next_wave(&mut srr, &channel_zero, &mut wave_info2); - - // Audio volume mix - let mut mix: [f32; 12] = [0f32; 12]; - mix[0] = 0.8; - mix[1] = 0.8; - channel_zero.set_mix(&mix); - - let mut altern = true; // true is wave_info1, false is wave_info2 - - // Main loop - while apt.main_loop() { - //Scan all the inputs. This should be done once for each frame - hid.scan_input(); - - if hid.keys_down().contains(KeyPad::KEY_START) { - break; - } - - let current: &mut WaveInfo = if altern { - &mut wave_info1 - } else { - &mut wave_info2 - }; - - match current.get_status() { - WaveStatus::Free | WaveStatus::Done => { - setup_next_wave(&mut srr, &channel_zero, current); - - altern = !altern; - } - _ => (), - } - - // Flush and swap framebuffers - gfx.flush_buffers(); - gfx.swap_buffers(); - - // We don't vsync here because that would slow down the sample's decoding. - // Audio code is supposed to run on a different thread in normal applications. - } -} diff --git a/ctru-rs/examples/romfs/music.ogg b/ctru-rs/examples/romfs/music.ogg deleted file mode 100644 index 677dc2b..0000000 Binary files a/ctru-rs/examples/romfs/music.ogg and /dev/null differ diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index 058177f..941c3d0 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -13,6 +13,7 @@ pub struct WaveInfo { #[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, @@ -21,6 +22,7 @@ pub enum WaveStatus { } 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, @@ -58,26 +60,49 @@ impl WaveInfo { } } + /// Return a slice to the audio data (on the LINEAR memory). pub fn get_buffer(&self) -> &[u8] { &self.buffer } - pub fn get_buffer_mut(&mut self) -> &mut [u8] { - &mut 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 => { + return 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) } @@ -87,7 +112,7 @@ impl WaveInfo { /// /// # Note /// - /// Operations of this kind are particularly usefulto allocate memory pools + /// Operations of this kind are particularly useful to allocate memory pools /// for VBR (Variable BitRate) Formats, like OGG Vorbis. /// /// # Errors