Add audio subsystem skeleton with a working Linux PCM playback path

Cross-platform audiostream/audio API with per-platform hooks for
PSP/Dolphin/Linux. Linux is fully wired end-to-end through SDL2
(device open, volume-mixed queueing, finish detection via
SDL_GetQueuedAudioSize) and verified playing a generated test tone
in engine.c. PSP and Dolphin hooks are stubbed for now.
This commit is contained in:
2026-08-31 10:24:02 -05:00
parent fcf0de72af
commit 8ea370a1d1
31 changed files with 1022 additions and 1 deletions
+1
View File
@@ -55,6 +55,7 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
add_subdirectory(animation)
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(audio)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(log)
+13
View File
@@ -0,0 +1,13 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
audio.c
audiostream.c
audiostreampcm.c
audiostreammp3.c
)
+52
View File
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audio.h"
#include "util/memory.h"
audio_t AUDIO;
errorret_t audioInit() {
memoryZero(&AUDIO, sizeof(audio_t));
errorChain(audioPlatformInit());
errorOk();
}
audiostream_t * audioAquireStream() {
for(uint8_t i = 0; i < AUDIO_STREAMS_MAX; i++) {
audiostream_t *stream = &AUDIO.streams[i];
if(stream->type == AUDIO_STREAM_TYPE_NULL) {
return stream;
}
}
return NULL;
}
errorret_t audioUpdate() {
errorChain(audioPlatformUpdate());
for(uint8_t i = 0; i < AUDIO_STREAMS_MAX; i++) {
audiostream_t *stream = &AUDIO.streams[i];
errorChain(audioStreamUpdate(stream));
}
errorOk();
}
errorret_t audioDispose() {
for(uint8_t i = 0; i < AUDIO_STREAMS_MAX; i++) {
audiostream_t *stream = &AUDIO.streams[i];
errorChain(audioStreamDispose(stream));
}
errorChain(audioPlatformDispose());
errorOk();
}
+59
View File
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "audio/audiostream.h"
#include "audio/audioplatform.h"
#ifndef audioPlatformInit
#error "audioPlatformInit is not defined"
#endif
#ifndef audioPlatformUpdate
#error "audioPlatformUpdate is not defined"
#endif
#ifndef audioPlatformDispose
#error "audioPlatformDispose is not defined"
#endif
typedef struct {
audiostream_t streams[AUDIO_STREAMS_MAX];
} audio_t;
extern audio_t AUDIO;
/**
* Initializes the audio subsystem.
*
* @return Error indicating success or failure of the operation.
*/
errorret_t audioInit();
/**
* Aquires an available audio stream, can return NULL if there is no available
* stream.
*
* @return Pointer to an available audio stream.
*/
audiostream_t * audioAquireStream();
/**
* Updates the audio subsystem, updating every active stream. Should be
* called once per frame.
*
* @return Error indicating success or failure of the operation.
*/
errorret_t audioUpdate();
/**
* Disposes the audio subsystem, stopping and disposing every active stream.
*
* @return Error indicating success or failure of the operation.
*/
errorret_t audioDispose();
+112
View File
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostream.h"
#include "assert/assert.h"
errorret_t audioStreamInit(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
stream->state = 0;
stream->data = NULL;
stream->dataSize = 0;
stream->volume = 0xFF;
stream->directionality = AUDIO_STREAM_CENTER;
stream->loopStart = -1;
stream->loopTo = 0;
stream->duration = 0;
stream->user = NULL;
stream->onLoop = NULL;
stream->onEnd = NULL;
// Type-specific setup (audioStreamPcmInit() / audioStreamMp3Init()) sets
// stream->type and asks the platform implementation to set up its state.
errorOk();
}
void audioStreamPlay(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
stream->state |= AUDIO_STREAM_STATE_PLAYING;
}
void audioStreamPause(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
stream->state &= ~AUDIO_STREAM_STATE_PLAYING;
}
void audioStreamSetPosition(audiostream_t *stream, const float_t position) {
assertNotNull(stream, "Stream cannot be NULL.");
// Clamp time.
float_t t = position;
while(t < 0) t += stream->duration;
while(t >= stream->duration) t -= stream->duration;
// Here I will set the stream time, this will require new data to be fetched
// next update() call.
// To confirm: If this is called late in the frame, will the PCM data be sent
// to the output despite time being adjusted? is this going to cause static?
}
void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume) {
assertNotNull(stream, "Stream cannot be NULL.");
stream->volume = volume;
// TODO: Do I need to update the device output? PSP may require this
}
void audioStreamSetDirectionality(
audiostream_t *stream,
const int8_t directionality
) {
assertNotNull(stream, "Stream cannot be NULL.");
stream->directionality = directionality;
// TODO: Need to update internal decoder?
}
errorret_t audioStreamUpdate(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
if(stream->type == AUDIO_STREAM_TYPE_NULL) {
errorOk();
}
// TODO: Once streamed (rather than fully in-memory) sources exist, this is
// where new data would be decoded/read in and re-buffered as playback
// consumes it, and looping (onLoop) would be handled.
if(
(stream->state & AUDIO_STREAM_STATE_PLAYING) &&
!(stream->state & AUDIO_STREAM_STATE_BUFFERED)
) {
errorChain(audioStreamPlatformBuffer(stream));
stream->state |= AUDIO_STREAM_STATE_BUFFERED;
} else if(
(stream->state & AUDIO_STREAM_STATE_PLAYING) &&
(stream->state & AUDIO_STREAM_STATE_BUFFERED) &&
audioStreamPlatformIsFinished(stream)
) {
stream->state &= ~(AUDIO_STREAM_STATE_PLAYING | AUDIO_STREAM_STATE_BUFFERED);
if(stream->onEnd != NULL) {
stream->onEnd(stream);
}
}
errorOk();
}
errorret_t audioStreamDispose(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
if(stream->type != AUDIO_STREAM_TYPE_NULL) {
errorChain(audioStreamPlatformDispose(stream));
}
stream->type = AUDIO_STREAM_TYPE_NULL;
errorOk();
}
+160
View File
@@ -0,0 +1,160 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audio/audiostreampcm.h"
#include "audio/audiostreammp3.h"
#include "audio/audiostreamplatform.h"
#ifndef audioStreamPlatformInit
#error "audioStreamPlatformInit is not defined"
#endif
#ifndef audioStreamPlatformDispose
#error "audioStreamPlatformDispose is not defined"
#endif
#ifndef audioStreamPlatformBuffer
#error "audioStreamPlatformBuffer is not defined"
#endif
#ifndef audioStreamPlatformIsFinished
#error "audioStreamPlatformIsFinished is not defined"
#endif
#define AUDIO_STREAMS_MAX 8
#define AUDIO_STREAM_STATE_PLAYING (1 << 0)
#define AUDIO_STREAM_STATE_LOOPING (1 << 1)
#define AUDIO_STREAM_STATE_BUFFERED (1 << 2)
#define AUDIO_STREAM_CENTER 0
#define AUDIO_STREAM_LEFT -128
#define AUDIO_STREAM_RIGHT 127
typedef enum {
AUDIO_STREAM_TYPE_NULL,
AUDIO_STREAM_TYPE_PCM,
AUDIO_STREAM_TYPE_MP3,
AUDIO_STREAM_TYPE_COUNT
} audistreamtype_t;
typedef struct audiostream_s audiostream_t;
typedef struct audiostream_s {
// Used for aquiring new data.
audistreamtype_t type;
// What state the stream is in.
uint8_t state;
// Raw PCM Data, already decoded. Not owned by the stream.
uint8_t *data;
// Size of the buffer pointed to by data, in bytes.
size_t dataSize;
// Loudness. Can only be 0 to 0xFF
uint8_t volume;
// In stereo space, where do we send the audio.
// TODO: Can we use a 3D vector + Pro Logic II?
int8_t directionality;
// At what point does the stream loop back, -1 means "at end of stream"
float_t loopStart;
// When a loop occurs, where do we loop to? Defaults to 0 or start of stream.
float_t loopTo;
// Cached duration of the stream in seconds.
float_t duration;
// Callbacks
void *user;
void (*onLoop)(audiostream_t *stream);
void (*onEnd)(audiostream_t *stream);
// Stream type specific data.
union {
audiostreampcm_t pcm;
audiostreammp3_t mp3;
};
// Platform-specific playback state (e.g. SDL2 device, PSP channel, ansnd
// voice). Defined by each platform's audiostreamplatform.h.
audiostreamplatform_t platform;
} audiostream_t;
/**
* Initializes an audio stream in preparation for playback. Does not begin
* playback; call audioStreamPlay() once the stream is configured (see
* audioStreamPcmInit() / audioStreamMp3Init() for the type-specific setup
* this must be followed by).
*
* @param stream The audio stream to initialize.
* @return Error indicating success or failure.
*/
errorret_t audioStreamInit(audiostream_t *stream);
/**
* Begins or resumes playback of the given audio stream.
*
* @param stream The audio stream to play.
*/
void audioStreamPlay(audiostream_t *stream);
/**
* Pauses playback of the given audio stream. Playback position is retained,
* so audioStreamPlay() resumes from the same point.
*
* @param stream The audio stream to pause.
*/
void audioStreamPause(audiostream_t *stream);
/**
* Sets the playback position of the given audio stream, in seconds.
*
* @param stream The audio stream to rewind.
*/
void audioStreamSetPosition(audiostream_t *stream, const float_t position);
/**
* Sets the playback volume of the given audio stream.
*
* @param stream The audio stream to update.
* @param volume The new volume, from 0 (silent) to 0xFF (loudest).
*/
void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume);
/**
* Sets the stereo directionality (panning) of the given audio stream.
*
* @param stream The audio stream to update.
* @param directionality The new panning value, from AUDIO_STREAM_LEFT to
* AUDIO_STREAM_RIGHT.
*/
void audioStreamSetDirectionality(
audiostream_t *stream,
const int8_t directionality
);
/**
* Updates the given audio stream, decoding new data and advancing playback
* as needed. Should be called every frame for every active stream.
*
* @param stream The audio stream to update.
* @return Error indicating success or failure.
*/
errorret_t audioStreamUpdate(audiostream_t *stream);
/**
* Disposes the audio stream, stopping playback and releasing any resources
* associated with it.
*
* @param stream The audio stream to dispose.
* @return Error indicating success or failure.
*/
errorret_t audioStreamDispose(audiostream_t *stream);
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostreammp3.h"
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct {
void *empty;
} audiostreammp3_t;
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostreampcm.h"
#include "audiostream.h"
#include "assert/assert.h"
errorret_t audioStreamPcmInit(
audiostream_t *stream,
uint8_t *data,
const size_t dataSize,
const uint32_t sampleRate,
const uint8_t channels
) {
assertNotNull(stream, "Stream cannot be NULL.");
assertNotNull(data, "Data cannot be NULL.");
stream->type = AUDIO_STREAM_TYPE_PCM;
stream->data = data;
stream->dataSize = dataSize;
stream->pcm.sampleRate = sampleRate;
stream->pcm.channels = channels;
stream->duration = (float_t) dataSize /
(float_t) (channels * sizeof(int16_t)) /
(float_t) sampleRate;
errorChain(audioStreamPlatformInit(stream));
errorOk();
}
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct audiostream_s audiostream_t;
typedef struct {
// Sample rate of the stream's data, in Hz.
uint32_t sampleRate;
// Number of interleaved channels in the stream's data.
uint8_t channels;
} audiostreampcm_t;
/**
* Configures the given audio stream to play raw, already-decoded 16-bit
* signed PCM data. Must be called after audioStreamInit() and before
* audioStreamPlay().
*
* @param stream The audio stream to configure.
* @param data The raw PCM data to play. Not copied; must remain valid for as
* long as the stream is playing it.
* @param dataSize The size of data, in bytes.
* @param sampleRate The sample rate of data, in Hz.
* @param channels The number of interleaved channels in data.
* @return Error indicating success or failure.
*/
errorret_t audioStreamPcmInit(
audiostream_t *stream,
uint8_t *data,
const size_t dataSize,
const uint32_t sampleRate,
const uint8_t channels
);
+36 -1
View File
@@ -21,10 +21,25 @@
#endif
#include "system/system.h"
#include "console/console.h"
#include "save/save.h"\
#include "save/save.h"
#include "audio/audio.h"
#include "util/math.h"
#include <math.h>
engine_t ENGINE;
#define AUDIO_TEST_TONE_SAMPLE_RATE 48000
#define AUDIO_TEST_TONE_FREQUENCY 440
#define AUDIO_TEST_TONE_SAMPLES AUDIO_TEST_TONE_SAMPLE_RATE
#define AUDIO_TEST_TONE_SIZE (AUDIO_TEST_TONE_SAMPLES * sizeof(int16_t))
// 1 second, 16-bit signed mono PCM sine wave, used to smoke-test playback.
static uint8_t AUDIO_TEST_TONE[AUDIO_TEST_TONE_SIZE];
void engineTestToneOnEnd(audiostream_t *stream) {
consolePrint("Test tone finished playing");
}
errorret_t engineInit(const int32_t argc, const char_t **argv) {
assertInit();
memoryZero(&ENGINE, sizeof(engine_t));
@@ -42,6 +57,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(saveInit());
errorChain(localeManagerInit());
errorChain(displayInit());
errorChain(audioInit());
errorChain(uiInit());
errorChain(rpgInit());
#ifdef DUSK_NETWORK
@@ -49,6 +65,23 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
#endif
errorChain(sceneInit());
int16_t *toneSamples = (int16_t *) AUDIO_TEST_TONE;
for(uint32_t i = 0; i < AUDIO_TEST_TONE_SAMPLES; i++) {
float_t t = (float_t) i / AUDIO_TEST_TONE_SAMPLE_RATE;
toneSamples[i] = (int16_t) (
sinf(2.0f * MATH_PI * AUDIO_TEST_TONE_FREQUENCY * t) * INT16_MAX
);
}
audiostream_t *stream = audioAquireStream();
errorChain(audioStreamInit(stream));
errorChain(audioStreamPcmInit(
stream, AUDIO_TEST_TONE, AUDIO_TEST_TONE_SIZE,
AUDIO_TEST_TONE_SAMPLE_RATE, 1
));
stream->onEnd = engineTestToneOnEnd;
audioStreamPlay(stream);
consolePrint("Engine initialized");
#ifdef DUSK_ASSERTIONS_FAKED
@@ -71,6 +104,7 @@ errorret_t engineUpdate(void) {
timeUpdate();
inputUpdate();
consoleUpdate();
errorChain(audioUpdate());
errorChain(rpgUpdate());
errorChain(sceneUpdate());
errorChain(assetUpdate());
@@ -94,6 +128,7 @@ errorret_t engineDispose(void) {
errorChain(rpgDispose());
localeManagerDispose();
errorChain(uiDispose());
errorChain(audioDispose());
consoleDispose();
errorChain(displayDispose());
errorChain(saveDispose());
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiodolphin.h"
errorret_t audioDolphinInit() {
errorOk();
}
errorret_t audioDolphinUpdate() {
errorOk();
}
errorret_t audioDolphinDispose() {
errorOk();
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
/**
* Initializes the GameCube/Wii-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioDolphinInit();
/**
* Updates the GameCube/Wii-specific audio subsystem state. Called once per
* frame.
*
* @return Error state if any.
*/
errorret_t audioDolphinUpdate();
/**
* Disposes the GameCube/Wii-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioDolphinDispose();
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiodolphin.h"
#define audioPlatformInit audioDolphinInit
#define audioPlatformUpdate audioDolphinUpdate
#define audioPlatformDispose audioDolphinDispose
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostreamdolphin.h"
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct audiostream_s audiostream_t;
typedef struct {
int32_t voiceId;
} audiostreamdolphin_t;
/**
* Initializes the GameCube/Wii-specific playback state of an audio stream
* (e.g. allocating a voice via AESND_AllocateVoice/ansnd equivalent).
*
* @param stream The audio stream to initialize.
* @return Error state if any.
*/
errorret_t audioStreamDolphinInit(audiostream_t *stream);
/**
* Disposes the GameCube/Wii-specific playback state of an audio stream,
* releasing its voice.
*
* @param stream The audio stream to dispose.
* @return Error state if any.
*/
errorret_t audioStreamDolphinDispose(audiostream_t *stream);
/**
* Sends the stream's currently staged PCM data (stream->data) to its
* allocated voice.
*
* @param stream The audio stream to output.
* @return Error state if any.
*/
errorret_t audioStreamDolphinBuffer(audiostream_t *stream);
/**
* Checks whether the stream's voice has finished playing its currently
* buffered data.
*
* @param stream The audio stream to check.
* @return true if playback has finished, false otherwise.
*/
bool_t audioStreamDolphinIsFinished(audiostream_t *stream);
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiostreamdolphin.h"
typedef audiostreamdolphin_t audiostreamplatform_t;
#define audioStreamPlatformInit audioStreamDolphinInit
#define audioStreamPlatformDispose audioStreamDolphinDispose
#define audioStreamPlatformBuffer audioStreamDolphinBuffer
#define audioStreamPlatformIsFinished audioStreamDolphinIsFinished
+1
View File
@@ -11,6 +11,7 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs
add_subdirectory(asset)
add_subdirectory(audio)
add_subdirectory(log)
add_subdirectory(input)
if(DUSK_NETWORK)
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
audiolinux.c
audiostreamlinux.c
)
+27
View File
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiolinux.h"
#include <SDL2/SDL.h>
errorret_t audioLinuxInit() {
if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
errorThrow("Failed to initialize SDL2 audio subsystem: %s", SDL_GetError());
}
errorOk();
}
errorret_t audioLinuxUpdate() {
errorOk();
}
errorret_t audioLinuxDispose() {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
errorOk();
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
/**
* Initializes the Linux-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioLinuxInit();
/**
* Updates the Linux-specific audio subsystem state. Called once per frame.
*
* @return Error state if any.
*/
errorret_t audioLinuxUpdate();
/**
* Disposes the Linux-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioLinuxDispose();
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiolinux.h"
#define audioPlatformInit audioLinuxInit
#define audioPlatformUpdate audioLinuxUpdate
#define audioPlatformDispose audioLinuxDispose
+67
View File
@@ -0,0 +1,67 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostreamlinux.h"
#include "audio/audiostream.h"
#include "assert/assert.h"
#include "util/memory.h"
errorret_t audioStreamLinuxInit(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
SDL_AudioSpec desired;
memoryZero(&desired, sizeof(SDL_AudioSpec));
desired.freq = (int) stream->pcm.sampleRate;
desired.format = AUDIO_S16SYS;
desired.channels = stream->pcm.channels;
desired.samples = 4096;
stream->platform.device = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0);
if(stream->platform.device == 0) {
errorThrow("Failed to open SDL2 audio device: %s", SDL_GetError());
}
errorOk();
}
errorret_t audioStreamLinuxDispose(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
SDL_CloseAudioDevice(stream->platform.device);
errorOk();
}
errorret_t audioStreamLinuxBuffer(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
uint8_t *mixed = memoryAllocate(stream->dataSize);
memoryZero(mixed, stream->dataSize);
SDL_MixAudioFormat(
mixed, stream->data, AUDIO_S16SYS, (Uint32) stream->dataSize,
(stream->volume * SDL_MIX_MAXVOLUME) / 0xFF
);
int queued = SDL_QueueAudio(
stream->platform.device, mixed, (Uint32) stream->dataSize
);
memoryFree(mixed);
if(queued != 0) {
errorThrow("Failed to queue SDL2 audio data: %s", SDL_GetError());
}
SDL_PauseAudioDevice(stream->platform.device, 0);
errorOk();
}
bool_t audioStreamLinuxIsFinished(audiostream_t *stream) {
assertNotNull(stream, "Stream cannot be NULL.");
return SDL_GetQueuedAudioSize(stream->platform.device) == 0;
}
+52
View File
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include <SDL2/SDL.h>
typedef struct audiostream_s audiostream_t;
typedef struct {
SDL_AudioDeviceID device;
} audiostreamlinux_t;
/**
* Initializes the Linux-specific playback state of an audio stream by
* opening an SDL2 audio device matching the stream's PCM format.
*
* @param stream The audio stream to initialize.
* @return Error state if any.
*/
errorret_t audioStreamLinuxInit(audiostream_t *stream);
/**
* Disposes the Linux-specific playback state of an audio stream, closing
* its SDL2 audio device.
*
* @param stream The audio stream to dispose.
* @return Error state if any.
*/
errorret_t audioStreamLinuxDispose(audiostream_t *stream);
/**
* Sends the stream's currently staged PCM data (stream->data) to its SDL2
* audio device and starts/resumes playback.
*
* @param stream The audio stream to output.
* @return Error state if any.
*/
errorret_t audioStreamLinuxBuffer(audiostream_t *stream);
/**
* Checks whether the stream's SDL2 audio device has finished playing all
* queued data.
*
* @param stream The audio stream to check.
* @return true if playback has finished, false otherwise.
*/
bool_t audioStreamLinuxIsFinished(audiostream_t *stream);
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiostreamlinux.h"
typedef audiostreamlinux_t audiostreamplatform_t;
#define audioStreamPlatformInit audioStreamLinuxInit
#define audioStreamPlatformDispose audioStreamLinuxDispose
#define audioStreamPlatformBuffer audioStreamLinuxBuffer
#define audioStreamPlatformIsFinished audioStreamLinuxIsFinished
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiopsp.h"
#define audioPlatformInit audioPSPInit
#define audioPlatformUpdate audioPSPUpdate
#define audioPlatformDispose audioPSPDispose
+20
View File
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiopsp.h"
errorret_t audioPSPInit() {
errorOk();
}
errorret_t audioPSPUpdate() {
errorOk();
}
errorret_t audioPSPDispose() {
errorOk();
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
/**
* Initializes the PSP-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioPSPInit();
/**
* Updates the PSP-specific audio subsystem state. Called once per frame.
*
* @return Error state if any.
*/
errorret_t audioPSPUpdate();
/**
* Disposes the PSP-specific audio subsystem state.
*
* @return Error state if any.
*/
errorret_t audioPSPDispose();
+16
View File
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "audiostreampsp.h"
typedef audiostreampsp_t audiostreamplatform_t;
#define audioStreamPlatformInit audioStreamPSPInit
#define audioStreamPlatformDispose audioStreamPSPDispose
#define audioStreamPlatformBuffer audioStreamPSPBuffer
#define audioStreamPlatformIsFinished audioStreamPSPIsFinished
+8
View File
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "audiostreampsp.h"
+51
View File
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
typedef struct audiostream_s audiostream_t;
typedef struct {
int channel;
} audiostreampsp_t;
/**
* Initializes the PSP-specific playback state of an audio stream (e.g.
* reserving a hardware output channel via sceAudioChReserve).
*
* @param stream The audio stream to initialize.
* @return Error state if any.
*/
errorret_t audioStreamPSPInit(audiostream_t *stream);
/**
* Disposes the PSP-specific playback state of an audio stream, releasing
* its hardware output channel.
*
* @param stream The audio stream to dispose.
* @return Error state if any.
*/
errorret_t audioStreamPSPDispose(audiostream_t *stream);
/**
* Sends the stream's currently staged PCM data (stream->data) to its
* reserved hardware output channel.
*
* @param stream The audio stream to output.
* @return Error state if any.
*/
errorret_t audioStreamPSPBuffer(audiostream_t *stream);
/**
* Checks whether the stream's reserved hardware channel has finished
* playing its currently buffered data.
*
* @param stream The audio stream to check.
* @return true if playback has finished, false otherwise.
*/
bool_t audioStreamPSPIsFinished(audiostream_t *stream);