Skip to content

Commit

Permalink
Engine: a fix for the queued sounds (PlayQueue) starting with a gap
Browse files Browse the repository at this point in the history
This is a slightly "hacky" way to fix the queued sounds starting with a small gap after the previous sound ends.
Because of the new audio playback subsystem in AGS 3.6.0, the clip start timing changed a little, and there appear to be small gap, about 1-2 game frame long, between previous and next sound in queue. The queue in AGS is not implemented ideally, because it relies on being updated once per game frame, rather than on an audio thread etc, but this is something we cannot change easily right now (maybe will reimplement whole thing later).

This commit does 2 things:
1. Sync logical channels with the audio subsystem also *prior* to updating queue and other things (crossfade etc). This fixes a 1 game frame queue delay.
2. Additionally, force queued clips to start 1 extra game frame earlier, by testing current playback position of active clips.
  • Loading branch information
ivan-mogilko committed Mar 24, 2023
1 parent c023e08 commit e87e3a8
Showing 1 changed file with 43 additions and 14 deletions.
57 changes: 43 additions & 14 deletions Engine/media/audio/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ extern CharacterInfo*playerchar;
extern CCAudioChannel ccDynamicAudio;

extern volatile int switching_away_from_game;
extern int frames_per_second; // for queue "hack"

ScriptAudioChannel scrAudioChannel[MAX_GAME_CHANNELS];
int reserved_channel_count = 0;
Expand Down Expand Up @@ -153,7 +154,8 @@ void stop_or_fade_out_channel(int fadeOutChannel, int fadeInChannel, ScriptAudio
}
}

static int find_free_audio_channel(ScriptAudioClip *clip, int priority, bool interruptEqualPriority)
static int find_free_audio_channel(ScriptAudioClip *clip, int priority, bool interruptEqualPriority,
bool for_queue = true)
{
int lowestPrioritySoFar = 9999999;
int lowestPriorityID = -1;
Expand Down Expand Up @@ -192,6 +194,20 @@ static int find_free_audio_channel(ScriptAudioClip *clip, int priority, bool int
lowestPrioritySoFar = ch->priority;
lowestPriorityID = i;
}
// NOTE: This is a "hack" for starting queued clips;
// since having a new audio system (3.6.0 onwards), the audio timing
// changed a little, and queued sounds have to start bit earlier
// if we want them to sound seamless with the previous clips.
// TODO: investigate better solutions? may require reimplementation of the sound queue.
if (for_queue && (ch->sourceClipType == clip->type))
{ // try to start queued sounds 1 frame earlier
const float trigger_pos = (1000.f / frames_per_second) * 1.f;
if (ch->get_pos_ms() >= (ch->get_length_ms() - trigger_pos))
{
lowestPrioritySoFar = priority;
lowestPriorityID = i;
}
}
}

if ((channelToUse < 0) && (lowestPriorityID >= 0) &&
Expand Down Expand Up @@ -304,7 +320,7 @@ static void audio_update_polled_stuff()
for (int i = 0; i < play.new_music_queue_size; i++)
{
ScriptAudioClip *clip = &game.audioClips[play.new_music_queue[i].audioClipIndex];
int channel = find_free_audio_channel(clip, clip->defaultPriority, false);
int channel = find_free_audio_channel(clip, clip->defaultPriority, false, true);
if (channel >= 0)
{
QueuedAudioItem itemToPlay = play.new_music_queue[i];
Expand Down Expand Up @@ -457,7 +473,7 @@ ScriptAudioChannel* play_audio_clip(ScriptAudioClip *clip, int priority, int rep
if (repeat == SCR_NO_VALUE)
repeat = clip->defaultRepeat;

int channel = find_free_audio_channel(clip, priority, !queueIfNoChannel);
int channel = find_free_audio_channel(clip, priority, !queueIfNoChannel, queueIfNoChannel);
if (channel < 0)
{
if (queueIfNoChannel)
Expand Down Expand Up @@ -853,7 +869,22 @@ void update_volume_drop_if_voiceover()
// (this should only be called once per game loop)
void update_audio_system_on_game_loop ()
{
update_polled_stuff ();
update_polled_stuff();

// Sync logical game channels with the audio backend
// NOTE: we update twice, first time here - because we need to know
// which clips are still playing before updating the sound transitions
// and queues, then second time later - because we need to apply any
// changes to channels / parameters.
// TODO: investigate options for optimizing this.
for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i)
{ // update the playing channels, and dispose the finished / invalid ones
auto *ch = AudioChans::GetChannelIfPlaying(i);
if (ch && !ch->update())
{
AudioChans::DeleteClipOnChannel(i);
}
}

process_scheduled_music_update();

Expand Down Expand Up @@ -890,22 +921,20 @@ void update_audio_system_on_game_loop ()
}
}

if (loopcounter % 5 == 0)
if (loopcounter % 5 == 0) // TODO: investigate why we do this each 5 frames?
{
update_ambient_sound_vol();
update_directional_sound_vol();
}

// Update and sync logical game channels with the audio backend
// Sync logical game channels with the audio backend:
// startup new assigned clips, apply changed parameters.
for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i)
{
auto *ch = AudioChans::GetChannel(i);
if (ch)
{ // update the playing channel, and if it's finished then dispose it
if (ch->is_ready() && !ch->update())
{
AudioChans::DeleteClipOnChannel(i);
}
{ // update the playing channels, and dispose the finished / invalid ones
auto *ch = AudioChans::GetChannelIfPlaying(i);
if (ch && !ch->update())
{
AudioChans::DeleteClipOnChannel(i);
}
}
}
Expand Down

0 comments on commit e87e3a8

Please sign in to comment.