mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user