Skip to content

audio tripple buffering; speed limit based on audio#47

Open
skmp wants to merge 1 commit into
mainfrom
buffered-audio
Open

audio tripple buffering; speed limit based on audio#47
skmp wants to merge 1 commit into
mainfrom
buffered-audio

Conversation

@skmp

@skmp skmp commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

i had to enable DSP LLE on dolphin for this to work correctly, no idea why. maybe i have an old build of dolphin?

@saulfabregwiivc

Copy link
Copy Markdown

@skmp most homebrew apps requires the user to use DSP LLE on Dolphin, even in newer development versions.

@BenoitAdam

Copy link
Copy Markdown
Owner

@skmp most homebrew apps requires the user to use DSP LLE on Dolphin, even in newer development versions.

Its strange because in the end I didn't have to use DSP LLE to make that PR work

Unfortunatly as we discuss on discord we lost 50% performance on this one so I'm waiting for an update of this PR as you said it could work without any performance loss (or a new PR, not sure what's the best practice)

@BenoitAdam

Copy link
Copy Markdown
Owner

some tests

settings.emulator.AudioBuffers = 0 + 735 samples = crackling audio - max FPS
settings.emulator.AudioBuffers = 0 + 1024 samples = crackling audio (less) - max FPS

settings.emulator.AudioBuffers = 1 + 735 samples = crackling audio - low FPS
settings.emulator.AudioBuffers = 1 + 1024 samples = perfect audio - mid FPS

settings.emulator.AudioBuffers = 2 + 735 samples = crackling audio - low FPS
settings.emulator.AudioBuffers = 2 + 1024 samples = perfect audio - mid FPS

settings.emulator.AudioBuffers = 3 + 735 samples = crackling audio - low FPS
settings.emulator.AudioBuffers = 3 + 1024 samples = perfect audio - mid FPS

AI sugest ring buffer logic but that's not perfect either

// wii_audio.cpp
// ASND-based audio output for nullAICA on Wii.
//
// Ring buffer architecture:
//   - NUM_RING_BUFFERS buffers of SAMPLES_PER_BUF stereo samples each.
//   - Producer (wii_audio_push_sample) writes into the current ring buffer.
//   - When full, the buffer is marked ready and the write index advances.
//   - Callback (audio_callback) copies the next ready buffer into a fixed
//     playback buffer and queues it, then advances the read index.
//   - AudioBuffers controls queue depth: 0 = never block (drop on overflow),
//     1..3 = block when ring_count >= AudioBuffers.

// config.h poisoning undefs (keep as before)
#ifdef BIG_ENDIAN
#  undef BIG_ENDIAN
#endif
#ifdef LITTLE_ENDIAN
#  undef LITTLE_ENDIAN
#endif

#include "../plugs/nullAICA/aica.h"
#include "../plugs/nullAICA/sgc_if.h"

#ifdef BIG_ENDIAN
#  undef BIG_ENDIAN
#endif
#ifdef LITTLE_ENDIAN
#  undef LITTLE_ENDIAN
#endif

#include "wii_audio.h"
#include <asndlib.h>
#include <string.h>
#include <unistd.h>
#include <ogc/cache.h>        // DCFlushRange

// ---------------------------------------------------------------------------
// AICA ready flag
// ---------------------------------------------------------------------------
static volatile int aica_ready = 0;

void wii_audio_aica_ready()
{
    printf("[WiiAudio] AICA ready, audio sink enabled\n");
    aica_ready = 1;
}

// ---------------------------------------------------------------------------
// Audio parameters (adjust SAMPLES_PER_BUF as needed)
// ---------------------------------------------------------------------------
#define SAMPLES_PER_BUF     1024   // or 1500, 2048, etc.
#define NUM_CHANNELS        2
#define BUF_BYTES           (SAMPLES_PER_BUF * NUM_CHANNELS * 2 /*s16*/)
#define SAMPLE_RATE         44100
#define VOICE_SLOT          0

// ---------------------------------------------------------------------------
// Ring buffer configuration
//   Increase NUM_RING_BUFFERS to absorb larger bursts.
//   Each buffer is BUF_BYTES (e.g., 4 KiB for 1024 samples).
// ---------------------------------------------------------------------------
#define NUM_RING_BUFFERS    8

// Two fixed playback buffers (alternated by callback) – must be 32‑byte aligned.
static s16 play_buf[2][SAMPLES_PER_BUF * NUM_CHANNELS] __attribute__((aligned(32)));

// Ring buffers – also aligned for safety.
static s16 ring_buf[NUM_RING_BUFFERS][SAMPLES_PER_BUF * NUM_CHANNELS] __attribute__((aligned(32)));

// Ring state (volatile because accessed by both producer and IRQ callback)
static volatile int ring_write  = 0;   // index of the buffer currently being filled
static volatile int ring_read   = 0;   // index of the next buffer to play
static volatile int ring_count  = 0;   // number of buffers ready to play (0..NUM_RING_BUFFERS)
static volatile int fill_pos    = 0;   // sample position within the current write buffer

// Playback index (which of the two play_buf is currently queued)
static volatile int play_idx    = 0;

