sound_flow 0.3.0

Execute graphs of functions in real time
Documentation
use sound_flow::Sockets;
use sound_flow::{Node, SocketDescription};

#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct Bar;

#[enum_delegate::implement_conversions]
#[derive(Debug)]
enum Data {
    Foo(Foo),
    Bar(Bar),
}

#[derive(Debug)]
struct FooGenerator;
impl Node for FooGenerator {
    type Config = ();
    type Context = ();
    type Data = Data;

    fn sockets(&self, config: &Self::Config) -> SocketDescription<Self::Data> {
        let mut s = SocketDescription::new();
        s.push_output_with_buffer(Foo);
        s
    }

    fn rt_process(&mut self, _context: &Self::Context, _sockets: Sockets<Self::Data>) {}
}

#[derive(Debug)]
struct FooProcessor;
impl Node for FooProcessor {
    type Config = ();
    type Context = ();
    type Data = Data;

    fn sockets(&self, config: &Self::Config) -> SocketDescription<Self::Data> {
        let mut s = SocketDescription::new();
        s.push_input::<Foo>();
        s.push_output_with_buffer(Foo);
        s
    }

    fn rt_process(&mut self, _context: &Self::Context, _sockets: Sockets<Self::Data>) {}
}

#[derive(Debug)]
struct BarGenerator;
impl Node for BarGenerator {
    type Config = ();
    type Context = ();
    type Data = Data;

    fn sockets(&self, config: &Self::Config) -> SocketDescription<Self::Data> {
        let mut s = SocketDescription::new();
        s.push_output_with_buffer(Bar);
        s
    }

    fn rt_process(&mut self, _context: &Self::Context, _sockets: Sockets<Self::Data>) {}
}

#[enum_delegate::implement(Node)]
#[derive(Debug)]
enum AnyNode {
    FooGenerator(FooGenerator),
    FooProcessor(FooProcessor),
    BarGenerator(BarGenerator),
}

#[cfg(test)]
mod tests {
    use super::*;
    use sound_flow::{BuildError, Connection, GraphBuilder, InvalidConnection};

    fn example_base_graph() -> GraphBuilder<i32, AnyNode> {
        let mut b: GraphBuilder<_, AnyNode> = GraphBuilder::new();
        b.add_node(0, FooGenerator).unwrap();
        b.add_node(1, FooProcessor).unwrap();
        b
    }

    #[test]
    fn valid_graph() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        b.build().unwrap();
    }

    #[test]
    fn invalid_from_node() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 404,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::InvalidConnection(_, InvalidConnection::InvalidFromNode)
        ))
    }

    #[test]
    fn invalid_from_socket() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 404,
            to_node: 1,
            to_input_socket: 0,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::InvalidConnection(_, InvalidConnection::InvalidFromOutputSocket)
        ))
    }

    #[test]
    fn invalid_to_node() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 404,
            to_input_socket: 0,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::InvalidConnection(_, InvalidConnection::InvalidToNode)
        ))
    }

    #[test]
    fn invalid_to_socket() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 404,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::InvalidConnection(_, InvalidConnection::InvalidToInputSocket)
        ))
    }

    #[test]
    fn type_mismatch() {
        let mut b = example_base_graph();
        b.add_node(2, BarGenerator).unwrap();
        b.add_connection(Connection {
            from_node: 2,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::TypeMismatch(_)
        ))
    }

    #[test]
    fn missing_input() {
        let b = example_base_graph();
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::MissingInput(_)
        ))
    }

    #[test]
    fn multiple_inputs() {
        let mut b = example_base_graph();
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        assert!(matches!(
            b.build().unwrap_err(),
            BuildError::MultipleInputs(_)
        ))
    }

    #[test]
    fn circular_dependencies() {
        let mut b: GraphBuilder<_, AnyNode> = GraphBuilder::new();
        b.add_node(0, FooProcessor).unwrap();
        b.add_node(1, FooProcessor).unwrap();

        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 1,
            to_input_socket: 0,
        });
        b.add_connection(Connection {
            from_node: 1,
            from_output_socket: 0,
            to_node: 0,
            to_input_socket: 0,
        });

        match b.build().unwrap_err() {
            BuildError::CircularDependencies(c) => {
                assert_eq!(c.len(), 2);
                assert!(c.contains(&Connection {
                    from_node: 0,
                    from_output_socket: 0,
                    to_node: 1,
                    to_input_socket: 0,
                }));
                assert!(c.contains(&Connection {
                    from_node: 1,
                    from_output_socket: 0,
                    to_node: 0,
                    to_input_socket: 0,
                }));
            }
            other => {
                panic!("Unexpected error {other:?}");
            }
        }
    }

    #[test]
    fn circular_dependencies_self_loop() {
        let mut b: GraphBuilder<_, AnyNode> = GraphBuilder::new();
        b.add_node(0, FooProcessor).unwrap();

        b.add_connection(Connection {
            from_node: 0,
            from_output_socket: 0,
            to_node: 0,
            to_input_socket: 0,
        });

        match b.build().unwrap_err() {
            BuildError::CircularDependencies(c) => {
                assert_eq!(c.len(), 1);
                assert!(c.contains(&Connection {
                    from_node: 0,
                    from_output_socket: 0,
                    to_node: 0,
                    to_input_socket: 0,
                }));
            }
            other => {
                panic!("Unexpected error {other:?}");
            }
        }
    }
}