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 Latency {
62 latency_ms: f64,
63 lift_ms: f64,
64 main_extra_ms: f64,
65 },
66 PhaseStrength(Vec<(String, f64)>),
67 PhaseBand(Vec<(String, Option<f64>)>),
68 LongModeBoost {
70 cap_db: f64,
71 modes: Vec<(f64, f64)>,
72 },
73 GdCuts {
74 role: String,
75 cuts: Vec<Peq>,
76 },
77 BaCost(f64),
79 BaBusPass {
80 first: f64,
81 second: f64,
82 kept_first: bool,
83 },
84 BaManual,
85 LayoutFallback(String),
86 Layout(String),
87 TwoWay {
88 roles: Vec<String>,
89 gain_db: f64,
90 xo_hz: f64,
91 },
92 BusGainAnchored(f64),
93 Mimo(MimoNote),
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub enum MimoNote {
99 InsufficientPositions {
100 measured: usize,
101 },
102 ExcludedSupports(Vec<String>),
103 SupportRanges(Vec<(String, Option<(f64, f64)>)>),
104 GuardReduced {
105 role: String,
106 gain_db: f64,
107 cause: GuardCause,
108 },
109 GuardDisabled {
110 role: String,
111 cause: GuardCause,
112 },
113 SupportLimit {
114 max_supports: usize,
115 },
116 FirBudget {
117 support_count: usize,
118 taps_total: usize,
119 },
120}
121
122#[derive(Debug, Clone, PartialEq)]
126pub struct GuardCause {
127 pub rejected_db: f64,
128 pub band: Option<(f64, f64, f64)>,
131 pub tail: Option<(f64, f64)>,
133}
134
135#[cfg(test)]
136impl MimoNote {
137 pub(crate) fn oracle_text(&self) -> String {
139 match self {
140 Self::InsufficientPositions { measured } => {
141 format!("측정 위치 {measured}곳 — MIMO 는 3곳 이상일 때만")
142 }
143 Self::ExcludedSupports(roles) => format!("서포트 제외(사용자): {}", roles.join(", ")),
144 Self::SupportRanges(ranges) => format!(
145 "서포트 대역 {}",
146 ranges
147 .iter()
148 .map(|(role, range)| match range {
149 Some((lo, hi)) => format!("{role} {lo:.0}–{hi:.0}"),
150 None => format!("{role} 제외"),
151 })
152 .collect::<Vec<_>>()
153 .join(", ")
154 ),
155 Self::GuardReduced { role, gain_db, .. } => {
156 format!("{role}: 대역 늦은 에너지 악화로 서포트 {gain_db:.0} dB")
157 }
158 Self::GuardDisabled { role, .. } => format!("{role}: MIMO 끔(악화)"),
159 Self::SupportLimit { max_supports } => format!("입력당 서포트 ≤ {max_supports}"),
160 Self::FirBudget {
161 support_count,
162 taps_total,
163 } => {
164 let digits = taps_total.to_string();
165 let grouped = digits
166 .chars()
167 .enumerate()
168 .fold(String::new(), |mut out, (i, ch)| {
169 if i > 0 && (digits.len() - i).is_multiple_of(3) {
170 out.push(',');
171 }
172 out.push(ch);
173 out
174 });
175 format!("FIR 서포트 {support_count}개 · 총 {grouped}탭(48 kHz)")
176 }
177 }
178 }
179}