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 PairPhase {
24 a: String,
25 b: String,
26 bands: Vec<crate::pair_phase::Band>,
27 },
28 LiftCapped {
29 from_ms: f64,
30 taps: usize,
31 lift_ms: f64,
32 },
33 SubFilters(Vec<(String, crate::setup::SubFilter)>),
34 LowProtect {
35 role: String,
36 rolloff_hz: f64,
37 hp_hz: Option<f64>,
38 limited: bool,
39 reduced: usize,
40 cuts: usize,
41 },
42 RcLimit {
43 hz: f64,
44 auto: bool,
45 schroeder_hz: Option<f64>,
46 basis: secs_types::calibration::RcHiBasis,
47 factor: u8,
48 band_hz: Option<f64>,
49 band_t60_s: Option<f64>,
50 positions: Option<usize>,
51 },
52 RcBands {
53 bands: Vec<(f64, f64)>,
54 },
55 LfAlign {
60 role: String,
61 strength: f64,
62 arrival_ms: Option<f64>,
63 limited: bool,
64 },
65 ModeDecay {
69 role: String,
70 target_t60_s: f64,
71 modes: Vec<(f64, f64, f64)>,
72 },
73 HfPhase {
78 role: String,
79 rotation_deg: Option<f64>,
80 limited: bool,
81 },
82 DipLimit {
85 role: String,
86 dips: Vec<(f64, f64, f64)>,
87 },
88 InputHp {
91 hz: f64,
92 },
93 Latency {
94 latency_ms: f64,
95 lift_ms: f64,
96 main_extra_ms: f64,
97 },
98 PhaseStrength(Vec<(String, f64)>),
99 PhaseBand(Vec<(String, Option<f64>)>),
100 LongModeBoost {
102 cap_db: f64,
103 modes: Vec<(f64, f64)>,
104 },
105 GdCuts {
106 role: String,
107 cuts: Vec<Peq>,
108 },
109 BaCost(f64),
111 BaBusPass {
112 first: f64,
113 second: f64,
114 kept_first: bool,
115 },
116 BaManual,
117 LayoutFallback(String),
118 Layout(String),
119 TwoWay {
120 roles: Vec<String>,
121 gain_db: f64,
122 xo_hz: f64,
123 },
124 BusGainAnchored(f64),
125 Mimo(MimoNote),
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub enum MimoNote {
131 InsufficientPositions {
132 measured: usize,
133 },
134 ExcludedSupports(Vec<String>),
135 SupportRanges(Vec<(String, Option<(f64, f64)>)>),
136 GuardReduced {
137 role: String,
138 gain_db: f64,
139 cause: GuardCause,
140 },
141 GuardDisabled {
142 role: String,
143 cause: GuardCause,
144 },
145 SupportLimit {
146 max_supports: usize,
147 },
148 FirBudget {
149 support_count: usize,
150 taps_total: usize,
151 },
152}
153
154#[derive(Debug, Clone, PartialEq)]
158pub struct GuardCause {
159 pub rejected_db: f64,
160 pub band_position: Option<u32>,
163 pub tail_position: Option<u32>,
165 pub band: Option<(f64, f64, f64)>,
168 pub tail: Option<(f64, f64)>,
170}
171
172#[cfg(test)]
173impl MimoNote {
174 pub(crate) fn oracle_text(&self) -> String {
176 match self {
177 Self::InsufficientPositions { measured } => {
178 format!("측정 위치 {measured}곳 — MIMO 는 3곳 이상일 때만")
179 }
180 Self::ExcludedSupports(roles) => format!("서포트 제외(사용자): {}", roles.join(", ")),
181 Self::SupportRanges(ranges) => format!(
182 "서포트 대역 {}",
183 ranges
184 .iter()
185 .map(|(role, range)| match range {
186 Some((lo, hi)) => format!("{role} {lo:.0}–{hi:.0}"),
187 None => format!("{role} 제외"),
188 })
189 .collect::<Vec<_>>()
190 .join(", ")
191 ),
192 Self::GuardReduced { role, gain_db, .. } => {
193 format!("{role}: 대역 늦은 에너지 악화로 서포트 {gain_db:.0} dB")
194 }
195 Self::GuardDisabled { role, .. } => format!("{role}: MIMO 끔(악화)"),
196 Self::SupportLimit { max_supports } => format!("입력당 서포트 ≤ {max_supports}"),
197 Self::FirBudget {
198 support_count,
199 taps_total,
200 } => {
201 let digits = taps_total.to_string();
202 let grouped = digits
203 .chars()
204 .enumerate()
205 .fold(String::new(), |mut out, (i, ch)| {
206 if i > 0 && (digits.len() - i).is_multiple_of(3) {
207 out.push(',');
208 }
209 out.push(ch);
210 out
211 });
212 format!("FIR 서포트 {support_count}개 · 총 {grouped}탭(48 kHz)")
213 }
214 }
215 }
216}