Skip to main content

secs_engine/
notes.rs

1//! Typed engine diagnostics; presentation owns their translated text.
2
3use crate::input::Peq;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Note {
7    LiftCapped {
8        from_ms: f64,
9        taps: usize,
10        lift_ms: f64,
11    },
12    SubFilters(Vec<(String, crate::setup::SubFilter)>),
13    LowProtect {
14        role: String,
15        rolloff_hz: f64,
16        hp_hz: Option<f64>,
17        limited: bool,
18    },
19    RcLimit {
20        hz: f64,
21        auto: bool,
22        schroeder_hz: Option<f64>,
23        basis: secs_types::calibration::RcHiBasis,
24        factor: u8,
25        band_hz: Option<f64>,
26        band_t60_s: Option<f64>,
27        positions: Option<usize>,
28    },
29    RcBands {
30        bands: Vec<(f64, f64)>,
31    },
32    Latency {
33        latency_ms: f64,
34        lift_ms: f64,
35        main_extra_ms: f64,
36    },
37    PhaseStrength(Vec<(String, f64)>),
38    PhaseBand(Vec<(String, Option<f64>)>),
39    // RC (P09)
40    LongModeBoost {
41        cap_db: f64,
42        modes: Vec<(f64, f64)>,
43    },
44    GdCuts {
45        role: String,
46        cuts: Vec<Peq>,
47    },
48    // BA (P10)
49    BaCost(f64),
50    BaBusPass {
51        first: f64,
52        second: f64,
53        kept_first: bool,
54    },
55    BaManual,
56    LayoutFallback(String),
57    Layout(String),
58    TwoWay {
59        roles: Vec<String>,
60        gain_db: f64,
61        xo_hz: f64,
62    },
63    BusGainAnchored(f64),
64    Mimo(MimoNote),
65}
66
67/// Semantic diagnostics in producer order; never translated presentation text.
68#[derive(Debug, Clone, PartialEq)]
69pub enum MimoNote {
70    InsufficientPositions {
71        measured: usize,
72    },
73    ExcludedSupports(Vec<String>),
74    SupportRanges(Vec<(String, Option<(f64, f64)>)>),
75    GuardReduced {
76        role: String,
77        gain_db: f64,
78    },
79    GuardDisabled {
80        role: String,
81    },
82    SupportLimit {
83        max_supports: usize,
84    },
85    FirBudget {
86        support_count: usize,
87        taps_total: usize,
88    },
89}
90
91#[cfg(test)]
92impl MimoNote {
93    // Only the oracle adapter renders the legacy diagnostic strings.
94    pub(crate) fn oracle_text(&self) -> String {
95        match self {
96            Self::InsufficientPositions { measured } => {
97                format!("측정 위치 {measured}곳 — MIMO 는 3곳 이상일 때만")
98            }
99            Self::ExcludedSupports(roles) => format!("서포트 제외(사용자): {}", roles.join(", ")),
100            Self::SupportRanges(ranges) => format!(
101                "서포트 대역 {}",
102                ranges
103                    .iter()
104                    .map(|(role, range)| match range {
105                        Some((lo, hi)) => format!("{role} {lo:.0}–{hi:.0}"),
106                        None => format!("{role} 제외"),
107                    })
108                    .collect::<Vec<_>>()
109                    .join(", ")
110            ),
111            Self::GuardReduced { role, gain_db } => {
112                format!("{role}: 대역 늦은 에너지 악화로 서포트 {gain_db:.0} dB")
113            }
114            Self::GuardDisabled { role } => format!("{role}: MIMO 끔(악화)"),
115            Self::SupportLimit { max_supports } => format!("입력당 서포트 ≤ {max_supports}"),
116            Self::FirBudget {
117                support_count,
118                taps_total,
119            } => {
120                let digits = taps_total.to_string();
121                let grouped = digits
122                    .chars()
123                    .enumerate()
124                    .fold(String::new(), |mut out, (i, ch)| {
125                        if i > 0 && (digits.len() - i).is_multiple_of(3) {
126                            out.push(',');
127                        }
128                        out.push(ch);
129                        out
130                    });
131                format!("FIR 서포트 {support_count}개 · 총 {grouped}탭(48 kHz)")
132            }
133        }
134    }
135}