use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Preset {
pub name: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
pub nodes: Vec<NodeConfig>,
pub connections: Vec<Connection>,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeConfig {
pub id: usize,
pub node_type: String,
#[serde(default)]
pub params: Vec<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<(f32, f32)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Connection {
pub src_id: usize,
pub dst_id: usize,
pub slot: usize,
}
impl Preset {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
author: None,
tags: Vec::new(),
nodes: Vec::new(),
connections: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn add_node(&mut self, id: usize, node_type: impl Into<String>) {
self.nodes.push(NodeConfig {
id,
node_type: node_type.into(),
params: Vec::new(),
position: None,
});
}
pub fn add_connection(&mut self, src_id: usize, dst_id: usize, slot: usize) {
self.connections.push(Connection {
src_id,
dst_id,
slot,
});
}
pub fn set_param(&mut self, node_id: usize, param_index: usize, value: f32) {
if let Some(node) = self.nodes.iter_mut().find(|n| n.id == node_id) {
if param_index >= node.params.len() {
node.params.resize(param_index + 1, 0.0);
}
node.params[param_index] = value;
}
}
pub fn set_position(&mut self, node_id: usize, x: f32, y: f32) {
if let Some(node) = self.nodes.iter_mut().find(|n| n.id == node_id) {
node.position = Some((x, y));
}
}
pub fn add_tag(&mut self, tag: impl Into<String>) {
self.tags.push(tag.into());
}
pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub fn validate(&self) -> Result<(), String> {
if self.name.is_empty() {
return Err("Preset name cannot be empty".to_string());
}
let mut seen_ids = std::collections::HashSet::new();
for node in &self.nodes {
if !seen_ids.insert(node.id) {
return Err(format!("Duplicate node ID: {}", node.id));
}
}
for conn in &self.connections {
if !self.nodes.iter().any(|n| n.id == conn.src_id) {
return Err(format!(
"Connection references non-existent source node: {}",
conn.src_id
));
}
if !self.nodes.iter().any(|n| n.id == conn.dst_id) {
return Err(format!(
"Connection references non-existent destination node: {}",
conn.dst_id
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preset_creation() {
let preset = Preset::new("Test Synth", "A test synthesizer");
assert_eq!(preset.name, "Test Synth");
assert_eq!(preset.description, "A test synthesizer");
assert_eq!(preset.nodes.len(), 0);
assert_eq!(preset.connections.len(), 0);
}
#[test]
fn test_add_nodes() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.add_node(1, "Filter");
preset.add_node(2, "Gain");
assert_eq!(preset.nodes.len(), 3);
assert_eq!(preset.nodes[0].node_type, "Oscillator");
assert_eq!(preset.nodes[1].node_type, "Filter");
}
#[test]
fn test_add_connections() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.add_node(1, "Filter");
preset.add_connection(0, 1, 0);
assert_eq!(preset.connections.len(), 1);
assert_eq!(preset.connections[0].src_id, 0);
assert_eq!(preset.connections[0].dst_id, 1);
}
#[test]
fn test_set_params() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.set_param(0, 0, 440.0);
preset.set_param(0, 1, 0.5);
assert_eq!(preset.nodes[0].params.len(), 2);
assert_eq!(preset.nodes[0].params[0], 440.0);
assert_eq!(preset.nodes[0].params[1], 0.5);
}
#[test]
fn test_json_serialization() {
let mut preset = Preset::new("Test", "Test preset");
preset.add_node(0, "Oscillator");
preset.set_param(0, 0, 440.0);
let json = preset.to_json().unwrap();
assert!(json.contains("Test")); assert!(json.contains("Oscillator"));
let loaded = Preset::from_json(&json).unwrap();
assert_eq!(loaded.name, "Test");
assert_eq!(loaded.nodes.len(), 1);
assert_eq!(loaded.nodes[0].params[0], 440.0);
}
#[test]
fn test_validation_valid_preset() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.add_node(1, "Filter");
preset.add_connection(0, 1, 0);
assert!(preset.validate().is_ok());
}
#[test]
fn test_validation_empty_name() {
let preset = Preset::new("", "Test");
assert!(preset.validate().is_err());
}
#[test]
fn test_validation_duplicate_ids() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.add_node(0, "Filter"); assert!(preset.validate().is_err());
}
#[test]
fn test_validation_invalid_connection() {
let mut preset = Preset::new("Test", "Test");
preset.add_node(0, "Oscillator");
preset.add_connection(0, 99, 0); assert!(preset.validate().is_err());
}
#[test]
fn test_tags_and_metadata() {
let mut preset = Preset::new("Test", "Test");
preset.add_tag("bass");
preset.add_tag("synth");
preset.set_metadata("bpm", "120");
preset.set_metadata("key", "C minor");
assert_eq!(preset.tags.len(), 2);
assert_eq!(preset.metadata.get("bpm"), Some(&"120".to_string()));
}
}