aether_core/node.rs
1//! Node abstraction for the DSP graph.
2//!
3//! Each node is a self-contained DSP unit. The `DspNode` trait is the only
4//! interface the graph scheduler calls — keeping the hot path minimal.
5
6use crate::{
7 arena::NodeId, buffer_pool::BufferId, param::ParamBlock, state::StateBlob, BUFFER_SIZE,
8 MAX_INPUTS,
9};
10
11/// The core DSP processing trait.
12/// Implementations MUST be real-time safe:
13/// - No allocation
14/// - No locks
15/// - No I/O
16/// - Bounded execution time
17pub trait DspNode: Send {
18 /// Process one buffer of audio.
19 ///
20 /// `inputs`: resolved input buffer slices (None = silence).
21 /// `output`: the node's output buffer to fill.
22 /// `params`: the node's parameter block (pre-ticked by scheduler).
23 /// `sample_rate`: current sample rate in Hz.
24 fn process(
25 &mut self,
26 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
27 output: &mut [f32; BUFFER_SIZE],
28 params: &mut ParamBlock,
29 sample_rate: f32,
30 );
31
32 /// Capture internal state for continuity transfer.
33 fn capture_state(&self) -> StateBlob {
34 StateBlob::EMPTY
35 }
36
37 /// Restore internal state after a graph mutation.
38 fn restore_state(&mut self, _state: StateBlob) {}
39
40 /// Human-readable node type name (for serialization/UI).
41 fn type_name(&self) -> &'static str;
42}
43
44/// Graph-level node record. Stored in the arena.
45pub struct NodeRecord {
46 /// The DSP implementation (boxed, allocated at node creation time — not in RT).
47 pub processor: Box<dyn DspNode>,
48 /// Input connections: each slot holds the NodeId of the upstream node.
49 pub inputs: [Option<NodeId>; MAX_INPUTS],
50 /// The buffer this node writes its output into.
51 pub output_buffer: BufferId,
52 /// Parameter block for this node.
53 pub params: ParamBlock,
54}
55
56impl NodeRecord {
57 pub fn new(processor: Box<dyn DspNode>, output_buffer: BufferId) -> Self {
58 Self {
59 processor,
60 inputs: [None; MAX_INPUTS],
61 output_buffer,
62 params: ParamBlock::new(),
63 }
64 }
65}