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:
| File | Description |
|---|
hle/shared_memory.h | Layout of the DSP shared memory region that games use to submit audio commands |
hle/source.h / source.cpp | Individual audio source (voice) — handles sample decoding, pitch shifting, and envelope |
hle/mixers.h / mixers.cpp | Mixes all active sources down to the final stereo output |
hle/filter.h / filter.cpp | Biquad filter implementation for per-source and final-mix EQ |
hle/decoder.h / decoder.cpp | Dispatcher for 3DS audio codec decoding (IMA-ADPCM, PCM8, PCM16) |
hle/aac_decoder.h / aac_decoder.cpp | AAC decoding support for games that use the 3DS AAC hardware path |
hle/common.h | Shared 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:
SinkType | Class / file | Notes |
|---|
Auto | Selects the best available sink | Prefers Cubeb when available |
Cubeb | cubeb_sink.h / cubeb_sink.cpp | Recommended. Low-latency, cross-platform audio via libcubeb. |
OpenAL | openal_sink.h / openal_sink.cpp | OpenAL Soft backend. Good fallback on Linux and macOS. |
SDL2 | sdl2_sink.h / sdl2_sink.cpp | SDL2 audio subsystem. Portable but higher latency. |
Null | null_sink.h | Discards 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.
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:
| File | Description |
|---|
cubeb_input.h / cubeb_input.cpp | Captures from the host microphone via libcubeb |
openal_input.h / openal_input.cpp | Captures using OpenAL Soft’s capture extension |
static_input.h / static_input.cpp | Plays back a static audio file as mic input |
null_input.h | Returns 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:
| Format | Description |
|---|
PCM8 | 8-bit signed PCM — converted to 16-bit by the codec |
PCM16 | 16-bit signed PCM — passed through directly |
ADPCM | IMA-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.