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:
2026-09-02 14:39:27 -05:00
co-authored by Claude Sonnet 5
parent b564d0b1c1
commit 619ce932f9
13 changed files with 268 additions and 63 deletions
+12 -2
View File
@@ -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
View File
@@ -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.
+15 -7
View File
@@ -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");
+21 -12
View File
@@ -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
+1
View File
@@ -9,4 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
audiostream.c
audiostreampcm.c
audiostreammp3.c
audiostreamtype.c
)
+39 -16
View File
@@ -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) {
+42 -17
View File
@@ -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.
+41
View File
@@ -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;
}
+68
View File
@@ -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);
+2 -1
View File
@@ -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();
}
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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);
+9 -2
View File
@@ -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);