// ---------------------------------------------------------------------------
// Voice callback (IRQ context)
// ---------------------------------------------------------------------------
static void audio_callback(s32 voice)
{
    (void)voice;

    int next = play_idx ^ 1;

    if (ring_count > 0)
    {
        // Copy the next ready ring buffer into the alternate playback buffer
        memcpy(play_buf[next], ring_buf[ring_read], BUF_BYTES);

        // Advance read pointer and decrement count (memory barrier ensures visibility)
        ring_read = (ring_read + 1) % NUM_RING_BUFFERS;
        __sync_synchronize();
        ring_count--;
    }
    else
    {
        // Underrun: replay the previous buffer (no audible glitch)
        // (play_buf[play_idx] already contains the last good data)
    }

    // Flush the playback buffer to main RAM so ASND DMA sees it
    DCFlushRange(play_buf[next], BUF_BYTES);

    // Queue it for playback
    ASND_AddVoice(VOICE_SLOT, (void *)play_buf[next], BUF_BYTES);
    play_idx = next;
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
void wii_audio_init()
{
    printf("[WiiAudio] init: rate=%d bytes=%d AudioBuffers=%d ring buffers=%d\n",
           SAMPLE_RATE, BUF_BYTES, (int)settings.emulator.AudioBuffers, NUM_RING_BUFFERS);

    // Clear all buffers to silence
    memset(play_buf, 0, sizeof(play_buf));
    memset(ring_buf, 0, sizeof(ring_buf));
    DCFlushRange(play_buf, sizeof(play_buf));
    DCFlushRange(ring_buf, sizeof(ring_buf));

    // Reset ring state
    ring_write = 0;
    ring_read  = 0;
    ring_count = 0;
    fill_pos   = 0;
    play_idx   = 0;

    // Configure the ASND voice with the first playback buffer
    ASND_SetVoice(VOICE_SLOT,
                  VOICE_STEREO_16BIT,
                  SAMPLE_RATE,
                  0,                    // delay (ms)
                  (void *)play_buf[0],
                  BUF_BYTES,
                  255, 255,             // L / R volume
                  audio_callback);

    ASND_Pause(0);   // ASND starts paused after ASND_Init()
}

void wii_audio_term()
{
    ASND_StopVoice(VOICE_SLOT);
}

// ---------------------------------------------------------------------------
// Audio sink – called once per generated stereo sample
// ---------------------------------------------------------------------------
void wii_audio_push_sample(s16 l, s16 r)
{
    if (!aica_ready)
        return;

    // Write to the current ring buffer at fill_pos
    s16 *dst = ring_buf[ring_write];
    dst[fill_pos * 2 + 0] = l;
    dst[fill_pos * 2 + 1] = r;
    fill_pos++;

    // If the current ring buffer is full, finalise it
    if (fill_pos < SAMPLES_PER_BUF)
        return;

    fill_pos = 0;

    // Flush the completed buffer out of CPU cache so ASND DMA sees it
    DCFlushRange(ring_buf[ring_write], BUF_BYTES);

    // -----------------------------------------------------------------------
    // Manage ring queue – handle overflow and blocking
    // -----------------------------------------------------------------------

    // If ring is full, we must either drop the oldest (AudioBuffers==0)
    // or block until space frees up (AudioBuffers >= 1).
    if (ring_count == NUM_RING_BUFFERS)
    {
        if (settings.emulator.AudioBuffers == 0)
        {
            // Drop the oldest buffer to make room (advance read pointer)
            ring_read = (ring_read + 1) % NUM_RING_BUFFERS;
            // ring_count stays at NUM_RING_BUFFERS - 1 after we increment below
            // We'll decrement after the increment? Actually we need to decrement count.
            // Simpler: decrement count, then increment later.
            __sync_synchronize();
            ring_count--;  // now room for one more
        }
        else
        {
            // Block until there is space in the ring (i.e., ring_count < NUM_RING_BUFFERS)
            // The setting AudioBuffers limits how many buffers we keep queued.
            // We block when ring_count >= AudioBuffers (so we never exceed that depth).
            while (ring_count >= (int)settings.emulator.AudioBuffers)
                usleep(50);
        }
    }

    // Now we have space – mark the buffer as ready and advance write pointer
    __sync_synchronize();
    ring_count++;
    ring_write = (ring_write + 1) % NUM_RING_BUFFERS;

    // Optional: if AudioBuffers > 0, we already blocked above if ring_count >= AudioBuffers
    // For AudioBuffers == 0, we don't block.
}

// ---------------------------------------------------------------------------
// Legacy no‑op (kept for linking)
// ---------------------------------------------------------------------------
void wii_audio_frame()
{
}

@BenoitAdam

Copy link
Copy Markdown
Owner

doesn't it play CD/GDRom track by the way ? Chuchu rocket music isn't heard

@BenoitAdam

Copy link
Copy Markdown
Owner

beeing testing this branch with no buffer and 1024 samples for some times now

When having full speed it's better
When not having full speed it's worse

Chuchu Rocket sound is glitchy (lots of echo/delay) in both

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants