dirkwhoffmann / virtualc64

VirtualC64 is a cycle-accurate C64 emulator for macOS
https://dirkwhoffmann.github.io/virtualc64
Other
356 stars 33 forks source link

Audio buffer runs dry from time to time #491

Closed dirkwhoffmann closed 5 years ago

dirkwhoffmann commented 5 years ago

In theory, the emulator produces audio samples at the exact same rate as they are grabbed by the AudioEngine (macOS side). As both sides are decoupled (two different threads), "exact" is just "nearly exact". As a result, a buffer underflow or overflow occurs from time to time.

Todo: Dynamically adjust the number of generated samples according to the current buffer fill level.

dirkwhoffmann commented 5 years ago

I've added a dynamic adjustment feature for the sample rate in the latest alpha:

http://www.dirkwhoffmann.de/virtualc64/VirtualC64_3.3_alpha8.zip

Background: The Mac's audio engine and the SID implementation both run with a sampling rate of 48k. However, the Mac seems to be a little too fast (or SID a little too slow) on my machine, so buffer underflows occur from time to time. Because the issue might be machine dependent, the underflow handler is now smart enough to find out the adequate sampling rate by itself. The code explains what happens:

void
SIDBridge::handleBufferUnderflow()
{
    // There are two common scenarios in which buffer underflows occur:
    //
    // (1) The consumer runs slightly faster than the producer.
    // (2) The producer is halted or not startet yet.

    debug(2, "SID RINGBUFFER UNDERFLOW (r: %ld w: %ld)\n", readPtr, writePtr);

    // Determine the elapsed seconds since the last pointer adjustment.
    uint64_t now = mach_absolute_time();
    double elapsedTime = (double)(now - lastAligment) / 1000000000.0;
    lastAligment = now;

    // Adjust the sample rate, if condition (1) holds.
    if (elapsedTime > 10.0) {

        bufferUnderflows++;

        // Increase the sample rate based on what we've measured.
        long offPerSecond = samplesAhead / elapsedTime;
        setSampleRate(getSampleRate() + (int)offPerSecond);
    }

    // Reset the write pointer
    alignWritePtr();
}

On my machine, the emulator starts with 48000 samples per second. After a couple of seconds, the first underflow occurs and the sample rate is adjusted to 48085. After a second while, another underflow occurs which sets the sampling frequency to 48123. After that, SID and Mac seem to be in (nearly) perfect sync and no further underflows occur.