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

Sound buffers? #141

Closed
wildweasel486 opened this issue Apr 16, 2016 · 16 comments
Closed

Sound buffers? #141

wildweasel486 opened this issue Apr 16, 2016 · 16 comments

Comments

@wildweasel486
Copy link

One thing has always bothered me about id Tech 4 games - Doom 3, Quake 4, and Prey specifically; Wolfenstein doesn't seem to have this problem - and that's the way rapid-fire weapons sound. Under sustained fire, the machine gun, chaingun, and plasma rifle all sound as if they're "missing" their firing sounds every few shots. Other things suffer from this, too, like footstep noises. I suspect it's a problem with the sound buffer size and response times, but I can't find any console variables to set to fix it.

I'd asked about this on the Doomworld forums, but was directed to ask here instead; is there some oddity to the id Tech 4 sound engine at work here? I can confirm that it happens in both vanilla Doom 3 (Windows 1.3.1) and in a release build of dhewm3 from last week.

@Evilbaka
Copy link

I think it is specific software/hardware combination, since I stumbled into that error in related to Idtech GoldSource engine on some HL versions and Dreamcast version. But this is not the place for discussing HL, I think.

@wildweasel486
Copy link
Author

Not necessarily, as I've experienced it on a number of different machines with wildly different hardware setups over the years.

@hemebond
Copy link

Apparently this is an issue with sound system synchronisation and has apparently been fixed in the Doom 3 BFG Edition.

@gabrielcuvillier
Copy link
Contributor

gabrielcuvillier commented Mar 13, 2019

Maybe you could try to tweak the "com_asyncSound" cvar to see if there is any differences (need to be done at compilation or startup time):

0: mix sound inline (in the main thread)
1: memory mapped async mix (separate thread, default on windows)
2: callback mixing (not sure if it is working)
3: write async mix (separate thread, default on Linux)

@hemebond
Copy link

Default was 3. No difference with 0 and 1. No sound at all with 2.

I did see mention of a cvar that could "fix" it but apparently it caused cutscene audio to desync.

@gabrielcuvillier
Copy link
Contributor

Ok, this is maybe related to the specific OpenAL implementation being used also

@hemebond
Copy link

hemebond commented Aug 22, 2019

BTW, this is the comment that mentioned a variable for audio sync https://steamcommunity.com/app/208200/discussions/0/343786746010619195/#c343787920131961205

I also tried disabling vsync, com_preciseTic, and com_fixedTic but if it wasn't still running at ~60fps with the stutter, it was running too fast to tell.

@ghost
Copy link

ghost commented Apr 15, 2020

This has always happened to me throughout Win XP and Win 10 and two different motherboards and two Soundblaster cards and happens now with the source port and 120 fps base.dll. I will test more. After playing awhile, it is quite immersion breaking to me since I am sound sensitive, especially when I have modded the weapon firing rates to be faster. However the 120+ fps now makes it worth dealing with!!!

@DanielGibson
Copy link
Member

Is this even a bug?
As it happens with original Doom3 on Windows as well (with and without OpenAL), maybe it's supposed to sound like that and the weapons just stutter every few shots or shot in bursts or something?

@hemebond
Copy link

hemebond commented May 29, 2020

I don't think it's an issue in the BFG edition of the game. I don't have the BFG edition so I'm unsure if the difference in the game files or the engine.

It only affects the sound of the gun, not the actual shots.

https://youtu.be/CNRoOSmkMLI?t=182

@ghost
Copy link

ghost commented May 30, 2020

No need to gaslight us over an obvious bug, DanielGibson. We know better being interested in modding a 15 year old game and this conspiracy is well out in the open to us. The real question is what are the sound coders trying to hide here and why are you helping them? It's been 15 years and the truth must come out!

@DanielGibson
Copy link
Member

No need to gaslight us over an obvious bug, DanielGibson.

Mind your manners, @thoughtartist. This is your first and only warning.

