aether_core/preset.rs
1//! Preset system for saving and loading DSP graph configurations.
2//!
3//! Presets store complete graph state including nodes, connections, and parameter values.
4//! They can be serialized to JSON for storage and sharing.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// A complete preset containing graph structure and parameter values.
10///
11/// # Example
12///
13/// ```
14/// use aether_core::preset::Preset;
15///
16/// let mut preset = Preset::new("My Synth", "A simple synthesizer");
17///
18/// // Add nodes
19/// preset.add_node(0, "Oscillator");
20/// preset.add_node(1, "Filter");
21/// preset.add_node(2, "Gain");
22///
23/// // Add connections
24/// preset.add_connection(0, 1, 0); // Osc -> Filter
25/// preset.add_connection(1, 2, 0); // Filter -> Gain
26///
27/// // Set parameters
28/// preset.set_param(0, 0, 440.0); // Oscillator frequency
29/// preset.set_param(1, 0, 1000.0); // Filter cutoff
30/// preset.set_param(2, 0, 0.75); // Gain level
31///
32/// // Serialize to JSON
33/// let json = preset.to_json().unwrap();
34/// println!("{}", json);
35///
36/// // Deserialize from JSON
37/// let loaded = Preset::from_json(&json).unwrap();
38/// assert_eq!(loaded.name, "My Synth");
39/// ```
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Preset {
42 /// Preset name
43 pub name: String,
44
45 /// Preset description
46 pub description: String,
47
48 /// Author name (optional)
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub author: Option<String>,
51
52 /// Tags for categorization
53 #[serde(default)]
54 pub tags: Vec<String>,
55
56 /// Nodes in the graph
57 pub nodes: Vec<NodeConfig>,
58
59 /// Connections between nodes
60 pub connections: Vec<Connection>,
61
62 /// Metadata (BPM, key, etc.)
63 #[serde(default)]
64 pub metadata: HashMap<String, String>,
65}
66
67/// Configuration for a single node in the preset.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct NodeConfig {
70 /// Node ID (unique within preset)
71 pub id: usize,
72
73 /// Node type name (e.g., "Oscillator", "Filter")
74 pub node_type: String,
75
76 /// Parameter values
77 #[serde(default)]
78 pub params: Vec<f32>,
79
80 /// UI position (optional, for visual editors)
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub position: Option<(f32, f32)>,
83}
84
85/// Connection between two nodes.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct Connection {
88 /// Source node ID
89 pub src_id: usize,
90
91 /// Destination node ID
92 pub dst_id: usize,
93
94 /// Input slot on destination node
95 pub slot: usize,
96}
97
98impl Preset {
99 /// Creates a new empty preset.
100 ///
101 /// # Arguments
102 ///
103 /// * `name` - Preset name
104 /// * `description` - Preset description
105 ///
106 /// # Example
107 ///
108 /// ```
109 /// use aether_core::preset::Preset;
110 ///
111 /// let preset = Preset::new("Bass Synth", "Deep bass synthesizer");
112 /// assert_eq!(preset.name, "Bass Synth");
113 /// assert_eq!(preset.nodes.len(), 0);
114 /// ```
115 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
116 Self {
117 name: name.into(),
118 description: description.into(),
119 author: None,
120 tags: Vec::new(),
121 nodes: Vec::new(),
122 connections: Vec::new(),
123 metadata: HashMap::new(),
124 }
125 }
126
127 /// Adds a node to the preset.
128 ///
129 /// # Arguments
130 ///
131 /// * `id` - Node ID (unique within preset)
132 /// * `node_type` - Node type name
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// use aether_core::preset::Preset;
138 ///
139 /// let mut preset = Preset::new("Test", "Test preset");
140 /// preset.add_node(0, "Oscillator");
141 /// preset.add_node(1, "Filter");
142 /// assert_eq!(preset.nodes.len(), 2);
143 /// ```
144 pub fn add_node(&mut self, id: usize, node_type: impl Into<String>) {
145 self.nodes.push(NodeConfig {
146 id,
147 node_type: node_type.into(),
148 params: Vec::new(),
149 position: None,
150 });
151 }
152
153 /// Adds a connection between two nodes.
154 ///
155 /// # Arguments
156 ///
157 /// * `src_id` - Source node ID
158 /// * `dst_id` - Destination node ID
159 /// * `slot` - Input slot on destination node
160 ///
161 /// # Example
162 ///
163 /// ```
164 /// use aether_core::preset::Preset;
165 ///
166 /// let mut preset = Preset::new("Test", "Test preset");
167 /// preset.add_node(0, "Oscillator");
168 /// preset.add_node(1, "Filter");
169 /// preset.add_connection(0, 1, 0); // Osc -> Filter input 0
170 /// assert_eq!(preset.connections.len(), 1);
171 /// ```
172 pub fn add_connection(&mut self, src_id: usize, dst_id: usize, slot: usize) {
173 self.connections.push(Connection {
174 src_id,
175 dst_id,
176 slot,
177 });
178 }
179
180 /// Sets a parameter value for a node.
181 ///
182 /// # Arguments
183 ///
184 /// * `node_id` - Node ID
185 /// * `param_index` - Parameter index
186 /// * `value` - Parameter value
187 ///
188 /// # Example
189 ///
190 /// ```
191 /// use aether_core::preset::Preset;
192 ///
193 /// let mut preset = Preset::new("Test", "Test preset");
194 /// preset.add_node(0, "Oscillator");
195 /// preset.set_param(0, 0, 440.0); // Set frequency to 440 Hz
196 /// preset.set_param(0, 1, 0.5); // Set amplitude to 0.5
197 /// ```
198 pub fn set_param(&mut self, node_id: usize, param_index: usize, value: f32) {
199 if let Some(node) = self.nodes.iter_mut().find(|n| n.id == node_id) {
200 // Extend params vec if needed
201 if param_index >= node.params.len() {
202 node.params.resize(param_index + 1, 0.0);
203 }
204 node.params[param_index] = value;
205 }
206 }
207
208 /// Sets the UI position for a node.
209 ///
210 /// # Arguments
211 ///
212 /// * `node_id` - Node ID
213 /// * `x` - X coordinate
214 /// * `y` - Y coordinate
215 pub fn set_position(&mut self, node_id: usize, x: f32, y: f32) {
216 if let Some(node) = self.nodes.iter_mut().find(|n| n.id == node_id) {
217 node.position = Some((x, y));
218 }
219 }
220
221 /// Adds a tag to the preset.
222 ///
223 /// # Arguments
224 ///
225 /// * `tag` - Tag to add
226 ///
227 /// # Example
228 ///
229 /// ```
230 /// use aether_core::preset::Preset;
231 ///
232 /// let mut preset = Preset::new("Bass", "Deep bass");
233 /// preset.add_tag("bass");
234 /// preset.add_tag("synth");
235 /// preset.add_tag("electronic");
236 /// assert_eq!(preset.tags.len(), 3);
237 /// ```
238 pub fn add_tag(&mut self, tag: impl Into<String>) {
239 self.tags.push(tag.into());
240 }
241
242 /// Sets metadata key-value pair.
243 ///
244 /// # Arguments
245 ///
246 /// * `key` - Metadata key
247 /// * `value` - Metadata value
248 ///
249 /// # Example
250 ///
251 /// ```
252 /// use aether_core::preset::Preset;
253 ///
254 /// let mut preset = Preset::new("Song", "Song preset");
255 /// preset.set_metadata("bpm", "120");
256 /// preset.set_metadata("key", "C minor");
257 /// ```
258 pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
259 self.metadata.insert(key.into(), value.into());
260 }
261
262 /// Serializes the preset to JSON.
263 ///
264 /// # Returns
265 ///
266 /// JSON string representation of the preset.
267 ///
268 /// # Errors
269 ///
270 /// Returns an error if serialization fails.
271 ///
272 /// # Example
273 ///
274 /// ```
275 /// use aether_core::preset::Preset;
276 ///
277 /// let mut preset = Preset::new("Test", "Test preset");
278 /// preset.add_node(0, "Oscillator");
279 /// let json = preset.to_json().unwrap();
280 /// assert!(json.contains("\"name\":\"Test\""));
281 /// ```
282 pub fn to_json(&self) -> Result<String, serde_json::Error> {
283 serde_json::to_string_pretty(self)
284 }
285
286 /// Deserializes a preset from JSON.
287 ///
288 /// # Arguments
289 ///
290 /// * `json` - JSON string
291 ///
292 /// # Returns
293 ///
294 /// Deserialized preset.
295 ///
296 /// # Errors
297 ///
298 /// Returns an error if deserialization fails.
299 ///
300 /// # Example
301 ///
302 /// ```
303 /// use aether_core::preset::Preset;
304 ///
305 /// let json = r#"{
306 /// "name": "Test",
307 /// "description": "Test preset",
308 /// "nodes": [],
309 /// "connections": []
310 /// }"#;
311 ///
312 /// let preset = Preset::from_json(json).unwrap();
313 /// assert_eq!(preset.name, "Test");
314 /// ```
315 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
316 serde_json::from_str(json)
317 }
318
319 /// Validates the preset structure.
320 ///
321 /// Checks for:
322 /// - Duplicate node IDs
323 /// - Invalid connections (referencing non-existent nodes)
324 /// - Empty preset name
325 ///
326 /// # Returns
327 ///
328 /// `Ok(())` if valid, `Err(String)` with error message if invalid.
329 ///
330 /// # Example
331 ///
332 /// ```
333 /// use aether_core::preset::Preset;
334 ///
335 /// let mut preset = Preset::new("Test", "Test preset");
336 /// preset.add_node(0, "Oscillator");
337 /// preset.add_node(1, "Filter");
338 /// preset.add_connection(0, 1, 0);
339 /// assert!(preset.validate().is_ok());
340 ///
341 /// // Invalid connection
342 /// preset.add_connection(0, 99, 0); // Node 99 doesn't exist
343 /// assert!(preset.validate().is_err());
344 /// ```
345 pub fn validate(&self) -> Result<(), String> {
346 // Check for empty name
347 if self.name.is_empty() {
348 return Err("Preset name cannot be empty".to_string());
349 }
350
351 // Check for duplicate node IDs
352 let mut seen_ids = std::collections::HashSet::new();
353 for node in &self.nodes {
354 if !seen_ids.insert(node.id) {
355 return Err(format!("Duplicate node ID: {}", node.id));
356 }
357 }
358
359 // Check connections reference valid nodes
360 for conn in &self.connections {
361 if !self.nodes.iter().any(|n| n.id == conn.src_id) {
362 return Err(format!(
363 "Connection references non-existent source node: {}",
364 conn.src_id
365 ));
366 }
367 if !self.nodes.iter().any(|n| n.id == conn.dst_id) {
368 return Err(format!(
369 "Connection references non-existent destination node: {}",
370 conn.dst_id
371 ));
372 }
373 }
374
375 Ok(())
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_preset_creation() {
385 let preset = Preset::new("Test Synth", "A test synthesizer");
386 assert_eq!(preset.name, "Test Synth");
387 assert_eq!(preset.description, "A test synthesizer");
388 assert_eq!(preset.nodes.len(), 0);
389 assert_eq!(preset.connections.len(), 0);
390 }
391
392 #[test]
393 fn test_add_nodes() {
394 let mut preset = Preset::new("Test", "Test");
395 preset.add_node(0, "Oscillator");
396 preset.add_node(1, "Filter");
397 preset.add_node(2, "Gain");
398 assert_eq!(preset.nodes.len(), 3);
399 assert_eq!(preset.nodes[0].node_type, "Oscillator");
400 assert_eq!(preset.nodes[1].node_type, "Filter");
401 }
402
403 #[test]
404 fn test_add_connections() {
405 let mut preset = Preset::new("Test", "Test");
406 preset.add_node(0, "Oscillator");
407 preset.add_node(1, "Filter");
408 preset.add_connection(0, 1, 0);
409 assert_eq!(preset.connections.len(), 1);
410 assert_eq!(preset.connections[0].src_id, 0);
411 assert_eq!(preset.connections[0].dst_id, 1);
412 }
413
414 #[test]
415 fn test_set_params() {
416 let mut preset = Preset::new("Test", "Test");
417 preset.add_node(0, "Oscillator");
418 preset.set_param(0, 0, 440.0);
419 preset.set_param(0, 1, 0.5);
420 assert_eq!(preset.nodes[0].params.len(), 2);
421 assert_eq!(preset.nodes[0].params[0], 440.0);
422 assert_eq!(preset.nodes[0].params[1], 0.5);
423 }
424
425 #[test]
426 fn test_json_serialization() {
427 let mut preset = Preset::new("Test", "Test preset");
428 preset.add_node(0, "Oscillator");
429 preset.set_param(0, 0, 440.0);
430
431 let json = preset.to_json().unwrap();
432 assert!(json.contains("Test")); // Check name is present
433 assert!(json.contains("Oscillator")); // Check node type is present
434
435 let loaded = Preset::from_json(&json).unwrap();
436 assert_eq!(loaded.name, "Test");
437 assert_eq!(loaded.nodes.len(), 1);
438 assert_eq!(loaded.nodes[0].params[0], 440.0);
439 }
440
441 #[test]
442 fn test_validation_valid_preset() {
443 let mut preset = Preset::new("Test", "Test");
444 preset.add_node(0, "Oscillator");
445 preset.add_node(1, "Filter");
446 preset.add_connection(0, 1, 0);
447 assert!(preset.validate().is_ok());
448 }
449
450 #[test]
451 fn test_validation_empty_name() {
452 let preset = Preset::new("", "Test");
453 assert!(preset.validate().is_err());
454 }
455
456 #[test]
457 fn test_validation_duplicate_ids() {
458 let mut preset = Preset::new("Test", "Test");
459 preset.add_node(0, "Oscillator");
460 preset.add_node(0, "Filter"); // Duplicate ID
461 assert!(preset.validate().is_err());
462 }
463
464 #[test]
465 fn test_validation_invalid_connection() {
466 let mut preset = Preset::new("Test", "Test");
467 preset.add_node(0, "Oscillator");
468 preset.add_connection(0, 99, 0); // Node 99 doesn't exist
469 assert!(preset.validate().is_err());
470 }
471
472 #[test]
473 fn test_tags_and_metadata() {
474 let mut preset = Preset::new("Test", "Test");
475 preset.add_tag("bass");
476 preset.add_tag("synth");
477 preset.set_metadata("bpm", "120");
478 preset.set_metadata("key", "C minor");
479
480 assert_eq!(preset.tags.len(), 2);
481 assert_eq!(preset.metadata.get("bpm"), Some(&"120".to_string()));
482 }
483}