Skip to content

Commit

Permalink
Update sounds at ~60Hz instead of ~10Hz, fixes #141
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
DanielGibson committed Jul 12, 2020
1 parent e6f3713 commit 504b572
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 31 deletions.
18 changes: 10 additions & 8 deletions neo/framework/Common.cpp
Expand Up @@ -83,12 +83,8 @@ idCVar com_purgeAll( "com_purgeAll", "0", CVAR_BOOL | CVAR_ARCHIVE | CVAR_SYSTEM
idCVar com_memoryMarker( "com_memoryMarker", "-1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_INIT, "used as a marker for memory stats" );
idCVar com_preciseTic( "com_preciseTic", "1", CVAR_BOOL|CVAR_SYSTEM, "run one game tick every async thread update" );
idCVar com_asyncInput( "com_asyncInput", "0", CVAR_BOOL|CVAR_SYSTEM, "sample input from the async thread" );
#define ASYNCSOUND_INFO "0: mix sound inline, 1: memory mapped async mix, 2: callback mixing, 3: write async mix"
#if defined( __unix__ ) && !defined( MACOS_X )
idCVar com_asyncSound( "com_asyncSound", "3", CVAR_INTEGER|CVAR_SYSTEM|CVAR_ROM, ASYNCSOUND_INFO );
#else
idCVar com_asyncSound( "com_asyncSound", "1", CVAR_INTEGER|CVAR_SYSTEM, ASYNCSOUND_INFO, 0, 1 );
#endif
#define ASYNCSOUND_INFO "0: mix sound inline, 1 or 3: async update every 16ms 2: async update about every 100ms (original behavior)"
idCVar com_asyncSound( "com_asyncSound", "1", CVAR_INTEGER|CVAR_SYSTEM, ASYNCSOUND_INFO, 0, 3 );
idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "force generic platform independent SIMD" );
idCVar com_developer( "developer", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "developer mode" );
idCVar com_allowConsole( "com_allowConsole", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "allow toggling console with the tilde key" );
Expand Down Expand Up @@ -2506,11 +2502,14 @@ void idCommonLocal::SingleAsyncTic( void ) {

switch ( com_asyncSound.GetInteger() ) {
case 1:
soundSystem->AsyncUpdate( stat->milliseconds );
break;
case 3:
// DG: these are now used for the new default behavior of "update every async tic (every 16ms)"
soundSystem->AsyncUpdateWrite( stat->milliseconds );
break;
case 2:
// DG: use 2 for the old "update only 10x/second" behavior in case anyone likes that..
soundSystem->AsyncUpdate( stat->milliseconds );
break;
}

// we update com_ticNumber after all the background tasks
Expand Down Expand Up @@ -2754,6 +2753,9 @@ static unsigned int AsyncTimer(unsigned int interval, void *) {
// calculate the next interval to get as close to 60fps as possible
unsigned int now = SDL_GetTicks();
unsigned int tick = com_ticNumber * USERCMD_MSEC;
// FIXME: this is pretty broken and basically always returns 1 because now now is much bigger than tic
// (probably com_tickNumber only starts incrementing a second after engine starts?)
// only reason this works is common->Async() checking again before calling SingleAsyncTic()

if (now >= tick)
return 1;
Expand Down
2 changes: 1 addition & 1 deletion neo/framework/Session.cpp
Expand Up @@ -2524,7 +2524,7 @@ extern bool CheckOpenALDeviceAndRecoverIfNeeded();
void idSessionLocal::Frame() {

if ( com_asyncSound.GetInteger() == 0 ) {
soundSystem->AsyncUpdate( Sys_Milliseconds() );
soundSystem->AsyncUpdateWrite( Sys_Milliseconds() );
}

// DG: periodically check if sound device is still there and try to reset it if not
Expand Down
32 changes: 11 additions & 21 deletions neo/sound/snd_system.cpp
Expand Up @@ -695,7 +695,10 @@ int idSoundSystemLocal::AsyncMix( int soundTime, float *mixBuffer ) {
/*
===================
idSoundSystemLocal::AsyncUpdate
called from async sound thread when com_asyncSound == 1 ( Windows )
called from async sound thread when com_asyncSound == 2
DG: using this for the "traditional" sound updates that
only happen about every 100ms (and lead to delays between 1 and 110ms between
starting a sound in gamecode and it being played), for people who like that..
===================
*/
int idSoundSystemLocal::AsyncUpdate( int inTime ) {
Expand Down Expand Up @@ -772,9 +775,12 @@ int idSoundSystemLocal::AsyncUpdate( int inTime ) {
/*
===================
idSoundSystemLocal::AsyncUpdateWrite
sound output using a write API. all the scheduling based on time
we mix MIXBUFFER_SAMPLES at a time, but we feed the audio device with smaller chunks (and more often)
called by the sound thread when com_asyncSound is 3 ( Linux )
DG: using this now for 60Hz sound updates
called from async sound thread when com_asyncSound is 3 or 1
also called from main thread if com_asyncSound == 0
(those were the default values used in dhewm3 on unix-likes (except mac) or rest)
with this, once every async tic new sounds are started and existing ones updated,
instead of once every ~100ms.
===================
*/
int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
Expand All @@ -783,21 +789,7 @@ int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
return 0;
}

unsigned int dwCurrentBlock = (unsigned int)( inTime * 44.1f / MIXBUFFER_SAMPLES );

if ( nextWriteBlock == 0xffffffff ) {
nextWriteBlock = dwCurrentBlock;
}

if ( dwCurrentBlock < nextWriteBlock ) {
return 0;
}

if ( nextWriteBlock != dwCurrentBlock ) {
Sys_Printf( "missed %d sound updates\n", dwCurrentBlock - nextWriteBlock );
}

int sampleTime = dwCurrentBlock * MIXBUFFER_SAMPLES;
int sampleTime = inTime * 44.1f;
int numSpeakers = s_numberOfSpeakers.GetInteger();

// enable audio hardware caching
Expand All @@ -811,8 +803,6 @@ int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
// disable audio hardware caching (this updates ALL settings since last alcSuspendContext)
alcProcessContext( openalContext );

// only move to the next block if the write was successful
nextWriteBlock = dwCurrentBlock + 1;
CurrentSoundTime = sampleTime;

return Sys_Milliseconds() - inTime;
Expand Down
7 changes: 6 additions & 1 deletion neo/sound/snd_world.cpp
Expand Up @@ -1857,7 +1857,11 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
chan->triggered = false;
}
}
} else {
}
#if 1 // DG: I /think/ this was only relevant for the old sound backends?
// FIXME: completely remove else branch, but for testing leave it in under com_asyncSound 2
// (which also does the old 92-100ms updates)
else if( com_asyncSound.GetInteger() == 2 ) {

if ( slowmoActive && !chan->disallowSlow ) {
idSlowChannel slow = sound->GetSlowChannel( chan );
Expand Down Expand Up @@ -1952,6 +1956,7 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
}

}
#endif // 1/0

soundSystemLocal.soundStats.activeSounds++;

Expand Down

0 comments on commit 504b572

Please sign in to comment.