Harden audio reserve and transient frame catch-up

This commit is contained in:
Matthew Stanley
2026-07-13 05:23:33 -07:00
parent b39387eb00
commit 2bebd29629
3 changed files with 23 additions and 15 deletions
+4 -4
View File
@@ -39,10 +39,10 @@ uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline,
uint64_t freq, uint64_t period);
/* Block until the pacer's next deadline (sleep + final sub-ms spin),
* then advance the deadline by one period. If the pacer has not
* started, or the caller fell more than one period behind, re-anchors
* to now + period without sleeping. period_ms is the frame period in
* milliseconds (e.g. 1000.0 / 59.94). */
* then advance the deadline by one period. If the pacer has not started,
* or the caller fell beyond the implementation's bounded transient-catch-up
* window, re-anchor to now + period without sleeping. period_ms is the frame
* period in milliseconds (e.g. 1000.0 / 59.94). */
void frame_pacer_wait(FramePacer *p, double period_ms);
#ifdef __cplusplus
+7 -1
View File
@@ -34,7 +34,13 @@ uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline,
* target = -0.4% chronic audio underrun). Only debt beyond this window —
* sustained sub-realtime emulation, not a hiccup — is forgiven, else the
* pacer would demand unbounded catch-up. */
#define FRAME_PACER_CATCHUP_MAX_PERIODS 8u
/* Vigilante 8's streamed FMV transitions have measured host stalls near
* 140 ms. Eight 60 Hz periods are only 133.3 ms, so the old bound classified
* those finite transitions as sustained slowness and permanently forgave the
* guest/audio debt. Keep a bounded 12-period (200 ms at 60 Hz) window: enough
* to repay the observed transition without turning a real hang, suspend, or
* sub-realtime workload into an unbounded catch-up burst. */
#define FRAME_PACER_CATCHUP_MAX_PERIODS 12u
void frame_pacer_wait(FramePacer *p, double period_ms) {
uint64_t freq = SDL_GetPerformanceFrequency();
+12 -10
View File
@@ -39,8 +39,8 @@ typedef struct {
double host_rate; /* host device sample rate (Hz) */
int taps; /* prototype length; 16 min, 32 preferred */
int phases; /* fractional phases; 256-1024 (512 default) */
double target_ms; /* steady-state ring fill target (default 160) */
double ring_ms; /* ring capacity in ms (default 250) */
double target_ms; /* steady-state ring fill target (default 180) */
double ring_ms; /* ring capacity in ms (default 280) */
double kp; /* proportional gain (default 0.02) */
double ki_per_s; /* optional integral gain per second (default 0):
P-only is bounded and cannot wind up across
@@ -50,7 +50,7 @@ typedef struct {
double slew_pp_per_s; /* correction slew, percent-points/sec (default 0.75) */
double deadband_ms; /* +/- band around target with no correction (1.0) */
double em_low_ms; /* below this = underrun emergency (default 12) */
double em_high_ms; /* above this = overflow emergency (default 105) */
double em_high_ms; /* above this = overflow emergency (default 235) */
} rab_config;
typedef struct {
@@ -143,12 +143,14 @@ void rab_config_defaults(rab_config *c) {
c->taps = 32;
c->phases = 512;
/* Streamed stage transitions can pause PSX audio production for ~140 ms
* while guest cadence catches up inside the same reporting window. Keep a
* measured 160 ms reserve; sustained slow emulation still drains it and
* remains visible in telemetry. The existing 250 ms ring leaves 90 ms of
* overflow headroom without changing guest timing or presentation. */
c->target_ms = 160.0;
c->ring_ms = 250.0;
* while guest cadence catches up inside the same reporting window. A
* 160 ms target proved just short once: the 1024-frame host callback
* crossed the remaining edge and emitted 216 faded frames (4.9 ms).
* Keep 180 ms so the measured pause retains more than one callback of
* reserve. Sustained slow emulation still drains it and remains visible
* in telemetry. A 280 ms ring preserves 100 ms of overflow headroom. */
c->target_ms = 180.0;
c->ring_ms = 280.0;
c->kp = 0.02;
/* P-only intentionally: the former 0.02/s integral retained a +0.5%
* correction after transition bursts and drained a steady 59.94 Hz
@@ -160,7 +162,7 @@ void rab_config_defaults(rab_config *c) {
c->slew_pp_per_s = 0.75;
c->deadband_ms = 1.0;
c->em_low_ms = 12.0;
c->em_high_ms = 205.0;
c->em_high_ms = 235.0;
}
int rab_init(rab_bridge *b, const rab_config *cfg) {