Skip to main content

DspGraph

Struct DspGraph 

Source
pub struct DspGraph {
    pub arena: Arena<NodeRecord>,
    pub buffers: BufferPool,
    pub execution_order: Vec<NodeId>,
    pub levels: Vec<Vec<NodeId>>,
    pub output_node: Option<NodeId>,
    /* private fields */
}
Expand description

Directed Acyclic Graph (DAG) for DSP routing.

The graph owns the node arena and buffer pool. It maintains a topologically sorted execution order and BFS level structure for parallel processing.

§Structure

  • Arena: Generational arena storing node records
  • Buffer Pool: Pre-allocated audio buffers (no RT allocation)
  • Execution Order: Flat topologically sorted node list
  • BFS Levels: Nodes grouped by dependency depth for parallel execution

§Example

use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct Gain { gain: f32 }
impl DspNode for Gain {
    fn process(&mut self, inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _params: &mut ParamBlock, _sr: f32) {
        if let Some(input) = inputs[0] {
            for (i, out) in output.iter_mut().enumerate() {
                *out = input[i] * self.gain;
            }
        }
    }
    fn type_name(&self) -> &'static str { "Gain" }
}

let mut graph = DspGraph::new();
let gain_id = graph.add_node(Box::new(Gain { gain: 0.5 })).unwrap();
graph.set_output_node(gain_id);

Fields§

§arena: Arena<NodeRecord>§buffers: BufferPool§execution_order: Vec<NodeId>

Topologically sorted execution order. Rebuilt on structural mutations.

§levels: Vec<Vec<NodeId>>

BFS wave levels: each inner Vec contains nodes that can execute in parallel. Level[i] nodes all depend only on nodes in levels 0..i.

§output_node: Option<NodeId>

The node whose output buffer is sent to the DAC.

Implementations§

Source§

impl DspGraph

Source

pub fn new() -> Self

Creates a new empty DSP graph.

Initializes the arena, buffer pool, and execution structures with pre-allocated capacity for MAX_NODES nodes.

§Example
use aether_core::graph::DspGraph;

let graph = DspGraph::new();
assert_eq!(graph.execution_order.len(), 0);
Source

pub fn add_node(&mut self, processor: Box<dyn DspNode>) -> Option<NodeId>

Adds a node to the graph and returns its ID.

Acquires a buffer from the pool, inserts the node into the arena, and rebuilds the topological execution order.

§Arguments
  • processor - Boxed DSP node implementation
§Returns
  • Some(NodeId) - The node’s unique identifier
  • None - If arena is full or buffer pool exhausted
§Example
use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct Oscillator { frequency: f32, phase: f32 }
impl DspNode for Oscillator {
    fn process(&mut self, _inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _params: &mut ParamBlock, sr: f32) {
        let phase_inc = self.frequency / sr;
        for sample in output.iter_mut() {
            *sample = (self.phase * std::f32::consts::TAU).sin();
            self.phase = (self.phase + phase_inc).fract();
        }
    }
    fn type_name(&self) -> &'static str { "Oscillator" }
}

let mut graph = DspGraph::new();
let osc = Box::new(Oscillator { frequency: 440.0, phase: 0.0 });
let id = graph.add_node(osc).unwrap();
§See Also
Source

pub fn remove_node(&mut self, id: NodeId) -> bool

Removes a node from the graph and releases its buffer.

Removes the node from the arena, releases its output buffer back to the pool, and removes all edges connected to this node. Rebuilds the topological execution order.

§Arguments
  • id - Node ID to remove
§Returns
  • true - Node removed successfully
  • false - Node doesn’t exist (invalid ID or already removed)
§Example
use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct SimpleNode;
impl DspNode for SimpleNode {
    fn process(&mut self, _: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _: &mut ParamBlock, _: f32) {
        output.fill(0.0);
    }
    fn type_name(&self) -> &'static str { "SimpleNode" }
}

let mut graph = DspGraph::new();
let node_id = graph.add_node(Box::new(SimpleNode)).unwrap();

assert!(graph.remove_node(node_id)); // Returns true
assert!(!graph.remove_node(node_id)); // Returns false (already removed)
§See Also
Source

pub fn connect(&mut self, src: NodeId, dst: NodeId, slot: usize) -> bool

Connects the output of one node to the input of another.

Creates an edge in the DAG from src to dst, routing audio from the source node’s output buffer to the destination node’s input slot. Rebuilds the topological execution order to maintain DAG invariants.

§Arguments
  • src - Source node ID (output)
  • dst - Destination node ID (input)
  • slot - Input slot index on destination node (0 to MAX_INPUTS-1)
§Returns
  • true - Connection successful
  • false - One or both nodes don’t exist
§Example
use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct SimpleNode;
impl DspNode for SimpleNode {
    fn process(&mut self, _: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _: &mut ParamBlock, _: f32) {
        output.fill(0.0);
    }
    fn type_name(&self) -> &'static str { "SimpleNode" }
}

let mut graph = DspGraph::new();
let node_a = graph.add_node(Box::new(SimpleNode)).unwrap();
let node_b = graph.add_node(Box::new(SimpleNode)).unwrap();

// Connect node_a output → node_b input slot 0
graph.connect(node_a, node_b, 0);
§See Also
Source

pub fn disconnect(&mut self, dst: NodeId, slot: usize) -> bool

Disconnects an input slot on a destination node.

Removes the connection to the specified input slot, clearing the audio routing. The slot will receive silence until reconnected. Rebuilds the topological execution order.

§Arguments
  • dst - Destination node ID
  • slot - Input slot index to disconnect (0 to MAX_INPUTS-1)
§Returns
  • true - Disconnection successful
  • false - Node doesn’t exist or slot was already empty
§Example
use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct SimpleNode;
impl DspNode for SimpleNode {
    fn process(&mut self, _: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _: &mut ParamBlock, _: f32) {
        output.fill(0.0);
    }
    fn type_name(&self) -> &'static str { "SimpleNode" }
}

let mut graph = DspGraph::new();
let node_a = graph.add_node(Box::new(SimpleNode)).unwrap();
let node_b = graph.add_node(Box::new(SimpleNode)).unwrap();

graph.connect(node_a, node_b, 0);
graph.disconnect(node_b, 0); // Disconnect slot 0
§See Also
Source

pub fn set_output_node(&mut self, id: NodeId)

Sets the output node whose buffer is sent to the DAC.

Designates which node’s output buffer should be copied to the audio device output. Only one node can be the output node at a time.

§Arguments
  • id - Node ID to use as output
§Example
use aether_core::graph::DspGraph;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};

struct Oscillator { phase: f32 }
impl DspNode for Oscillator {
    fn process(&mut self, _: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
               output: &mut [f32; BUFFER_SIZE], _: &mut ParamBlock, _: f32) {
        for sample in output.iter_mut() {
            *sample = (self.phase * std::f32::consts::TAU).sin();
            self.phase = (self.phase + 0.01).fract();
        }
    }
    fn type_name(&self) -> &'static str { "Oscillator" }
}

let mut graph = DspGraph::new();
let osc_id = graph.add_node(Box::new(Oscillator { phase: 0.0 })).unwrap();
graph.set_output_node(osc_id);

Trait Implementations§

Source§

impl Default for DspGraph

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.