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