1use serde::Serialize;
9use ts_rs::TS;
10
11use crate::role::{HEIGHT_GROUP, TOP_GROUP};
12
13#[derive(Debug, Clone, Copy, PartialEq, Serialize, TS)]
16#[ts(export)]
17pub struct CalibrationLimits {
18 pub rc_lo: (f64, f64),
21 pub rc_hi: (f64, f64),
23 pub rc_band: (f64, f64),
25 pub rc_bands_max: (f64, f64),
27 pub volume: (f64, f64),
29 pub dim: (f64, f64),
31 pub dim_valid: (f64, f64),
33 pub boost: (f64, f64),
35 pub input_hp: (f64, f64),
37 pub ultra_ms: (f64, f64),
39 pub xo: (f64, f64),
41 pub xo_peq: (f64, f64),
43 pub infra_hz: (f64, f64),
45 pub infra_boost: (f64, f64),
47 pub main_delay: (f64, f64),
49 pub rear_trim: (f64, f64),
51 pub fsiso: (f64, f64),
53 pub mimo_low: (f64, f64),
55 pub mimo_max: (f64, f64),
57 pub mimo_early_ms: (f64, f64),
59}
60
61pub const RC_LO_READ: (f64, f64) = (10.0, 200.0);
64
65pub const CALIBRATION_LIMITS: CalibrationLimits = CalibrationLimits {
66 rc_lo: (20.0, 200.0),
67 rc_hi: (100.0, 20000.0),
68 rc_band: (20.0, 20000.0),
69 rc_bands_max: (0.0, 4.0),
70 volume: (0.0, 100000.0),
71 dim: (0.0, 100.0),
72 dim_valid: (0.5, 100.0),
73 boost: (0.0, 10.0),
74 input_hp: (10.0, 50.0),
75 ultra_ms: (30.0, 200.0),
76 xo: (40.0, 200.0),
77 xo_peq: (0.0, 1000.0),
78 infra_hz: (10.0, 300.0),
79 infra_boost: (0.0, 24.0),
80 main_delay: (0.0, 100.0),
81 rear_trim: (-20.0, 20.0),
82 fsiso: (50.0, 150.0),
83 mimo_low: (15.0, 150.0),
84 mimo_max: (0.0, 12.0),
85 mimo_early_ms: (5.0, 60.0),
86};
87
88pub const SWEEP_LENGTHS: [&str; 7] = ["64k", "128k", "256k", "512k", "1M", "2M", "4M"];
90
91pub const START_DELAYS: [&str; 4] = ["끄기", "5 초", "10 초", "15 초"];
94
95pub const RECENT_PROJECTS_MAX: usize = 10;
97
98pub const CONFIG_GROUPS: [(&str, &str); 9] = [
101 ("프론트", "front"),
102 ("센터", "center"),
103 ("서브", "sub"),
104 ("서라운드", "surround"),
105 ("백", "back"),
106 ("와이드", "wide"),
107 (TOP_GROUP, "top"),
108 (HEIGHT_GROUP, "height"),
109 ("기타", "other"),
110];
111
112pub const ARRANGEMENT_COUNTS: [usize; 4] = [1, 9, 13, 17];
114
115pub const JOB_HISTORY_MAX: usize = 32;
118
119#[cfg(test)]
120mod typescript {
121 use serde::Serialize;
123 use serde_json::Value;
124
125 use super::*;
126
127 const HEADER: &str = "// This file was generated by `cargo test -p secs-types` from crates/secs-types/src/rules.rs. Do not edit this file manually.\n";
128
129 fn literal(value: &Value) -> String {
130 match value {
131 Value::Null => "null".into(),
132 Value::Bool(value) => value.to_string(),
133 Value::Number(number) => match (number.as_i64(), number.as_f64()) {
136 (Some(int), _) => int.to_string(),
137 (None, Some(float)) => float.to_string(),
138 (None, None) => number.to_string(),
139 },
140 Value::String(_) => value.to_string(),
141 Value::Array(items) => {
142 format!(
143 "[{}]",
144 items.iter().map(literal).collect::<Vec<_>>().join(", ")
145 )
146 }
147 Value::Object(fields) => format!(
148 "{{\n{}}}",
149 fields
150 .iter()
151 .map(|(key, value)| format!(" {key}: {},\n", literal(value)))
152 .collect::<String>()
153 ),
154 }
155 }
156
157 pub(super) fn constant(name: &str, ty: Option<&str>, value: &impl Serialize) -> String {
159 let value = serde_json::to_value(value).expect("rule constants serialize");
160 let annotation = ty.map(|ty| format!(": {ty}")).unwrap_or_default();
161 let suffix = if value.is_array() || value.is_object() {
162 " as const"
163 } else {
164 ""
165 };
166 format!(
167 "export const {name}{annotation} = {}{suffix};\n",
168 literal(&value)
169 )
170 }
171
172 pub(super) fn rules_ts() -> String {
173 [
174 HEADER.to_owned(),
175 "import type { CalibrationLimits } from \"./CalibrationLimits\";\n\n".to_owned(),
176 constant(
177 "CALIBRATION_LIMITS",
178 Some("CalibrationLimits"),
179 &CALIBRATION_LIMITS,
180 ),
181 constant("SWEEP_LENGTHS", None, &SWEEP_LENGTHS),
182 constant("START_DELAYS", None, &START_DELAYS),
183 constant("RECENT_PROJECTS_MAX", None, &RECENT_PROJECTS_MAX),
184 constant("CONFIG_GROUPS", None, &CONFIG_GROUPS),
185 constant("ARRANGEMENT_COUNTS", None, &ARRANGEMENT_COUNTS),
186 constant("JOB_HISTORY_MAX", None, &JOB_HISTORY_MAX),
187 ]
188 .concat()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::typescript::{constant, rules_ts};
195 use super::*;
196
197 #[test]
198 fn renders_constants_as_typescript() {
199 assert_eq!(
200 constant("RECENT_PROJECTS_MAX", None, &RECENT_PROJECTS_MAX),
201 "export const RECENT_PROJECTS_MAX = 10;\n"
202 );
203 assert_eq!(
204 constant("EDGES", None, &[(0.5, 100.0), (-20.0, 0.71)]),
205 "export const EDGES = [[0.5, 100], [-20, 0.71]] as const;\n"
206 );
207 assert_eq!(
208 constant("GROUPS", None, &[("탑", "top")]),
209 "export const GROUPS = [[\"탑\", \"top\"]] as const;\n"
210 );
211 let limits = constant("LIMITS", Some("CalibrationLimits"), &CALIBRATION_LIMITS);
212 assert!(
213 limits.starts_with(
214 "export const LIMITS: CalibrationLimits = {\n rc_lo: [20, 200],\n rc_hi: [100, 20000],\n rc_band: [20, 20000],\n rc_bands_max: [0, 4],\n"
215 ),
216 "{limits}"
217 );
218 assert!(
219 limits.ends_with(" mimo_max: [0, 12],\n mimo_early_ms: [5, 60],\n} as const;\n"),
220 "{limits}"
221 );
222 }
223
224 #[test]
227 fn export_rules_ts() {
228 let dir = ts_rs::Config::from_env().out_dir().to_path_buf();
229 std::fs::create_dir_all(&dir).unwrap();
230 std::fs::write(dir.join("rules.ts"), rules_ts()).unwrap();
231 }
232}