Browse Source

WIP commit for NDSP

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
d00a608a19
  1. 183
      ctru-rs/examples/audio_filters.rs
  2. 1
      ctru-rs/src/error.rs
  3. 1
      ctru-rs/src/services/mod.rs
  4. 192
      ctru-rs/src/services/ndsp.rs

183
ctru-rs/examples/audio_filters.rs

@ -0,0 +1,183 @@
use ctru::prelude::*;
use ctru::services::ndsp::{Ndsp, OutputMode, InterpolationType};
const SAMPLERATE: u32 = 22050;
const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735
const BYTESPERSAMPLE: u32 = 4;
fn array_size(array: &[u8]) -> usize {
array.len()
} // (sizeof(array)/sizeof(array[0]))
// audioBuffer is stereo PCM16
void fill_buffer(void* audioBuffer, size_t offset, size_t size, int frequency) {
u32* dest = (u32*) audioBuffer;
for (int i = 0; i < size; i++) {
// This is a simple sine wave, with a frequency of `frequency` Hz, and an amplitude 30% of maximum.
s16 sample = 0.3 * 0x7FFF * sin(frequency * (2 * M_PI) * (offset + i) / SAMPLERATE);
// Stereo samples are interleaved: left and right channels.
dest[i] = (sample << 16) | (sample & 0xffff);
}
DSP_FlushDataCache(audioBuffer, size);
}
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());
println!("libctru filtered streamed audio\n");
let audioBuffer = [0u32; (SAMPLESPERBUF * BYTESPERSAMPLE * 2)];
let fillBlock = false;
let ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller");
ndsp.set_output_mode(OutputMode::Stereo);
let channel_zero = ndsp.channel(0);
channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_sample_rate(SAMPLERATE);
channel_zero.set_format(NDSP_FORMAT_STEREO_PCM16);
// 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);
// Note Frequencies
int notefreq[] = {
220,
440, 880, 1760, 3520, 7040,
14080,
7040, 3520, 1760, 880, 440
};
int note = 4;
// Filters
const char* filter_names[] = {
"None",
"Low-Pass",
"High-Pass",
"Band-Pass",
"Notch",
"Peaking"
};
int filter = 0;
// We set up two wave buffers and alternate between the two,
// effectively streaming an infinitely long sine wave.
ndspWaveBuf waveBuf[2];
memset(waveBuf,0,sizeof(waveBuf));
waveBuf[0].data_vaddr = &audioBuffer[0];
waveBuf[0].nsamples = SAMPLESPERBUF;
waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF];
waveBuf[1].nsamples = SAMPLESPERBUF;
size_t stream_offset = 0;
fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2, notefreq[note]);
stream_offset += SAMPLESPERBUF;
ndspChnWaveBufAdd(0, &waveBuf[0]);
ndspChnWaveBufAdd(0, &waveBuf[1]);
println!("Press up/down to change tone frequency\n");
println!("Press left/right to change filter\n");
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
println!("\x1b[7;1Hfilter = {} ", filter_names[filter]);
while(aptMainLoop()) {
gfxSwapBuffers();
gfxFlushBuffers();
gspWaitForVBlank();
hidScanInput();
u32 kDown = hidKeysDown();
if (kDown & KEY_START)
break; // break in order to return to hbmenu
if (kDown & KEY_DOWN) {
note--;
if (note < 0) {
note = ARRAY_SIZE(notefreq) - 1;
}
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
} else if (kDown & KEY_UP) {
note++;
if (note >= ARRAY_SIZE(notefreq)) {
note = 0;
}
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
}
bool update_params = false;
if (kDown & KEY_LEFT) {
filter--;
if (filter < 0) {
filter = ARRAY_SIZE(filter_names) - 1;
}
update_params = true;
} else if (kDown & KEY_RIGHT) {
filter++;
if (filter >= ARRAY_SIZE(filter_names)) {
filter = 0;
}
update_params = true;
}
if (update_params) {
println!("\x1b[7;1Hfilter = {} ", filter_names[filter]);
switch (filter) {
default:
ndspChnIirBiquadSetEnable(0, false);
break;
case 1:
ndspChnIirBiquadSetParamsLowPassFilter(0, 1760.f, 0.707f);
break;
case 2:
ndspChnIirBiquadSetParamsHighPassFilter(0, 1760.f, 0.707f);
break;
case 3:
ndspChnIirBiquadSetParamsBandPassFilter(0, 1760.f, 0.707f);
break;
case 4:
ndspChnIirBiquadSetParamsNotchFilter(0, 1760.f, 0.707f);
break;
case 5:
ndspChnIirBiquadSetParamsPeakingEqualizer(0, 1760.f, 0.707f, 3.0f);
break;
}
}
if (waveBuf[fillBlock].status == NDSP_WBUF_DONE) {
fill_buffer(waveBuf[fillBlock].data_pcm16, stream_offset, waveBuf[fillBlock].nsamples, notefreq[note]);
ndspChnWaveBufAdd(0, &waveBuf[fillBlock]);
stream_offset += waveBuf[fillBlock].nsamples;
fillBlock = !fillBlock;
}
}
ndspExit();
linearFree(audioBuffer);
gfxExit();
return 0;
}

