1use serde::Deserialize;
11
12#[derive(Debug, Clone, PartialEq)]
18pub enum ParamType {
19 Bool { default: bool },
21
22 Float { min: f64, max: f64, default: f64 },
24
25 Choice {
27 choices: &'static [&'static str],
28 default_index: usize,
29 },
30}
31
32#[derive(Debug, Clone)]
44pub struct ParamDef {
45 pub id: &'static str,
47 pub name: &'static str,
49 pub param_type: ParamType,
51}
52
53impl ParamDef {
54 pub const fn bool(id: &'static str, name: &'static str, default: bool) -> Self {
56 Self {
57 id,
58 name,
59 param_type: ParamType::Bool { default },
60 }
61 }
62
63 pub const fn float(id: &'static str, name: &'static str, min: f64, max: f64, default: f64) -> Self {
65 Self {
66 id,
67 name,
68 param_type: ParamType::Float { min, max, default },
69 }
70 }
71
72 pub const fn choice(
74 id: &'static str,
75 name: &'static str,
76 choices: &'static [&'static str],
77 default_index: usize,
78 ) -> Self {
79 Self {
80 id,
81 name,
82 param_type: ParamType::Choice { choices, default_index },
83 }
84 }
85}
86
87pub fn generate_rust_indices_from_defs(params: &[ParamDef]) -> String {
96 let mut code = String::from("// Auto-generated parameter indices - DO NOT EDIT\n\n");
97
98 for (index, param) in params.iter().enumerate() {
99 code.push_str(&format!("pub const PARAM_{}: usize = {};\n", param.id, index));
100 }
101
102 code.push_str(&format!(
103 "\n#[allow(dead_code)]\npub const PARAM_COUNT: usize = {};\n",
104 params.len()
105 ));
106
107 code
108}
109
110pub fn generate_c_header_from_defs(params: &[ParamDef]) -> String {
120 let mut content = String::new();
121 content.push_str("/* Auto-generated parameter indices - DO NOT EDIT */\n\n");
122 content.push_str("#ifndef BBX_PARAMS_H\n");
123 content.push_str("#define BBX_PARAMS_H\n\n");
124
125 for (index, param) in params.iter().enumerate() {
126 content.push_str(&format!("#define PARAM_{} {}\n", param.id, index));
127 }
128
129 content.push_str(&format!("\n#define PARAM_COUNT {}\n\n", params.len()));
130
131 if !params.is_empty() {
133 content.push_str("static const char* PARAM_IDS[PARAM_COUNT] = {\n");
134 for (i, param) in params.iter().enumerate() {
135 let comma = if i < params.len() - 1 { "," } else { "" };
136 content.push_str(&format!(" \"{}\"{}\n", param.id, comma));
137 }
138 content.push_str("};\n\n");
139 }
140
141 content.push_str("#endif /* BBX_PARAMS_H */\n");
142
143 content
144}
145
146#[derive(Debug, Clone, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct JsonParamDef {
154 pub id: String,
156 pub name: String,
158 #[serde(rename = "type")]
160 pub param_type: String,
161 #[serde(default)]
163 pub default_value: Option<serde_json::Value>,
164 #[serde(default)]
166 pub default_value_index: Option<usize>,
167 #[serde(default)]
169 pub min: Option<f64>,
170 #[serde(default)]
172 pub max: Option<f64>,
173 #[serde(default)]
175 pub unit: Option<String>,
176 #[serde(default)]
178 pub midpoint: Option<f64>,
179 #[serde(default)]
181 pub interval: Option<f64>,
182 #[serde(default)]
184 pub fraction_digits: Option<u32>,
185 #[serde(default)]
187 pub choices: Option<Vec<String>>,
188}
189
190#[derive(Debug, Clone, Deserialize)]
192pub struct ParamsFile {
193 pub parameters: Vec<JsonParamDef>,
195}
196
197impl ParamsFile {
198 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
200 serde_json::from_str(json)
201 }
202
203 pub fn generate_rust_indices(&self) -> String {
212 let mut code = String::from("// Auto-generated from parameters.json - DO NOT EDIT\n\n");
213
214 for (index, param) in self.parameters.iter().enumerate() {
215 code.push_str(&format!("pub const PARAM_{}: usize = {};\n", param.id, index));
216 }
217
218 code.push_str(&format!(
219 "\n#[allow(dead_code)]\npub const PARAM_COUNT: usize = {};\n",
220 self.parameters.len()
221 ));
222
223 code
224 }
225
226 pub fn generate_c_header(&self) -> String {
236 let mut content = String::new();
237 content.push_str("/* Auto-generated from parameters.json - DO NOT EDIT */\n\n");
238 content.push_str("#ifndef BBX_PARAMS_H\n");
239 content.push_str("#define BBX_PARAMS_H\n\n");
240
241 for (index, param) in self.parameters.iter().enumerate() {
242 content.push_str(&format!("#define PARAM_{} {}\n", param.id, index));
243 }
244
245 content.push_str(&format!("\n#define PARAM_COUNT {}\n\n", self.parameters.len()));
246
247 if !self.parameters.is_empty() {
249 content.push_str("static const char* PARAM_IDS[PARAM_COUNT] = {\n");
250 for (i, param) in self.parameters.iter().enumerate() {
251 let comma = if i < self.parameters.len() - 1 { "," } else { "" };
252 content.push_str(&format!(" \"{}\"{}\n", param.id, comma));
253 }
254 content.push_str("};\n\n");
255 }
256
257 content.push_str("#endif /* BBX_PARAMS_H */\n");
258
259 content
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_param_def_constructors() {
269 let bool_param = ParamDef::bool("MONO", "Mono", false);
270 assert_eq!(bool_param.id, "MONO");
271 assert_eq!(bool_param.param_type, ParamType::Bool { default: false });
272
273 let float_param = ParamDef::float("GAIN", "Gain", -60.0, 30.0, 0.0);
274 assert_eq!(float_param.id, "GAIN");
275
276 let choice_param = ParamDef::choice("MODE", "Mode", &["A", "B"], 0);
277 assert_eq!(choice_param.id, "MODE");
278 }
279
280 #[test]
281 fn test_generate_rust_indices_from_defs() {
282 const PARAMS: &[ParamDef] = &[
283 ParamDef::float("GAIN", "Gain", -60.0, 30.0, 0.0),
284 ParamDef::bool("MONO", "Mono", false),
285 ];
286
287 let code = generate_rust_indices_from_defs(PARAMS);
288 assert!(code.contains("pub const PARAM_GAIN: usize = 0;"));
289 assert!(code.contains("pub const PARAM_MONO: usize = 1;"));
290 assert!(code.contains("#[allow(dead_code)]"));
291 assert!(code.contains("pub const PARAM_COUNT: usize = 2;"));
292 }
293
294 #[test]
295 fn test_generate_c_header_from_defs() {
296 const PARAMS: &[ParamDef] = &[
297 ParamDef::float("GAIN", "Gain", -60.0, 30.0, 0.0),
298 ParamDef::bool("MONO", "Mono", false),
299 ];
300
301 let header = generate_c_header_from_defs(PARAMS);
302 assert!(header.contains("#define PARAM_GAIN 0"));
303 assert!(header.contains("#define PARAM_MONO 1"));
304 assert!(header.contains("#define PARAM_COUNT 2"));
305 assert!(header.contains("static const char* PARAM_IDS[PARAM_COUNT]"));
306 assert!(header.contains("\"GAIN\""));
307 assert!(header.contains("\"MONO\""));
308 }
309
310 #[test]
311 fn test_params_file_from_json() {
312 let json = r#"{
313 "parameters": [
314 {"id": "GAIN", "name": "Gain", "type": "float", "min": -60.0, "max": 30.0, "defaultValue": 0.0},
315 {"id": "MONO", "name": "Mono", "type": "boolean", "defaultValue": false}
316 ]
317 }"#;
318
319 let params = ParamsFile::from_json(json).unwrap();
320 assert_eq!(params.parameters.len(), 2);
321 assert_eq!(params.parameters[0].id, "GAIN");
322 assert_eq!(params.parameters[1].id, "MONO");
323 }
324
325 #[test]
326 fn test_params_file_generate_indices() {
327 let json = r#"{"parameters": [{"id": "GAIN", "name": "Gain", "type": "float"}]}"#;
328 let params = ParamsFile::from_json(json).unwrap();
329
330 let rust_code = params.generate_rust_indices();
331 assert!(rust_code.contains("pub const PARAM_GAIN: usize = 0;"));
332
333 let c_header = params.generate_c_header();
334 assert!(c_header.contains("#define PARAM_GAIN 0"));
335 assert!(c_header.contains("static const char* PARAM_IDS[PARAM_COUNT]"));
336 assert!(c_header.contains("\"GAIN\""));
337 }
338}