1use crate::input::Peq;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Note {
7 PolarityReversed {
12 role: String,
13 confidence: f64,
14 compared: usize,
15 opposite: usize,
16 },
17 PolaritySplit {
18 groups: (Vec<String>, Vec<String>),
19 confidence: f64,
20 },
21 LiftCapped {
22 from_ms: f64,
23 taps: usize,
24 lift_ms: f64,
25 },
26 SubFilters(Vec<(String, crate::setup::SubFilter)>),
27 LowProtect {
28 role: String,
29 rolloff_hz: f64,
30 hp_hz: Option<f64>,
31 limited: bool,
32 reduced: usize,
33 cuts: usize,
34 },
35 RcLimit {
36 hz: f64,
37 auto: bool,
38 schroeder_hz: Option<f64>,
39 basis: secs_types::calibration::RcHiBasis,
40 factor: u8,
41 band_hz: Option<f64>,
42 band_t60_s: Option<f64>,
43 positions: Option<usize>,
44 },
45 RcBands {
46 bands: Vec<(f64, f64)>,
47 },
48 LfAlign {
53 role: String,
54 strength: f64,
55 arrival_ms: Option<f64>,
56 limited: bool,
57 },
58 ModeDecay {
62 role: String,
63 target_t60_s: f64,
64 modes: Vec<(f64, f64, f64)>,
65 },
66 HfPhase {
71 role: String,
72 rotation_deg: Option<f64>,
73 limited: bool,
74 },
75 DipLimit {
78 role: String,
79 dips: Vec<(f64, f64, f64)>,
80 },
81 InputHp {
84 hz: f64,
85 },
86 Latency {
87 latency_ms: f64,
88 lift_ms: f64,
89 main_extra_ms: f64,
90 },
91 PhaseStrength(Vec<(String, f64)>),
92 PhaseBand(Vec<(String, Option<f64>)>),
93 LongModeBoost {
95 cap_db: f64,
96 modes: Vec<(f64, f64)>,
97 },
98 GdCuts {
99 role: String,
100 cuts: Vec<Peq>,
101 },
102 BaCost(f64),
104 BaBusPass {
105 first: f64,
106 second: f64,
107 kept_first: bool,
108 },
109 BaManual,
110 LayoutFallback(String),
111 Layout(String),
112 TwoWay {
113 roles: Vec<String>,
114 gain_db: f64,
115 xo_hz: f64,
116 },
117 BusGainAnchored(f64),
118 Mimo(MimoNote),
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub enum MimoNote {
124 InsufficientPositions {
125 measured: usize,
126 },
127 ExcludedSupports(Vec<String>),
128 SupportRanges(Vec<(String, Option<(f64, f64)>)>),
129 GuardReduced {
130 role: String,
131 gain_db: f64,
132 cause: GuardCause,
133 },
134 GuardDisabled {
135 role: String,
136 cause: GuardCause,
137 },
138 SupportLimit {
139 max_supports: usize,
140 },
141 FirBudget {
142 support_count: usize,
143 taps_total: usize,
144 },
145}
146
147#[derive(Debug, Clone, PartialEq)]
151pub struct GuardCause {
152 pub rejected_db: f64,
153 pub band: Option<(f64, f64, f64)>,
156 pub tail: Option<(f64, f64)>,
158}
159
160#[cfg(test)]
161impl MimoNote {
162 pub(crate) fn oracle_text(&self) -> String {
164 match self {
165 Self::InsufficientPositions { measured } => {
166 format!("측정 위치 {measured}곳 — MIMO 는 3곳 이상일 때만")
167 }
168 Self::ExcludedSupports(roles) => format!("서포트 제외(사용자): {}", roles.join(", ")),
169 Self::SupportRanges(ranges) => format!(
170 "서포트 대역 {}",
171 ranges
172 .iter()
173 .map(|(role, range)| match range {
174 Some((lo, hi)) => format!("{role} {lo:.0}–{hi:.0}"),
175 None => format!("{role} 제외"),
176 })
177 .collect::<Vec<_>>()
178 .join(", ")
179 ),
180 Self::GuardReduced { role, gain_db, .. } => {
181 format!("{role}: 대역 늦은 에너지 악화로 서포트 {gain_db:.0} dB")
182 }
183 Self::GuardDisabled { role, .. } => format!("{role}: MIMO 끔(악화)"),
184 Self::SupportLimit { max_supports } => format!("입력당 서포트 ≤ {max_supports}"),
185 Self::FirBudget {
186 support_count,
187 taps_total,
188 } => {
189 let digits = taps_total.to_string();
190 let grouped = digits
191 .chars()
192 .enumerate()
193 .fold(String::new(), |mut out, (i, ch)| {
194 if i > 0 && (digits.len() - i).is_multiple_of(3) {
195 out.push(',');
196 }
197 out.push(ch);
198 out
199 });
200 format!("FIR 서포트 {support_count}개 · 총 {grouped}탭(48 kHz)")
201 }
202 }
203 }
204}