Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal audio latency #714

Open
triplefox opened this issue Sep 1, 2018 · 8 comments
Open

Internal audio latency #714

triplefox opened this issue Sep 1, 2018 · 8 comments

Comments

@triplefox
Copy link

@triplefox triplefox commented Sep 1, 2018

When running TIC-80, across multiple platforms(Windows desktop, HTML5 player, Android), I always notice small pops and creaks when it plays sound, symptoms of frequent buffer underruns during audio playback. The current method it uses is to call SDL_QueueAudio from within the render tick and blit everything that has been rendered in that frame, which leaves the program heavily reliant on making every frame deadline every time.

Most audio programs run a separate audio thread with intentionally added frames of latency to smooth this out: instead of handing over all the samples that have been constructed right at that moment, they're repackaged into fixed-size audio frames for API consumption, and the renderer stays ahead of the API by a target number of frames. This adds a margin of safety, and the fixed size plays better with most audio APIs. Many apps also expose some user configuration options for buffer sizes.

@nesbox
Copy link
Owner

@nesbox nesbox commented Sep 2, 2018

In few words, we have to add empty buffer to the audio frames to allow slightly late for the sound system...
I'll try to add this, thanks.

@nesbox nesbox added this to the 0.80.0 milestone Sep 27, 2019
@nesbox nesbox self-assigned this Sep 27, 2019
@borbware
Copy link

@borbware borbware commented May 27, 2020

probably related to this: on my machine, if i leave tic-80 running for a long time, audio latency keeps building up, and before long, audio is played with a latency of several seconds. (0.70.6 pro, win10)

@nesbox
Copy link
Owner

@nesbox nesbox commented May 27, 2020

Does your machine go to sleep mode during this time?

@borbware
Copy link

@borbware borbware commented May 31, 2020

no, it does not! It happens steadily and gradually if I just keep TIC-80 open for a while (e.g., if it's on for a couple of hours, there is a guaranteed audio latency of a few seconds)

@nesbox
Copy link
Owner

@nesbox nesbox commented May 31, 2020

need investigation, thank you @borbware

@nesbox nesbox removed this from the 0.80.0 milestone Jul 15, 2020
@nesbox nesbox added this to To do in dev version 0.90 Jul 15, 2020
@runlow
Copy link

@runlow runlow commented Feb 21, 2021

Experiencing the same problem as @borbware is describing but on Archlinux.

If I open a game or the music tracker for too long then I have to close TIC-80 and open it again. If I don't do this every 10 minutes or so the sound delay becomes greater and greater. It doesn't take hours - few minutes is enough. This makes the music tracker or any game with longer gameplay unusable/unplayable for me.

When I run TIC80 from the command line it would slowly keep showing the following message in the terminal but the program would keep running
ALSA lib pcm.c:8545:(snd_pcm_recover) underrun occurred

From alsa documentation:
"The underrun can happen when an application does not feed new samples in time to alsa-lib (due CPU usage)"

I've noticed that when I run pulseaudio this delay problem seems to go away and the sound on TIC-80 seems OK but running pulseaudio in turn causes other problems with other programs that use the soundcard so I would prefer not to run pulseaudio.
For example if something is already playing sound with pulsaudio running and I start TIC-80 then after closing it - TIC-80 still keeps running in the background and I have to type pkill -9 tic80 to force it to end the process.

If anyone has suggestions for workarounds or patches please let me know. I was thinking about maybe wrapping the tic80 command in a shell script which checks if the sound card is in use and if it's not then starting and exiting pulseaudio with tic80, and otherwise not starting pulseaudio it at all. Another idea was that whatever pulseaudio is doing that makes the delay go away could be maybe accomplished somehow with alsa-tools. I could try to figure out how preparing samples for the sound card works (guessing it's this part here) and patch and build TIC-80 but it's a bit over my head.

Edit: I've made sure that everything that uses audio is using sdl or pulseaudio as the audio device and showing up with a moving bar on pavucontrol and this seems to prevent tic80 from not exiting correcty - as strange as this may sound. This may be a good enough workaround for now. Hope this helps someone.
Edit2: installing the pulseaudio-alsa package may have helped. Not an ideal solution but I think it works well enough for now.

@nesbox please add a "bug" label on this one as making the audio work correctly is not an enhancement in my opinion

@nesbox nesbox removed this from To do in dev version 0.90 Jul 16, 2021
@nesbox nesbox added this to To do in developing version 1.0 via automation Jul 16, 2021
@terrybrash
Copy link

@terrybrash terrybrash commented Sep 18, 2021

Same problem as @borbware. Leaving TIC-80 open and not interacting with it for maybe 5-20 minutes will cause long audio latency that can only be fixed by restarting TIC-80.

