Split audio update into early/late phases, add asset<->stream type map, switch volume/pan to float, add stream loop limit
- audioUpdate()/audioMixerUpdate() split into Early (before rendering, per-stream buffering) and Late (end of frame, starts newly-queued mixer sounds) phases, wired into engineUpdate() around displayUpdate(); mixer is now actually initialized/disposed/updated from audio.c.
- New src/dusk/audio/stream/audiostreamtype.{h,c}: asset-loader-type <-> audio-stream-type map (with file extension), plus audioStreamAssetTypeForPath()/audioStreamTypeForAssetType()/audioStreamAssetTypeForStreamType() lookups.
- audiostream_t/audiomixerchanneldata_t volume and directionality/pan switched from integer (0-0xFF / -128..127) to float (0.0-1.0 / -1.0..1.0), removing the manual scaling each platform backend had to do.
- audiostream_t gains loopLimit/loopRestartCount so a stream can be told to stop (fire onEnd) after a fixed number of loop restarts instead of looping forever; wired into the shared restart path (Linux/Dolphin) and PSP's own read-ahead loop decision.
Built and verified on Linux, PSP (Docker) and GameCube (Docker).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+12
-2
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "audio.h"
|
||||
#include "audio/mixer/audiomixer.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
@@ -15,6 +16,7 @@ errorret_t audioInit() {
|
||||
memoryZero(&AUDIO, sizeof(audio_t));
|
||||
|
||||
errorChain(audioPlatformInit());
|
||||
errorChain(audioMixerInit());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -26,7 +28,7 @@ audiostream_t * audioAquireStream(assetentry_t *asset) {
|
||||
// too catches the mistake as early as possible, before any stream slot
|
||||
// is handed out for it.
|
||||
assertTrue(
|
||||
asset->type == ASSET_LOADER_TYPE_WAV || asset->type == ASSET_LOADER_TYPE_MP3,
|
||||
audioStreamTypeForAssetType(asset->type) != AUDIO_STREAM_TYPE_NULL,
|
||||
"Unsupported asset type for an audio stream."
|
||||
);
|
||||
|
||||
@@ -40,8 +42,9 @@ audiostream_t * audioAquireStream(assetentry_t *asset) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
errorret_t audioUpdate() {
|
||||
errorret_t audioUpdateEarly() {
|
||||
errorChain(audioPlatformUpdate());
|
||||
errorChain(audioMixerUpdateEarly());
|
||||
|
||||
for(uint8_t i = 0; i < AUDIO_STREAMS_MAX; i++) {
|
||||
audiostream_t *stream = &AUDIO.streams[i];
|
||||
@@ -51,12 +54,19 @@ errorret_t audioUpdate() {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t audioUpdateLate() {
|
||||
errorChain(audioMixerUpdateLate());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t audioDispose() {
|
||||
for(uint8_t i = 0; i < AUDIO_STREAMS_MAX; i++) {
|
||||
audiostream_t *stream = &AUDIO.streams[i];
|
||||
errorChain(audioStreamDispose(stream));
|
||||
}
|
||||
|
||||
errorChain(audioMixerDispose());
|
||||
errorChain(audioPlatformDispose());
|
||||
|
||||
errorOk();
|
||||
|
||||
+14
-2
@@ -51,11 +51,23 @@ audiostream_t * audioAquireStream(assetentry_t *asset);
|
||||
|
||||
/**
|
||||
* Updates the audio subsystem, updating every active stream. Should be
|
||||
* called once per frame.
|
||||
* called once per frame, before rendering - keeps stream buffering/decode
|
||||
* work off the render thread's critical path (see duskmad's MP3 decode
|
||||
* ring for why this matters).
|
||||
*
|
||||
* @return Error indicating success or failure of the operation.
|
||||
*/
|
||||
errorret_t audioUpdate();
|
||||
errorret_t audioUpdateEarly();
|
||||
|
||||
/**
|
||||
* Updates the audio subsystem's end-of-frame work - currently just starting
|
||||
* whatever the audio mixer queued this frame (see
|
||||
* audioMixerUpdateLate()'s own comment). Should be called once per frame,
|
||||
* after rendering.
|
||||
*
|
||||
* @return Error indicating success or failure of the operation.
|
||||
*/
|
||||
errorret_t audioUpdateLate();
|
||||
|
||||
/**
|
||||
* Disposes the audio subsystem, stopping and disposing every active stream.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "audiomixer.h"
|
||||
#include "audio/stream/audiostream.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
@@ -18,7 +19,11 @@ errorret_t audioMixerInit() {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t audioMixerUpdate() {
|
||||
errorret_t audioMixerUpdateEarly() {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t audioMixerUpdateLate() {
|
||||
// For each channel.
|
||||
audiomixerchannel_t channel = 0;
|
||||
do {
|
||||
@@ -46,8 +51,8 @@ errorret_t audioMixerUpdate() {
|
||||
void audioMixerPlay(
|
||||
const char_t *file,
|
||||
const audiomixerchannel_t channel,
|
||||
const uint8_t volume,
|
||||
const int8_t pan
|
||||
const float_t volume,
|
||||
const float_t pan
|
||||
) {
|
||||
audioMixerPlayLooped(file, channel, volume, pan, 0, 0.0f, 0.0f);
|
||||
}
|
||||
@@ -55,16 +60,19 @@ void audioMixerPlay(
|
||||
void audioMixerPlayLooped(
|
||||
const char_t *file,
|
||||
const audiomixerchannel_t channel,
|
||||
const uint8_t volume,
|
||||
const int8_t pan,
|
||||
const float_t volume,
|
||||
const float_t pan,
|
||||
const uint8_t loopCount,
|
||||
const float_t loopStart,
|
||||
const float_t loopTo
|
||||
) {
|
||||
assertStrLenMin(file, 1, "File is required, to stop use audioMixerStop");
|
||||
assertTrue(channel < AUDIO_MIXER_CHANNEL_COUNT, "Channel is out of range");
|
||||
assertTrue(volume <= 0xFF, "Volume is out of range");
|
||||
assertTrue(volume > 0, "Volume must be greater than 0");
|
||||
assertTrue(volume > 0.0f && volume <= 1.0f, "Volume is out of range");
|
||||
assertTrue(
|
||||
pan >= AUDIO_STREAM_LEFT && pan <= AUDIO_STREAM_RIGHT,
|
||||
"Pan is out of range"
|
||||
);
|
||||
assertTrue(loopStart >= 0.0f, "Loop start incorrect");
|
||||
assertTrue(loopTo >= 0.0f, "Loop to incorrect");
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ typedef struct {
|
||||
assetentry_t *asset;
|
||||
|
||||
char_t file[AUDIO_PATH_MAX];
|
||||
uint8_t volume;
|
||||
int8_t pan;
|
||||
float_t volume;
|
||||
float_t pan;
|
||||
uint8_t loopCount;
|
||||
float_t loopStart;
|
||||
float_t loopTo;
|
||||
@@ -45,24 +45,33 @@ extern audiomixer_t AUDIO_MIXER;
|
||||
errorret_t audioMixerInit();
|
||||
|
||||
/**
|
||||
* Updates the audio mixer.
|
||||
* Updates the audio mixer, called before rendering. Reserved for
|
||||
* time-sensitive mixer state (e.g. volume fades) that should be reflected
|
||||
* before this frame's streams are buffered - nothing uses it yet.
|
||||
*/
|
||||
errorret_t audioMixerUpdate();
|
||||
errorret_t audioMixerUpdateEarly();
|
||||
|
||||
/**
|
||||
* Updates the audio mixer, called at the end of each frame - starts any
|
||||
* sound queued this frame via audioMixerPlay()/audioMixerPlayLooped(), once
|
||||
* every other system has had a chance to queue one.
|
||||
*/
|
||||
errorret_t audioMixerUpdateLate();
|
||||
|
||||
/**
|
||||
* Same as audioMixerPlayLooped() but with no looping.
|
||||
*
|
||||
* @param file File to be played.
|
||||
* @param channel Channel you want to play this sound on.
|
||||
* @param volume Volume of the sound, from 0 (silent) to 0xFF (loudest).
|
||||
* @param volume Volume of the sound, from 0.0 (silent) to 1.0 (loudest).
|
||||
* @param pan Stereo panning of the sound, from AUDIO_STREAM_LEFT to
|
||||
* AUDIO_STREAM_RIGHT.
|
||||
*/
|
||||
void audioMixerPlay(
|
||||
const char_t *file,
|
||||
const audiomixerchannel_t channel,
|
||||
const uint8_t volume,
|
||||
const int8_t pan
|
||||
const float_t volume,
|
||||
const float_t pan
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -73,12 +82,12 @@ void audioMixerPlay(
|
||||
* to queue multiple sounds on the same channel only the last one will be played
|
||||
* and will stop any actively playing on that channel currently.
|
||||
*
|
||||
* Volume is also mixed based on the channel, so supplying volume 0xFF is then
|
||||
* multiplied by the channel's volume, e.g. 0xDD*0xFF = 0xDD.
|
||||
* Volume is also mixed based on the channel, so supplying volume 1.0 is then
|
||||
* multiplied by the channel's volume, e.g. 0.5*1.0 = 0.5.
|
||||
*
|
||||
* @param file File to be played.
|
||||
* @param channel Channel you want to play this sound on.
|
||||
* @param volume Volume of the sound, from 0 (silent) to 0xFF (loudest).
|
||||
* @param volume Volume of the sound, from 0.0 (silent) to 1.0 (loudest).
|
||||
* @param pan Stereo panning of the sound, from AUDIO_STREAM_LEFT to
|
||||
* AUDIO_STREAM_RIGHT.
|
||||
* @param loopCount How many times to loop the sound, 0 for infinite looping.
|
||||
@@ -89,8 +98,8 @@ void audioMixerPlay(
|
||||
void audioMixerPlayLooped(
|
||||
const char_t *file,
|
||||
const audiomixerchannel_t channel,
|
||||
const uint8_t volume,
|
||||
const int8_t pan,
|
||||
const float_t volume,
|
||||
const float_t pan,
|
||||
const uint8_t loopCount,
|
||||
const float_t loopStart,
|
||||
const float_t loopTo
|
||||
|
||||
@@ -9,4 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
audiostream.c
|
||||
audiostreampcm.c
|
||||
audiostreammp3.c
|
||||
audiostreamtype.c
|
||||
)
|
||||
@@ -16,17 +16,17 @@ errorret_t audioStreamInit(audiostream_t *stream, assetentry_t *asset) {
|
||||
asset->state == ASSET_ENTRY_STATE_LOADED,
|
||||
"Asset must be loaded before it can back an audio stream."
|
||||
);
|
||||
// Only WAV (-> PCM) and MP3 assets can back an audio stream - a caller
|
||||
// passing the wrong kind of asset entry is a programmer error, not
|
||||
// something that can happen from untrusted data, so this is an assert
|
||||
// rather than an errorThrow (see feedback_assert_vs_error convention).
|
||||
// A caller passing an asset type that can't back an audio stream is a
|
||||
// programmer error, not something that can happen from untrusted data, so
|
||||
// this is an assert rather than an errorThrow (see
|
||||
// feedback_assert_vs_error convention).
|
||||
assertTrue(
|
||||
asset->type == ASSET_LOADER_TYPE_WAV || asset->type == ASSET_LOADER_TYPE_MP3,
|
||||
audioStreamTypeForAssetType(asset->type) != AUDIO_STREAM_TYPE_NULL,
|
||||
"Unsupported asset type for an audio stream."
|
||||
);
|
||||
|
||||
stream->state = 0;
|
||||
stream->volume = 0xFF;
|
||||
stream->volume = 1.0f;
|
||||
stream->directionality = AUDIO_STREAM_CENTER;
|
||||
stream->loopStart = -1;
|
||||
stream->loopTo = 0;
|
||||
@@ -38,6 +38,8 @@ errorret_t audioStreamInit(audiostream_t *stream, assetentry_t *asset) {
|
||||
stream->onEnd = NULL;
|
||||
stream->loopCount = 0;
|
||||
stream->lastLoopCount = 0;
|
||||
stream->loopLimit = 0;
|
||||
stream->loopRestartCount = 0;
|
||||
|
||||
// Locked for as long as the stream is in use (see audiostream_t.asset's
|
||||
// own comment) - released in audioStreamDispose().
|
||||
@@ -146,10 +148,11 @@ void audioStreamGetPanFactors(
|
||||
assertNotNull(outLeft, "outLeft cannot be NULL.");
|
||||
assertNotNull(outRight, "outRight cannot be NULL.");
|
||||
|
||||
// directionality is an int8_t clamped to AUDIO_STREAM_LEFT..RIGHT
|
||||
// (-128..127) by its own type, so pan is always within [-1.0, 0.992] -
|
||||
// no further clamping needed.
|
||||
const float_t pan = (float_t) stream->directionality / 128.0f;
|
||||
// directionality is already a pan factor in AUDIO_STREAM_LEFT..RIGHT
|
||||
// (-1.0..1.0) - assertTrue'd here rather than clamped, since a caller
|
||||
// passing something outside that range is a programmer error (see
|
||||
// audioStreamSetDirectionality()'s own assert).
|
||||
const float_t pan = stream->directionality;
|
||||
|
||||
*outLeft = pan > 0 ? (1.0f - pan) : 1.0f;
|
||||
*outRight = pan < 0 ? (1.0f + pan) : 1.0f;
|
||||
@@ -173,17 +176,22 @@ size_t audioStreamComputeEndFrame(
|
||||
return endFrame;
|
||||
}
|
||||
|
||||
void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume) {
|
||||
void audioStreamSetVolume(audiostream_t *stream, const float_t volume) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
assertTrue(volume >= 0.0f && volume <= 1.0f, "Volume is out of range.");
|
||||
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
|
||||
const float_t directionality
|
||||
) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
assertTrue(
|
||||
directionality >= AUDIO_STREAM_LEFT && directionality <= AUDIO_STREAM_RIGHT,
|
||||
"Directionality is out of range."
|
||||
);
|
||||
stream->directionality = directionality;
|
||||
// TODO: Need to update internal decoder?
|
||||
}
|
||||
@@ -198,6 +206,12 @@ void audioStreamSetLooping(audiostream_t *stream, const bool_t looping) {
|
||||
}
|
||||
}
|
||||
|
||||
void audioStreamSetLoopLimit(audiostream_t *stream, const uint32_t loopLimit) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
stream->loopLimit = loopLimit;
|
||||
stream->loopRestartCount = 0;
|
||||
}
|
||||
|
||||
errorret_t audioStreamUpdate(audiostream_t *stream) {
|
||||
assertNotNull(stream, "Stream cannot be NULL.");
|
||||
|
||||
@@ -232,13 +246,22 @@ errorret_t audioStreamUpdate(audiostream_t *stream) {
|
||||
(stream->state & AUDIO_STREAM_STATE_BUFFERED) &&
|
||||
audioStreamPlatformIsFinished(stream)
|
||||
) {
|
||||
if(stream->state & AUDIO_STREAM_STATE_LOOPING) {
|
||||
// A limit of 0 means unlimited; otherwise this pass's restart only goes
|
||||
// ahead while fewer than loopLimit restarts have already happened -
|
||||
// once it's reached, fall through to the same stop/onEnd path as a
|
||||
// non-looping stream instead of restarting again.
|
||||
const bool_t loopLimitReached = stream->loopLimit > 0 &&
|
||||
stream->loopRestartCount >= stream->loopLimit;
|
||||
|
||||
if((stream->state & AUDIO_STREAM_STATE_LOOPING) && !loopLimitReached) {
|
||||
stream->state &= ~AUDIO_STREAM_STATE_BUFFERED;
|
||||
stream->loopRestartCount++;
|
||||
|
||||
// Resume from loopTo rather than the very start of the buffer - only
|
||||
// matters for platforms that reach this generic restart path at all;
|
||||
// PSP/Dolphin loop entirely on their own (thread/hardware) and never
|
||||
// report "finished" while looping, so in practice this is Linux-only.
|
||||
// matters for platforms that reach this generic restart path at all
|
||||
// (Linux and Dolphin, both of which re-buffer per pass and report
|
||||
// "finished" at every loop boundary); PSP loops entirely on its own
|
||||
// read-ahead ring instead (see loopRestartCount's own comment).
|
||||
stream->startFrame = (size_t) (stream->loopTo * stream->sampleRate);
|
||||
|
||||
if(stream->onLoop != NULL) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "audio/stream/audiostreamtype.h"
|
||||
#include "audio/stream/audiostreampcm.h"
|
||||
#include "audio/stream/audiostreammp3.h"
|
||||
#include "audio/audiostreamplatform.h"
|
||||
@@ -31,16 +32,9 @@
|
||||
#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;
|
||||
#define AUDIO_STREAM_CENTER 0.0f
|
||||
#define AUDIO_STREAM_LEFT -1.0f
|
||||
#define AUDIO_STREAM_RIGHT 1.0f
|
||||
|
||||
typedef struct audiostream_s audiostream_t;
|
||||
|
||||
@@ -60,12 +54,14 @@ typedef struct audiostream_s {
|
||||
// playback may also be holding.
|
||||
assetentry_t *asset;
|
||||
|
||||
// Loudness. Can only be 0 to 0xFF
|
||||
uint8_t volume;
|
||||
// Loudness, from 0.0 (silent) to 1.0 (loudest).
|
||||
float_t volume;
|
||||
|
||||
// In stereo space, where do we send the audio.
|
||||
// In stereo space, where do we send the audio - from AUDIO_STREAM_LEFT
|
||||
// (-1.0) to AUDIO_STREAM_RIGHT (1.0), AUDIO_STREAM_CENTER (0.0) by
|
||||
// default.
|
||||
// TODO: Can we use a 3D vector + Pro Logic II?
|
||||
int8_t directionality;
|
||||
float_t directionality;
|
||||
|
||||
// In seconds, where the loop segment ends and playback jumps back to
|
||||
// loopTo - -1 (the default) means "at the end of the stream", i.e. loop
|
||||
@@ -119,6 +115,22 @@ typedef struct audiostream_s {
|
||||
// onLoop for - only ever touched from the main thread.
|
||||
uint32_t lastLoopCount;
|
||||
|
||||
// Maximum number of times this stream is allowed to loop before stopping
|
||||
// (firing onEnd) instead of restarting again - 0 (the default) means loop
|
||||
// forever. Has no effect unless looping is also enabled via
|
||||
// audioStreamSetLooping(). Set via audioStreamSetLoopLimit().
|
||||
uint32_t loopLimit;
|
||||
|
||||
// How many loop restarts this stream has already committed to - compared
|
||||
// against loopLimit each time a restart is considered, then incremented
|
||||
// if it goes ahead; reset to 0 by audioStreamSetLoopLimit(). Distinct
|
||||
// from loopCount (which tracks when a loop becomes audible, purely for
|
||||
// onLoop notification, and is PSP-only) - this counts decided restarts,
|
||||
// main-thread-only on every platform (PSP decides whether to loop from
|
||||
// its own read-ahead ring, itself driven from the main thread via
|
||||
// audioStreamPSPIsFinished() - see its own comment).
|
||||
uint32_t loopRestartCount;
|
||||
|
||||
// PCM format of the stream's decoded output - read identically by every
|
||||
// platform backend regardless of stream type (PCM decodes straight
|
||||
// through; MP3 decodes to this same format), so it lives here rather
|
||||
@@ -286,9 +298,9 @@ size_t audioStreamComputeEndFrame(
|
||||
* 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).
|
||||
* @param volume The new volume, from 0.0 (silent) to 1.0 (loudest).
|
||||
*/
|
||||
void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume);
|
||||
void audioStreamSetVolume(audiostream_t *stream, const float_t volume);
|
||||
|
||||
/**
|
||||
* Sets the stereo directionality (panning) of the given audio stream.
|
||||
@@ -299,7 +311,7 @@ void audioStreamSetVolume(audiostream_t *stream, const uint8_t volume);
|
||||
*/
|
||||
void audioStreamSetDirectionality(
|
||||
audiostream_t *stream,
|
||||
const int8_t directionality
|
||||
const float_t directionality
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -313,6 +325,19 @@ void audioStreamSetDirectionality(
|
||||
*/
|
||||
void audioStreamSetLooping(audiostream_t *stream, const bool_t looping);
|
||||
|
||||
/**
|
||||
* Sets the maximum number of times the given audio stream is allowed to
|
||||
* loop before stopping (firing onEnd) instead of restarting again. Has no
|
||||
* effect unless looping is also enabled via audioStreamSetLooping(). Resets
|
||||
* the stream's current loop-restart progress back to 0, so the stream gets
|
||||
* the full count from this call onward.
|
||||
*
|
||||
* @param stream The audio stream to update.
|
||||
* @param loopLimit Maximum number of loop restarts, or 0 (the default) to
|
||||
* loop forever.
|
||||
*/
|
||||
void audioStreamSetLoopLimit(audiostream_t *stream, const uint32_t loopLimit);
|
||||
|
||||
/**
|
||||
* Updates the given audio stream, decoding new data and advancing playback
|
||||
* as needed. Should be called every frame for every active stream.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "audiostreamtype.h"
|
||||
#include "util/string.h"
|
||||
|
||||
const audiostreamassettypeinfo_t AUDIO_STREAM_TYPE_FOR_ASSET_TYPE[ASSET_LOADER_TYPE_COUNT] = {
|
||||
[ASSET_LOADER_TYPE_WAV] = { .type = AUDIO_STREAM_TYPE_PCM, .extension = ".wav" },
|
||||
[ASSET_LOADER_TYPE_MP3] = { .type = AUDIO_STREAM_TYPE_MP3, .extension = ".mp3" }
|
||||
};
|
||||
|
||||
assetloadertype_t audioStreamAssetTypeForPath(const char_t *path) {
|
||||
for(assetloadertype_t i = 0; i < ASSET_LOADER_TYPE_COUNT; i++) {
|
||||
const char_t *extension = AUDIO_STREAM_TYPE_FOR_ASSET_TYPE[i].extension;
|
||||
if(extension != NULL && stringEndsWithCaseInsensitive(path, extension)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return ASSET_LOADER_TYPE_NULL;
|
||||
}
|
||||
|
||||
audistreamtype_t audioStreamTypeForAssetType(const assetloadertype_t assetType) {
|
||||
return AUDIO_STREAM_TYPE_FOR_ASSET_TYPE[assetType].type;
|
||||
}
|
||||
|
||||
assetloadertype_t audioStreamAssetTypeForStreamType(const audistreamtype_t streamType) {
|
||||
if(streamType == AUDIO_STREAM_TYPE_NULL) return ASSET_LOADER_TYPE_NULL;
|
||||
|
||||
for(assetloadertype_t i = 0; i < ASSET_LOADER_TYPE_COUNT; i++) {
|
||||
if(AUDIO_STREAM_TYPE_FOR_ASSET_TYPE[i].type == streamType) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return ASSET_LOADER_TYPE_NULL;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
|
||||
typedef enum {
|
||||
AUDIO_STREAM_TYPE_NULL,
|
||||
AUDIO_STREAM_TYPE_PCM,
|
||||
AUDIO_STREAM_TYPE_MP3,
|
||||
AUDIO_STREAM_TYPE_COUNT
|
||||
} audistreamtype_t;
|
||||
|
||||
typedef struct {
|
||||
// The audistreamtype_t an audio stream backed by this asset type should
|
||||
// become - AUDIO_STREAM_TYPE_NULL (the zero-initialized default) for any
|
||||
// asset type that can't back an audio stream.
|
||||
audistreamtype_t type;
|
||||
|
||||
// This asset type's file extension, including the leading '.' (e.g.
|
||||
// ".wav"). NULL for any asset type that can't back an audio stream.
|
||||
const char_t *extension;
|
||||
} audiostreamassettypeinfo_t;
|
||||
|
||||
// Maps an assetloadertype_t to the info describing the audio stream it
|
||||
// backs. Indexed directly by assetentry_t.type; see audioStreamInit()'s own
|
||||
// use of this for the canonical "is this asset type supported" check. Use
|
||||
// the functions below rather than indexing this directly, except where
|
||||
// you already have an assetloadertype_t in hand.
|
||||
extern const audiostreamassettypeinfo_t AUDIO_STREAM_TYPE_FOR_ASSET_TYPE[ASSET_LOADER_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Determines which asset loader type a file path's extension corresponds
|
||||
* to, by matching it against AUDIO_STREAM_TYPE_FOR_ASSET_TYPE's own
|
||||
* extensions (case-insensitively).
|
||||
*
|
||||
* @param path The file path to inspect, e.g. "audio/boa.mp3".
|
||||
* @return The matching assetloadertype_t, or ASSET_LOADER_TYPE_NULL if the
|
||||
* path's extension doesn't match any audio asset type.
|
||||
*/
|
||||
assetloadertype_t audioStreamAssetTypeForPath(const char_t *path);
|
||||
|
||||
/**
|
||||
* Looks up the audistreamtype_t an audio stream backed by the given asset
|
||||
* type should become.
|
||||
*
|
||||
* @param assetType The asset loader type to look up.
|
||||
* @return The matching audistreamtype_t, or AUDIO_STREAM_TYPE_NULL if
|
||||
* assetType can't back an audio stream.
|
||||
*/
|
||||
audistreamtype_t audioStreamTypeForAssetType(const assetloadertype_t assetType);
|
||||
|
||||
/**
|
||||
* Looks up which asset loader type backs audio streams of the given type -
|
||||
* the reverse of audioStreamTypeForAssetType(). Scans
|
||||
* AUDIO_STREAM_TYPE_FOR_ASSET_TYPE for the first entry whose type matches,
|
||||
* since it isn't itself indexed by audistreamtype_t.
|
||||
*
|
||||
* @param streamType The audio stream type to look up.
|
||||
* @return The matching assetloadertype_t, or ASSET_LOADER_TYPE_NULL if
|
||||
* streamType is AUDIO_STREAM_TYPE_NULL or otherwise unmapped.
|
||||
*/
|
||||
assetloadertype_t audioStreamAssetTypeForStreamType(const audistreamtype_t streamType);
|
||||
@@ -99,7 +99,7 @@ errorret_t engineUpdate(void) {
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
errorChain(audioUpdate());
|
||||
errorChain(audioUpdateEarly());
|
||||
errorChain(rpgUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
@@ -107,6 +107,7 @@ errorret_t engineUpdate(void) {
|
||||
|
||||
// Render
|
||||
errorChain(displayUpdate());
|
||||
errorChain(audioUpdateLate());
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ errorret_t audioStreamDolphinBuffer(audiostream_t *stream) {
|
||||
|
||||
float_t leftFactor, rightFactor;
|
||||
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
|
||||
const float_t baseVolume = (float_t) stream->volume / 255.0f;
|
||||
const float_t baseVolume = stream->volume;
|
||||
stream->platform.volumeLeft = (s32) mathClamp(baseVolume * leftFactor * MAX_VOLUME, 0, MAX_VOLUME);
|
||||
stream->platform.volumeRight = (s32) mathClamp(baseVolume * rightFactor * MAX_VOLUME, 0, MAX_VOLUME);
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ errorret_t audioStreamLinuxFeed(audiostream_t *stream) {
|
||||
const size_t bytesRead = framesRead * frameSize;
|
||||
int queued;
|
||||
|
||||
if(stream->volume == 0xFF) {
|
||||
if(stream->volume >= 1.0f) {
|
||||
// Nothing to mix at full volume - queue the window directly instead of
|
||||
// allocating/zeroing a same-size scratch buffer just to copy it in.
|
||||
queued = SDL_QueueAudio(stream->platform.device, chunk, (Uint32) bytesRead);
|
||||
@@ -200,7 +200,7 @@ errorret_t audioStreamLinuxFeed(audiostream_t *stream) {
|
||||
memoryZero(mixed, bytesRead);
|
||||
SDL_MixAudioFormat(
|
||||
mixed, (uint8_t *) chunk, AUDIO_S16SYS, (Uint32) bytesRead,
|
||||
(stream->volume * SDL_MIX_MAXVOLUME) / 0xFF
|
||||
(int) (stream->volume * SDL_MIX_MAXVOLUME)
|
||||
);
|
||||
queued = SDL_QueueAudio(stream->platform.device, mixed, (Uint32) bytesRead);
|
||||
memoryFree(mixed);
|
||||
|
||||
@@ -232,9 +232,15 @@ void audioStreamPSPTopUp(audiostream_t *stream) {
|
||||
// detection ("racing through content"). A short read that isn't
|
||||
// genuinely the end just means less got queued this call; the next
|
||||
// TopUp() call (next engine frame) naturally retries.
|
||||
|
||||
// A limit of 0 means unlimited - see loopRestartCount's own comment for
|
||||
// why counting decided (not yet necessarily audible) restarts here is
|
||||
// still the right thing to compare against loopLimit.
|
||||
const bool_t loopLimitReached = stream->loopLimit > 0 &&
|
||||
stream->loopRestartCount >= stream->loopLimit;
|
||||
const bool_t reachesSegmentEnd = stream->platform.readPosition >= currentEndFrame;
|
||||
const bool_t willLoop = reachesSegmentEnd &&
|
||||
(stream->state & AUDIO_STREAM_STATE_LOOPING);
|
||||
(stream->state & AUDIO_STREAM_STATE_LOOPING) && !loopLimitReached;
|
||||
|
||||
if(reachesSegmentEnd) {
|
||||
if(willLoop) {
|
||||
@@ -243,6 +249,7 @@ void audioStreamPSPTopUp(audiostream_t *stream) {
|
||||
return;
|
||||
}
|
||||
stream->platform.readPosition = stream->platform.loopToFrame;
|
||||
stream->loopRestartCount++;
|
||||
|
||||
threadMutexLock(&stream->platform.ringLock);
|
||||
if(stream->platform.loopMarkerCount < AUDIO_PSP_LOOP_MARKER_MAX) {
|
||||
@@ -379,7 +386,7 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
float_t leftFactor, rightFactor;
|
||||
audioStreamGetPanFactors(stream, &leftFactor, &rightFactor);
|
||||
|
||||
const int baseVolume = (stream->volume * PSP_AUDIO_VOLUME_MAX) / 0xFF;
|
||||
const int baseVolume = (int) (stream->volume * PSP_AUDIO_VOLUME_MAX);
|
||||
const int leftVolume = (int) (baseVolume * leftFactor);
|
||||
const int rightVolume = (int) (baseVolume * rightFactor);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user