Files
psxrecomp/runtime/include/audio_shadow.h
T
Matthew Stanley cb00b9c0af feat(psx): verified-enhancement shadow — SPU audio + 15-bit CRT LUT
SPU Catmull-Rom float shadow (spu_shadow), 15-bit CRT color LUT (raw bit-exact), verifier. Opt-in (env-gated), default OFF: raw output byte-identical and remains the verify oracle. Reverts loud (DEGRADED) on divergence. ShadowVerifier ported from JRickey/gba-recomp (MIT OR Apache-2.0). Compiles clean; full game-build A/B is the remaining integration gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 04:25:56 -07:00

96 lines
3.7 KiB
C

/* audio_shadow.h — engine-agnostic HLE-shadow differential verifier (C).
*
* The gate that makes a "verified-enhancement shadow" SAFE: a higher-fidelity
* re-render of a guest audio subsystem (here the PS1 SPU's ADPCM voices, free
* of the hardware Gaussian interpolation and 16-bit intermediate truncation)
* runs ALONGSIDE the canon hardware mix, never replacing it. This verifier
* decides, from the running canon stream, whether the shadow is proven enough
* to substitute, and reverts loudly the instant it stops matching:
*
* - envelope correlation + level-ratio self-check vs the canon stream
* - probation auto-gain calibration (constant mixer-scale differences)
* - prove-then-substitute, strike-then-pause, escalating re-prove
*
* The canon (hardware-modeled SPU) output stays the verify oracle; the shadow
* is never ground truth. If this verifier is wrong, the worst case is "we keep
* playing the authentic hardware mix" — it cannot corrupt output.
*
* Ported (C) from JRickey/gba-recomp crates/gba-core/src/shadow.rs via the
* gbarecomp C++ port (src/gba/audio_shadow.*) and the snesrecomp C port
* (runner/src/snes/audio_shadow.*), © Jrickey, MIT OR Apache-2.0, used with
* permission. Engine-agnostic; reused across the recomp ecosystem.
*/
#ifndef PSXRECOMP_AUDIO_SHADOW_H
#define PSXRECOMP_AUDIO_SHADOW_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
SHADOW_JUDGE_NONE = 0, /* mid-window, or canon silent (no verdict) */
SHADOW_JUDGE_PASS = 1, /* window passed all gates */
SHADOW_JUDGE_FAIL = 2, /* window failed (see fail_structural) */
} ShadowJudgement;
/* Window/decimation geometry (see the .c for the rationale). */
#define SHADOW_DECIM 64u
#define SHADOW_WINDOW 1024u
#define SHADOW_MAX_LAG 56u
/* Differential self-check: shadow mix vs the canon DAC stream. Polices
* STRUCTURE (same notes, same loudness, same time) via rectified-lowpassed
* envelopes, decimated and Pearson-correlated over a coarse lag range. */
typedef struct {
float lp_x[2]; /* canon envelope follower (L,R) */
float lp_y[2]; /* shadow envelope follower (L,R) */
float ex[SHADOW_WINDOW][2]; /* decimated canon envelope */
float ey[SHADOW_WINDOW][2]; /* decimated shadow envelope */
uint32_t count; /* entries filled in ex/ey this window */
uint32_t phase; /* decimation phase */
uint32_t strikes;
} ShadowSelfCheck;
/* The engine-agnostic verification state machine. */
typedef struct {
ShadowSelfCheck check;
float gain; /* calibrated output gain (1.0 = stock scale) */
bool calibrated;
float ratio_hist[3];
uint32_t ratio_n;
bool proven; /* frontend substitutes only when true */
uint32_t prove_need;
uint32_t consec_pass;
uint64_t pauses;
char reverted[160]; /* reason on each pause (caller logs + clears); "" = none */
float last_r;
float last_ratio;
bool fail_structural; /* last Fail broke correlation at an in-band level */
} ShadowVerifier;
/* Zero-init then call this (sets gain=1, prove_need=1). */
void shadow_verifier_init(ShadowVerifier* v);
/* Feed one grid sample of (canon, shadow-check-copy) stereo pairs, both in the
* canon's normalized domain. Drives calibration, proving, strikes, pauses. */
ShadowJudgement shadow_verifier_judge(ShadowVerifier* v,
float canon_l, float canon_r,
float chk_l, float chk_r);
static inline float shadow_verifier_gain(const ShadowVerifier* v) {
return v->gain;
}
static inline bool shadow_verifier_proven(const ShadowVerifier* v) {
return v->proven;
}
#ifdef __cplusplus
}
#endif
#endif /* PSXRECOMP_AUDIO_SHADOW_H */