mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
cb00b9c0af
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>
96 lines
3.7 KiB
C
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 */
|