Browse Source

First attempt at audio playback with OGG Vorbis

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
82d8ae68b6
  1. 1
      ctru-rs/Cargo.toml
  2. 135
      ctru-rs/examples/ogg-audio.rs
  3. BIN
      ctru-rs/examples/romfs/music.ogg
  4. 4
      ctru-rs/src/services/ndsp/mod.rs
  5. 38
      ctru-rs/src/services/ndsp/wave.rs

1
ctru-rs/Cargo.toml

@ -31,6 +31,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" bytemuck = "1.12.3"
lewton = "0.10.2"
[features] [features]
default = ["romfs", "big-stack"] default = ["romfs", "big-stack"]

135
ctru-rs/examples/ogg-audio.rs

@ -0,0 +1,135 @@
#![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<File>,
channel: &Channel,
wave_info: &mut WaveInfo,
) {
// Interleaved Dual Channel (Stereo PCM16)
match stream_reader
.read_dec_packet_generic::<InterleavedSamples<i16>>()
.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();
}
}

BIN
ctru-rs/examples/romfs/music.ogg

Binary file not shown.

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

@ -43,6 +43,7 @@ pub enum NdspError {
InvalidChannel(u8), // channel id InvalidChannel(u8), // channel id
ChannelAlreadyInUse(u8), // channel id ChannelAlreadyInUse(u8), // channel id
WaveAlreadyQueued(u8), // channel id WaveAlreadyQueued(u8), // channel id
SampleCountOutOfBounds(u32, u32), // amount requested, maximum amount
} }
pub struct Channel<'ndsp> { pub struct Channel<'ndsp> {
@ -288,7 +289,8 @@ impl fmt::Display for NdspError {
match self { 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::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::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::WaveAlreadyQueued(id) => write!(f, "The selected WaveInfo is already playing on channel {id}.") Self::WaveAlreadyQueued(id) => write!(f, "The selected WaveInfo is already 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}."),
} }
} }
} }

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

@ -1,4 +1,4 @@
use super::AudioFormat; use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator; use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf] /// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf]
@ -81,6 +81,42 @@ impl WaveInfo {
pub(crate) fn set_channel(&mut self, id: u8) { pub(crate) fn set_channel(&mut self, id: u8) {
self.played_on_channel = Some(id) 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 usefulto 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::WaveAlreadyQueued(
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 { impl TryFrom<u8> for WaveStatus {

Loading…
Cancel
Save