1
ctru-rs/src/error.rs

@ -51,6 +51,7 @@ pub enum Error {
Libc(String), Libc(String),
ServiceAlreadyActive, ServiceAlreadyActive,
OutputAlreadyRedirected, OutputAlreadyRedirected,
InvalidChannel,
} }
impl Error { impl Error {

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

@ -4,6 +4,7 @@ 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;

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

@ -0,0 +1,192 @@
use crate::error::ResultCode;
use crate::services::ServiceReference;
#[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 InterpolationType {
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
Linear = ctru_sys::NDSP_INTERP_LINEAR,
None = ctru_sys::NDSP_INTERP_NONE,
}
#[derive(Copy, Clone, Debug)]
#[repr(u16)]
pub enum AudioFormat {
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
ADPCMMono = ctru_sys::NDSP_FORMAT_MONO_ADPCM,
PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
FrontBypass = ctru_sys::NDSP_FRONT_BYPASS,
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,
}
#[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,
}
#[non_exhaustive]
pub struct Channel {
id: u8, // From 0 to 23.
}
static NDSP_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0));
impl Ndsp {
pub fn init() -> crate::Result<Self> {
let _service_handler = ServiceReference::new(
&NDSP_ACTIVE,
false,
|| unsafe {
ctru_sys::ndspInit();
Ok(())
},
|| unsafe { ctru_sys::ndspExit() },
)?;
Ok(Self {
_service_handler,
channel_mask: 0,
})
}
/// 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 {
return Err(crate::Error::InvalidChannel);
}
Ok(Channel(id as i32))
}
/// Set the audio output mode. Defaults to `OutputMode::Stereo`.
pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ndspSetOutputMode(mode) };
}
}
impl Channel {
/// Reset the channel
pub fn reset(&mut self) {
unsafe { ctru_sys::ndspChnReset(self.id) };
}
/// Initialize the channel's parameters
pub fn init_parameters(&mut self) {
unsafe { ctru_sys::ndspChnInitParams(self.id) };
}
/// Returns whether the channel is playing any audio.
pub fn is_playing(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPlaying(self.id) }
}
/// Returns whether the channel's playback is currently paused.
pub fn is_paused(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPaused(self.id) }
}
/// Returns the channel's current sample's position.
pub fn get_sample_position(&self) -> u32 {
unsafe { ctru_sys::ndspChnGetSamplePos(self.id) }
}
/// Returns the channel's current wave sequence's id.
pub fn get_wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id) }
}
/// Pause or un-pause the channel's playback.
pub fn set_paused(&mut 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) };
}
/// Set the channel's interpolation mode.
pub fn set_interpolation(&mut self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id, interp_type) };
}
/// 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) }
}
/// Set the channel's rate of sampling.
pub fn set_sample_rate(&mut 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) {
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) };
}
}
impl Drop for Ndsp {
fn drop(&mut self) {
unsafe {
ctru_sys::ndspExit();
}
}
}
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(),
};
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 _,
offset: 0,
status: 0,
}
}
}
Loading…
Cancel
Save