Skip to main content

dirtydata_core/
ir.rs

1//! Canonical IR — Layer 1: Machine Truth.
2//!
3//! The single Source of Truth.
4//! Git manages it. The compiler interprets it. The runtime depends on it.
5//!
6//! GUI や DSL による直接上書きを禁止。
7//! すべての変更は PatchSet を経由して適用される。
8
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11
12use crate::types::*;
13
14// ──────────────────────────────────────────────
15// §5.1 — Node
16// ──────────────────────────────────────────────
17
18/// A node in the Canonical IR graph.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct Node {
21    pub id: StableId,
22    pub kind: NodeKind,
23    pub ports: Vec<TypedPort>,
24    pub config: ConfigSnapshot,
25    pub metadata: MetadataRef,
26    pub confidence: ConfidenceScore,
27}
28
29impl Node {
30    /// Create a minimal node with standard audio I/O ports.
31    pub fn new_processor(name: &str) -> Self {
32        Self {
33            id: StableId::new(),
34            kind: NodeKind::Processor,
35            ports: vec![
36                TypedPort {
37                    name: "in".into(),
38                    direction: PortDirection::Input,
39                    domain: ExecutionDomain::Sample,
40                    data_type: DataType::Audio { channels: 2 },
41                    semantic: PortSemantic::Signal,
42                    polarity: PortPolarity::Bipolar,
43                },
44                TypedPort {
45                    name: "out".into(),
46                    direction: PortDirection::Output,
47                    domain: ExecutionDomain::Sample,
48                    data_type: DataType::Audio { channels: 2 },
49                    semantic: PortSemantic::Signal,
50                    polarity: PortPolarity::Bipolar,
51                },
52            ],
53            config: {
54                let mut c = BTreeMap::new();
55                c.insert("name".into(), ConfigValue::String(name.into()));
56                c
57            },
58            metadata: MetadataRef(None),
59            confidence: ConfidenceScore::Verified,
60        }
61    }
62
63    /// Create a source node (audio file, input device).
64    pub fn new_source(name: &str) -> Self {
65        Self {
66            id: StableId::new(),
67            kind: NodeKind::Source,
68            ports: vec![TypedPort {
69                name: "out".into(),
70                direction: PortDirection::Output,
71                domain: ExecutionDomain::Sample,
72                data_type: DataType::Audio { channels: 2 },
73                semantic: PortSemantic::Signal,
74                polarity: PortPolarity::Bipolar,
75            }],
76            config: {
77                let mut c = BTreeMap::new();
78                c.insert("name".into(), ConfigValue::String(name.into()));
79                c
80            },
81            metadata: MetadataRef(None),
82            confidence: ConfidenceScore::Verified,
83        }
84    }
85
86    /// Create a sink node (output, export target).
87    pub fn new_sink(name: &str) -> Self {
88        Self {
89            id: StableId::new(),
90            kind: NodeKind::Sink,
91            ports: vec![TypedPort {
92                name: "in".into(),
93                direction: PortDirection::Input,
94                domain: ExecutionDomain::Sample,
95                data_type: DataType::Audio { channels: 2 },
96                semantic: PortSemantic::Signal,
97                polarity: PortPolarity::Bipolar,
98            }],
99            config: {
100                let mut c = BTreeMap::new();
101                c.insert("name".into(), ConfigValue::String(name.into()));
102                c
103            },
104            metadata: MetadataRef(None),
105            confidence: ConfidenceScore::Verified,
106        }
107    }
108
109    pub fn new_subgraph(name: &str) -> Self {
110        Self {
111            id: StableId::new(),
112            kind: NodeKind::SubGraph,
113            ports: vec![
114                TypedPort { name: "in".into(), direction: PortDirection::Input, domain: ExecutionDomain::Sample, data_type: DataType::Audio { channels: 2 }, semantic: PortSemantic::Signal, polarity: PortPolarity::Bipolar },
115                TypedPort { name: "out".into(), direction: PortDirection::Output, domain: ExecutionDomain::Sample, data_type: DataType::Audio { channels: 2 }, semantic: PortSemantic::Signal, polarity: PortPolarity::Bipolar },
116            ],
117            config: {
118                let mut c = BTreeMap::new();
119                c.insert("name".into(), ConfigValue::String(name.into()));
120                c.insert("graph_json".into(), ConfigValue::String("{}".into()));
121                c
122            },
123            metadata: MetadataRef(None),
124            confidence: ConfidenceScore::Verified,
125        }
126    }
127
128    pub fn new_input_proxy(name: &str) -> Self {
129        Self {
130            id: StableId::new(),
131            kind: NodeKind::InputProxy,
132            ports: vec![TypedPort { name: "out".into(), direction: PortDirection::Output, domain: ExecutionDomain::Sample, data_type: DataType::Audio { channels: 2 }, semantic: PortSemantic::Signal, polarity: PortPolarity::Bipolar }],
133            config: {
134                let mut c = BTreeMap::new();
135                c.insert("name".into(), ConfigValue::String(name.into()));
136                c
137            },
138            metadata: MetadataRef(None),
139            confidence: ConfidenceScore::Verified,
140        }
141    }
142
143    pub fn new_output_proxy(name: &str) -> Self {
144        Self {
145            id: StableId::new(),
146            kind: NodeKind::OutputProxy,
147            ports: vec![TypedPort { name: "in".into(), direction: PortDirection::Input, domain: ExecutionDomain::Sample, data_type: DataType::Audio { channels: 2 }, semantic: PortSemantic::Signal, polarity: PortPolarity::Bipolar }],
148            config: {
149                let mut c = BTreeMap::new();
150                c.insert("name".into(), ConfigValue::String(name.into()));
151                c
152            },
153            metadata: MetadataRef(None),
154            confidence: ConfidenceScore::Verified,
155        }
156    }
157}
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
159pub enum EdgeKind {
160    /// Normal feed-forward connection (causal dependency).
161    #[default]
162    Normal,
163    /// Feedback connection (1-sample delay, breaks DAG constraint).
164    Feedback,
165}
166
167/// A modulation connection between a port and a parameter.
168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
169pub struct Modulation {
170    pub id: StableId,
171    pub source: PortRef,
172    pub target_node: StableId,
173    pub target_param: String,
174    pub amount: f32,
175}
176
177impl Modulation {
178    pub fn new(source: PortRef, target_node: StableId, target_param: String, amount: f32) -> Self {
179        Self {
180            id: StableId::new(),
181            source,
182            target_node,
183            target_param,
184            amount,
185        }
186    }
187}
188
189/// An edge connecting two ports in the Canonical IR graph.
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191pub struct Edge {
192    pub id: StableId,
193    pub source: PortRef,
194    pub target: PortRef,
195    pub kind: EdgeKind,
196    #[serde(default)]
197    pub modulations: BTreeMap<StableId, Modulation>,
198}
199
200impl Edge {
201    pub fn new(source: PortRef, target: PortRef) -> Self {
202        Self {
203            id: StableId::new(),
204            source,
205            target,
206            kind: EdgeKind::Normal,
207            modulations: BTreeMap::new(),
208        }
209    }
210}
211
212/// Layer 1: Topology (The Current Geometric Truth)
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
214pub struct Topology {
215    pub nodes: BTreeMap<StableId, Node>,
216    pub edges: BTreeMap<StableId, Edge>,
217    pub modulations: BTreeMap<StableId, Modulation>,
218}
219
220/// Layer 3: Lineage (Time's Genealogy - History DAG)
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
222pub struct Lineage {
223    pub applied_patches: Vec<PatchId>,
224    pub history: BTreeMap<PatchId, crate::patch::Patch>,
225    pub snapshots: BTreeMap<String, PatchId>,
226}
227
228/// Layer 2: Circuit Registry (The Mythic Blueprints)
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
230pub struct CircuitRegistry {
231    pub definitions: BTreeMap<StableId, crate::types::CircuitDefinition>,
232}
233
234
235/// The Canonical IR Graph — The Forensic Record of Sound.
236#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
237pub struct Graph {
238    pub spec_version: String,
239    pub topology: Topology,
240    pub lineage: Lineage,
241    pub registry: CircuitRegistry,
242    pub verification: Verification,
243    pub revision: Revision,
244    
245    // Compatibility fields for immediate legacy support
246    #[serde(skip)]
247    pub nodes: BTreeMap<StableId, Node>,
248    #[serde(skip)]
249    pub edges: BTreeMap<StableId, Edge>,
250    #[serde(skip)]
251    pub modulations: BTreeMap<StableId, Modulation>,
252}
253
254impl Graph {
255    /// Create an empty graph at revision zero.
256    pub fn new() -> Self {
257        Self {
258            spec_version: "1.0.0".into(),
259            topology: Topology::default(),
260            lineage: Lineage::default(),
261            registry: CircuitRegistry::default(),
262            verification: Verification {
263                null_test: true,
264                hash: String::new(),
265                trust_state: "verified".into(),
266            },
267            revision: Revision::zero(),
268            nodes: BTreeMap::new(),
269            edges: BTreeMap::new(),
270            modulations: BTreeMap::new(),
271        }
272    }
273
274    /// Synchronizes legacy compatibility fields from the layered structure.
275    /// Call this after deserializing or modifying layers.
276    pub fn sync(&mut self) {
277        self.nodes = self.topology.nodes.clone();
278        self.edges = self.topology.edges.clone();
279        self.modulations = self.topology.modulations.clone();
280    }
281
282    pub fn squash_history(&mut self) {
283        let empty = Graph::new();
284        let patch_set = empty.diff(self);
285        
286        if let Some(baseline) = patch_set.patches.first() {
287            self.lineage.applied_patches = vec![baseline.identity];
288            self.lineage.history.clear();
289            self.lineage.history.insert(baseline.identity, baseline.clone());
290        }
291    }
292
293    pub fn create_snapshot(&mut self, name: &str) {
294        if let Some(&last_patch_id) = self.lineage.applied_patches.last() {
295            self.lineage.snapshots.insert(name.to_string(), last_patch_id);
296        }
297    }
298
299    pub fn node(&self, id: &StableId) -> Option<&Node> {
300        self.topology.nodes.get(id)
301    }
302
303    pub fn edge(&self, id: &StableId) -> Option<&Edge> {
304        self.topology.edges.get(id)
305    }
306
307    pub fn validate_port_ref(&self, port_ref: &PortRef) -> bool {
308        self.topology.nodes
309            .get(&port_ref.node_id)
310            .map(|n| n.ports.iter().any(|p| p.name == port_ref.port_name))
311            .unwrap_or(false)
312    }
313
314    // --- High Level API ---
315
316    pub fn add_node(&mut self, node: Node) {
317        self.topology.nodes.insert(node.id, node);
318        self.sync();
319    }
320
321    pub fn remove_node(&mut self, id: StableId) {
322        self.topology.nodes.remove(&id);
323        // Remove associated edges
324        let edge_ids: Vec<StableId> = self.topology.edges.iter()
325            .filter(|(_, e)| e.source.node_id == id || e.target.node_id == id)
326            .map(|(&eid, _)| eid)
327            .collect();
328        for eid in edge_ids {
329            self.topology.edges.remove(&eid);
330        }
331        self.sync();
332    }
333
334    pub fn connect(&mut self, source: PortRef, target: PortRef) -> Result<StableId, String> {
335        if !self.validate_port_ref(&source) { return Err(format!("Invalid source port: {:?}", source)); }
336        if !self.validate_port_ref(&target) { return Err(format!("Invalid target port: {:?}", target)); }
337        
338        let edge = Edge::new(source, target);
339        let id = edge.id;
340        self.topology.edges.insert(id, edge);
341        self.sync();
342        Ok(id)
343    }
344
345    pub fn disconnect(&mut self, edge_id: StableId) -> Result<(), String> {
346        if self.topology.edges.remove(&edge_id).is_some() {
347            self.sync();
348            Ok(())
349        } else {
350            Err("Edge not found".into())
351        }
352    }
353
354    pub fn set_config(&mut self, node_id: StableId, key: &str, value: ConfigValue) -> Result<(), String> {
355        if let Some(node) = self.topology.nodes.get_mut(&node_id) {
356            node.config.insert(key.to_string(), value);
357            self.sync();
358            Ok(())
359        } else {
360            Err("Node not found".into())
361        }
362    }
363}
364
365impl Default for Graph {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371impl std::fmt::Display for Graph {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        write!(f, "Graph(rev={}, nodes={}, edges={})", self.revision.0, self.nodes.len(), self.edges.len())
374    }
375}