1use 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 reduced: usize,
19 cuts: usize,
20 },
21 RcLimit {
22 hz: f64,
23 auto: bool,
24 schroeder_hz: Option<f64>,
25 basis: secs_types::calibration::RcHiBasis,
26 factor: u8,
27 band_hz: Option<f64>,
28 band_t60_s: Option<f64>,
29 positions: Option<usize>,
30 },
31 RcBands {
32 bands: Vec<(f64, f64)>,
33 },
34 LfAlign {
39 role: String,
40 strength: f64,
41 arrival_ms: Option<f64>,
42 limited: bool,
43 },
44 ModeDecay {
48 role: String,
49 target_t60_s: f64,
50 modes: Vec<(f64, f64, f64)>,
51 },
52 HfPhase {
57 role: String,
58 rotation_deg: Option<f64>,
59 limited: bool,
60 },
61 InputHp {
64 hz: f64,
65 },
66 Latency {
67 latency_ms: f64,
68 lift_ms: f64,
69 main_extra_ms: f64,
70 },
71 PhaseStrength(Vec<(String, f64)>),
72 PhaseBand(Vec<(String, Option<f64>)>),
73 LongModeBoost {
75 cap_db: f64,
76 modes: Vec<(f64, f64)>,
77 },
78 GdCuts {
79 role: String,
80 cuts: Vec<Peq>,
81 },
82 BaCost(f64),
84 BaBusPass {
85 first: f64,
86 second: f64,
87 kept_first: bool,
88 },
89 BaManual,
90 LayoutFallback(String),
91 Layout(String),
92 TwoWay {
93 roles: Vec<String>,
94 gain_db: f64,
95 xo_hz: f64,
96 },
97 BusGainAnchored(f64),
98 Mimo(MimoNote),
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub enum MimoNote {
104 InsufficientPositions {
105 measured: usize,
106 },
107 ExcludedSupports(Vec<String>),
108 SupportRanges(Vec<(String, Option<(f64, f64)>)>),
109 GuardReduced {
110 role: String,
111 gain_db: f64,
112 cause: GuardCause,
113 },
114 GuardDisabled {
115 role: String,
116 cause: GuardCause,
117 },
118 SupportLimit {
119 max_supports: usize,
120 },
121 FirBudget {
122 support_count: usize,
123 taps_total: usize,
124 },
125}
126
127#[derive(Debug, Clone, PartialEq)]
131pub struct GuardCause {
132 pub rejected_db: f64,
133 pub band: Option<(f64, f64, f64)>,
136 pub tail: Option<(f64, f64)>,
138}
139
140#[cfg(test)]
141impl MimoNote {
142 pub(crate) fn oracle_text(&self) -> String {
144 match self {
145 Self::InsufficientPositions { measured } => {
146 format!("측정 위치 {measured}곳 — MIMO 는 3곳 이상일 때만")
147 }
148 Self::ExcludedSupports(roles) => format!("서포트 제외(사용자): {}", roles.join(", ")),
149 Self::SupportRanges(ranges) => format!(
150 "서포트 대역 {}",
151 ranges
152 .iter()
153 .map(|(role, range)| match range {
154 Some((lo, hi)) => format!("{role} {lo:.0}–{hi:.0}"),
155 None => format!("{role} 제외"),
156 })
157 .collect::<Vec<_>>()
158 .join(", ")
159 ),
160 Self::GuardReduced { role, gain_db, .. } => {
161 format!("{role}: 대역 늦은 에너지 악화로 서포트 {gain_db:.0} dB")
162 }
163 Self::GuardDisabled { role, .. } => format!("{role}: MIMO 끔(악화)"),
164 Self::SupportLimit { max_supports } => format!("입력당 서포트 ≤ {max_supports}"),
165 Self::FirBudget {
166 support_count,
167 taps_total,
168 } => {
169 let digits = taps_total.to_string();
170 let grouped = digits
171 .chars()
172 .enumerate()
173 .fold(String::new(), |mut out, (i, ch)| {
174 if i > 0 && (digits.len() - i).is_multiple_of(3) {
175 out.push(',');
176 }
177 out.push(ch);
178 out
179 });
180 format!("FIR 서포트 {support_count}개 · 총 {grouped}탭(48 kHz)")
181 }
182 }
183 }
184}