@DanielGibson
Copy link
Member

DanielGibson commented Jun 1, 2020

The fundamental problem is that sound is only updated around 10 times per second, by the async timer in the async thread: https://github.com/dhewm/dhewm3/blob/master/neo/sound/snd_system.cpp#L680-L688 (AsyncUpdate() basically does the same but with more complicated code):

unsigned int dwCurrentBlock = (unsigned int)( inTime * 44.1f / MIXBUFFER_SAMPLES );
// inTime is current Time in milliseconds since the game was started
// MIXBUFFER_SAMPLES = 4096 (it's a constant defined somewhere)
// 44.1f / MIXBUFFER_SAMPLES == 0.010766602
// ...
if ( dwCurrentBlock < nextWriteBlock ) {
	return 0; // !!!
}
// ...
currentSoundWorld->MixLoop( sampleTime, numSpeakers, finalMixBuffer );
// ...
nextWriteBlock = dwCurrentBlock + 1;

so only if inTime has increased enough since last successful update to increase dwCurrentBlock, currentSoundWorld->MixLoop(...); is called - that is the case roughly every 93 milliseconds (=> about 10 updates per second).
MixLoop() starts openal sounds and update ones already playing - for longer sounds streaming from .ogg files - and I think it also does sound effects or something, and calls into SIMD (SSE/Altivec/...) code. I think because of this streaming (decompressing chunks of data, MIXBUFFER_SAMPLES at a time) and the SIMD stuff (which needs correctly aligned buffers) this is only done every MIXBUFFER_SAMPLES samples. I'm not 100% sure which of these reasons (if any) are really relevant for the OpenAL backend, I assume with the software mixing code this was (even?) more important.

So imagine the gamecode tries to play a sound right after MixLoop() has been called - then can theoretically take up to 93 ms until MixLoop() actually tells OpenAL to play it!
And in practice it can even be a bit longer, because AsyncUpdateWrite() is called about every 16 milliseconds and if it's called one millisecond to early for MixLoop() to be called, the time MixLoop() gets called its 15 milliseconds late, which can cause delays of up to 108ms or so.. though those last 15-16ms don't make much of a difference at that point (but could be avoided with only small changes to the code).
And the actual delay drifts over time, due to the gamecode being updated every 16ms and MixLoop() (ideally) being updated every 92-94ms. For example, with the Machine Gun the delays were often like this:

delay: 80ms
delay: 76ms
delay: 39ms
delay: 17ms
delay: 13ms

delay: 90ms
delay: 52ms
delay: 32ms
delay: 28ms
delay: 8ms

delay: 66ms
delay: 62ms
delay: 41ms
delay: 20ms
delay: 15ms

delay: 75ms
... etc

(empty lines added to make those blocks more visible)
So one shot with high delay, then 4 more shots, each a bit less delay, then it starts again with high delay - and keep in mind that the MP shoots 10x/second => one shot every 100ms!
This leads to the shots sounding like bursts and it can happen that two sounds are played so quickly after another that it sounds like one sound, which makes it sound like one is dropped (I didn't actually observe this, but if it somehow happens that the gamecode "starts" two shot sounds between MixLoop() calls, that the second actually cancels the first and it's really dropped).

As a side-note (mostly to myself) that async thread thing is pretty broken, it tries to only get the AsyncTimer() callback called about every 16ms, but in fact it's called every millisecond, calls idCommonLocal::Async() and that function will then make sure things (incl. idSoundSystemLocal::AsyncUpdate()) are only called every 16ms.
So as idCommonLocal::Async() is in fact called every millisecond, it could just call idSoundSystemLocal::AsyncUpdate() every time instead of every 16ms, so it'll indeed be called every 92-94ms. But as I said above, that doesn't fix the main issue..

