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