nCine::NdspAudioDevice class

Implementation of IAudioDevice on top of the Nintendo 3DS's NDSP.

The console's DSP mixes 24 hardware channels with per-channel resampling, volume and a filter stage, which is more than this game ever asks for - but a sound effect here is a whole sample handed to a channel, and the streaming queue every channel keeps holds a few buffers at most, so the OpenAL-shaped contract (sources, queued buffers, a sample-offset query) would have to be emulated on top of the channel API anyway. So this is the PSP backend's design, which is the SDL backend's mixer (which is the Amiga one, which is the N64 one): the sources are mixed by hand into one 16-bit stereo stream with a 32-bit integer accumulator, 32.32 fixed-point cursors and Q15 gains, and the mix goes out through ONE NDSP channel as a ring of three wave buffers. Where it runs is the same departure the PSP made from the main-thread mixers: a thread of higher priority than the game's mixes each block right after the DSP finishes one and hands it back, so a frame that runs long on this console - a busy scene, a level load - costs nothing audible. The price is a lock: the mixer reads source and buffer state the game writes from the main thread, so every operation on them takes a LightLock, held for microseconds by the game and for the length of one block's mix by the mixer. A blocking lock rather than a spin lock, because the mixer thread has the higher priority - spinning on a lock the main thread holds would never let it be released.

What the DSP does contribute is the resampling: the channel is told the rate the sources are mixed at - 22050 Hz by default, the user's "Sample Rate" option otherwise (see setMixingFrequency()) - and the hardware brings it to its own 32728 Hz with linear interpolation, so unlike on the PSP no upsampling pass is needed and any rate is a valid choice. nativeFrequency() reports the mixing rate, so the module decoder renders at it too.

NDSP needs the DSP's firmware, which libctru loads from sdmc:/3ds/dspfirm.cdc - a file every console running homebrew has (DSP1 dumps it) but which cannot be shipped. Without it the device stays silent and the game runs on regardless; Azahar, whose DSP is emulated in software, accepts any file by that name.

Base classes

class AudioDeviceBase
Backend-independent part of an audio device.

Constructors, destructors, conversion operators

NdspAudioDevice()
~NdspAudioDevice() override

Public functions

auto isValid() const -> bool override
Returns true if the device was initialized successfully.
auto name() const -> const char* override
Returns the name of the underlying device.
void setGain(float gain) override
Sets the listener gain (master volume).
void updateListener(const Vector3f& position, const Vector3f& velocity) override
Updates the position and velocity of the listener.
auto nativeFrequency() -> std::int32_t override
Returns the native sample rate of the device.
void setMixingFrequency(std::int32_t frequency) override
Changes the rate the device mixes at, if the backend has one to change.
auto registerPlayer(IAudioPlayer* player) -> std::uint32_t override
Registers a player so it receives state and buffer queue updates, returning its source id.
void updatePlayers() override
Updates the state of every registered player, including the buffer queue of stream players.
auto createBuffer(BufferUsage usage) -> std::uint32_t override
Creates an empty backend buffer, returning its id or 0 on failure.
void deleteBuffer(std::uint32_t bufferId) override
Destroys a buffer previously returned by createBuffer().
auto uploadBuffer(std::uint32_t bufferId, BufferFormat format, const void* data, std::int32_t size, std::int32_t frequency) -> bool override
Replaces the contents of a buffer with the specified samples.
void setSourceBuffer(std::uint32_t sourceId, std::uint32_t bufferId) override
Attaches a buffer to a source for non-streamed playback, 0 detaches the current one.
void setSourceGain(std::uint32_t sourceId, float gain) override
Sets the gain of a source.
void setSourcePitch(std::uint32_t sourceId, float pitch) override
Sets the pitch of a source, as a multiplier of its natural playback rate.
void setSourceLooping(std::uint32_t sourceId, bool looping) override
Sets whether a source repeats its attached buffer.
void setSourceRelative(std::uint32_t sourceId, bool relative) override
Sets whether the position of a source is relative to the listener.
void setSourcePosition(std::uint32_t sourceId, const Vector3f& position) override
Sets the position of a source, in physical units.
void setSourceLowPass(std::uint32_t sourceId, float value) override
Sets the low-pass amount of a source, 1.0f disables the filter.
auto sourceSampleOffset(std::uint32_t sourceId) -> std::int32_t override
Returns the playback position of a source in samples.
void setSourceSampleOffset(std::uint32_t sourceId, std::int32_t offset) override
Sets the playback position of a source in samples.
void playSource(std::uint32_t sourceId) override
Starts or resumes a source.
void pauseSource(std::uint32_t sourceId) override
Pauses a source at its current position.
void stopSource(std::uint32_t sourceId) override
Stops a source.
auto isSourcePlaying(std::uint32_t sourceId) -> bool override
Returns true if a source is still producing sound.
void queueBuffer(std::uint32_t sourceId, std::uint32_t bufferId) override
Appends a buffer to the streaming queue of a source.
auto numProcessedBuffers(std::uint32_t sourceId) -> std::int32_t override
Returns the number of queued buffers a source has finished playing.
void unqueueBuffers(std::uint32_t sourceId, std::int32_t count, std::uint32_t* bufferIds) override
Removes the specified number of played buffers from the front of the queue.
void suspendDevice() override
Suspends the audio device.
void resumeDevice() override
Resumes the audio device.

Function documentation

void nCine::NdspAudioDevice::setMixingFrequency(std::int32_t frequency) override

Changes the rate the device mixes at, if the backend has one to change.

Only the software-mixing backends whose cost is linear in this rate honour it (the PSP's, where the mix is upsampled to the hardware's fixed rate, and the Amiga's, where AHI resamples the output); nativeFrequency() then reports the new rate, so the module music decoders that size themselves by it follow on the next stream they open. A rate the backend cannot run at, or 0, is ignored. Everywhere else this is a no-op.

void nCine::NdspAudioDevice::unqueueBuffers(std::uint32_t sourceId, std::int32_t count, std::uint32_t* bufferIds) override

Removes the specified number of played buffers from the front of the queue.

Parameters
sourceId
count
bufferIds Receives the ids of the removed buffers, must hold count entries