Unfortunately, I'm not sure how to fix this issue, I'd have to find out if this "only update every 4096 samples" stuff is really needed and if it is, work around it somehow, and otherwise call MixLoop() more often.
All in all the OpenAL backend has been hacked ontop the old sound backend and thus carries restrictions from the (apparently already sub-optimal) old software mixing backends :-/
Not sure when/if I get around to this, just debugging to get these insights took a lot of time..

DanielGibson added a commit to DanielGibson/dhewm3 that referenced this issue Jun 1, 2020
For some reason sounds were only updated/started about every 100ms,
which could lead to delays of up to around 100ms after a sound gets
started (with idSoundEmitterLocal::StartSound()) until OpenAL is finally
told to play the sound.
Also, the actual delay drifted over time between 1ms and 100ms, as the
sound ticks weren't a fixed multiple of the (16ms) gameticks - and the
sound updates didn't even happen at the regular 92-94ms intervals they
should because they run in the async thread which only updates every
16ms...
Because of this, the machine gun and other rapid firing weapons sounded
like they shot in bursts or left out shots occasionally, even though
they don't.
Anyway, now sound is updated every 16ms in the async thread so delays
are <= 16ms and hopefully less noticeable.

You can still get the old behavior with com_asyncSound 2 if you want.
@DanielGibson
Copy link
Member

DanielGibson commented Jun 1, 2020

I might have a fix (which assumes that the calls to software mixing functions were superfluous) - now sound is updated 60 times per second instead of 10 times per second which keeps delays below 16ms.
I tested it a bit an didn't notice any issues, but it definitely needs thorough testing for any new sound-related issues anywhere in the game before going into master.

Windows binaries: dhewm3-oal-timing-fix.zip
Source code: https://github.com/dhewm/dhewm3/tree/openal-timing-fix

You can set com_asyncSound 2 to get the old behavior for comparison (the meaning of com_asyncSounds values have changed a bit with this change), com_asyncSound 1 (or 3) give you the new behavior.

Thanks in advance for testing!

@hemebond
Copy link

hemebond commented Jun 2, 2020

You got it 🎉. That is so much better. It's even more obvious with the plasma gun. Thank you very much.

I even tested with a little mod that increased the fire-rate of the machinegun and plasmagun and that was really good too.

@DanielGibson
Copy link
Member

Great, I'm glad it works!
I just hope I didn't break anything else..

DanielGibson added a commit that referenced this issue May 28, 2022
It happened a lot more since
  504b572 Update sounds at ~60Hz instead of ~10Hz, fixes #141
(because then MixLoop() is more likely to be called in the narrow
 timeframe this can happen during level load) but could happen before.
So far I only observed it when starting a new game in Classic Doom 3.
See comment in the change and #461 for more information.
rorgoroth pushed a commit to rorgoroth/dhewm3 that referenced this issue Apr 8, 2023
For some reason sounds were only updated/started about every 100ms,
which could lead to delays of up to around 100ms after a sound gets
started (with idSoundEmitterLocal::StartSound()) until OpenAL is finally
told to play the sound.
Also, the actual delay drifted over time between 1ms and 100ms, as the
sound ticks weren't a fixed multiple of the (16ms) gameticks - and the
sound updates didn't even happen at the regular 92-94ms intervals they
should because they run in the async thread which only updates every
16ms...
Because of this, the machine gun and other rapid firing weapons sounded
like they shot in bursts or left out shots occasionally, even though
they don't.
Anyway, now sound is updated every 16ms in the async thread so delays
are <= 16ms and hopefully less noticeable.

You can still get the old behavior with com_asyncSound 2 if you want.
rorgoroth pushed a commit to rorgoroth/dhewm3 that referenced this issue Apr 8, 2023
It happened a lot more since
  504b572 Update sounds at ~60Hz instead of ~10Hz, fixes dhewm#141
(because then MixLoop() is more likely to be called in the narrow
 timeframe this can happen during level load) but could happen before.
So far I only observed it when starting a new game in Classic Doom 3.
See comment in the change and dhewm#461 for more information.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants