Skip to main content

auracle_features/
vet.rs

1//! The vetting gate: safety layer 1.
2//!
3//! *No candidate is ever played live unvetted.* The standard-phrase render is
4//! inspected **before** normalization; failures are quarantined — never
5//! auditioned, never featurized, and reported to the session layer so
6//! evolution learns to avoid the region (safety layer 2).
7
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11/// Why a render was quarantined.
12#[derive(Clone, Debug, PartialEq, Error, Serialize, Deserialize)]
13pub enum VetFailure {
14    /// A non-finite sample survived every DSP-level defense.
15    #[error("render contains non-finite samples")]
16    NonFinite,
17    /// Effectively silent (below the RMS floor / loudness gate).
18    #[error("render is effectively silent (rms {rms:.2e})")]
19    Silent {
20        /// Measured RMS.
21        rms: f64,
22    },
23    /// Peak beyond the limiter ceiling plus overshoot headroom — runaway.
24    #[error("render peak {peak:.2} exceeds the safety ceiling")]
25    Overlevel {
26        /// Measured peak.
27        peak: f64,
28    },
29    /// Dominated by DC offset rather than audio.
30    #[error("render is DC-dominated (|mean|/rms = {dc_ratio:.2})")]
31    DcDominated {
32        /// |mean| / RMS ratio.
33        dc_ratio: f64,
34    },
35}
36
37/// Measurements taken on the raw (pre-normalization) render.
38#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
39pub struct VetReport {
40    /// Peak absolute sample.
41    pub peak: f64,
42    /// Whole-phrase RMS.
43    pub rms: f64,
44    /// |mean| / RMS — DC dominance.
45    pub dc_ratio: f64,
46    /// Fraction of samples pinned near the limiter ceiling (heavy limiting
47    /// indicator; informational, not a failure).
48    pub pinned_fraction: f64,
49}
50
51/// Vet thresholds. Defaults are deliberately lenient — the gate exists to
52/// catch pathology, not to encode taste (that's the model's job).
53///
54/// **Re-checked against the v2 palette's drive modules and left unchanged.**
55/// A gate tuned before distortion existed is exactly the kind that starts
56/// quarantining a whole timbre as pathology, so the three thresholds were
57/// measured over the full cross of `{soft, hard, tube} × drive
58/// {0.3, 0.6, 0.85, 1.0} × {saw, square, supersaw}`, plus a stacked
59/// fold → tube drive → resonant ladder chain:
60///
61/// - **peak** never exceeded 2.00 against a 3.5 ceiling. quiver's shapers all
62///   normalize into the ±1 domain and rescale, so the module is bounded at
63///   ±5 V *by construction* however hard it is driven — drive buys harmonics,
64///   not level.
65/// - **|mean|/rms** never exceeded 0.0016 against a 0.6 limit, because
66///   `compile::makes_dc` puts a blocker in front of every tube-mode patch.
67///   Without it the same renders measure 1–8% — still nowhere near the
68///   threshold, which is the point: this gate was never the thing protecting
69///   the feature extractor from that offset.
70/// - **rms** stayed far above the floor; distortion raises level, it cannot
71///   silence a patch.
72///
73/// So no threshold moved. The one that would have needed to, had the module
74/// not been bounded, is `peak_ceiling`.
75#[derive(Clone, Copy, Debug)]
76pub struct VetConfig {
77    /// RMS below this is "silent".
78    pub rms_floor: f64,
79    /// Peak above this is runaway (limiter ceiling 0.8 + generous headroom).
80    pub peak_ceiling: f64,
81    /// |mean|/rms above this is DC-dominated.
82    pub max_dc_ratio: f64,
83}
84
85impl Default for VetConfig {
86    fn default() -> Self {
87        Self {
88            rms_floor: 1e-4,
89            peak_ceiling: 2.0,
90            max_dc_ratio: 0.6,
91        }
92    }
93}
94
95impl VetConfig {
96    /// Defaults with the peak ceiling scaled for the phrase's polyphony.
97    ///
98    /// The default ceiling (2.0) is one limiter-bounded voice (~1.5 peak in
99    /// the ±1.0 float domain) plus overshoot headroom. N gate-synced voices
100    /// legitimately sum toward N× one voice, and that summing is exactly the
101    /// stacking information the chord segment exists to measure — so each
102    /// additional simultaneous voice raises the ceiling by one voice's worth
103    /// (1.5) rather than the gate quarantining honest polyphony as runaway.
104    pub fn for_spec(spec: &crate::phrase::PhraseSpec) -> Self {
105        Self {
106            peak_ceiling: 2.0 + 1.5 * (spec.max_voices() as f64 - 1.0),
107            ..Self::default()
108        }
109    }
110}
111
112/// Inspect a raw render. `Ok(report)` admits the candidate to normalization
113/// and feature extraction; `Err` quarantines it.
114pub fn vet(samples: &[f64], cfg: &VetConfig) -> Result<VetReport, VetFailure> {
115    if samples.is_empty() {
116        return Err(VetFailure::Silent { rms: 0.0 });
117    }
118    if samples.iter().any(|s| !s.is_finite()) {
119        return Err(VetFailure::NonFinite);
120    }
121    let n = samples.len() as f64;
122    let peak = samples.iter().fold(0.0f64, |p, s| p.max(s.abs()));
123    let mean = samples.iter().sum::<f64>() / n;
124    let rms = (samples.iter().map(|s| s * s).sum::<f64>() / n).sqrt();
125    let dc_ratio = mean.abs() / (rms + 1e-30);
126    let pinned_fraction = if peak > 0.0 {
127        samples.iter().filter(|s| s.abs() >= 0.98 * peak).count() as f64 / n
128    } else {
129        0.0
130    };
131
132    let report = VetReport {
133        peak,
134        rms,
135        dc_ratio,
136        pinned_fraction,
137    };
138    if rms < cfg.rms_floor {
139        return Err(VetFailure::Silent { rms });
140    }
141    if peak > cfg.peak_ceiling {
142        return Err(VetFailure::Overlevel { peak });
143    }
144    if dc_ratio > cfg.max_dc_ratio {
145        return Err(VetFailure::DcDominated { dc_ratio });
146    }
147    Ok(report)
148}