Ps2AudioDevice class
PlayStation 2 implementation of IAudioDevice on top of audsrv.
The SPU2 is not on the Emotion Engine's side of the machine. It hangs off the I/O Processor, and the only thing the EE can do with it is ask the IOP to, which is what audsrv is: an IRX module that owns the SPU2, keeps a ring buffer of PCM in IOP memory and streams it out through two of the chip's voices at a pitch derived from the format it was given. So this backend is a software mixer - the same one the Nintendo 64, PS3, Amiga, SDL and PSP backends are - whose output stage is audsrv_play_audio() rather than a DMA queue.
There is no mixer thread. The engine's threading is off on this console (a thread created through PS2SDK's libpthreadglue is never scheduled, see the NCINE_WITH_THREADS arm in ncine_options.cmake), so the ring is topped up from updatePlayers(), once per frame, on the main thread - the N64 backend's arrangement rather than the PSP's. That works because the ring is deep: whatever audsrv was built with, it is measured at startup (see _ringCapacity) and kept as full as it will go, so a frame that runs long is covered by what is already queued on the IOP. Nothing here ever blocks on the hardware: only as much is submitted as audsrv_available() says will fit, which is why audsrv_wait_audio() - the call the module's own samples are written around - is never used.
The mixing rate is not the hardware's rate. The SPU2 runs at 48 kHz and audsrv programs the voice pitch from the format it is handed, so a stream submitted at 22050 Hz is resampled by the SPU2 itself, in hardware, for free. The mixer's cost is linear in its rate and the game's content is 8- and 16-bit samples at 11-22 kHz, so the default is 22050 Hz and the per-source loop runs half as often as it would at 44100 for content that has nothing above 11 kHz to lose. setMixingFrequency() reprograms the format (the "Sample Rate" option), and nativeFrequency() reports it so the module decoders render at the rate they will be played at.
Uploaded sounds keep the width they arrived in rather than being widened to the mixer's 16 bits, the way the N64 backend keeps them: nearly all of the game's own sounds are 8-bit, and the console's 32 MB is shared with a renderer that pages every texture through main memory. Unlike the N64 there is no decimation ladder on top of that - 32 MB is not 8 MB, and the whole sound set of a level fits.
Base classes
- class AudioDeviceBase
- Backend-independent part of an audio device.
Public static functions
- static auto InitializeModules() -> bool
- Brings the IOP side up -
rom0:LIBSDand the embeddedaudsrv.irx.
Constructors, destructors, conversion operators
- Ps2AudioDevice()
- ~Ps2AudioDevice() override
Public functions
- auto isValid() const -> bool override
- Returns
trueif 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
0on 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,
0detaches 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.0fdisables 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
trueif 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 onBlockingOperationBegan() override
- Called when the outermost blocking operation begins, for a backend that has to react.
- void onBlockingOperationEnded() override
- Called when the last outstanding blocking operation ends (see onBlockingOperationBegan()).
- void suspendDevice() override
- Suspends the audio device.
- void resumeDevice() override
- Resumes the audio device.
Function documentation
static bool nCine:: Ps2AudioDevice:: InitializeModules()
Brings the IOP side up - rom0:LIBSD and the embedded audsrv.irx.
| Returns | false if the modules could not be loaded, which makes the backend report itself invalid |
|---|
An IRX can only be loaded once per process, but the device is constructed and destroyed with the service locator, which the application may do more than once. So this is idempotent and remembers what it found. MainApplication::Run() calls it first, while the boot console is still on the display, and the constructor then only opens a stream format on what it found.
void nCine:: Ps2AudioDevice:: 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:: Ps2AudioDevice:: 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 |
void nCine:: Ps2AudioDevice:: onBlockingOperationBegan() override
Called when the outermost blocking operation begins, for a backend that has to react.
The counting half of beginBlockingOperation() is done here so that no backend has to: two independent owners already open these windows (a level load and the episode scan), and a backend whose "stopped" state is a flag rather than a count would have the inner end reopen its stream in the middle of the outer block. Overridden instead of beginBlockingOperation() itself.