Skip to main content

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}