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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user