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