Browse Source

WIP Rustification of the example

pull/83/head
Andrea Ciliberti 2 years ago
parent
commit
169d353d9f
  1. 308
      ctru-rs/examples/audio_filters.rs
  2. 36
      ctru-rs/src/services/ndsp.rs

308
ctru-rs/examples/audio_filters.rs

@ -1,180 +1,170 @@
#![feature(allocator_api)] #![feature(allocator_api)]
use ctru::prelude::*;
use ctru::services::ndsp::{Ndsp, OutputMode, InterpolationType};
use ctru::linear::LinearAllocator; use ctru::linear::LinearAllocator;
use ctru::prelude::*;
use ctru::services::ndsp::{
AudioFormat, InterpolationType, Ndsp, OutputMode, WaveBuffer, WaveInfo,
};
const SAMPLERATE: u32 = 22050; const SAMPLERATE: u32 = 22050;
const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735 const SAMPLESPERBUF: u32 = SAMPLERATE / 30; // 735
const BYTESPERSAMPLE: u32 = 4; const BYTESPERSAMPLE: u32 = 4;
fn array_size(array: &[u8]) -> usize { fn array_size(array: &[u8]) -> usize {
array.len() array.len()
} // (sizeof(array)/sizeof(array[0])) } // (sizeof(array)/sizeof(array[0]))
// audioBuffer is stereo PCM16 // audioBuffer is stereo PCM16
void fill_buffer(void* audioBuffer, size_t offset, size_t size, int frequency) { fn fill_buffer(audioData: &mut Box<[u8], LinearAlloc>, frequency: i32) {
u32* dest = (u32*) audioBuffer; for i in 0..size {
// This is a simple sine wave, with a frequency of `frequency` Hz, and an amplitude 30% of maximum.
for (int i = 0; i < size; i++) { let sample: i16 = 0.3 * 0x7FFF * sin(frequency * (2 * std::f32::PI) * i / SAMPLERATE);
// 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.
audioData[i] = (sample << 16) | (sample & 0xffff);
// Stereo samples are interleaved: left and right channels. }
dest[i] = (sample << 16) | (sample & 0xffff);
}
DSP_FlushDataCache(audioBuffer, size);
} }
fn main() { fn main() {
ctru::init(); ctru::init();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let apt = Apt::init().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::init(gfx.top_screen.borrow_mut());
println!("libctru filtered streamed audio\n"); println!("libctru filtered streamed audio\n");
let audioBuffer = Box::new_in([0u32; (SAMPLESPERBUF * BYTESPERSAMPLE * 2)], LinearAllocator); let audioBuffer = Box::new_in(
[0u32; (SAMPLESPERBUF * BYTESPERSAMPLE * 2)],
let fillBlock = false; LinearAllocator,
);
let ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller"); fill_buffer(audioBuffer, notefreq[note]);
// This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo` let audioBuffer1 =
ndsp.set_output_mode(OutputMode::Stereo); WaveBuffer::new(audioBuffer, AudioFormat::PCM16Stereo).expect("Couldn't sync DSP cache");
let audioBuffer2 = audioBuffer1.clone();
let channel_zero = ndsp.channel(0);
channel_zero.set_interpolation(InterpolationType::Linear); let fillBlock = false;
channel_zero.set_sample_rate(SAMPLERATE);
channel_zero.set_format(NDSP_FORMAT_STEREO_PCM16); let ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller");
// Output at 100% on the first pair of left and right channels. // This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo`
ndsp.set_output_mode(OutputMode::Stereo);
let mix = [0f32; 12];
mix[0] = 1.0; let channel_zero = ndsp.channel(0);
mix[1] = 1.0; channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_mix(mix); channel_zero.set_sample_rate(SAMPLERATE);
channel_zero.set_format(NDSP_FORMAT_STEREO_PCM16);
// Note Frequencies
// Output at 100% on the first pair of left and right channels.
let notefreq = [
220, let mix = [0f32; 12];
440, 880, 1760, 3520, 7040, mix[0] = 1.0;
14080, mix[1] = 1.0;
7040, 3520, 1760, 880, 440 channel_zero.set_mix(mix);
];
// Note Frequencies
let note: i32 = 4;
let notefreq = [
// Filters 220, 440, 880, 1760, 3520, 7040, 14080, 7040, 3520, 1760, 880, 440,
];
let filter_names = [
"None", let note: i32 = 4;
"Low-Pass",
"High-Pass", // Filters
"Band-Pass",
"Notch", let filter_names = [
"Peaking" "None",
]; "Low-Pass",
"High-Pass",
let filter = 0; "Band-Pass",
"Notch",
// We set up two wave buffers and alternate between the two, "Peaking",
// effectively streaming an infinitely long sine wave. ];
ndspWaveBuf waveBuf[2]; let filter = 0;
memset(waveBuf,0,sizeof(waveBuf));
waveBuf[0].data_vaddr = &audioBuffer[0]; // We set up two wave buffers and alternate between the two,
waveBuf[0].nsamples = SAMPLESPERBUF; // effectively streaming an infinitely long sine wave.
waveBuf[1].data_vaddr = &audioBuffer[SAMPLESPERBUF];
waveBuf[1].nsamples = SAMPLESPERBUF; let mut buf1 = WaveInfo::new(&mut audioBuffer1, false);
let mut buf2 = WaveInfo::new(&mut audioBuffer2, false);
let stream_offset = 0;
unsafe {
fill_buffer(audioBuffer,stream_offset, SAMPLESPERBUF * 2, notefreq[note]); channel_zero.add_wave_buffer(buf1);
channel_zero.add_wave_buffer(buf2);
stream_offset += SAMPLESPERBUF; };
channel_zero.add_wave_buffer(&waveBuf[0]); println!("Press up/down to change tone frequency\n");
channel_zero.add_wave_buffer(&waveBuf[1]); println!("Press left/right to change filter\n");
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
println!("Press up/down to change tone frequency\n"); println!("\x1b[7;1Hfilter = {} ", filter_names[filter]);
println!("Press left/right to change filter\n");
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]); while apt.main_loop() {
println!("\x1b[7;1Hfilter = {} ", filter_names[filter]); hid.scan_input();
let keys_down = hid.keys_down();
while(aptMainLoop()) {
if keys_down.contains(KeyPad::KEY_START) {
gfxSwapBuffers(); break;
gfxFlushBuffers(); } // break in order to return to hbmenu
gspWaitForVBlank();
if keys_down.contains(KeyPad::KEY_DOWN) {
hidScanInput(); note -= 1;
u32 kDown = hidKeysDown(); if (note < 0) {
note = notefreq.len() - 1;
if (kDown & KEY_START) }
break; // break in order to return to hbmenu println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
} else if keys_down.contains(KeyPad::KEY_UP) {
if (kDown & KEY_DOWN) { note += 1;
note--; if (note >= notefreq.len()) {
if (note < 0) { note = 0;
note = ARRAY_SIZE(notefreq) - 1; }
} println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]);
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]); }
} else if (kDown & KEY_UP) {
note++; let update_params = false;
if (note >= ARRAY_SIZE(notefreq)) { if keys_down.contains(KeyPad::KEY_LEFT) {
note = 0; filter -= 1;
} if (filter < 0) {
println!("\x1b[6;1Hnote = {} Hz ", notefreq[note]); filter = filter_names.len() - 1;
} }
update_params = true;
bool update_params = false; } else if keys_down.contains(KeyPad::KEY_LEFT) {
if (kDown & KEY_LEFT) { filter += 1;
filter--; if (filter >= filter_names.len()) {
if (filter < 0) { filter = 0;
filter = ARRAY_SIZE(filter_names) - 1; }
} update_params = true;
update_params = true; }
} else if (kDown & KEY_RIGHT) {
filter++; if update_params {
if (filter >= ARRAY_SIZE(filter_names)) { println!("\x1b[7;1Hfilter = {} ", filter_names[filter]);
filter = 0; match filter {
} 1 => ndspChnIirBiquadSetParamsLowPassFilter(0, 1760., 0.707),
update_params = true; 2 => ndspChnIirBiquadSetParamsHighPassFilter(0, 1760., 0.707),
} 3 => ndspChnIirBiquadSetParamsBandPassFilter(0, 1760., 0.707),
4 => ndspChnIirBiquadSetParamsNotchFilter(0, 1760., 0.707),
if (update_params) { 5 => ndspChnIirBiquadSetParamsPeakingEqualizer(0, 1760., 0.707, 3.0),
println!("\x1b[7;1Hfilter = {} ", filter_names[filter]); _ => ndspChnIirBiquadSetEnable(0, false),
switch (filter) { }
default: }
ndspChnIirBiquadSetEnable(0, false);
break; if waveBuf[fillBlock].status == NDSP_WBUF_DONE {
case 1: if fillBlock {
ndspChnIirBiquadSetParamsLowPassFilter(0, 1760.f, 0.707f); fill_buffer(buf1.data_pcm16, notefreq[note]);
break; channel_zero.add_wave_buffer(buf1);
case 2: } else {
ndspChnIirBiquadSetParamsHighPassFilter(0, 1760.f, 0.707f); fill_buffer(waveBuf[fillBlock].data_pcm16, notefreq[note]);
break; channel_zero.add_wave_buffer(buf2);
case 3: }
ndspChnIirBiquadSetParamsBandPassFilter(0, 1760.f, 0.707f); fillBlock = !fillBlock;
break; }
case 4:
ndspChnIirBiquadSetParamsNotchFilter(0, 1760.f, 0.707f); // Flush and swap framebuffers
break; gfx.flush_buffers();
case 5: gfx.swap_buffers();
ndspChnIirBiquadSetParamsPeakingEqualizer(0, 1760.f, 0.707f, 3.0f);
break; //Wait for VBlank
} gfx.wait_for_vblank();
} }
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;
}
}
} }

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

