audio: fix corner cases in fade implementation

Properly handle the case where the stream loops while fading, by
tracking the elapsed frame count rather than comparing the current frame
with a computed end position.

Properly handle the case where time=0, by applying the volume
immediately instead of in the audio callback.

This fixes some audio bugs in Rance VI.
This commit is contained in:
Nunuhara Cabbage
2022-04-15 11:31:55 -07:00
parent 0ab9a59e99
commit f58c1b43bc
+48 -26
View File
@@ -44,7 +44,8 @@ struct fade {
atomic_bool fading;
bool stop;
uint_least32_t start_pos;
uint_least32_t end_pos;
uint_least32_t frames;
uint_least32_t elapsed;
float start_volume;
float end_volume;
};
@@ -67,8 +68,8 @@ struct channel {
// main thread read-only
atomic_uint_least32_t frame;
atomic_uint volume;
atomic_uint volume;
atomic_bool swapped;
uint_least32_t loop_start;
uint_least32_t loop_end;
@@ -143,19 +144,19 @@ static bool cb_loop(struct channel *ch)
* Callback to refill the stream's audio data.
* Called from sys_mixer_mix_audio.
*/
static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count)
static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count, uint_least32_t *num_read)
{
sf_count_t num_read = 0;
*num_read = 0;
// handle case where chunk crosses loop point (seamless)
// NOTE: it's assumed that the length of the loop is greater than the chunk length
if (ch->frame + frame_count >= ch->loop_end) {
// read frames up to loop_end
num_read = sf_readf_float(ch->file, out, ch->loop_end - ch->frame);
*num_read = sf_readf_float(ch->file, out, ch->loop_end - ch->frame);
// adjust parameters for later
ch->frame += num_read;
out += num_read;
frame_count -= num_read;
ch->frame += *num_read;
out += *num_read;
frame_count -= *num_read;
// seek to loop_start
if (!cb_loop(ch))
return STS_STREAM_COMPLETE;
@@ -167,10 +168,10 @@ static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count
// read remaining data
sf_count_t n = sf_readf_float(ch->file, out, frame_count);
num_read += n;
*num_read += n;
ch->frame += n;
out += num_read;
frame_count -= num_read;
out += *num_read;
frame_count -= *num_read;
// XXX: This *shouldn't* be necessary, but sometimes libsndfile seems to stop reading
// just before the end of file (i.e. ch->frame + frame_count is < ch->info.frames,
@@ -179,7 +180,7 @@ static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count
if (frame_count > 0) {
if (!cb_loop(ch))
return STS_STREAM_COMPLETE;
num_read += sf_readf_float(ch->file, out, frame_count);
*num_read += sf_readf_float(ch->file, out, frame_count);
}
return STS_STREAM_CONTINUE;
@@ -188,13 +189,12 @@ static int cb_read_frames(struct channel *ch, float *out, sf_count_t frame_count
/*
* Calculate the gain for a fade at a given sample.
*/
static float cb_calc_fade(struct fade *fade, uint_least32_t sample)
static float cb_calc_fade(struct fade *fade)
{
if (sample >= fade->end_pos) {
if (fade->elapsed >= fade->frames)
return fade->stop ? 0.0 : fade->end_volume;
}
float progress = (float)(sample - fade->start_pos) / (float)(fade->end_pos - fade->start_pos);
float progress = (float)fade->elapsed / (float)fade->frames;
float delta_v = fade->end_volume - fade->start_volume;
float gain = fade->start_volume + delta_v * progress;
if (gain < 0.0) return 0.0;
@@ -205,10 +205,11 @@ static float cb_calc_fade(struct fade *fade, uint_least32_t sample)
static int refill_stream(sts_mixer_sample_t *sample, void *data)
{
struct channel *ch = data;
uint_least32_t frames_read;
memset(ch->data, 0, sizeof(float) * sample->length);
// read audio data from file
int r = cb_read_frames(ch, ch->data, CHUNK_SIZE);
int r = cb_read_frames(ch, ch->data, CHUNK_SIZE, &frames_read);
// convert mono to stereo
if (ch->info.channels == 1) {
@@ -229,17 +230,21 @@ static int refill_stream(sts_mixer_sample_t *sample, void *data)
// set gain for fade
if (ch->fade.fading) {
float gain = cb_calc_fade(&ch->fade, ch->frame);
float gain = cb_calc_fade(&ch->fade);
mixers[ch->mixer_no].mixer.voices[ch->voice].gain = gain;
ch->volume = gain * 100.0;
if (ch->frame >= ch->fade.end_pos) {
ch->fade.elapsed += frames_read;
if (ch->fade.elapsed >= ch->fade.frames) {
ch->fade.fading = false;
if (ch->fade.stop) {
cb_seek(ch, 0);
r = STS_STREAM_COMPLETE;
}
}
} else {
float gain = ch->volume / 100.0;
mixers[ch->mixer_no].mixer.voices[ch->voice].gain = gain;
}
if (r == STS_STREAM_COMPLETE) {
@@ -323,13 +328,30 @@ int channel_set_loop_end_pos(struct channel *ch, int pos)
int channel_fade(struct channel *ch, int time, int volume, bool stop)
{
if (!time && stop)
return channel_stop(ch);
SDL_LockAudioDevice(audio_device);
ch->fade.fading = true;
ch->fade.stop = stop;
ch->fade.start_pos = ch->frame;
ch->fade.start_volume = (float)ch->volume / 100.0;
ch->fade.end_pos = ch->fade.start_pos + ((time * ch->info.samplerate) / 1000);
ch->fade.end_volume = clamp(0.0f, 1.0f, (float)volume / 100.0f);
if (!time) {
// XXX: Fade with time=0 is used to set volume. This needs to
// take effect immediately, not via the audio callback
// because the stream isn't necessarily playing yet.
// E.g. the below call sequence should fade from 0 -> 33:
//
// SACT2.Music_Fade(ch, 0, 0, 0);
// SACT2.Music_Fade(ch, 33, 4000, 0);
// SACT2.Music_Play(ch);
ch->fade.fading = false;
ch->volume = max(0, min(100, volume));
} else {
ch->fade.fading = true;
ch->fade.stop = stop;
ch->fade.start_pos = ch->frame;
ch->fade.start_volume = (float)ch->volume / 100.0;
ch->fade.frames = (time * ch->info.samplerate) / 1000;
ch->fade.elapsed = 0;
ch->fade.end_volume = clamp(0.0f, 1.0f, (float)volume / 100.0f);
}
SDL_UnlockAudioDevice(audio_device);
return 1;
}
@@ -339,7 +361,7 @@ int channel_stop_fade(struct channel *ch)
SDL_LockAudioDevice(audio_device);
// XXX: we need to set the volume to end_volume and potentially stop the
// stream here; better to let the callback do it
ch->fade.end_pos = ch->frame;
ch->fade.elapsed = ch->fade.frames;
SDL_UnlockAudioDevice(audio_device);
return 1;
}