Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/azahar-emu/azahar/llms.txt

Use this file to discover all available pages before exploring further.

The Nintendo 3DS runs audio processing on a dedicated Teak DSP chip that operates independently of the two ARM11 cores. Games communicate with the DSP through shared memory and a set of named pipes rather than direct register access, which means the DSP can be emulated either by reimplementing its behavior at a high level (HLE) or by running the actual DSP firmware microcode (LLE). Azahar supports both approaches through a common AudioCore::DspInterface abstraction.

DspInterface — the abstract DSP layer

AudioCore::DspInterface (in src/audio_core/dsp_interface.h) defines the contract that both HLE and LLE implementations must fulfill:
// src/audio_core/dsp_interface.h
class DspInterface {
public:
    virtual u16  RecvData(u32 register_number) = 0;
    virtual bool RecvDataIsReady(u32 register_number) const = 0;
    virtual void SetSemaphore(u16 semaphore_value) = 0;
    virtual std::vector<u8> PipeRead(DspPipe pipe_number, std::size_t length) = 0;
    virtual std::size_t GetPipeReadableSize(DspPipe pipe_number) const = 0;
    virtual void PipeWrite(DspPipe pipe_number, std::span<const u8> buffer) = 0;
    virtual void SetInterruptHandler(
        std::function<void(Service::DSP::InterruptType, DspPipe)> handler) = 0;
    virtual void LoadComponent(std::span<const u8> buffer) = 0;
    virtual void UnloadComponent() = 0;

    // Sink selection and time-stretching are handled in the base class
    void SetSink(SinkType sink_type, std::string_view audio_device);
    Sink& GetSink();
    void EnableStretching(bool enable);
    // ...
};
Core::System owns a std::unique_ptr<AudioCore::DspInterface> dsp_core and exposes it through System::DSP(). The active backend is selected at initialization time based on your audio emulation setting.

HLE audio (DspHle)

AudioCore::DspHle (in src/audio_core/hle/hle.h) re-implements the DSP’s behavior in C++ without running any actual DSP firmware. It is the default backend and provides the best compatibility for most games.
// src/audio_core/hle/hle.h
class DspHle final : public DspInterface {
public:
    explicit DspHle(Core::System& system);
    // ... overrides for all DspInterface pure virtuals
};
Internally, DspHle processes the structures that a real 3DS game writes into DSP shared memory (src/audio_core/hle/shared_memory.h) and produces PCM output through a software mixer. The HLE subsystem is organized across several files:
FileDescription
hle/shared_memory.hLayout of the DSP shared memory region that games use to submit audio commands
hle/source.h / source.cppIndividual audio source (voice) — handles sample decoding, pitch shifting, and envelope
hle/mixers.h / mixers.cppMixes all active sources down to the final stereo output
hle/filter.h / filter.cppBiquad filter implementation for per-source and final-mix EQ
hle/decoder.h / decoder.cppDispatcher for 3DS audio codec decoding (IMA-ADPCM, PCM8, PCM16)
hle/aac_decoder.h / aac_decoder.cppAAC decoding support for games that use the 3DS AAC hardware path
hle/common.hShared types and constants used across the HLE audio subsystem
HLE audio is the recommended backend. It starts instantly (no firmware loading required), consumes less CPU than LLE, and is compatible with nearly all commercial titles.

LLE audio (DspLle)

AudioCore::DspLle (in src/audio_core/lle/lle.h) runs the actual Teak DSP microcode dumped from a real 3DS. This makes it more accurate than HLE for edge cases, at the cost of higher CPU usage and the requirement that you supply a DSP firmware dump.
// src/audio_core/lle/lle.h
class DspLle final : public DspInterface {
public:
    explicit DspLle(Core::System& system, bool multithread);
    // ... overrides for all DspInterface pure virtuals
};
The multithread constructor parameter controls whether the DSP runs on a dedicated host thread (faster) or on the CPU thread (simpler but slower). When LoadComponent() is called with the firmware binary, DspLle boots the Teak core and begins executing DSP microcode.
LLE audio requires a DSP firmware dump (dspfirm.cdc) extracted from a real 3DS. It is slower than HLE and may cause audio glitches in games that the LLE implementation has not been tested against.

