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