diff --git a/cmake/targets/dolphin.cmake b/cmake/targets/dolphin.cmake index e7e813ca..46f5b2d5 100644 --- a/cmake/targets/dolphin.cmake +++ b/cmake/targets/dolphin.cmake @@ -61,7 +61,7 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE zstd z lzma - ansnd + asnd mad ) diff --git a/src/dusk/audio/stream/audiostream.h b/src/dusk/audio/stream/audiostream.h index 060e7467..1ef4c342 100644 --- a/src/dusk/audio/stream/audiostream.h +++ b/src/dusk/audio/stream/audiostream.h @@ -134,7 +134,7 @@ typedef struct audiostream_s { audiostreammp3_t mp3; }; - // Platform-specific playback state (e.g. SDL2 device, PSP channel, ansnd + // Platform-specific playback state (e.g. SDL2 device, PSP channel, ASND // voice). Defined by each platform's audiostreamplatform.h. audiostreamplatform_t platform; } audiostream_t; diff --git a/src/dusk/audio/stream/audiostreampcm.c b/src/dusk/audio/stream/audiostreampcm.c index c7899d30..fc6862f9 100644 --- a/src/dusk/audio/stream/audiostreampcm.c +++ b/src/dusk/audio/stream/audiostreampcm.c @@ -10,6 +10,7 @@ #include "assert/assert.h" #include "util/math.h" #include "util/memory.h" +#include "util/endian.h" errorret_t audioStreamPcmInit(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); @@ -131,10 +132,19 @@ errorret_t audioStreamPcmRead( } if(sourceSampleBytes == sizeof(int16_t)) { - // Output format already matches the source - read straight through. + // Output format already matches the source in width, but WAV samples + // are always little-endian per the format spec regardless of host - + // read straight through, then fix up byte order in place. A no-op on + // every little-endian platform (endianLittleToHost16() compiles down + // to returning its argument unchanged there) - only actually swaps + // anything on a big-endian host (Dolphin/GameCube/Wii). errorChain(assetFileRead( &stream->pcm.file, buffer, framesToRead * sourceFrameSize )); + const size_t sampleCount = framesToRead * channels; + for(size_t i = 0; i < sampleCount; i++) { + buffer[i] = (int16_t) endianLittleToHost16((uint16_t) buffer[i]); + } } else { // 24-bit source (the only other width assetWavParseHeader() accepts) - // read the raw 3-byte samples into a scratch buffer, then truncate diff --git a/src/duskdolphin/audio/audiodolphin.c b/src/duskdolphin/audio/audiodolphin.c index 19a95b90..b9823a5e 100644 --- a/src/duskdolphin/audio/audiodolphin.c +++ b/src/duskdolphin/audio/audiodolphin.c @@ -6,10 +6,13 @@ */ #include "audiodolphin.h" -#include +#include errorret_t audioDolphinInit() { - ansnd_initialize(); + ASND_Init(); + // ASND_Init() starts the sound engine paused - unpause immediately so + // voices actually play once configured. + ASND_Pause(0); errorOk(); } @@ -19,7 +22,7 @@ errorret_t audioDolphinUpdate() { } errorret_t audioDolphinDispose() { - ansnd_uninitialize(); + ASND_End(); errorOk(); } diff --git a/src/duskdolphin/audio/audiostreamdolphin.c b/src/duskdolphin/audio/audiostreamdolphin.c index 49c76dfa..80d9fa76 100644 --- a/src/duskdolphin/audio/audiostreamdolphin.c +++ b/src/duskdolphin/audio/audiostreamdolphin.c @@ -10,23 +10,30 @@ #include "assert/assert.h" #include "util/memory.h" #include "util/math.h" -#include -#include -#include +#include errorret_t audioStreamDolphinInit(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - const s32 voiceId = ansnd_allocate_voice(); - if(voiceId < 0) { - errorThrow( - "Failed to allocate ansnd voice: %s", ansnd_get_error_string(voiceId) - ); + const s32 voiceId = ASND_GetFirstUnusedVoice(); + if(voiceId == SND_INVALID) { + errorThrow("No free ASND voice available."); } + const size_t frameSize = stream->channels * sizeof(int16_t); + const size_t rawBytes = AUDIO_DOLPHIN_WINDOW_FRAMES * frameSize; + const size_t alignedBytes = (rawBytes + 31) & ~((size_t) 31); + stream->platform.voiceId = voiceId; - stream->platform.finished = false; - stream->platform.buffer = NULL; + stream->platform.bufferCapacityBytes = alignedBytes; + stream->platform.buffer[0] = (uint8_t *) memoryAlign(32, alignedBytes); + stream->platform.buffer[1] = (uint8_t *) memoryAlign(32, alignedBytes); + stream->platform.nextBufferIndex = 0; + stream->platform.primed = false; + stream->platform.position = 0; + stream->platform.endFrame = 0; + stream->platform.volumeLeft = 0; + stream->platform.volumeRight = 0; errorOk(); } @@ -34,12 +41,9 @@ errorret_t audioStreamDolphinInit(audiostream_t *stream) { errorret_t audioStreamDolphinDispose(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - ansnd_deallocate_voice((u32) stream->platform.voiceId); - - if(stream->platform.buffer != NULL) { - memoryFree(stream->platform.buffer); - stream->platform.buffer = NULL; - } + ASND_StopVoice(stream->platform.voiceId); + memoryFree(stream->platform.buffer[0]); + memoryFree(stream->platform.buffer[1]); errorOk(); } @@ -47,139 +51,160 @@ errorret_t audioStreamDolphinDispose(audiostream_t *stream) { errorret_t audioStreamDolphinBuffer(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - const size_t frameSize = stream->channels * sizeof(int16_t); const size_t totalFrames = audioStreamGetTotalFrames(stream); - // loopEndFrame/loopToFrame define the loop segment [loopToFrame, - // loopEndFrame) the DSP wraps within, once looping is enabled - - // defaulting to the whole clip (loopStart == -1, loopTo == 0) so - // behaviour is unchanged when no explicit loop points are configured. - // Same math as PSP/Linux - see their own comments. - const size_t loopEndFrame = stream->loopStart >= 0 - ? mathMin((size_t) (stream->loopStart * stream->sampleRate), totalFrames) - : totalFrames; - const size_t loopToFrame = mathMin( - (size_t) (stream->loopTo * stream->sampleRate), loopEndFrame - ); + // endFrame is this pass's stop point - the current loop segment's end, + // or the true end of the clip if not looping. Same math as PSP/Linux - + // see their own comments. + size_t endFrame = totalFrames; + if((stream->state & AUDIO_STREAM_STATE_LOOPING) && stream->loopStart >= 0) { + endFrame = mathMin( + (size_t) (stream->loopStart * stream->sampleRate), totalFrames + ); + } const size_t startFrame = mathMin(stream->startFrame, totalFrames); stream->startFrame = 0; stream->seeking = false; - // A seek can legitimately land past the loop segment's end (e.g. into an - // outro) - buffer out to the true end of the clip in that case, same as - // PSP/Linux, instead of only ever buffering the loop segment. - const size_t bufferFrames = startFrame < loopEndFrame ? loopEndFrame : totalFrames; + // A seek (or a loop restart landing exactly on the loop end) can put + // startFrame at or past endFrame - e.g. seeking into an outro after the + // loop point. Play out to the true end of the buffer once instead, same + // as PSP/Linux. + if(startFrame >= endFrame) endFrame = totalFrames; float_t leftFactor, rightFactor; audioStreamGetPanFactors(stream, &leftFactor, &rightFactor); - const float_t baseVolume = (float_t) stream->volume / 255.0f; + stream->platform.volumeLeft = (s32) mathClamp(baseVolume * leftFactor * MAX_VOLUME, 0, MAX_VOLUME); + stream->platform.volumeRight = (s32) mathClamp(baseVolume * rightFactor * MAX_VOLUME, 0, MAX_VOLUME); - stream->platform.finished = false; + // A fresh pass discards whatever windows were previously queued (both + // ASND buffer slots) rather than letting it play out - a natural loop + // restart costs a small gap here (unlike Linux, which deliberately + // leaves its own tail queued for gapless looping); acceptable for now, + // see this backend's own history of iterating on click/gap reduction + // once real hardware confirms it's actually audible. + ASND_StopVoice(stream->platform.voiceId); + stream->platform.nextBufferIndex = 0; + stream->platform.primed = false; - // ansnd's hardware loop needs one contiguous buffer spanning everything - // the voice might play - both start_offset and the loop segment address - // the same frame_data_ptr - so (unlike PSP/Linux, which stream bounded - // windows on demand) this reads the whole segment up front. Still - // bounded to the current loop segment's length rather than the whole - // clip if the clip is longer than one loop - see this struct field's - // own comment. Re-read fresh every Buffer() call. - if(stream->platform.buffer != NULL) { - memoryFree(stream->platform.buffer); - stream->platform.buffer = NULL; + errorChain(audioStreamSeek(stream, startFrame)); + stream->platform.position = startFrame; + stream->platform.endFrame = endFrame; + + // Prime only the first buffer here - the second window can't be queued + // back-to-back with this one, since ASND_SetVoice() leaves VOICE_UPDATE + // set until its own audio-DMA-interrupt tick picks it up, and + // ASND_AddVoice() refuses to queue anything while that's still pending + // (see audioStreamDolphinFeed()'s own comment). Later polls queue + // everything after this. + errorChain(audioStreamDolphinFeed(stream)); + + errorOk(); +} + +errorret_t audioStreamDolphinFeed(audiostream_t *stream) { + assertNotNull(stream, "Stream cannot be NULL."); + + if(stream->platform.position >= stream->platform.endFrame) { + errorOk(); } - errorChain(audioStreamSeek(stream, 0)); - uint8_t *buffer = (uint8_t *) memoryAllocate(bufferFrames * frameSize); - size_t framesRead = 0; - errorret_t readRet = audioStreamRead( - stream, (int16_t *) buffer, bufferFrames, &framesRead - ); - if(errorIsNotOk(readRet)) { - memoryFree(buffer); - errorChain(readRet); - } - if(framesRead < bufferFrames) { - // The asset is shorter than its declared header size (corrupt or - // truncated) - pad what's missing with silence. - memoryZero( - buffer + (framesRead * frameSize), (bufferFrames - framesRead) * frameSize - ); - } - stream->platform.buffer = buffer; - - ansnd_pcm_voice_config_t config; - memoryZero(&config, sizeof(ansnd_pcm_voice_config_t)); - config.samplerate = stream->sampleRate; - config.format = ANSND_VOICE_PCM_FORMAT_SIGNED_16_PCM; - config.channels = stream->channels; - config.pitch = 1.0f; - config.left_volume = baseVolume * leftFactor; - config.right_volume = baseVolume * rightFactor; - // The DSP DMAs frame_data_ptr directly out of main memory, bypassing the - // CPU cache, and rejects any pointer in cached virtual address space - // (0x8xxxxxxx.. - checked as "negative" internally, returned as - // ANSND_ERROR_INVALID_MEMORY) - it needs the physical address instead. - // Flush first so the DMA sees what the CPU actually wrote, not stale - // memory contents. - DCFlushRange(buffer, bufferFrames * frameSize); - config.frame_data_ptr = MEM_VIRTUAL_TO_PHYSICAL(buffer); - config.frame_count = (u32) bufferFrames; - config.start_offset = (u32) startFrame; - config.voice_callback = audioStreamDolphinVoiceCallback; - config.stream_callback = NULL; // single-buffer playback - config.user_pointer = stream; - - // Loop entirely in hardware rather than mirroring PSP/Linux's - // detect-finished-then-restart-from-software approach: ansnd's DSP mixer - // wraps loop_end_offset back to loop_start_offset on its own, so there's - // no restart latency to create a gap in the first place (unlike a - // software restart, which always costs at least a little). + // The very first window of a pass always has room (nothing's queued + // yet) - every window after that needs an actual free slot, which only + // opens up once ASND has finished consuming the previous "primary" + // buffer and promoted the "secondary" one in its place. This is only a + // fast-path skip, not a full guarantee ASND_AddVoice() will actually + // succeed below - see that call's own comment. // - // Trade-off: onLoop never fires for a Dolphin voice looping this way - - // there's no ANSND_VOICE_STATE for "wrapped", only state transitions like - // FINISHED/STOPPED, which a looping voice never reaches. And unlike - // PSP's per-restart tail fade, the DSP does no smoothing at the wrap - // point - it requires loopStart's frame to already flow cleanly into - // loopTo's (true today only because the shared 441Hz test tone was - // deliberately chosen to divide evenly into the sample rate). - if(stream->state & AUDIO_STREAM_STATE_LOOPING) { - config.loop_start_offset = (u32) loopToFrame; - config.loop_end_offset = (u32) loopEndFrame - 1; + // ASND_TestVoiceBufferReady() returns SND_OK (0) when ready and + // SND_BUSY (1) when not - opposite of a plain boolean. Comparing + // explicitly against SND_OK matters: treating the raw result as a bool + // reads a genuinely-ready voice's SND_OK (0) as false/not-ready, + // silently skipping every ASND_AddVoice() call forever after the first + // window - confirmed the hard way (only ever heard a clip's first + // buffered window, nothing ever played after it). + const bool_t ready = ASND_TestVoiceBufferReady(stream->platform.voiceId) == SND_OK; + if(stream->platform.primed && !ready) { + errorOk(); } - s32 result = ansnd_configure_pcm_voice((u32) stream->platform.voiceId, &config); - if(result != ANSND_ERROR_OK) { - errorThrow( - "Failed to configure ansnd voice: %s", ansnd_get_error_string(result) - ); + const size_t frameSize = stream->channels * sizeof(int16_t); + const size_t framesRemaining = stream->platform.endFrame - stream->platform.position; + const size_t framesToRead = mathMin( + (size_t) AUDIO_DOLPHIN_WINDOW_FRAMES, framesRemaining + ); + + uint8_t *buffer = stream->platform.buffer[stream->platform.nextBufferIndex]; + size_t framesRead = 0; + errorChain(audioStreamRead( + stream, (int16_t *) buffer, framesToRead, &framesRead + )); + + const size_t bytesRead = framesRead * frameSize; + const size_t alignedBytes = (bytesRead + 31) & ~((size_t) 31); + if(alignedBytes > bytesRead) { + // Pad up to ASND's required 32-byte alignment - only matters for + // whatever the final (possibly short) window of a pass is, since + // every full window is already a multiple of 32 bytes by + // construction (bufferCapacityBytes itself is pre-aligned). + memoryZero(buffer + bytesRead, alignedBytes - bytesRead); } - result = ansnd_start_voice((u32) stream->platform.voiceId); - if(result != ANSND_ERROR_OK) { - errorThrow( - "Failed to start ansnd voice: %s", ansnd_get_error_string(result) + if(!stream->platform.primed) { + const s32 format = stream->channels == 1 ? VOICE_MONO_16BIT : VOICE_STEREO_16BIT; + const s32 pitch = (s32) stream->sampleRate; + const s32 result = ASND_SetVoice( + stream->platform.voiceId, format, pitch, 0, + buffer, (s32) alignedBytes, + stream->platform.volumeLeft, stream->platform.volumeRight, NULL ); + if(result != SND_OK) { + errorThrow("Failed to set ASND voice: %d", result); + } + stream->platform.primed = true; + } else { + // ASND_SetVoice() leaves VOICE_UPDATE set in this voice's flags until + // ASND's own audio-DMA-interrupt tick actually picks up the change - + // ASND_AddVoice() refuses to queue anything while that (or its own + // VOICE_UPDATEADD, set the same way on a successful call here) is + // still pending, returning SND_INVALID rather than SND_BUSY for it + // despite this being a purely transient, expected condition, not a + // real usage error. ASND_TestVoiceBufferReady() above doesn't catch + // this - it only checks whether the buffer slot itself is empty, a + // completely separate piece of state from these flags - so treat + // *any* non-OK result here as "not ready yet, retry on the next + // poll" rather than a hard failure: don't advance position or + // rotate the buffer index, just leave this same window to be reread + // and requeued next time. + const s32 result = ASND_AddVoice( + stream->platform.voiceId, buffer, (s32) alignedBytes + ); + if(result != SND_OK) { + errorOk(); + } } + stream->platform.position += framesRead; + stream->platform.nextBufferIndex = 1 - stream->platform.nextBufferIndex; + errorOk(); } bool_t audioStreamDolphinIsFinished(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - return stream->platform.finished; -} - -void audioStreamDolphinVoiceCallback(void *userPointer, int32_t voiceState) { - audiostream_t *stream = (audiostream_t *) userPointer; - - if( - voiceState == ANSND_VOICE_STATE_FINISHED || - voiceState == ANSND_VOICE_STATE_STOPPED || - voiceState == ANSND_VOICE_STATE_ERROR - ) { - stream->platform.finished = true; + if(stream->platform.position < stream->platform.endFrame) { + errorret_t ret = audioStreamDolphinFeed(stream); + if(errorIsNotOk(ret)) { + errorCatch(errorPrint(ret)); + return true; // Can't recover - let the shared layer end/loop it. + } + return false; } + + // Nothing left to read for this pass - finished once ASND has actually + // drained both buffer slots too, not just when we stopped feeding it. + return ASND_StatusVoice(stream->platform.voiceId) == SND_UNUSED; } diff --git a/src/duskdolphin/audio/audiostreamdolphin.h b/src/duskdolphin/audio/audiostreamdolphin.h index 0b7daabc..363e3a23 100644 --- a/src/duskdolphin/audio/audiostreamdolphin.h +++ b/src/duskdolphin/audio/audiostreamdolphin.h @@ -10,33 +10,66 @@ typedef struct audiostream_s audiostream_t; +// How many frames of PCM each of the two rotating buffers holds - just +// needs to be comfortably longer than one Update() tick's worth of real +// time, with margin, so polling once per frame never lets ASND run dry. +// Sized in frames (not bytes) to match PSP/Linux's own window constants; +// at 44100Hz mono this is ~371ms per buffer, ~743ms of total lookahead +// across both. Deliberately NOT "read the whole segment" (this backend's +// original design, inherited from its ansnd-based predecessor) - that +// allocated size scaled with asset length and crashed GameCube/Wii's +// limited RAM (24MB/88MB total) outright on a real multi-minute WAV. +#define AUDIO_DOLPHIN_WINDOW_FRAMES 16384 + typedef struct { - // Allocated ansnd voice id. + // Allocated ASND voice slot (0-15, see ASND_GetFirstUnusedVoice()). int32_t voiceId; - // Set by audioStreamDolphinVoiceCallback(), which ansnd invokes from its - // own audio DMA interrupt context. There is no polling API on the voice - // itself (unlike PSP/Linux), so this flag is how audioStreamDolphinIsFinished() - // answers without a callback of its own. - volatile bool_t finished; + // Two fixed-size, 32-byte-aligned scratch buffers (ASND requires this + // alignment), allocated once in Init() and reused for the stream's + // whole lifetime - never resized/reallocated per pass, unlike this + // backend's previous whole-segment-per-Buffer()-call design. Rotated + // as a classic double buffer: one plays via ASND_SetVoice() (the + // "primary" slot), the other is queued ahead via ASND_AddVoice() (the + // "secondary" slot) - see audioStreamDolphinFeed()'s own comment for + // the exact protocol. + uint8_t *buffer[2]; - // ansnd's DSP hardware loop (loop_start_offset/loop_end_offset) needs one - // contiguous physical buffer spanning the whole segment it may play - - // both the pre-loop lead-in and the loop segment itself, since the DSP - // addresses both as offsets into the same frame_data_ptr. Unlike - // PSP/Linux (which stream bounded windows on demand), a Dolphin voice - // can't be handed data incrementally mid-playback, so this buffer is - // read from the asset and owned here for the lifetime of one pass - - // bounded to the current loop segment's length (not the whole clip, if - // the clip is longer than one loop), freed/reallocated on each - // audioStreamDolphinBuffer() call and released for good in - // audioStreamDolphinDispose(). - uint8_t *buffer; + // Byte capacity of each buffer[] slot - fixed at Init() from this + // stream's channel count (frames * channels * sizeof(int16_t), padded + // to 32 bytes). + size_t bufferCapacityBytes; + + // Which buffer[] slot audioStreamDolphinFeed() fills next - alternates + // between 0 and 1 every time it successfully hands one to ASND. + size_t nextBufferIndex; + + // Set once audioStreamDolphinBuffer() has handed the very first window + // of a pass to ASND_SetVoice() - every window after that goes through + // ASND_AddVoice() instead, which requires an already-active voice. + // Cleared at the start of every new pass. + bool_t primed; + + // This pass's own read cursor and stop point, in frames relative to + // the start of the decoded stream - mirrors audiostreamlinux_t.position/ + // endFrame exactly (this backend now streams bounded windows on demand + // just like Linux/PSP, rather than buffering a whole segment up front). + size_t position; + size_t endFrame; + + // This pass's per-voice volume, resolved once in audioStreamDolphinBuffer() + // (pan/volume are only ever set via ASND_SetVoice(), not per-window + // ASND_AddVoice() calls) and reused by every audioStreamDolphinFeed() + // call for that pass's first (ASND_SetVoice()) window. + int32_t volumeLeft; + int32_t volumeRight; } audiostreamdolphin_t; /** - * Initializes the GameCube/Wii-specific playback state of an audio stream - * (e.g. allocating a voice via AESND_AllocateVoice/ansnd equivalent). + * Initializes the GameCube/Wii-specific playback state of an audio stream: + * allocates an ASND voice slot (ASND_GetFirstUnusedVoice()) and this + * stream's two fixed-size double-buffer windows (sized from its channel + * count, AUDIO_DOLPHIN_WINDOW_FRAMES frames each). * * @param stream The audio stream to initialize. * @return Error state if any. @@ -44,8 +77,9 @@ typedef struct { errorret_t audioStreamDolphinInit(audiostream_t *stream); /** - * Disposes the GameCube/Wii-specific playback state of an audio stream, - * releasing its voice. + * Disposes the GameCube/Wii-specific playback state of an audio stream: + * stops its voice (releasing the slot back to ASND_GetFirstUnusedVoice()) + * and frees its two buffers. * * @param stream The audio stream to dispose. * @return Error state if any. @@ -53,9 +87,13 @@ errorret_t audioStreamDolphinInit(audiostream_t *stream); errorret_t audioStreamDolphinDispose(audiostream_t *stream); /** - * Reads the current loop segment's PCM data from the stream's asset (see - * audiostreamdolphin_t.buffer's own comment) and sends it to its - * allocated voice. + * Starts a new playback pass: seeks the stream's decode position to + * stream->startFrame, determines this pass's loop-segment end, resolves + * this pass's volume/pan, stops whatever the voice was doing before (so a + * fresh pass never plays out stale queued windows from the last one), and + * primes it via audioStreamDolphinFeed() - once for the first + * (ASND_SetVoice()) window, once more to also queue the second + * (ASND_AddVoice()) window ahead of time. * * @param stream The audio stream to output. * @return Error state if any. @@ -63,22 +101,27 @@ errorret_t audioStreamDolphinDispose(audiostream_t *stream); errorret_t audioStreamDolphinBuffer(audiostream_t *stream); /** - * Checks whether the stream's voice has finished playing its currently - * buffered data. + * Reads and queues one bounded window (AUDIO_DOLPHIN_WINDOW_FRAMES) of + * PCM data, if (and only if) ASND's voice actually has room for another + * one right now: the very first call of a pass always does (there's + * nothing queued yet - this hands it to ASND_SetVoice()), and every call + * after that only proceeds once ASND_TestVoiceBufferReady() confirms the + * voice's secondary buffer slot has been freed (handed to + * ASND_AddVoice()). A no-op once this pass's platform.position has + * reached platform.endFrame. + * + * @param stream The audio stream to feed. + * @return Error state if any. + */ +errorret_t audioStreamDolphinFeed(audiostream_t *stream); + +/** + * Checks whether the stream's voice has finished playing all of this + * pass's data. As a side effect, tops up the voice's buffers (via + * audioStreamDolphinFeed()) whenever there's room and more of the current + * pass left to read, rather than reporting finished early. * * @param stream The audio stream to check. * @return true if playback has finished, false otherwise. */ bool_t audioStreamDolphinIsFinished(audiostream_t *stream); - -/** - * ansnd voice state callback, registered on every voice by - * audioStreamDolphinBuffer(). Runs from ansnd's audio DMA interrupt context, - * not the main thread. Marks the stream (passed as userPointer) finished - * once its voice stops running. - * - * @param userPointer The audiostream_t this callback belongs to. - * @param voiceState The voice's new state, one of the ANSND_VOICE_STATE_* - * values. - */ -void audioStreamDolphinVoiceCallback(void *userPointer, int32_t voiceState); diff --git a/src/duskdolphin/audio/audiostreammp3decoder.h b/src/duskdolphin/audio/audiostreammp3decoder.h index ec9c0f76..7d77039c 100644 --- a/src/duskdolphin/audio/audiostreammp3decoder.h +++ b/src/duskdolphin/audio/audiostreammp3decoder.h @@ -9,7 +9,7 @@ // GameCube/Wii have no usable hardware MP3 decoder for this engine's // architecture (libogc's MP3Player wraps libmad but drives its own -// internal audio output, bypassing the ansnd-based pipeline +// internal audio output, bypassing the ASND-based pipeline // audiostreamdolphin.c already owns - not a fit here), so Dolphin uses the // same shared libmad-based software backend as Linux, driven directly at // the mad_stream/mad_frame/mad_synth level instead of through MP3Player -