Audio sinks

The base class DspInterface owns a std::unique_ptr<Sink> and manages sample output independently of which DSP backend is active. The abstract AudioCore::Sink interface (in src/audio_core/sink.h) accepts stereo signed PCM16 samples at the native sample rate:
// src/audio_core/sink.h
class Sink {
public:
    virtual unsigned int GetNativeSampleRate() const = 0;
    virtual void SetCallback(std::function<void(s16*, std::size_t)> cb) = 0;
    virtual bool ImmediateSubmission() { return false; }
    virtual void PushSamples(const void* data, std::size_t num_samples) {}
};
Available sink implementations are registered in src/audio_core/sink_details.h as SinkType enum values:
SinkTypeClass / fileNotes
AutoSelects the best available sinkPrefers Cubeb when available
Cubebcubeb_sink.h / cubeb_sink.cppRecommended. Low-latency, cross-platform audio via libcubeb.
OpenALopenal_sink.h / openal_sink.cppOpenAL Soft backend. Good fallback on Linux and macOS.
SDL2sdl2_sink.h / sdl2_sink.cppSDL2 audio subsystem. Portable but higher latency.
Nullnull_sink.hDiscards all audio. Useful for headless testing or benchmarking.
Sink selection is controlled via DspInterface::SetSink(SinkType, std::string_view audio_device). The SinkDetails struct bundles a factory function and a device-listing function for each type, letting the frontend enumerate available audio devices without depending on a specific backend.

Audio input (microphone)

Azahar supports microphone emulation through an abstract AudioCore::Input interface (in src/audio_core/input.h). Available input implementations are registered in src/audio_core/input_details.h:
FileDescription
cubeb_input.h / cubeb_input.cppCaptures from the host microphone via libcubeb
openal_input.h / openal_input.cppCaptures using OpenAL Soft’s capture extension
static_input.h / static_input.cppPlays back a static audio file as mic input
null_input.hReturns silence; used when mic permission is denied
The active backend is chosen based on your audio input setting. Before opening the host microphone, Azahar checks System::HasMicPermission(), which delegates to a platform-specific callback registered by the frontend.

Time stretching

AudioCore::TimeStretcher (in src/audio_core/time_stretch.h) compensates for emulation speed variation by pitch-correcting the output audio stream. When the emulator runs slower than real-time, the stretcher slows audio down to match without changing its pitch; when the emulator catches up, audio speeds back up. The stretcher sits between DspInterface and the active sink:
DspInterface::OutputFrame()
    → RingBuffer<s16, 0x2000, 2> fifo
        → TimeStretcher (when enabled)
            → Sink::SetCallback() / PushSamples()
Stretching is toggled with DspInterface::EnableStretching(bool enable) and can be disabled in settings if you prefer silence or speed-up artifacts over pitch correction.

Audio codecs

src/audio_core/codec.cpp (with codec.h) provides format conversions for the sample data that games write into HLE voice slots. The 3DS supports three native sample formats:
FormatDescription
PCM88-bit signed PCM — converted to 16-bit by the codec
PCM1616-bit signed PCM — passed through directly
ADPCMIMA-ADPCM — decoded to PCM16 using the codec’s predictor state
The HLE decoder dispatcher (hle/decoder.h) routes each voice’s sample data through the appropriate codec path before the mixer combines it. src/audio_core/interpolate.h / interpolate.cpp handles sample-rate conversion and interpolation (linear, cosine, and polyphase) for voices that play at a sample rate different from the DSP output rate of 32,728 Hz.

Build docs developers (and LLMs) love