Andrea Ciliberti
2 years ago
4 changed files with 377 additions and 0 deletions
@ -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; |
||||||
|
} |
@ -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…
Reference in new issue