From 506e1fa0f66ad9a3d13fa3745ac41f57a2cf3e48 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 9 Oct 2015 20:50:05 +1100 Subject: [PATCH 1/4] Switch from using petgraph directly to the daggy crate. Massively improve API with walkers, access to the visit order, connection buffers and more. Update examples and REAMDE --- .travis.yml | 1 - Cargo.toml | 4 +- README.md | 19 +- examples/synth.rs | 32 +- examples/volume.rs | 19 +- src/graph.rs | 848 +++++++++++++++++++++++++++++---------------- src/lib.rs | 37 +- 7 files changed, 605 insertions(+), 355 deletions(-) diff --git a/.travis.yml b/.travis.yml index 345bc9a..ee079d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ notifications: - mitchell.nordine@gmail.com os: - linux - - osx env: - LD_LIBRARY_PATH: /usr/local/lib install: diff --git a/Cargo.toml b/Cargo.toml index cd779e7..90c9051 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dsp-chain" -version = "0.6.0" +version = "0.7.0" authors = [ "mitchmindtree ", "bvssvni ", @@ -21,6 +21,6 @@ name = "dsp" path = "./src/lib.rs" [dependencies] +daggy = "0.1.3" num = { version = "0.1.27", default-features = false } -petgraph = "0.1.11" sound_stream = "0.4.4" diff --git a/README.md b/README.md index 7adedb5..9596812 100644 --- a/README.md +++ b/README.md @@ -17,24 +17,21 @@ Here's what it looks like: ```Rust // Construct our dsp graph. -let mut dsp_graph = Graph::new(); +let mut graph = Graph::new(); // Construct our fancy Synth and add it to the graph! -let synth = dsp_graph.add_node(DspNode::Synth); +let synth = graph.add_node(DspNode::Synth); -// Construct a few oscillators, add them to the graph and connect them to the synth. -let oscillator_a = dsp_graph.add_node(DspNode::Oscillator(0.0, A5_HZ, 0.2)); -let oscillator_b = dsp_graph.add_node(DspNode::Oscillator(0.0, D5_HZ, 0.1)); -let oscillator_c = dsp_graph.add_node(DspNode::Oscillator(0.0, F5_HZ, 0.15)); -dsp_graph.add_input(oscillator_a, synth).unwrap(); -dsp_graph.add_input(oscillator_b, synth).unwrap(); -dsp_graph.add_input(oscillator_c, synth).unwrap(); +// Add a few oscillators as inputs to the synth. +graph.add_input(DspNode::Oscillator(0.0, A5_HZ, 0.2), synth); +graph.add_input(DspNode::Oscillator(0.0, D5_HZ, 0.1), synth); +graph.add_input(DspNode::Oscillator(0.0, F5_HZ, 0.15), synth); // Set the synth as the master node for the graph. -dsp_graph.set_master(Some(synth)); +graph.set_master(Some(synth)); // Request audio from our Graph. -dsp_graph.audio_requested(&mut buffer, settings); +graph.audio_requested(&mut buffer, settings); ``` Here are [two working examples](https://github.com/PistonDevelopers/dsp-chain/blob/master/examples) of using dsp-chain to create a very basic synth and an oscillating volume. diff --git a/examples/synth.rs b/examples/synth.rs index ac3c3c7..6949786 100644 --- a/examples/synth.rs +++ b/examples/synth.rs @@ -23,26 +23,23 @@ const F5_HZ: Frequency = 698.46; fn main() { // Construct our dsp graph. - let mut dsp_graph = Graph::new(); + let mut graph = Graph::new(); // Construct our fancy Synth and add it to the graph! - let synth = dsp_graph.add_node(DspNode::Synth); + let synth = graph.add_node(DspNode::Synth); - // Construct a few oscillators, add them to the graph and connect them to the synth. - let oscillator_a = dsp_graph.add_node(DspNode::Oscillator(0.0, A5_HZ, 0.2)); - let oscillator_b = dsp_graph.add_node(DspNode::Oscillator(0.0, D5_HZ, 0.1)); - let oscillator_c = dsp_graph.add_node(DspNode::Oscillator(0.0, F5_HZ, 0.15)); - dsp_graph.add_input(oscillator_a, synth).unwrap(); - dsp_graph.add_input(oscillator_b, synth).unwrap(); - dsp_graph.add_input(oscillator_c, synth).unwrap(); + // Connect a few oscillators to the synth. + let (_, oscillator_a) = graph.add_input(DspNode::Oscillator(0.0, A5_HZ, 0.2), synth); + graph.add_input(DspNode::Oscillator(0.0, D5_HZ, 0.1), synth); + graph.add_input(DspNode::Oscillator(0.0, F5_HZ, 0.15), synth); // If adding a connection between two nodes would create a cycle, Graph will return an Err. - if let Err(err) = dsp_graph.add_input(synth, oscillator_a) { - println!("Test for graph cycle error: {:?}", ::std::error::Error::description(&err)); + if let Err(err) = graph.add_connection(synth, oscillator_a) { + println!("Testing for cycle error: {:?}", ::std::error::Error::description(&err)); } // Set the synth as the master node for the graph. - dsp_graph.set_master(Some(synth)); + graph.set_master(Some(synth)); // We'll use this to count down from three seconds and then break from the loop. let mut timer: f64 = 3.0; @@ -50,13 +47,18 @@ fn main() { // The callback we'll use to pass to the Stream. It will request audio from our dsp_graph. let callback = Box::new(move |output: &mut[Output], settings: Settings, dt: f64, _: CallbackFlags| { Sample::zero_buffer(output); - dsp_graph.audio_requested(output, settings); + graph.audio_requested(output, settings); timer -= dt; - for input in dsp_graph.inputs_mut(synth) { - if let DspNode::Oscillator(_, ref mut pitch, _) = *input { + + // Traverse inputs or outputs of a node with the following pattern. + let mut inputs = graph.walk_inputs(synth); + while let Some(input_idx) = inputs.next_node(&mut graph) { + if let DspNode::Oscillator(_, ref mut pitch, _) = graph[input_idx] { + // Pitch down our oscillators for fun. *pitch -= 0.1; } } + if timer >= 0.0 { CallbackResult::Continue } else { CallbackResult::Complete } }); diff --git a/examples/volume.rs b/examples/volume.rs index e23a702..623ed6e 100644 --- a/examples/volume.rs +++ b/examples/volume.rs @@ -8,34 +8,31 @@ use dsp::{CallbackFlags, CallbackResult, Graph, Node, Sample, fn main() { // Construct our dsp graph. - let mut dsp_graph = Graph::new(); + let mut graph = Graph::new(); // Construct our fancy Synth and add it to the graph! - let synth = dsp_graph.add_node(DspNode::Synth(0.0)); + let synth = graph.add_node(DspNode::Synth(0.0)); - // Construct our volume node. - let volume = dsp_graph.add_node(DspNode::Volume(1.0)); - - // Plug the synth into our Volume node. - dsp_graph.add_input(synth, volume).unwrap(); + // Output our synth to a marvellous volume node. + let (_, volume) = graph.add_output(synth, DspNode::Volume(1.0)); // Set the synth as the master node for the graph. - dsp_graph.set_master(Some(volume)); + graph.set_master(Some(volume)); // We'll use this to count down from three seconds and then break from the loop. let mut timer: f64 = 0.0; - // The callback we'll use to pass to the Stream. It will request audio from our dsp_graph. + // The callback we'll use to pass to the Stream. It will request audio from our graph. let callback = Box::new(move |output: &mut[f32], settings: Settings, dt: f64, _: CallbackFlags| { // Zero the sample buffer. Sample::zero_buffer(output); // Request audio from the graph. - dsp_graph.audio_requested(output, settings); + graph.audio_requested(output, settings); // Oscillate the volume. - if let &mut DspNode::Volume(ref mut vol) = &mut dsp_graph[volume] { + if let &mut DspNode::Volume(ref mut vol) = &mut graph[volume] { *vol = (4.0 * timer as f32).sin(); } diff --git a/src/graph.rs b/src/graph.rs index 0b8bfff..957fe1e 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -6,273 +6,476 @@ //! +use daggy; use node::Node; -use petgraph as pg; use sound_stream::{Sample, Settings}; /// An alias for our Graph's Node Index. -pub type NodeIndex = pg::graph::NodeIndex; +pub type NodeIndex = daggy::NodeIndex; /// An alias for our Graph's Edge Index. -pub type EdgeIndex = pg::graph::EdgeIndex; +pub type EdgeIndex = daggy::EdgeIndex; -/// An alias for the petgraph Graph used within our DSP Graph. -pub type PetGraph = pg::Graph, Connection, pg::Directed>; +/// An alias for the iterator yielding mutable access to all node weights. +pub type NodesMut<'a, N> = daggy::NodeWeightsMut<'a, N, usize>; -/// An alias representing neighboring nodes. -pub type PgNeighbors<'a, S> = pg::graph::Neighbors<'a, Connection, u32>; +/// Read only access to a **Graph**'s internal node array. +pub type RawNodes<'a, N> = daggy::RawNodes<'a, N, usize>; +/// Read only access to a **Graph**'s internal edge array. +pub type RawEdges<'a, S> = daggy::RawEdges<'a, Connection, usize>; + +/// An alias for the **Dag** used within our **Graph**. +pub type Dag = daggy::Dag, usize>; + +/// An alias for the **PetGraph** used by our **Graph**'s internal **Dag**. +pub type PetGraph = daggy::PetGraph, usize>; /// A directed, acyclic DSP graph. +/// +/// Designed for easily and safely setting up high performance audio signal generating, processing +/// and mixing. Useful for both simple and highly complex signal graphs. +/// +/// There are a variety of use cases for `Graph`. +/// - Designing effects. +/// - Creating an audio mixer. +/// - Making a sampler. +/// - Writing a DSP backend for a DAW. +/// - Any kind of modular audio synthesis or processing. +/// +/// `Graph` is a wrapper around [daggy](http://mitchmindtree.github.io/daggy/daggy/)'s +/// [`Dag`](http://mitchmindtree.github.io/daggy/daggy/struct.Dag.html) type - an abstraction for +/// working with directed acyclic graph's where high performance node adding and accessing is +/// required. +/// +/// An input -> output connection in this `Graph` is represented as a parent -> child connection +/// within the internal `Dag`. The following terms are all equivalent: +/// - input -> output +/// - src -> dest +/// - parent -> child +/// +/// Audio can be requested from any node in the **Graph** using the +/// [`audio_requested_from`](./struct.Graph.html#method.audio_requested_from) method. +/// +/// When [`audio_requested`](../node/trait.Node.html#method.audio_requested) is called on the +/// **Graph**, audio will be requested from the node specified by the index at `maybe_master`. If +/// `maybe_master` is `None`, audio will be requested from the first, input-only node found - that +/// is, the first node that is found with only input connections and no outputs. +/// +/// **NodeIndex** is a type that acts as a reference to a node, while **EdgeIndex** is a type that +/// acts as a reference to an edge (which in this case describes a *src -> dest* **Connection** +/// between two nodes). It should be noted that these are only stable across certain operations. +/// **Removing indices may shift other indices of the same type!** Adding nodes or edges to the +/// **Graph** keeps all indices stable, but removing a node or edge will force the last node/edge +/// to shift its index to take its place. +/// +/// **Graph** also offers methods for accessing its underlying **Dag** or **PetGraph**. #[derive(Clone, Debug)] pub struct Graph { - graph: PetGraph, + dag: Dag, + /// The order in which audio will be requested from each node. visit_order: Vec, + /// The node from which audio will be requested upon a call to `Node::audio_requested`. maybe_master: Option, + /// A buffer to re-use when mixing the dry and wet signals when audio is requested. dry_buffer: Vec, } - -/// A Dsp object and its sample buffer. -#[derive(Clone, Debug)] -struct Slot { - /// User defined DspNode type. - node: N, -} - -/// Describes a connection between two Nodes within the Graph. +/// Describes a connection between two Nodes within the Graph: *input -> connection -> output*. +/// +/// **Graph**'s API only allows for read-only access to **Connection**s, so you can be sure that +/// their buffers always represent the last samples rendered by their input node. #[derive(Clone, Debug)] -struct Connection { - buffer: Vec, +pub struct Connection { + /// The buffer used to pass audio between nodes. + /// + /// After `Graph::audio_requested_from` is called, this buffer will contain the audio rendered + /// by the **Connection**'s input node. + pub buffer: Vec, } -/// A type for representing an error on the occasion -/// that a connection would create a cyclic graph. +/// The error returned when adding an edge that would create a cycle. #[derive(Copy, Clone, Debug)] pub struct WouldCycle; -/// An iterator over references to the inputs of a Graph node. -pub type Inputs<'a, S, N> = Neighbors<'a, S, N>; -/// An iterator over references to the inputs of a Graph node along with their indices. -pub type InputsWithIndices<'a, S, N> = NeighborsWithIndices<'a, S, N>; -/// An iterator over mutable references to the inputs of a Graph node. -pub type InputsMut<'a, S, N> = NeighborsMut<'a, S, N>; -/// An iterator over mutable references to the inputs of a Graph node along with their indices. -pub type InputsMutWithIndices<'a, S, N> = NeighborsMutWithIndices<'a, S, N>; -/// An iterator over references to the outputs of a Graph node. -pub type Outputs<'a, S, N> = Neighbors<'a, S, N>; -/// An iterator over references to the outputs of a Graph node along with their indices. -pub type OutputsWithIndices<'a, S, N> = NeighborsWithIndices<'a, S, N>; -/// An iterator over mutable references to the outputs of a Graph node. -pub type OutputsMut<'a, S, N> = NeighborsMut<'a, S, N>; -/// An iterator over mutable references to the outputs of a Graph node alonw with their indices. -pub type OutputsMutWithIndices<'a, S, N> = NeighborsMutWithIndices<'a, S, N>; - -/// An iterator over references to the neighbors of a Graph node. -pub struct Neighbors<'a, S: 'a, N: 'a> { - graph: &'a PetGraph, - neighbors: PgNeighbors<'a, S>, +/// An iterator yielding indices to nodes that are inputs to some node. +pub type Inputs<'a, S> = daggy::Parents<'a, Connection, usize>; + +/// A walker object for walking over nodes that are inputs to some node. +pub struct WalkInputs { + walk_parents: daggy::WalkParents, } -/// An iterator over references to the neighbors of a Graph node. -pub struct NeighborsWithIndices<'a, S: 'a, N: 'a> { - graph: &'a PetGraph, - neighbors: PgNeighbors<'a, S>, +/// An iterator yielding indices to nodes that are outputs to some node. +pub type Outputs<'a, S> = daggy::Children<'a, Connection, usize>; + +/// A walker object for walking over nodes that are outputs to some node. +pub struct WalkOutputs { + walk_children: daggy::WalkChildren, +} + +/// An iterator yielding a **Graph**'s node indices in the order in which they will be visited when +/// audio is requested from the **Graph**. +pub struct VisitOrder<'a> { + indices: ::std::slice::Iter<'a, NodeIndex>, } -/// An iterator over mutable references to the neighbors of a Graph node. -pub struct NeighborsMut<'a, S: 'a, N: 'a> { - graph: &'a mut PetGraph, - neighbors: PgNeighbors<'a, S>, +/// A walker type for walking over a **Graph**'s nodes in the order in which they will visited when +/// audio is requested from the **Graph**. +pub struct WalkVisitOrder { + current_visit_order_idx: usize, } -/// An iterator over mutable references to the neighbors of a Graph node. -pub struct NeighborsMutWithIndices<'a, S: 'a, N: 'a> { - graph: &'a mut PetGraph, - neighbors: PgNeighbors<'a, S>, +/// A walker type for walking over a **Graph**'s nodes in the order in which they will visited when +/// audio is requested from the **Graph**. +pub struct WalkVisitOrderReverse { + current_visit_order_idx: usize, } + impl Graph where S: Sample, N: Node { /// Constructor for a new dsp Graph. - pub fn new() -> Graph { - let graph = pg::Graph::new(); + /// + /// [`with_capacity`](./struct.Graph.html#method.with_capacity) is recommended if you have a + /// rough idea of the number of nodes, connections and samples per buffer upon the **Graph**'s + /// instantiation. + pub fn new() -> Self { + let dag = daggy::Dag::new(); Graph { - graph: graph, + dag: dag, visit_order: Vec::new(), - maybe_master: None, dry_buffer: Vec::new(), + maybe_master: None, } } - /// Set the master node for the Graph. - /// Graph will check to see if a node exists for the given index before assigning. - /// Graph's Dsp implementation will use the dsp at the master node when the audio_requested - /// method is called. - pub fn set_master(&mut self, maybe_index: Option) { - let maybe_index = match maybe_index { - Some(index) => match self.graph.node_weight(index) { - Some(_) => Some(index), - None => None, - }, - None => None, - }; - self.maybe_master = maybe_index; - self.prepare_visit_order(); + /// Constructor for a new dsp Graph with some minimum capacity. + /// - **nodes** is the capacity for the underlying **Dag**'s node `Vec`. + /// - **connections** is the capacity for the underlying **Dag**'s edge `Vec`. + /// - **samples_per_buffer** is the capacity for the **Graph**'s `dry_buffer`, which is used + /// for mixing the dry and wet signals when `Node::audio_requested` is called. + pub fn with_capacity(nodes: usize, connections: usize, samples_per_buffer: usize) -> Self { + Graph { + dag: daggy::Dag::with_capacity(nodes, connections), + visit_order: Vec::with_capacity(nodes), + dry_buffer: Vec::with_capacity(samples_per_buffer), + maybe_master: None, + } + } + + /// A reference to the underlying **Dag**. + pub fn dag(&self) -> &Dag { + &self.dag + } + + /// Takes ownership of the **Graph** and returns the underlying **Dag**. + pub fn into_dag(self) -> Dag { + let Graph { dag, .. } = self; + dag + } + + /// A reference to the internal **Dag**'s underlying **PetGraph**. + pub fn pet_graph(&self) -> &PetGraph { + self.dag.graph() + } + + /// Takes ownership of the **Graph** and returns the internal **Dag**'s underlying **PetGraph**. + pub fn into_pet_graph(self) -> PetGraph { + self.into_dag().into_graph() + } + + /// The total number of nodes in the **Graph**. + pub fn node_count(&self) -> usize { + self.dag.node_count() + } + + /// The total number of connections in the **Graph**. + pub fn connection_count(&self) -> usize { + self.dag.edge_count() } - /// Return the master index if there is one. + /// Return the **Graph**'s master index if there is one. + /// + /// **Graph**'s **Node** implementation will request audio from the node at `maybe_master` + /// when the `Node::audio_requested` method is called. pub fn master_index(&self) -> Option { self.maybe_master } + /// Set the master node for the **Graph**. + /// + /// **Graph** will check to see if a node exists for the given index before assigning. + /// + /// **Graph**'s **Node** implementation will request audio from the node at `maybe_master` + /// when the `Node::audio_requested` method is called. + pub fn set_master(&mut self, maybe_index: Option) { + let maybe_index = maybe_index.and_then(|index| { + if self.dag.node_weight(index).is_some() { Some(index) } else { None } + }); + self.maybe_master = maybe_index; + self.prepare_visit_order(); + } + /// Add a node to the dsp graph. + /// + /// This computes in **O(1)** time. pub fn add_node(&mut self, node: N) -> NodeIndex { - let idx = self.graph.add_node(Slot { node: node }); - self.prepare_visit_order(); + let idx = self.dag.add_node(node); idx } - /// Prepare the visit order for the graph in its current state. + /// A reference to the node at the given index (or `None` if it doesn't exist). + pub fn node(&self, node: NodeIndex) -> Option<&N> { + self.dag.node_weight(node) + } + + /// A mutable reference to the node at the given index (or `None` if it doesn't exist). + pub fn node_mut(&mut self, node: NodeIndex) -> Option<&mut N> { + self.dag.node_weight_mut(node) + } + + /// Read only access to the internal node array. + pub fn raw_nodes(&self) -> RawNodes { + self.dag.raw_nodes() + } + + /// An iterator yielding mutable access to all nodes. + /// + /// The order in which nodes are yielded matches the order of their indices. + pub fn nodes_mut(&mut self) -> NodesMut { + self.dag.node_weights_mut() + } + + /// A reference to the connection at the given index (or `None` if it doesn't exist). + pub fn connection(&self, edge: EdgeIndex) -> Option<&Connection> { + self.dag.edge_weight(edge) + } + + /// Read only access to the internal edge array. + pub fn raw_edges(&self) -> RawEdges { + self.dag.raw_edges() + } + + /// Index the **Graph** by two `NodeIndex`s at once. /// - /// When audio is requested from the graph, we need to iterate through all nodes so that all - /// child nodes are visited before their parents. To do this, we can use petgraph's toposort - /// algorithm to return the topological order of our graph. - fn prepare_visit_order(&mut self) { - self.visit_order = pg::algo::toposort(&self.graph); + /// **Panics** if the indices are equal or if they are out of bounds. + pub fn index_twice_mut(&mut self, a: NodeIndex, b: NodeIndex) -> (&mut N, &mut N) { + self.dag.index_twice_mut(a, b) } /// Remove a node from the dsp graph. - /// Reset maybe_master to None if the index matches the current master index. + /// + /// Resets the master to None if the index matches the current master index. + /// + /// **Note:** This method may shift (and in turn invalidate) previously returned node indices! + /// + /// **Graph** will re-prepare its visit order if some node was removed. pub fn remove_node(&mut self, idx: NodeIndex) -> Option { - if let Some(master_idx) = self.maybe_master { - if idx == master_idx { - self.maybe_master = None; - } + if self.maybe_master == Some(idx) { + self.maybe_master = None; } - let maybe_removed = self.graph.remove_node(idx).map(|slot| { - let Slot { node, .. } = slot; + self.dag.remove_node(idx).map(|node| { + self.prepare_visit_order(); node - }); - self.prepare_visit_order(); - maybe_removed + }) } - /// Adds a connection from `a` to `b`. That is, `a` is now an input to `b`. + /// Adds an edge from `src` to `dest`. That is, `src` is now an input to `dest`. + /// /// Returns an error instead if the input would create a cycle in the graph. - pub fn add_input(&mut self, a: NodeIndex, b: NodeIndex) -> Result<(), WouldCycle> { + /// + /// **Graph** will re-prepare its visit order if some connection was successfully added. + /// + /// If you're using `add_node` followed by this method, consider using + /// [`add_input`](./struct.Graph.html#method.add_input) or + /// [`add_output`](./struct.Graph.html#method.add_output) instead for greater performance. + /// This is because when adding the node and edge simultaneously, we don't have to check + /// whether adding the edge would create a cycle. + /// + /// **Panics** if there is no node for either `src` or `dest`. + /// + /// **Panics** if the Graph is at the maximum number of edges for its index. + pub fn add_connection(&mut self, src: NodeIndex, dest: NodeIndex) + -> Result + { + self.dag.add_edge(src, dest, Connection { buffer: Vec::new() }) + .map(|edge| { self.prepare_visit_order(); edge }) + .map_err(|_| WouldCycle) + } - // Add the input connection between the two nodes with a Buffer the size of the output's. - let edge = self.graph.add_edge(a, b, Connection { buffer: Vec::new() }); + /// Find and return the index to the edge that describes the connection where `src` is an input + /// to `dest`. + /// + /// Computes in **O(e')** time, where **e'** is the number of edges connected to the nodes `a` + /// and `b`. + pub fn find_connection(&self, src: NodeIndex, dest: NodeIndex) -> Option { + self.dag.find_edge(src, dest) + } - // If the connection would create a cycle, remove the node and return an error. - if pg::algo::is_cyclic_directed(&self.graph) { - self.graph.remove_edge(edge); - Err(WouldCycle) - } - // Otherwise the method was successful. - else { + /// Remove the connection described by the edge at the given index. + /// + /// Returns true if an edge was removed, returns false if there was no edge at the given index. + /// + /// Re-prepares the visit order if some edge was removed. + pub fn remove_edge(&mut self, edge: EdgeIndex) -> bool { + if self.dag.remove_edge(edge).is_some() { self.prepare_visit_order(); - Ok(()) + true + } else { + false } } - /// Remove the input between the nodes at the given indices if there is one. - pub fn remove_input(&mut self, a: NodeIndex, b: NodeIndex) { - if let Some(edge) = self.graph.find_edge(a, b) { - self.graph.remove_edge(edge); - } else if let Some(edge) = self.graph.find_edge(b, a) { - self.graph.remove_edge(edge); + /// Find and remove any connection between a and b if there is one, whether it is "a -> b" or + /// "b -> a". We know that their may only be one edge as our API does not allow for creating a + /// cyclic graph. + /// + /// Returns true if an edge was removed, returns false if there was no edge at the given index. + /// + /// Graph will re-prepare its visit order if some edge was removed. + /// + /// Note: If you have an index to the edge you want to remove, + /// [`remove_edge`](./struct.Graph.html#method.remove_edge) is a more performant option. + pub fn remove_connection(&mut self, a: NodeIndex, b: NodeIndex) -> bool { + match self.dag.find_edge(a, b).or_else(|| self.dag.find_edge(b, a)) { + Some(edge) => self.remove_edge(edge), + None => false, } - self.prepare_visit_order(); } - /// Returns an iterator over references to each neighboring node in the given direction. - fn neighbors<'a>(&'a self, - idx: NodeIndex, - direction: pg::EdgeDirection) -> Neighbors<'a, S, N> { - Neighbors { - graph: &self.graph, - neighbors: self.graph.neighbors_directed(idx, direction), - } + /// Add a new node weight to the graph as an input to the wait at the given `dest` node index. + /// + /// *src -> new edge -> dest* + /// + /// Returns an index to both the new `src` node and the edge that represents the new connection + /// between it and the node at `dest`. + /// + /// Computes in **O(1)** time. + /// + /// **Panics** if there is no node for the given `dest` index. + /// + /// **Panics** if the Graph is at the maximum number of edges for its index. + pub fn add_input(&mut self, src: N, dest: NodeIndex) -> (EdgeIndex, NodeIndex) { + self.dag.add_parent(dest, Connection { buffer: Vec::new() }, src) } - /// Returns an iterator over mutable references to each neighboring node in the given direction. - fn neighbors_mut<'a>(&'a mut self, - idx: NodeIndex, - direction: pg::EdgeDirection) -> NeighborsMut<'a, S, N> { - let graph = &mut self.graph as *mut PetGraph; - // Here we use `unsafe` to allow for aliasing references to the Graph. - // We allow aliasing in this case because we know that it is impossible - // for a user to use NeighborsMut unsafely as its fields are private and - // it only exposes its Iterator implementation, which we know is safe. - // (see the Iterator implementation below the Graph implementation). - NeighborsMut { - graph: unsafe { ::std::mem::transmute(graph) }, - neighbors: unsafe { (*graph).neighbors_directed(idx, direction) }, - } + /// Add a new node weight to the graph as an output to the wait at the given `src` node index. + /// + /// *src -> new edge -> dest* + /// + /// Returns an index to both the new `dest` node and the edge that represents the new connection + /// between it and the node at `src`. + /// + /// Computes in **O(1)** time. + /// + /// **Panics** if there is no node for the given `dest` index. + /// + /// **Panics** if the Graph is at the maximum number of edges for its index. + pub fn add_output(&mut self, src: NodeIndex, dest: N) -> (EdgeIndex, NodeIndex) { + self.dag.add_child(src, Connection { buffer: Vec::new() }, dest) } - /// Returns an iterator over references to each input node. - pub fn inputs<'a>(&'a self, idx: NodeIndex) -> Inputs<'a, S, N> { - self.neighbors(idx, pg::Incoming) + /// An iterator yielding indices to the nodes that are inputs to the node at the given index. + /// + /// Produces an empty iterator if there is no node at the given index. + pub fn inputs(&self, idx: NodeIndex) -> Inputs { + self.dag.parents(idx) } - /// Returns an iterator over mutable references to each input node. - pub fn inputs_mut<'a>(&'a mut self, idx: NodeIndex) -> InputsMut<'a, S, N> { - self.neighbors_mut(idx, pg::Incoming) + /// A "walker" object that may be used to step through the inputs of the given node. + /// + /// Unlike the `Inputs` type, `WalkInputs` does not borrow the `Graph`. + pub fn walk_inputs(&self, idx: NodeIndex) -> WalkInputs { + WalkInputs { walk_parents: self.dag.walk_parents(idx) } } - /// Returns an iterator over references to each output node. - pub fn outputs<'a>(&'a self, idx: NodeIndex) -> Outputs<'a, S, N> { - self.neighbors(idx, pg::Outgoing) + /// An iterator yielding indices to the nodes that are outputs to the node at the given index. + /// + /// Produces an empty iterator if there is no node at the given index. + pub fn outputs(&self, idx: NodeIndex) -> Outputs { + self.dag.children(idx) + } + + /// A "walker" object that may be used to step through the outputs of the given node. + /// + /// Unlike the `Outputs` type, `WalkOutputs` does not borrow the **Graph**. + pub fn walk_outputs(&self, idx: NodeIndex) -> WalkOutputs { + WalkOutputs { walk_children: self.dag.walk_children(idx) } } - /// Returns an iterator over mutable references to each output node. - pub fn outputs_mut<'a>(&'a mut self, idx: NodeIndex) -> OutputsMut<'a, S, N> { - self.neighbors_mut(idx, pg::Outgoing) + /// An iterator yielding node indices in the order in which they will be visited when audio is + /// requested from the **Graph**. + pub fn visit_order(&self) -> VisitOrder { + VisitOrder { indices: self.visit_order.iter() } + } + + /// A "walker" type that may be used to step through all node indices in the order in which + /// they will be visited when audio is requested from the **Graph**. + /// + /// Unlike the VisitOrder type, WalkVisitOrder does not borrow the **Graph**. + pub fn walk_visit_order(&self) -> WalkVisitOrder { + WalkVisitOrder { current_visit_order_idx: 0 } + } + + /// A "walker" type that may be used to step through all node indices in the order in which + /// they will be visited when audio is requested from the **Graph**. + /// + /// Unlike the VisitOrder type, WalkVisitOrder does not borrow the **Graph**. + pub fn walk_visit_order_rev(&self) -> WalkVisitOrderReverse { + WalkVisitOrderReverse { current_visit_order_idx: self.visit_order.len() } } /// Remove all incoming connections to the node at the given index. + /// /// Return the number of connections removed. - pub fn remove_all_inputs(&mut self, idx: NodeIndex) -> usize { - let input_indices: Vec<_> = self.graph.neighbors_directed(idx, pg::Incoming).collect(); - let num = input_indices.len(); - for input_idx in input_indices { - self.remove_input(input_idx, idx); + pub fn remove_all_input_connections(&mut self, idx: NodeIndex) -> usize { + let mut inputs = self.walk_inputs(idx); + let mut num = 0; + while let Some(connection) = inputs.next_connection(&self) { + self.remove_edge(connection); + num += 1; } num } /// Remove all outgoing connections from the node at the given index. + /// /// Return the number of connections removed. - pub fn remove_all_outputs(&mut self, idx: NodeIndex) -> usize { - let output_indices: Vec<_> = self.graph.neighbors_directed(idx, pg::Outgoing).collect(); - let num = output_indices.len(); - for output_idx in output_indices { - self.remove_input(output_idx, idx); + pub fn remove_all_output_connections(&mut self, idx: NodeIndex) -> usize { + let mut outputs = self.walk_outputs(idx); + let mut num = 0; + while let Some(connection) = outputs.next_connection(&self) { + self.remove_edge(connection); + num += 1; } num } - /// Clear all dsp nodes that have no inputs and that are not inputs to any other nodes. - pub fn clear_disconnected(&mut self) { - let no_incoming: Vec<_> = self.graph.without_edges(pg::Incoming).collect(); - let no_outgoing: Vec<_> = self.graph.without_edges(pg::Outgoing).collect(); - let indices_for_removal = no_incoming.into_iter() - .filter(|incoming| no_outgoing.iter().any(|outgoing| outgoing == incoming)); - for idx in indices_for_removal { - if let Some(master_idx) = self.maybe_master { - if master_idx == idx { + /// Clear all dsp nodes that have no inputs or outputs. + /// + /// Returns the number of nodes removed. + /// + /// Note: this may shift (and in turn invalidate) previously returned node and edge indices! + pub fn clear_disconnected(&mut self) -> usize { + let mut num_removed = 0; + for i in 0..self.dag.node_count() { + let idx = NodeIndex::new(i); + let num_inputs = self.inputs(idx).count(); + let num_outputs = self.outputs(idx).count(); + if num_inputs == 0 && num_outputs == 0 { + if self.maybe_master == Some(idx) { self.maybe_master = None; } + self.dag.remove_node(idx); + num_removed += 1; } - self.graph.remove_node(idx); } + num_removed } /// Clear all dsp nodes. pub fn clear(&mut self) { - self.graph.clear(); + self.dag.clear(); self.visit_order.clear(); self.maybe_master = None; } @@ -285,67 +488,61 @@ impl Graph where S: Sample, N: Node { resize_buffer_to(&mut self.dry_buffer, target_len); // Initialise all connection buffers. - for connection in self.graph.edge_weights_mut() { + for connection in self.dag.edge_weights_mut() { resize_buffer_to(&mut connection.buffer, target_len); } } -} - - -impl ::std::ops::Index for Graph { - type Output = N; - #[inline] - fn index<'a>(&'a self, index: NodeIndex) -> &'a N { - &self.graph[index].node - } -} - -impl ::std::ops::IndexMut for Graph { - #[inline] - fn index_mut(&mut self, index: NodeIndex) -> &mut N { - &mut self.graph[index].node - } -} + /// Request audio from the node at the given index. + /// + /// **Panics** if there is no node for the given index. + pub fn audio_requested_from(&mut self, + output_node: NodeIndex, + output: &mut [S], + settings: Settings) + { + // We can only go on if a node actually exists for the given index. + if self.node(output_node).is_none() { + panic!("No node for the given index"); + } - -impl Node for Graph where - S: Sample, - N: Node, -{ - fn audio_requested(&mut self, output: &mut [S], settings: Settings) { - let Graph { ref visit_order, ref mut graph, ref mut dry_buffer, .. } = *self; + let buffer_size = output.len(); // Ensure the dry_buffer is the same length as the output buffer. - if dry_buffer.len() != output.len() { - resize_buffer_to(dry_buffer, output.len()); + if self.dry_buffer.len() != buffer_size { + resize_buffer_to(&mut self.dry_buffer, buffer_size); } - for &node_idx in visit_order.iter() { + let mut visit_order = self.walk_visit_order(); + while let Some(node_idx) = visit_order.next(self) { - // Zero the buffers, ready to sum the inputs. - for (sample, dry_sample) in output.iter_mut().zip(dry_buffer.iter_mut()) { - *sample = S::zero(); - *dry_sample = S::zero(); + // Zero the buffers, ready to sum the inputs of the current node. + for i in 0..buffer_size { + output[i] = S::zero(); + self.dry_buffer[i] = S::zero(); } - // Walk over each of the incoming connections to sum their buffers to the output. - let mut incoming_edges = graph.walk_edges_directed(node_idx, pg::Incoming); - while let Some(edge) = incoming_edges.next(graph) { - let connection = &graph[edge]; - for (sample, in_sample) in output.iter_mut().zip(connection.buffer.iter()) { - *sample = *sample + *in_sample; + // Walk over each of the input connections to sum their buffers to the output. + let mut inputs = self.walk_inputs(node_idx); + while let Some(connection_idx) = inputs.next_connection(self) { + let connection = &self[connection_idx]; + for i in 0..buffer_size { + // We can be certain that `connection`'s buffer is the same size as the + // `output` buffer as all connections are visited from their input nodes + // (towards the end of the visit_order while loop) before being visited here + // by their output nodes. + output[i] = output[i] + connection.buffer[i]; } } - // Render the audio with the current node and sum the dry and wet signals. - { - let node = &mut graph[node_idx].node; + // Store the dry signal in the dry buffer for later summing. + for i in 0..buffer_size { + self.dry_buffer[i] = output[i]; + } - // Store the dry signal in the dry buffer for later summing. - for (dry_sample, output_sample) in dry_buffer.iter_mut().zip(output.iter()) { - *dry_sample = *output_sample; - } + // Render the audio with the current node and sum the dry and wet signals. + let (dry, wet) = { + let node = &mut self[node_idx]; // Render our `output` buffer with the current node. // The `output` buffer is now representative of a fully wet signal. @@ -353,17 +550,23 @@ impl Node for Graph where let dry = node.dry(); let wet = node.wet(); + (dry, wet) + }; - // Combine the dry and wet signals. - for (output_sample, dry_sample) in output.iter_mut().zip(dry_buffer.iter()) { - *output_sample = output_sample.mul_amp(wet) + dry_sample.mul_amp(dry); - } + // Combine the dry and wet signals. + for i in 0..buffer_size { + output[i] = output[i].mul_amp(wet) + self.dry_buffer[i].mul_amp(dry); + } + + // If we've reached our output node, we're done! + if node_idx == output_node { + return; } // Walk over each of the outgoing connections and write the rendered output to them. - let mut outgoing_edges = graph.walk_edges_directed(node_idx, pg::Outgoing); - while let Some(edge) = outgoing_edges.next(graph) { - let connection = &mut graph[edge]; + let mut outputs = self.walk_outputs(node_idx); + while let Some(connection_idx) = outputs.next_connection(self) { + let connection = &mut self.dag[connection_idx]; // Ensure the buffer matches the target length. if connection.buffer.len() != output.len() { @@ -371,129 +574,170 @@ impl Node for Graph where } // Write the rendered audio to the outgoing connection buffers. - for (out_sample, rendered) in connection.buffer.iter_mut().zip(output.iter()) { - *out_sample = *rendered; + for i in 0..buffer_size { + connection.buffer[i] = output[i]; } } - } } + + /// Prepare the visit order for the graph in its current state. + /// + /// This is called whenever the **Graph** is mutated in some way that may change the flow of + /// its edges. + /// + /// When audio is requested from the graph, we need to iterate through all nodes so that all + /// child nodes are visited before their parents. To do this, we can use petgraph's toposort + /// algorithm to return the topological order of our graph. + /// + /// The user should never have to worry about this, thus the method is private. + fn prepare_visit_order(&mut self) { + self.visit_order = daggy::petgraph::algo::toposort(self.dag.graph()); + } + } -/// Resize the given buffer to the given target length. -fn resize_buffer_to(buffer: &mut Vec, target_len: usize) where S: Sample { - let len = buffer.len(); - if len < target_len { - buffer.extend((len..target_len).map(|_| S::zero())) - } else if len > target_len { - buffer.truncate(target_len); +impl ::std::ops::Index for Graph { + type Output = N; + #[inline] + fn index<'a>(&'a self, index: NodeIndex) -> &'a N { + &self.dag[index] } } - -impl ::std::fmt::Display for WouldCycle { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { - writeln!(f, "{:?}", self) +impl ::std::ops::IndexMut for Graph { + #[inline] + fn index_mut(&mut self, index: NodeIndex) -> &mut N { + &mut self.dag[index] } } -impl ::std::error::Error for WouldCycle { - fn description(&self) -> &str { - "Adding this input would have caused the graph to cycle!" +impl ::std::ops::Index for Graph { + type Output = Connection; + #[inline] + fn index<'a>(&'a self, index: EdgeIndex) -> &'a Connection { + &self.dag[index] } } -impl<'a, S, N> Neighbors<'a, S, N> { - /// Return an adaptor that will also return the neighor's NodeIndex. - #[inline] - pub fn with_indices(self) -> NeighborsWithIndices<'a, S, N> { - let Neighbors { graph, neighbors } = self; - NeighborsWithIndices { - graph: graph, - neighbors: neighbors, +impl Node for Graph where + S: Sample, + N: Node, +{ + fn audio_requested(&mut self, output: &mut [S], settings: Settings) { + match self.maybe_master { + Some(master) => self.audio_requested_from(master, output, settings), + None => { + // If there is no set master node, we'll start from the back of the visit_order and + // use the first node that has no output connections. + let mut visit_order_rev = self.walk_visit_order_rev(); + while let Some(node) = visit_order_rev.next(self) { + if self.inputs(node).count() == 0 { + self.audio_requested_from(node, output, settings); + return; + } + } + }, } } } -impl<'a, S, N> NeighborsMut <'a, S, N> { - /// Return an adaptor that will also return the neighor's NodeIndex. - #[inline] - pub fn with_indices(self) -> NeighborsMutWithIndices<'a, S, N> { - let NeighborsMut { graph, neighbors } = self; - NeighborsMutWithIndices { - graph: graph, - neighbors: neighbors, - } + +impl WalkInputs { + + /// The next (connection, node) input pair to some node in our walk for the given **Graph**. + pub fn next(&mut self, graph: &Graph) -> Option<(EdgeIndex, NodeIndex)> { + self.walk_parents.next_parent(&graph.dag) } + + /// The next input connection to some node in our walk for the given **Graph**. + pub fn next_connection(&mut self, graph: &Graph) -> Option { + self.walk_parents.next(&graph.dag) + } + + /// The next input node to some node in our walk for the given **Graph**. + pub fn next_node(&mut self, graph: &Graph) -> Option { + self.walk_parents.next_parent(&graph.dag).map(|(_, node)| node) + } + } -impl<'a, S, N> Iterator for Neighbors<'a, S, N> { - type Item = &'a N; - #[inline] - fn next(&mut self) -> Option<&'a N> { - match self.neighbors.next() { - Some(idx) => Some(&self.graph[idx].node), - None => None, - } +impl WalkOutputs { + + /// The next (connection, node) output pair from some node in our walk for the given **Graph**. + pub fn next(&mut self, graph: &Graph) -> Option<(EdgeIndex, NodeIndex)> { + self.walk_children.next_child(&graph.dag) } + + /// The next output connection from some node in our walk for the given **Graph**. + pub fn next_connection(&mut self, graph: &Graph) -> Option { + self.walk_children.next(&graph.dag) + } + + /// The next output node from some node in our walk for the given **Graph**. + pub fn next_node(&mut self, graph: &Graph) -> Option { + self.walk_children.next_child(&graph.dag).map(|(_, node)| node) + } + } -impl<'a, S, N> Iterator for NeighborsWithIndices<'a, S, N> { - type Item = (&'a N, NodeIndex); - #[inline] - fn next(&mut self) -> Option<(&'a N, NodeIndex)> { - match self.neighbors.next() { - Some(idx) => Some((&self.graph[idx].node, idx)), - None => None, - } + +impl<'a> Iterator for VisitOrder<'a> { + type Item = NodeIndex; + fn next(&mut self) -> Option { + self.indices.next().map(|&idx| idx) } } +impl WalkVisitOrder { + /// The index of the next node that would be visited during audio requested in our walk of the + /// given **Graph**'s visit order. + pub fn next(&mut self, graph: &Graph) -> Option { + graph.visit_order.get(self.current_visit_order_idx).map(|&idx| { + self.current_visit_order_idx += 1; + idx + }) + } +} -impl<'a, S, N> Iterator for NeighborsMut<'a, S, N> { - type Item = &'a mut N; - #[inline] - fn next(&mut self) -> Option<&'a mut N> { - let NeighborsMut { ref mut graph, ref mut neighbors } = *self; - match neighbors.next() { - Some(idx) => { - let node: &mut N = &mut graph[idx].node; - // Without the following unsafe block, rustc complains about - // input_ref_mut not having a suitable life time. This is because - // it is concerned about creating aliasing mutable references, - // however we know that only one mutable reference will be returned - // at a time and that they will never alias. Thus, we transmute to - // silence the lifetime warning! - Some(unsafe { ::std::mem::transmute(node) }) - }, - None => None, +impl WalkVisitOrderReverse { + /// The index of the next node that would be visited during audio requested in our walk of the + /// given **Graph**'s visit order. + pub fn next(&mut self, graph: &Graph) -> Option { + if self.current_visit_order_idx > 0 { + self.current_visit_order_idx -= 1; + graph.visit_order.get(self.current_visit_order_idx).map(|&idx| idx) + } else { + None } } } -impl<'a, S, N> Iterator for NeighborsMutWithIndices<'a, S, N> { - type Item = (&'a mut N, NodeIndex); - #[inline] - fn next(&mut self) -> Option<(&'a mut N, NodeIndex)> { - let NeighborsMutWithIndices { ref mut graph, ref mut neighbors } = *self; - match neighbors.next() { - Some(idx) => { - let node: &mut N = &mut graph[idx].node; - // Without the following unsafe block, rustc complains about - // input_ref_mut not having a suitable life time. This is because - // it is concerned about creating aliasing mutable references, - // however we know that only one mutable reference will be returned - // at a time and that they will never alias. Thus, we transmute to - // silence the lifetime warning! - Some((unsafe { ::std::mem::transmute(node) }, idx)) - }, - None => None, - } + + +/// Resize the given buffer to the given target length. +fn resize_buffer_to(buffer: &mut Vec, target_len: usize) where S: Sample { + let len = buffer.len(); + if len < target_len { + buffer.extend((len..target_len).map(|_| S::zero())) + } else if len > target_len { + buffer.truncate(target_len); + } +} + +impl ::std::fmt::Display for WouldCycle { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + writeln!(f, "{:?}", self) } } +impl ::std::error::Error for WouldCycle { + fn description(&self) -> &str { + "Adding this input would have caused the graph to cycle!" + } +} diff --git a/src/lib.rs b/src/lib.rs index d26a155..e89f59c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,39 @@ //! -//! A pure Rust audio digital signal processing library for Piston. +//! A generic, fast, audio digital signal processing library. //! -//! There are two primary modules of interest within this library. -//! 1. graph.rs and the `Graph` type. -//! 2. node.rs and the `Node` trait. +//! There are two primary points of interest: +//! 1. The [**Graph** type](./graph/struct.Graph.html) - a directed, acyclic audio DSP graph. +//! 2. The [**Node** trait](./node/trait.Node.html) - to be implemented for types used within the +//! **Graph**. //! +#![forbid(unsafe_code)] #![deny(missing_docs)] +extern crate daggy as daggy_lib; extern crate num; -extern crate petgraph; -extern crate sound_stream; +extern crate sound_stream as sound_stream_lib; + +pub use daggy_lib as daggy; +pub use sound_stream_lib as sound_stream; pub use graph::{ + Connection, + Dag, + EdgeIndex, Graph, - NodeIndex, Inputs, - InputsWithIndices, - InputsMut, - InputsMutWithIndices, + NodeIndex, + NodesMut, Outputs, - OutputsWithIndices, - OutputsMut, - OutputsMutWithIndices, + PetGraph, + RawEdges, + RawNodes, + VisitOrder, + WalkInputs, + WalkOutputs, + WalkVisitOrder, + WalkVisitOrderReverse, WouldCycle, }; pub use node::Node; From dc1bf39deb15dc6e1392b33e061f75e8b92a4d72 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 9 Oct 2015 20:57:19 +1100 Subject: [PATCH 2/4] Add badges and tweak example comment --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9596812..afbee87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# dsp-chain [![Build Status](https://travis-ci.org/RustAudio/dsp-chain.svg?branch=master)](https://travis-ci.org/RustAudio/dsp-chain) +# dsp-chain [![Build Status](https://travis-ci.org/RustAudio/dsp-chain.svg?branch=master)](https://travis-ci.org/RustAudio/dsp-chain) [![Crates.io](https://img.shields.io/crates/v/dsp-chain.svg)](https://crates.io/crates/dsp-chain) [![Crates.io](https://img.shields.io/crates/l/dsp-chain.svg)](https://github.com/RustAudio/dsp-chain/blob/master/LICENSE) -A simple library for chaining together multiple audio dsp processors/generators, written in Rust! + +A library for chaining together multiple audio dsp processors/generators, written in Rust! Use cases for dsp-chain include: - Designing effects. @@ -10,6 +11,12 @@ Use cases for dsp-chain include: - Any kind of modular audio synthesis/processing. +Documenation +------------ + +[API documentation here!](http://RustAudio.github.io/dsp-chain/dsp) + + Usage ----- @@ -28,6 +35,7 @@ graph.add_input(DspNode::Oscillator(0.0, D5_HZ, 0.1), synth); graph.add_input(DspNode::Oscillator(0.0, F5_HZ, 0.15), synth); // Set the synth as the master node for the graph. +// This can be inferred by the graph so calling this is optional, but it's nice to be explicit. graph.set_master(Some(synth)); // Request audio from our Graph. From fe17aa5bebda9a4dda5c8490710608a79c8a11d7 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 9 Oct 2015 20:58:23 +1100 Subject: [PATCH 3/4] Amend portaudio section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afbee87..b995a47 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ dsp-chain = "*" PortAudio --------- -dsp-chain uses [PortAudio](http://www.portaudio.com) as a cross-platform audio backend. The [rust-portaudio](https://github.com/jeremyletang/rust-portaudio) dependency will first try to find an already installed version on your system before trying to download it and build PortAudio itself. +The dsp-chain examples uses [PortAudio](http://www.portaudio.com) as a cross-platform audio backend. The [rust-portaudio](https://github.com/jeremyletang/rust-portaudio) dependency will first try to find an already installed version on your system before trying to download it and build PortAudio itself. License From 10df76fe5c5cb47425ccc34689953489eaa75c18 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 9 Oct 2015 21:00:58 +1100 Subject: [PATCH 4/4] Added gh-pages stuff to travis --- .travis.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index ee079d5..da3e1b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,25 @@ language: rust rust: - - nightly - - stable +- nightly +- stable notifications: - email: - - mitchell.nordine@gmail.com + email: + - mitchell.nordine@gmail.com os: - - linux +- linux env: - - LD_LIBRARY_PATH: /usr/local/lib + matrix: + - LD_LIBRARY_PATH: /usr/local/lib + global: + secure: H/FCcjwpQQNeWGRM6zrDojNwRJud/FsAu5N6iGz2kxog0v7FFSuFreYeklNfwX+4Mf+BtYYpblZ8xHF24tOmJHeOMKwGPBoopyjXzJx+QVd3mlj30dmvm1/jcy/7pzH0Q2GhVhSbBtV+BCQvjUXZ/PNk9/wiMSxnNtTchyAOpHQ= install: - - curl -O http://www.portaudio.com/archives/pa_stable_v19_20140130.tgz - - tar xfz pa_stable_v19_20140130.tgz - - (cd portaudio/ && ./configure && make && sudo make install) +- curl -O http://www.portaudio.com/archives/pa_stable_v19_20140130.tgz +- tar xfz pa_stable_v19_20140130.tgz +- (cd portaudio/ && ./configure && make && sudo make install) before_script: - - rustc --version - - cargo --version +- rustc --version +- cargo --version script: - - cargo build --verbose - - cargo test --verbose - - cargo doc --verbose +- cargo build --verbose +- cargo test --verbose +- cargo doc --verbose