Keep the audio renderer from going silent or hanging on edge values.

- An audio_prefill_ms of 180 or more asked for more queued audio than the renderer ever fills the ring to (half of
  it), so the output never started and stayed silent. The prefill is now held to that target; nx2navi.ini.example
  says so.
- An infinite or huge pitch or sample rate from the game kept the per-sample loops going for good under the
  renderer's lock, which then blocked the game's RequestUpdate. The step is held to 64 source samples per output
  sample.
- An endless loop buffer with no samples (start equal to end), which the voice keeps as the DSP does, made
  advance_voice go round for good; it now starves the voice as intended.
- A NaN sample or effect state reached the device, where it plays as full scale; it now plays as silence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-08 09:12:38 -05:00
parent 1795b186a4
commit 2f7e1806a2
3 changed files with 13 additions and 4 deletions

View File

@@ -266,7 +266,7 @@
[audio]
# none mixes without opening an audio device.
# audio = default
# Audio the output queues before it starts, in ms (0 to 500).
# Audio the output queues before it starts, in ms (0 to 500; above about 170, half the output ring, it is held there).
# audio_prefill_ms = 20
# Volume in percent (0 to 100), on top of the game's own. Changes while the game runs take effect at once.
# volume = 100

View File

@@ -156,7 +156,9 @@ static void ring_pull(Mixer *m, float *out, uint32_t frames) {
}
uint64_t prefill = (uint64_t)m->frame * (uint64_t)m->prefill;
if (prefill < frames) prefill = frames;
if (prefill > RING_FRAMES) prefill = RING_FRAMES;
// no more than the target the renderer fills the ring to (output_open caps it at half the ring): a longer
// prefill (audio_prefill_ms of 180 and up) was never reached, and the output stayed silent for good
if (prefill > RING_FRAMES / 2) prefill = RING_FRAMES / 2;
if (!m->ready) {
if (have < prefill) {
memset(out, 0, (size_t)frames * oc * sizeof(float));
@@ -575,8 +577,12 @@ static void walk_step(Walk *w) { /* the same transitions as advance_voice in a
walk_skip_empty(w);
}
/* Decodes `frames` output samples per channel into out[c][k]; the renderer advances the voice afterwards by the same amount. */
// Source samples per output sample. The guest's sample rate and pitch are not checked: an infinite or huge step
// would keep the per-sample loops below (audio_voice_consumed, voice_render) running for good under the renderer's
// lock, so it is held to 64 source samples per output sample, far above any pitch the game asks for.
static double voice_step(const AudioRenderer *r, const ServerVoice *v) {
return v->in.sample_rate * (v->in.pitch > 0 ? v->in.pitch : 1.0) / r->param.sample_rate;
double step = v->in.sample_rate * (v->in.pitch > 0 ? v->in.pitch : 1.0) / r->param.sample_rate;
return step >= 0 && step <= 64 ? step : step > 64 ? 64 : 0; // NaN: no progress
}
/* Source samples the voice consumes over `frames` output samples, counted with the exact accumulation voice_render steps by
(float rounding makes `fraction + seconds * rate * pitch` differ from it by one sample on some frames, a seam click);
@@ -918,7 +924,8 @@ void audio_mixer_render(AudioRenderer *r, double seconds) {
raw[2 * k] = s[0] * gain; raw[2 * k + 1] = s[1] * gain; /* NX2NAVI_AUDIO_DUMP: the front pair before clipping */
for (int c = 0; c < oc; c++) {
float x = s[c] * gain;
out[oc * k + c] = x > 1 ? 1 : x < -1 ? -1 : x;
// NaN (a NaN sample, an effect's state gone NaN) reaches the device as silence, not full scale
out[oc * k + c] = x > 1 ? 1 : x < -1 ? -1 : x == x ? x : 0;
}
}
sink_dump(raw, frames);

View File

@@ -322,6 +322,8 @@ static void advance_voice(AudioRenderer *r, ServerVoice *v) {
uint64_t samples = audio_voice_consumed(r, v, (int)r->param.sample_count);
while (samples && v->valid[v->index]) {
AudrenWaveBuffer *b = &v->buffers[v->index];
if (audio_buffer_empty(b)) break; // an endless empty loop skip_empty kept: it starves the voice, and its
// zero length would make the loop below go round for good
uint64_t length = (uint64_t)(b->end - b->start), left = length - v->offset;
uint64_t step = samples < left ? samples : left;
v->offset += step;