@ -32,17 +32,19 @@ pub enum AudioFormat {
} }
/// Base struct to represent audio wave data. This requires audio format information. /// Base struct to represent audio wave data. This requires audio format information.
#[derive(Debug, Clone)]
pub struct WaveBuffer { pub struct WaveBuffer {
/// Buffer data. This data must be allocated on the LINEAR memory. /// Buffer data. This data must be allocated on the LINEAR memory.
data: Box<[u8], LinearAllocator>, data: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, audio_format: AudioFormat,
length: usize, nsamples: usize, // We don't use the slice's length here because depending on the format it may vary
// adpcm_data: AdpcmData, TODO: Requires research on how this format is handled. // adpcm_data: AdpcmData, TODO: Requires research on how this format is handled.
} }
pub struct WaveInfo { /// Informational struct holding the raw audio data and playaback info. This corresponds to [ctru_sys::ndspWaveBuf]
pub struct WaveInfo<'b> {
/// Data block of the audio wave (plus its format information). /// Data block of the audio wave (plus its format information).
buffer: WaveBuffer, buffer: &'b mut WaveBuffer,
// Holding the data with the raw format is necessary since `libctru` will access it. // Holding the data with the raw format is necessary since `libctru` will access it.
raw_data: ctru_sys::ndspWaveBuf, raw_data: ctru_sys::ndspWaveBuf,
} }
@ -178,12 +180,22 @@ impl AudioFormat {
} }
impl WaveBuffer { impl WaveBuffer {
pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> Self { pub fn new(data: Box<[u8], LinearAllocator>, audio_format: AudioFormat) -> crate::Result<Self> {
WaveBuffer { let nsamples = data.len() / format.size();
unsafe {
ResultCode(ctru_sys::DSP_FlushDataCache(data.as_ptr(), data.len()))?;
}
Ok(WaveBuffer {
data, data,
audio_format, audio_format,
length: data.len() / format.size(), nsamples,
} })
}
pub fn get_mut_data(&mut self) -> &mut Box<[u8], LinearAllocator> {
&mut self.data
} }
pub fn format(&self) -> AudioFormat { pub fn format(&self) -> AudioFormat {
@ -195,8 +207,8 @@ impl WaveBuffer {
} }
} }
impl WaveInfo { impl<'b> WaveInfo<'b> {
pub fn new(buffer: WaveBuffer, looping: bool) -> Self { pub fn new(buffer: &'b mut WaveBuffer, looping: bool) -> Self {
let raw_data = ctru_sys::ndspWaveBuf { let raw_data = ctru_sys::ndspWaveBuf {
__bindgen_anon_1: buffer.data.as_ptr(), // Buffer data virtual address __bindgen_anon_1: buffer.data.as_ptr(), // Buffer data virtual address
nsamples: buffer.length, nsamples: buffer.length,
@ -211,6 +223,10 @@ impl WaveInfo {
Self { buffer, raw_data } Self { buffer, raw_data }
} }
pub fn get_mut_wavebuffer(&mut self) -> &'b mut WaveBuffer {
&mut self.buffer
}
} }
impl Drop for Ndsp { impl Drop for Ndsp {

Loading…
Cancel
Save