[Day 019] - Possible solution to the 3-frame latency

Hi everyone!

I know I'm a few years late, and Casey probably already discussed it again down the line, but I'd like to drop some thoughts about Day 19's episode. I'm very inexperienced so please forgive any newbie mistakes.

So the smallest latency we could get from Windows/DirectSound was 3 frames.
With that in mind wouldn't it be worth to use the approach Casey previously mentioned of overwriting the audio we wrote in the previous frame? Or wouldn't it work for some reason?

PS: Wouldn't it be better if we had a forum thread per episode, so we can organize the discussion a bit more, when it is very dependent of the episode?

Thanks!
Dan Zaidan
The problem is that you're not supposed to write in the buffer between the play cursor and the write cursor. It basically means that the minimum latency is equal to writeCursor - playCursor (on Windows Vista and later DirectSound is emulated, so you never get your real sound card latency, the latency is fixed at 30ms I believe). To get the minimum latency you would always write at the write cursor. But you'll not get better than 30ms using DirectSound.

An other issue is that if you overwrite sound, it means that you need to re-mix sounds and effects that you already mixed before. And it can cost some time that would possibly make you late since you already read the cursors to know how much to overwrite. Unless the game really need low audio latency the change will be imperceptible.

So to summarize, it could work, but with not much benefits.

A way I used to reduce latency is to track how much the write cursor advance in the frame for a period of time. For example keep track of the write cursor advance for the last 10 seconds. If in most frame the advance is 960 samples, try to have 960 samples after the write cursor each frame. If for a frame the advance is 480 than write only 480 samples (960 - 480).
1
2
3
4
5
frame n + 0: write cursor = 0, advance 0, write 960 samples, buffer filled up to 960
frame n + 1: write cursor = 960, advance 960, write 960 samples (960 - (960-advance)), buffer filled up to 1920
frame n + 2: write cursor = 1440, advance 480, write 480 samples (960 - (960-advance)), buffer filled up to 2400
frame n + 3: write cursor = 2400, advance 960, write 960 samples (960 - (960-advance)), buffer filled up to 3360
...

You still have a latency of writeCursor - playCursor, but it reduces added latency to avoid clicking sound.

Edited by Simon Anciaux on Reason: Fix advance
Thinking about the write/play cursor relation as write cursor - play cursor = sound latency is really clarifying.

Thanks so much for your explanation!!

When you say that we should keep track of the amount the write cursor changes in a frame, is that what Casey was calculating when he wrote that closed loop calling GetCurrentPosition and analyzing how much the write cursor advanced? If so, can we expect that this value is consistent across all machines running our game with DirectSound or do we have to calculate that in the game itself?
The method I explained is a way I used and that was working for me on a small project. Please don't take it has THE WAY to do things.

It's been a long time since I've watch those handmade hero episodes but I remember that Casey did a loop to see how fast the cursors were updated. The write cursor advance is not consistent on one machine (it can advance different values every frame) so you need to measure it at game runtime and constantly.

When the game starts the advance varies a lot. After a few seconds it varies less but it can still change so you need to continue to monitor it during the wall run of your application.

But remember that DirectSound is emulated on recent OS. A better sound alternatives on windows is WASAPI (mmozeiko did a version of day 19).

Edited by Simon Anciaux on Reason: Typo
I'll definitely will keep that in mind.
It's awesome that mmozeiko shared his WASAPI implementation.

Thanks again for helping me out. ;)