TIC-80: version 0.90.1723 Pro (9c38a80)
OS: Windows Version 10.0.19043

Related issue: #1199

vsariola added a commit to vsariola/TIC-80 that referenced this issue Oct 6, 2021
The audio is synthesized in SDL_AudioCallback. At the end of a tick, SDL_LockAudio is issued, sound registers are copied to the part of memory owned by the audio thread, and then the audio is released with SDL_UnlockAudio.
vsariola added a commit to vsariola/TIC-80 that referenced this issue Oct 7, 2021
Previously, the sound registers were used to synthesize fixed amount of sound after every TIC() and this was pushed to SDL. The problem with this approach was crackling if TIC() takes too long to reach 60Hz. In this fix, the sound registers are instead pushed to a ring buffer and the audio is synthesize in SDL callback to render audio. If the ring buffer does not have enough values, the sound synthesis uses the last good values from the ring buffer, so the music slows down yes, but at least it does not crackle.

The corollary to this is that calling tic_core_tick_end(tic) is not enough during the Studio sound export; one should also call tic_core_synthesize_sound which does the job of pulling the sound registers from the tail of the ring buffer and doing the sound synthesis.

The ring buffer is kept short on purpose, because if there slight desync between the TIC() frequency and sound frequency, the TIC() frequency might run faster and delay might build up. So, the ring buffer length sets a maximum on the delay that this technique can create.
vsariola added a commit to vsariola/TIC-80 that referenced this issue Oct 7, 2021
Previously, the sound registers were used to synthesize fixed amount of sound after every TIC() and this was pushed to SDL. The problem with this approach was crackling if TIC() takes too long to reach 60Hz. In this fix, the sound registers are instead pushed to a ring buffer and the audio is synthesize in SDL callback to render audio. If the ring buffer does not have enough values, the sound synthesis uses the last good values from the ring buffer, so the music slows down yes, but at least it does not crackle.

The corollary to this is that calling tic_core_tick_end(tic) is not enough during the Studio sound export; one should also call tic_core_synthesize_sound which does the job of pulling the sound registers from the tail of the ring buffer and doing the sound synthesis.

The ring buffer is kept short on purpose, because if there slight desync between the TIC() frequency and sound frequency, the TIC() frequency might run faster and delay might build up. So, the ring buffer length sets a maximum on the delay that this technique can create.
@runlow
Copy link

@runlow runlow commented Oct 9, 2021

The fork @vsariola wrote seems to fix all the problems I reported here.

The audio doesn't (perceptibly) de-sync at any point - no matter how long tic80 is open - and I could uninstall pulseaudio.

Thank you!

vsariola added a commit to vsariola/TIC-80 that referenced this issue Oct 9, 2021
Previously, the sound registers were used to synthesize fixed amount of sound after every TIC() and this was pushed to SDL. The problem with this approach was crackling if TIC() takes too long to reach 60Hz. In this fix, the sound registers are instead pushed to a ring buffer and the audio is synthesized in SDL callback to render audio. If the ring buffer does not have enough values, the sound synthesis uses the last good values from the ring buffer, so the music slows down yes, but at least it does not crackle.

The corollary to this is that calling tic_core_tick_end(tic) is not enough during the Studio sound export; one should also call tic_core_synthesize_sound which does the job of pulling the sound registers from the tail of the ring buffer and doing the sound synthesis.

The ring buffer is kept short on purpose, because if there slight desync between the TIC() frequency and sound frequency, the TIC() frequency might run faster and delay might build up. So, the ring buffer length sets a maximum on the delay that this technique can create.
vsariola added a commit to vsariola/TIC-80 that referenced this issue Oct 9, 2021
Previously, the sound registers were used to synthesize fixed amount of sound after every TIC() and this was pushed to SDL. The problem with this approach was crackling if TIC() takes too long to reach 60Hz. In this fix, the sound registers are instead pushed to a ring buffer and the audio is synthesized in SDL callback to render audio. If the ring buffer does not have enough values, the sound synthesis uses the last good values from the ring buffer, so the music slows down yes, but at least it does not crackle.

The corollary to this is that calling tic_core_tick_end(tic) is not enough during the Studio sound export; one should also call tic_core_synthesize_sound which does the job of pulling the sound registers from the tail of the ring buffer and doing the sound synthesis.

The ring buffer is kept short on purpose, because if there slight desync between the TIC() frequency and sound frequency, the TIC() frequency might run faster and delay might build up. So, the ring buffer length sets a maximum on the delay that this technique can create.
nesbox added a commit that referenced this issue Nov 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants