diff --git a/src/dusk/audio/audio.c b/src/dusk/audio/audio.c index 07c20307..f941c442 100644 --- a/src/dusk/audio/audio.c +++ b/src/dusk/audio/audio.c @@ -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(); diff --git a/src/dusk/audio/audio.h b/src/dusk/audio/audio.h index 73e23aac..3ded86b5 100644 --- a/src/dusk/audio/audio.h +++ b/src/dusk/audio/audio.h @@ -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. diff --git a/src/dusk/audio/mixer/audiomixer.c b/src/dusk/audio/mixer/audiomixer.c index 778dba03..5e4aa893 100644 --- a/src/dusk/audio/mixer/audiomixer.c +++ b/src/dusk/audio/mixer/audiomixer.c @@ -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"); diff --git a/src/dusk/audio/mixer/audiomixer.h b/src/dusk/audio/mixer/audiomixer.h index 29d560d1..69e10cc9 100644 --- a/src/dusk/audio/mixer/audiomixer.h +++ b/src/dusk/audio/mixer/audiomixer.h @@ -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 diff --git a/src/dusk/audio/stream/CMakeLists.txt b/src/dusk/audio/stream/CMakeLists.txt index 00e2a660..81d37fdd 100644 --- a/src/dusk/audio/stream/CMakeLists.txt +++ b/src/dusk/audio/stream/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} audiostream.c audiostreampcm.c audiostreammp3.c + audiostreamtype.c ) \ No newline at end of file diff --git a/src/dusk/audio/stream/audiostream.c b/src/dusk/audio/stream/audiostream.c index 550faabb..61d720de 100644 --- a/src/dusk/audio/stream/audiostream.c +++ b/src/dusk/audio/stream/audiostream.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) { diff --git a/src/dusk/audio/stream/audiostream.h b/src/dusk/audio/stream/audiostream.h index e7eb4371..0886f5fd 100644 --- a/src/dusk/audio/stream/audiostream.h +++ b/src/dusk/audio/stream/audiostream.h @@ -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. diff --git a/src/dusk/audio/stream/audiostreamtype.c b/src/dusk/audio/stream/audiostreamtype.c new file mode 100644 index 00000000..5dbc8e2a --- /dev/null +++ b/src/dusk/audio/stream/audiostreamtype.c @@ -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; +} diff --git a/src/dusk/audio/stream/audiostreamtype.h b/src/dusk/audio/stream/audiostreamtype.h new file mode 100644 index 00000000..14c237a2 --- /dev/null +++ b/src/dusk/audio/stream/audiostreamtype.h @@ -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); diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 4ed3d426..856c2e51 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -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(); } diff --git a/src/duskdolphin/audio/audiostreamdolphin.c b/src/duskdolphin/audio/audiostreamdolphin.c index a54ffbab..cfb1847c 100644 --- a/src/duskdolphin/audio/audiostreamdolphin.c +++ b/src/duskdolphin/audio/audiostreamdolphin.c @@ -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); diff --git a/src/dusklinux/audio/audiostreamlinux.c b/src/dusklinux/audio/audiostreamlinux.c index ac963946..978908ef 100644 --- a/src/dusklinux/audio/audiostreamlinux.c +++ b/src/dusklinux/audio/audiostreamlinux.c @@ -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); diff --git a/src/duskpsp/audio/audiostreampsp.c b/src/duskpsp/audio/audiostreampsp.c index ecc83681..56ff7a12 100644 --- a/src/duskpsp/audio/audiostreampsp.c +++ b/src/duskpsp/audio/audiostreampsp.c @@ -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);