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