ctru/services/ndsp/mod.rs
1//! NDSP (Audio) service.
2//!
3//! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard.
4//! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device
5//! connected via the audio jack.
6//!
7//! To use NDSP audio, you will need to dump DSP firmware from a real 3DS using
8//! something like [DSP1](https://www.gamebrew.org/wiki/DSP1_3DS).
9//!
10//! `libctru` expects to find it at `sdmc:/3ds/dspfirm.cdc` when initializing the NDSP service.
11#![doc(alias = "audio")]
12
13// As a result of requiring DSP firmware to initialize, all of the doctests in
14// this module are `no_run`, since Citra doesn't provide a stub for the DSP firmware:
15// https://github.com/citra-emu/citra/issues/6111
16
17pub mod wave;
18use wave::{Status, Wave};
19
20use crate::error::ResultCode;
21use crate::linear::LinearAllocation;
22use crate::services::ServiceReference;
23
24use std::cell::{RefCell, RefMut};
25use std::error;
26use std::fmt;
27use std::sync::Mutex;
28
29const NUMBER_OF_CHANNELS: u8 = 24;
30
31/// Audio output mode.
32#[doc(alias = "ndspOutputMode")]
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
34#[repr(u8)]
35pub enum OutputMode {
36 /// Single-Channel.
37 Mono = ctru_sys::NDSP_OUTPUT_MONO,
38 /// Dual-Channel.
39 Stereo = ctru_sys::NDSP_OUTPUT_STEREO,
40 /// Surround.
41 Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
42}
43
44/// PCM formats supported by the audio engine.
45#[derive(Copy, Clone, Debug, PartialEq, Eq)]
46#[repr(u8)]
47pub enum AudioFormat {
48 /// PCM 8bit single-channel.
49 PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
50 /// PCM 16bit single-channel.
51 PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
52 /// PCM 8bit interleaved dual-channel.
53 PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
54 /// PCM 16bit interleaved dual-channel.
55 PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
56}
57
58/// Representation of the volume mix for a channel.
59#[derive(Copy, Clone, Debug, PartialEq)]
60pub struct AudioMix {
61 raw: [f32; 12],
62}
63
64/// Auxiliary Device index.
65#[derive(Copy, Clone, Debug, PartialEq, Eq)]
66#[repr(usize)]
67pub enum AuxDevice {
68 /// Aux device with index 0.
69 Zero = 0,
70 /// Aux device with index 1.
71 One = 1,
72}
73
74/// Interpolation used between audio frames.
75#[doc(alias = "ndspInterpType")]
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
77#[repr(u8)]
78pub enum InterpolationType {
79 /// Polyphase interpolation.
80 Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
81 /// Linear interpolation.
82 Linear = ctru_sys::NDSP_INTERP_LINEAR,
83 /// No interpolation.
84 None = ctru_sys::NDSP_INTERP_NONE,
85}
86
87/// Errors returned by [`ndsp`](self) functions.
88#[non_exhaustive]
89#[derive(Copy, Clone, Debug, PartialEq, Eq)]
90pub enum Error {
91 /// Channel with the specified ID does not exist.
92 InvalidChannel(u8),
93 /// Channel with the specified ID is already being used.
94 ChannelAlreadyInUse(u8),
95 /// The wave is already busy playing in the channel with the specified ID.
96 WaveBusy(u8),
97 /// The sample amount requested was larger than the maximum.
98 SampleCountOutOfBounds(usize, usize),
99}
100
101/// NDSP Channel representation.
102///
103/// There are 24 individual channels in total and each can play a different audio [`Wave`] simultaneuosly.
104///
105/// # Default
106///
107/// NDSP initialises all channels with default values on initialization, but the developer is supposed to change these values to correctly work with the service.
108///
109/// In particular:
110/// - Default audio format is set to [`AudioFormat::PCM16Mono`].
111/// - Default sample rate is set to 1 Hz.
112/// - Default interpolation type is set to [`InterpolationType::Polyphase`].
113/// - Default mix is set to [`AudioMix::default()`]
114///
115/// The handle to a channel can be retrieved with [`Ndsp::channel()`]
116pub struct Channel<'ndsp> {
117 id: u8,
118 _rf: RefMut<'ndsp, ()>, // we don't need to hold any data
119}
120
121static NDSP_ACTIVE: Mutex<()> = Mutex::new(());
122
123/// Handle to the DSP service.
124///
125/// Only one handle for this service can exist at a time.
126pub struct Ndsp {
127 _service_handler: ServiceReference,
128 channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize],
129}
130
131impl Ndsp {
132 /// Initialize the DSP service and audio units.
133 ///
134 /// # Errors
135 ///
136 /// This function will return an error if an instance of the [`Ndsp`] struct already exists
137 /// or if there are any issues during initialization (for example, DSP firmware
138 /// cannot be found. See [module documentation](super::ndsp) for more details.).
139 ///
140 /// # Example
141 ///
142 /// ```no_run
143 /// # use std::error::Error;
144 /// # fn main() -> Result<(), Box<dyn Error>> {
145 /// #
146 /// use ctru::services::ndsp::Ndsp;
147 ///
148 /// let ndsp = Ndsp::new()?;
149 /// #
150 /// # Ok(())
151 /// # }
152 /// ```
153 #[doc(alias = "ndspInit")]
154 pub fn new() -> crate::Result<Self> {
155 let _service_handler = ServiceReference::new(
156 &NDSP_ACTIVE,
157 || {
158 ResultCode(unsafe { ctru_sys::ndspInit() })?;
159
160 Ok(())
161 },
162 || unsafe {
163 ctru_sys::ndspExit();
164 },
165 )?;
166
167 Ok(Self {
168 _service_handler,
169 channel_flags: Default::default(),
170 })
171 }
172
173 /// Return a representation of the specified channel.
174 ///
175 /// # Errors
176 ///
177 /// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used.
178 ///
179 /// # Example
180 ///
181 /// ```no_run
182 /// # use std::error::Error;
183 /// # fn main() -> Result<(), Box<dyn Error>> {
184 /// #
185 /// use ctru::services::ndsp::Ndsp;
186 /// let ndsp = Ndsp::new()?;
187 ///
188 /// let channel_0 = ndsp.channel(0)?;
189 /// #
190 /// # Ok(())
191 /// # }
192 /// ```
193 pub fn channel(&self, id: u8) -> std::result::Result<Channel<'_>, Error> {
194 let in_bounds = self.channel_flags.get(id as usize);
195
196 match in_bounds {
197 Some(ref_cell) => {
198 let flag = ref_cell.try_borrow_mut();
199 match flag {
200 Ok(_rf) => Ok(Channel { id, _rf }),
201 Err(_) => Err(Error::ChannelAlreadyInUse(id)),
202 }
203 }
204 None => Err(Error::InvalidChannel(id)),
205 }
206 }
207
208 /// Set the audio output mode. Defaults to [`OutputMode::Stereo`].
209 ///
210 /// # Example
211 ///
212 /// ```no_run
213 /// # use std::error::Error;
214 /// # fn main() -> Result<(), Box<dyn Error>> {
215 /// #
216 /// use ctru::services::ndsp::{Ndsp, OutputMode};
217 /// let mut ndsp = Ndsp::new()?;
218 ///
219 /// // Use dual-channel output.
220 /// ndsp.set_output_mode(OutputMode::Stereo);
221 /// #
222 /// # Ok(())
223 /// # }
224 /// ```
225 #[doc(alias = "ndspSetOutputMode")]
226 pub fn set_output_mode(&mut self, mode: OutputMode) {
227 unsafe { ctru_sys::ndspSetOutputMode(mode.into()) };
228 }
229}
230
231impl Channel<'_> {
232 /// Reset the channel (clear the queue and reset parameters).
233 ///
234 /// # Example
235 ///
236 /// ```no_run
237 /// # use std::error::Error;
238 /// # fn main() -> Result<(), Box<dyn Error>> {
239 /// #
240 /// use ctru::services::ndsp::Ndsp;
241 /// let ndsp = Ndsp::new()?;
242 /// let mut channel_0 = ndsp.channel(0)?;
243 ///
244 /// channel_0.reset();
245 /// #
246 /// # Ok(())
247 /// # }
248 /// ```
249 #[doc(alias = "ndspChnReset")]
250 pub fn reset(&mut self) {
251 unsafe { ctru_sys::ndspChnReset(self.id.into()) };
252 }
253
254 /// Initialize the channel's parameters with default values.
255 ///
256 /// # Example
257 ///
258 /// ```no_run
259 /// # use std::error::Error;
260 /// # fn main() -> Result<(), Box<dyn Error>> {
261 /// #
262 /// use ctru::services::ndsp::Ndsp;
263 /// let ndsp = Ndsp::new()?;
264 /// let mut channel_0 = ndsp.channel(0)?;
265 ///
266 /// channel_0.init_parameters();
267 /// #
268 /// # Ok(())
269 /// # }
270 /// ```
271 #[doc(alias = "ndspChnInitParams")]
272 pub fn init_parameters(&mut self) {
273 unsafe { ctru_sys::ndspChnInitParams(self.id.into()) };
274 }
275
276 /// Returns whether the channel is playing any audio.
277 ///
278 /// # Example
279 ///
280 /// ```no_run
281 /// # use std::error::Error;
282 /// # fn main() -> Result<(), Box<dyn Error>> {
283 /// #
284 /// use ctru::services::ndsp::Ndsp;
285 /// let ndsp = Ndsp::new()?;
286 /// let mut channel_0 = ndsp.channel(0)?;
287 ///
288 /// // The channel is not playing any audio.
289 /// assert!(!channel_0.is_playing());
290 /// #
291 /// # Ok(())
292 /// # }
293 /// ```
294 #[doc(alias = "ndspChnIsPlaying")]
295 pub fn is_playing(&self) -> bool {
296 unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) }
297 }
298
299 /// Returns whether the channel's playback is currently paused.
300 ///
301 /// # Example
302 ///
303 /// ```no_run
304 /// # use std::error::Error;
305 /// # fn main() -> Result<(), Box<dyn Error>> {
306 /// #
307 /// use ctru::services::ndsp::Ndsp;
308 /// let ndsp = Ndsp::new()?;
309 /// let mut channel_0 = ndsp.channel(0)?;
310 ///
311 /// // The channel is not paused.
312 /// assert!(!channel_0.is_paused());
313 /// #
314 /// # Ok(())
315 /// # }
316 /// ```
317 #[doc(alias = "ndspChnIsPaused")]
318 pub fn is_paused(&self) -> bool {
319 unsafe { ctru_sys::ndspChnIsPaused(self.id.into()) }
320 }
321
322 /// Returns the channel's index.
323 ///
324 /// # Example
325 ///
326 /// ```no_run
327 /// # use std::error::Error;
328 /// # fn main() -> Result<(), Box<dyn Error>> {
329 /// #
330 /// use ctru::services::ndsp::Ndsp;
331 /// let ndsp = Ndsp::new()?;
332 /// let mut channel_0 = ndsp.channel(0)?;
333 ///
334 /// // The channel's index is 0.
335 /// assert_eq!(channel_0.id(), 0);
336 /// #
337 /// # Ok(())
338 /// # }
339 /// ```
340 pub fn id(&self) -> u8 {
341 self.id
342 }
343
344 /// Returns the index of the currently played sample.
345 ///
346 /// Because of how fast this value changes, it should only be used as a rough estimate of the current progress.
347 #[doc(alias = "ndspChnGetSamplePos")]
348 pub fn sample_position(&self) -> usize {
349 (unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize
350 }
351
352 /// Returns the channel's current wave sequence's id.
353 #[doc(alias = "ndspChnGetWaveBufSeq")]
354 pub fn wave_sequence_id(&self) -> u16 {
355 unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
356 }
357
358 /// Pause or un-pause the channel's playback.
359 ///
360 /// # Example
361 ///
362 /// ```no_run
363 /// # use std::error::Error;
364 /// # fn main() -> Result<(), Box<dyn Error>> {
365 /// #
366 /// use ctru::services::ndsp::Ndsp;
367 /// let ndsp = Ndsp::new()?;
368 /// let mut channel_0 = ndsp.channel(0)?;
369 ///
370 /// channel_0.set_paused(true);
371 ///
372 /// // The channel is paused.
373 /// assert!(channel_0.is_paused());
374 /// #
375 /// # Ok(())
376 /// # }
377 /// ```
378 #[doc(alias = "ndspChnSetPaused")]
379 pub fn set_paused(&mut self, state: bool) {
380 unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) };
381 }
382
383 /// Set the channel's output format.
384 ///
385 /// Change this setting based on the used wave's format.
386 ///
387 /// # Example
388 ///
389 /// ```no_run
390 /// # use std::error::Error;
391 /// # fn main() -> Result<(), Box<dyn Error>> {
392 /// #
393 /// use ctru::services::ndsp::{AudioFormat, Ndsp};
394 /// let ndsp = Ndsp::new()?;
395 /// let mut channel_0 = ndsp.channel(0)?;
396 ///
397 /// // Use the PCM16 interleaved dual-channel audio format.
398 /// channel_0.set_format(AudioFormat::PCM16Stereo);
399 /// #
400 /// # Ok(())
401 /// # }
402 /// ```
403 // TODO: Channels treat all waves as equal and do not read their format when playing them. Another good reason to re-write the service.
404 #[doc(alias = "ndspChnSetFormat")]
405 pub fn set_format(&mut self, format: AudioFormat) {
406 unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) };
407 }
408
409 /// Set the channel's interpolation mode.
410 ///
411 /// # Example
412 ///
413 /// ```no_run
414 /// # use std::error::Error;
415 /// # fn main() -> Result<(), Box<dyn Error>> {
416 /// #
417 /// use ctru::services::ndsp::{InterpolationType, Ndsp};
418 /// let ndsp = Ndsp::new()?;
419 /// let mut channel_0 = ndsp.channel(0)?;
420 ///
421 /// // Use linear interpolation within frames.
422 /// channel_0.set_interpolation(InterpolationType::Linear);
423 /// #
424 /// # Ok(())
425 /// # }
426 /// ```
427 #[doc(alias = "ndspChnSetInterp")]
428 pub fn set_interpolation(&mut self, interp_type: InterpolationType) {
429 unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) };
430 }
431
432 /// Set the channel's volume mix.
433 ///
434 /// Look at [`AudioMix`] for more information on the volume mix.
435 ///
436 /// # Example
437 ///
438 /// ```no_run
439 /// # use std::error::Error;
440 /// # use std::default::Default;
441 /// # fn main() -> Result<(), Box<dyn Error>> {
442 /// #
443 /// use ctru::services::ndsp::{AudioMix, Ndsp};
444 /// let ndsp = Ndsp::new()?;
445 /// let mut channel_0 = ndsp.channel(0)?;
446 ///
447 /// // Front-left and front-right channel maxed.
448 /// channel_0.set_mix(&AudioMix::default());
449 /// #
450 /// # Ok(())
451 /// # }
452 /// ```
453 #[doc(alias = "ndspChnSetMix")]
454 pub fn set_mix(&mut self, mix: &AudioMix) {
455 unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) }
456 }
457
458 /// Set the channel's rate of sampling in hertz.
459 ///
460 /// # Example
461 ///
462 /// ```no_run
463 /// # use std::error::Error;
464 /// # fn main() -> Result<(), Box<dyn Error>> {
465 /// #
466 /// use ctru::services::ndsp::Ndsp;
467 /// let ndsp = Ndsp::new()?;
468 /// let mut channel_0 = ndsp.channel(0)?;
469 ///
470 /// // Standard CD sample rate. (44100 Hz)
471 /// channel_0.set_sample_rate(44100.);
472 /// #
473 /// # Ok(())
474 /// # }
475 /// ```
476 #[doc(alias = "ndspChnSetRate")]
477 pub fn set_sample_rate(&mut self, rate: f32) {
478 unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) };
479 }
480
481 // TODO: wrap ADPCM format helpers.
482
483 /// Clear the wave buffer queue and stop playback.
484 ///
485 /// # Example
486 ///
487 /// ```no_run
488 /// # use std::error::Error;
489 /// # fn main() -> Result<(), Box<dyn Error>> {
490 /// #
491 /// use ctru::services::ndsp::Ndsp;
492 /// let ndsp = Ndsp::new()?;
493 /// let mut channel_0 = ndsp.channel(0)?;
494 ///
495 /// // Clear the audio queue and stop playback.
496 /// channel_0.clear_queue();
497 /// #
498 /// # Ok(())
499 /// # }
500 /// ```
501 #[doc(alias = "ndspChnWaveBufClear")]
502 pub fn clear_queue(&mut self) {
503 unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
504 }
505
506 /// Add a wave buffer to the channel's queue.
507 /// If there are no other buffers in queue, playback for this buffer will start.
508 ///
509 /// # Warning
510 ///
511 /// `libctru` expects the user to manually keep the info data (in this case [`Wave`]) alive during playback.
512 /// To ensure safety, checks within [`Wave`] will clear the whole channel queue if any queued [`Wave`] is dropped prematurely.
513 ///
514 /// # Example
515 ///
516 /// ```no_run
517 /// # #![feature(allocator_api)]
518 /// # use std::error::Error;
519 /// # fn main() -> Result<(), Box<dyn Error>> {
520 /// #
521 /// # use ctru::linear::LinearAllocator;
522 /// use ctru::services::ndsp::wave::Wave;
523 /// use ctru::services::ndsp::{AudioFormat, Ndsp};
524 /// let ndsp = Ndsp::new()?;
525 /// let mut channel_0 = ndsp.channel(0)?;
526 ///
527 /// # let audio_data: Box<[_], _> = Box::new_in([0u8; 96], LinearAllocator);
528 ///
529 /// // Provide your own audio data.
530 /// let mut wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false);
531 ///
532 /// // Clear the audio queue and stop playback.
533 /// channel_0.queue_wave(&mut wave);
534 /// #
535 /// # Ok(())
536 /// # }
537 /// ```
538 // TODO: Find a better way to handle the wave lifetime problem.
539 // These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust.
540 #[doc(alias = "ndspChnWaveBufAdd")]
541 pub fn queue_wave<Buffer: LinearAllocation + AsRef<[u8]>>(
542 &mut self,
543 wave: &mut Wave<Buffer>,
544 ) -> std::result::Result<(), Error> {
545 match wave.status() {
546 Status::Playing | Status::Queued => return Err(Error::WaveBusy(self.id)),
547 _ => (),
548 }
549
550 wave.set_channel(self.id);
551
552 unsafe { ctru_sys::ndspChnWaveBufAdd(self.id.into(), &mut wave.raw_data) };
553
554 Ok(())
555 }
556}
557
558/// Functions to handle audio filtering.
559///
560/// Refer to [`libctru`](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
561impl Channel<'_> {
562 /// Enables/disables monopole filters.
563 #[doc(alias = "ndspChnIirMonoSetEnable")]
564 pub fn iir_mono_set_enabled(&mut self, enable: bool) {
565 unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) };
566 }
567
568 /// Set the monopole to be a high pass filter.
569 ///
570 /// # Notes
571 ///
572 /// This is a lower quality filter than the Biquad alternative.
573 #[doc(alias = "ndspChnIirMonoSetParamsHighPassFilter")]
574 pub fn iir_mono_set_params_high_pass_filter(&mut self, cut_off_freq: f32) {
575 unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) };
576 }
577
578 /// Set the monopole to be a low pass filter.
579 ///
580 /// # Notes
581 ///
582 /// This is a lower quality filter than the Biquad alternative.
583 #[doc(alias = "ndspChnIirMonoSetParamsLowPassFilter")]
584 pub fn iir_mono_set_params_low_pass_filter(&mut self, cut_off_freq: f32) {
585 unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
586 }
587
588 /// Enables/disables biquad filters.
589 #[doc(alias = "ndspChnIirBiquadSetEnable")]
590 pub fn iir_biquad_set_enabled(&mut self, enable: bool) {
591 unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) };
592 }
593
594 /// Set the biquad to be a high pass filter.
595 #[doc(alias = "ndspChnIirBiquadSetParamsHighPassFilter")]
596 pub fn iir_biquad_set_params_high_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
597 unsafe {
598 ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
599 };
600 }
601
602 /// Set the biquad to be a low pass filter.
603 #[doc(alias = "ndspChnIirBiquadSetParamsLowPassFilter")]
604 pub fn iir_biquad_set_params_low_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
605 unsafe {
606 ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
607 };
608 }
609
610 /// Set the biquad to be a notch filter.
611 #[doc(alias = "ndspChnIirBiquadSetParamsNotchFilter")]
612 pub fn iir_biquad_set_params_notch_filter(&mut self, notch_freq: f32, quality: f32) {
613 unsafe {
614 ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
615 };
616 }
617
618 /// Set the biquad to be a band pass filter.
619 #[doc(alias = "ndspChnIirBiquadSetParamsBandPassFilter")]
620 pub fn iir_biquad_set_params_band_pass_filter(&mut self, mid_freq: f32, quality: f32) {
621 unsafe {
622 ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
623 };
624 }
625
626 /// Set the biquad to be a peaking equalizer.
627 #[doc(alias = "ndspChnIirBiquadSetParamsPeakingEqualizer")]
628 pub fn iir_biquad_set_params_peaking_equalizer(
629 &mut self,
630 central_freq: f32,
631 quality: f32,
632 gain: f32,
633 ) {
634 unsafe {
635 ctru_sys::ndspChnIirBiquadSetParamsPeakingEqualizer(
636 self.id.into(),
637 central_freq,
638 quality,
639 gain,
640 )
641 };
642 }
643}
644
645impl AudioFormat {
646 /// Returns the amount of bytes needed to store one sample
647 ///
648 /// # Example
649 ///
650 /// - 8 bit mono formats return 1 (byte)
651 /// - 16 bit stereo (dual-channel) formats return 4 (bytes)
652 pub const fn size(self) -> usize {
653 match self {
654 Self::PCM8Mono => 1,
655 Self::PCM16Mono | Self::PCM8Stereo => 2,
656 Self::PCM16Stereo => 4,
657 }
658 }
659}
660
661impl AudioMix {
662 /// Creates a new [`AudioMix`] with all volumes set to 0.
663 pub fn zeroed() -> Self {
664 Self { raw: [0.; 12] }
665 }
666
667 /// Returns a reference to the raw data.
668 pub fn as_raw(&self) -> &[f32; 12] {
669 &self.raw
670 }
671
672 /// Returns a mutable reference to the raw data.
673 pub fn as_raw_mut(&mut self) -> &mut [f32; 12] {
674 &mut self.raw
675 }
676
677 /// Returns the values set for the "front" volume mix (left and right channel).
678 pub fn front(&self) -> (f32, f32) {
679 (self.raw[0], self.raw[1])
680 }
681
682 /// Returns the values set for the "back" volume mix (left and right channel).
683 pub fn back(&self) -> (f32, f32) {
684 (self.raw[2], self.raw[3])
685 }
686
687 /// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
688 pub fn aux_front(&self, id: AuxDevice) -> (f32, f32) {
689 let index = 4 + (id as usize * 4);
690
691 (self.raw[index], self.raw[index + 1])
692 }
693
694 /// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
695 pub fn aux_back(&self, id: AuxDevice) -> (f32, f32) {
696 let index = 6 + (id as usize * 4);
697
698 (self.raw[index], self.raw[index + 1])
699 }
700
701 /// Set the values for the "front" volume mix (left and right channel).
702 ///
703 /// # Notes
704 ///
705 /// [`Channel`] will normalize the mix values to be within 0 and 1.
706 /// However, an [`AudioMix`] instance with larger/smaller values is valid.
707 pub fn set_front(&mut self, left: f32, right: f32) {
708 self.raw[0] = left;
709 self.raw[1] = right;
710 }
711
712 /// Set the values for the "back" volume mix (left and right channel).
713 ///
714 /// # Notes
715 ///
716 /// [`Channel`] will normalize the mix values to be within 0 and 1.
717 /// However, an [`AudioMix`] instance with larger/smaller values is valid.
718 pub fn set_back(&mut self, left: f32, right: f32) {
719 self.raw[2] = left;
720 self.raw[3] = right;
721 }
722
723 /// Set the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
724 ///
725 /// # Notes
726 ///
727 /// [`Channel`] will normalize the mix values to be within 0 and 1.
728 /// However, an [`AudioMix`] instance with larger/smaller values is valid.
729 pub fn set_aux_front(&mut self, left: f32, right: f32, id: AuxDevice) {
730 let index = 4 + (id as usize * 4);
731
732 self.raw[index] = left;
733 self.raw[index + 1] = right;
734 }
735
736 /// Set the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
737 ///
738 /// # Notes
739 ///
740 /// [`Channel`] will normalize the mix values to be within 0 and 1.
741 /// However, an [`AudioMix`] instance with larger/smaller values is valid.
742 pub fn set_aux_back(&mut self, left: f32, right: f32, id: AuxDevice) {
743 let index = 6 + (id as usize * 4);
744
745 self.raw[index] = left;
746 self.raw[index + 1] = right;
747 }
748}
749
750impl Default for AudioMix {
751 /// Returns an [`AudioMix`] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%.
752 fn default() -> Self {
753 let mut mix = AudioMix::zeroed();
754 mix.set_front(1.0, 1.0);
755
756 mix
757 }
758}
759
760impl From<[f32; 12]> for AudioMix {
761 fn from(value: [f32; 12]) -> Self {
762 Self { raw: value }
763 }
764}
765
766impl fmt::Display for Error {
767 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
768 match self {
769 Self::InvalidChannel(id) => write!(
770 f,
771 "audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23"
772 ),
773 Self::ChannelAlreadyInUse(id) => write!(
774 f,
775 "audio Channel with ID {id} is already being used. Drop the other instance if you want to use it here"
776 ),
777 Self::WaveBusy(id) => write!(f, "the selected Wave is busy playing on channel {id}"),
778 Self::SampleCountOutOfBounds(samples_requested, max_samples) => write!(
779 f,
780 "the sample count requested is too big (requested = {samples_requested}, maximum = {max_samples})"
781 ),
782 }
783 }
784}
785
786impl error::Error for Error {}
787
788impl Drop for Ndsp {
789 #[doc(alias = "ndspExit")]
790 fn drop(&mut self) {
791 for i in 0..NUMBER_OF_CHANNELS {
792 self.channel(i).unwrap().reset();
793 }
794 }
795}
796
797from_impl!(InterpolationType, ctru_sys::ndspInterpType);
798from_impl!(OutputMode, ctru_sys::ndspOutputMode);
799from_impl!(AudioFormat, u16);