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);