servo_media_audio/
graph.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{RefCell, RefMut};
6use std::{cmp, fmt, hash};
7
8use malloc_size_of::MallocSizeOf as MallocSizeOfTrait;
9use malloc_size_of_derive::MallocSizeOf;
10use petgraph::Direction;
11use petgraph::graph::DefaultIx;
12use petgraph::stable_graph::{NodeIndex, StableGraph};
13use petgraph::visit::{DfsPostOrder, EdgeRef, Reversed};
14use smallvec::SmallVec;
15
16use crate::block::{Block, Chunk};
17use crate::destination_node::DestinationNode;
18use crate::listener::AudioListenerNode;
19use crate::node::{AudioNodeEngine, BlockInfo, ChannelCountMode, ChannelInterpretation};
20use crate::param::ParamType;
21
22#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, MallocSizeOf)]
23/// A unique identifier for nodes in the graph. Stable
24/// under graph mutation.
25pub struct NodeId(#[ignore_malloc_size_of = "External Type"] NodeIndex<DefaultIx>);
26
27impl NodeId {
28    pub fn input(self, port: u32) -> PortId<InputPort> {
29        PortId(self, PortIndex::Port(port))
30    }
31    pub fn param(self, param: ParamType) -> PortId<InputPort> {
32        PortId(self, PortIndex::Param(param))
33    }
34    pub fn output(self, port: u32) -> PortId<OutputPort> {
35        PortId(self, PortIndex::Port(port))
36    }
37    pub(crate) fn listener(self) -> PortId<InputPort> {
38        PortId(self, PortIndex::Listener(()))
39    }
40}
41
42/// A zero-indexed "port" for a node. Most nodes have one
43/// input and one output port, but some may have more.
44/// For example, a channel splitter node will have one output
45/// port for each channel.
46///
47/// These are essentially indices into the Chunks
48///
49/// Kind is a zero sized type and is useful for distinguishing
50/// between input and output ports (which may otherwise share indices)
51#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, MallocSizeOf)]
52pub enum PortIndex<Kind: PortKind> {
53    Port(u32),
54    Param(Kind::ParamId),
55    /// special variant only used for the implicit connection
56    /// from listeners to params
57    Listener(Kind::Listener),
58}
59
60impl<Kind: PortKind> PortId<Kind> {
61    pub fn node(&self) -> NodeId {
62        self.0
63    }
64}
65
66pub trait PortKind {
67    type ParamId: Copy
68        + Eq
69        + PartialEq
70        + Ord
71        + PartialOrd
72        + hash::Hash
73        + fmt::Debug
74        + MallocSizeOfTrait;
75    type Listener: Copy
76        + Eq
77        + PartialEq
78        + Ord
79        + PartialOrd
80        + hash::Hash
81        + fmt::Debug
82        + MallocSizeOfTrait;
83}
84
85/// An identifier for a port.
86#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, MallocSizeOf)]
87pub struct PortId<Kind: PortKind>(NodeId, PortIndex<Kind>);
88
89#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, MallocSizeOf)]
90/// Marker type for denoting that the port is an input port
91/// of the node it is connected to
92pub struct InputPort;
93#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, MallocSizeOf)]
94/// Marker type for denoting that the port is an output port
95/// of the node it is connected to
96pub struct OutputPort;
97
98impl PortKind for InputPort {
99    type ParamId = ParamType;
100    type Listener = ();
101}
102
103#[derive(Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Copy, Clone, MallocSizeOf)]
104pub enum Void {}
105
106impl PortKind for OutputPort {
107    // Params are only a feature of input ports. By using an empty type here
108    // we ensure that the PortIndex enum has zero overhead for outputs,
109    // taking up no extra discriminant space and eliminating PortIndex::Param
110    // branches entirely from the compiled code
111    type ParamId = Void;
112    type Listener = Void;
113}
114
115pub struct AudioGraph {
116    graph: StableGraph<Node, Edge>,
117    dest_id: NodeId,
118    dests: Vec<NodeId>,
119    listener_id: NodeId,
120}
121
122pub(crate) struct Node {
123    node: RefCell<Box<dyn AudioNodeEngine>>,
124}
125
126/// An edge in the graph
127///
128/// This connects one or more pair of ports between two
129/// nodes, each connection represented by a `Connection`.
130/// WebAudio allows for multiple connections to/from the same port
131/// however it does not allow for duplicate connections between pairs
132/// of ports
133pub(crate) struct Edge {
134    connections: SmallVec<[Connection; 1]>,
135}
136
137impl Edge {
138    /// Find if there are connections between two given ports, return the index
139    fn has_between(
140        &self,
141        output_idx: PortIndex<OutputPort>,
142        input_idx: PortIndex<InputPort>,
143    ) -> bool {
144        self.connections
145            .iter()
146            .any(|e| e.input_idx == input_idx && e.output_idx == output_idx)
147    }
148
149    fn remove_by_output(&mut self, output_idx: PortIndex<OutputPort>) {
150        self.connections.retain(|i| i.output_idx != output_idx)
151    }
152
153    fn remove_by_input(&mut self, input_idx: PortIndex<InputPort>) {
154        self.connections.retain(|i| i.input_idx != input_idx)
155    }
156
157    fn remove_by_pair(
158        &mut self,
159        output_idx: PortIndex<OutputPort>,
160        input_idx: PortIndex<InputPort>,
161    ) {
162        self.connections
163            .retain(|i| i.output_idx != output_idx || i.input_idx != input_idx)
164    }
165}
166
167/// A single connection between ports
168struct Connection {
169    /// The index of the port on the input node
170    /// This is actually the /output/ of this edge
171    input_idx: PortIndex<InputPort>,
172    /// The index of the port on the output node
173    /// This is actually the /input/ of this edge
174    output_idx: PortIndex<OutputPort>,
175    /// When the from node finishes processing, it will push
176    /// its data into this cache for the input node to read
177    cache: RefCell<Option<Block>>,
178}
179
180impl AudioGraph {
181    pub fn new(channel_count: u8) -> Self {
182        let mut graph = StableGraph::new();
183        let dest_id =
184            NodeId(graph.add_node(Node::new(Box::new(DestinationNode::new(channel_count)))));
185        let listener_id = NodeId(graph.add_node(Node::new(Box::new(AudioListenerNode::new()))));
186        AudioGraph {
187            graph,
188            dest_id,
189            dests: vec![dest_id],
190            listener_id,
191        }
192    }
193
194    /// Create a node, obtain its id
195    pub(crate) fn add_node(&mut self, node: Box<dyn AudioNodeEngine>) -> NodeId {
196        NodeId(self.graph.add_node(Node::new(node)))
197    }
198
199    /// Connect an output port to an input port
200    ///
201    /// The edge goes *from* the output port *to* the input port, connecting two nodes
202    pub fn add_edge(&mut self, out: PortId<OutputPort>, inp: PortId<InputPort>) {
203        let edge = self
204            .graph
205            .edges(out.node().0)
206            .find(|e| e.target() == inp.node().0)
207            .map(|e| e.id());
208        if let Some(e) = edge {
209            // .find(|e| e.weight().has_between(out.1, inp.1));
210            let w = self
211                .graph
212                .edge_weight_mut(e)
213                .expect("This edge is known to exist");
214            if w.has_between(out.1, inp.1) {
215                return;
216            }
217            w.connections.push(Connection::new(inp.1, out.1))
218        } else {
219            // add a new edge
220            self.graph
221                .add_edge(out.node().0, inp.node().0, Edge::new(inp.1, out.1));
222        }
223    }
224
225    /// Disconnect all outgoing connections from a node
226    ///
227    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect>
228    pub fn disconnect_all_from(&mut self, node: NodeId) {
229        let edges = self.graph.edges(node.0).map(|e| e.id()).collect::<Vec<_>>();
230        for edge in edges {
231            self.graph.remove_edge(edge);
232        }
233    }
234
235    /// Disconnect all outgoing connections from a node's output
236    ///
237    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-output>
238    pub fn disconnect_output(&mut self, out: PortId<OutputPort>) {
239        let candidates: Vec<_> = self
240            .graph
241            .edges(out.node().0)
242            .map(|e| (e.id(), e.target()))
243            .collect();
244        for (edge, to) in candidates {
245            let mut e = self
246                .graph
247                .remove_edge(edge)
248                .expect("Edge index is known to exist");
249            e.remove_by_output(out.1);
250            if !e.connections.is_empty() {
251                self.graph.add_edge(out.node().0, to, e);
252            }
253        }
254    }
255
256    /// Disconnect connections from a node to another node
257    ///
258    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode>
259    pub fn disconnect_between(&mut self, from: NodeId, to: NodeId) {
260        let edge = self
261            .graph
262            .edges(from.0)
263            .find(|e| e.target() == to.0)
264            .map(|e| e.id());
265        if let Some(i) = edge {
266            self.graph.remove_edge(i);
267        }
268    }
269
270    /// Disconnect all outgoing connections from a node's output to another node
271    ///
272    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output>
273    pub fn disconnect_output_between(&mut self, out: PortId<OutputPort>, to: NodeId) {
274        let edge = self
275            .graph
276            .edges(out.node().0)
277            .find(|e| e.target() == to.0)
278            .map(|e| e.id());
279        if let Some(edge) = edge {
280            let mut e = self
281                .graph
282                .remove_edge(edge)
283                .expect("Edge index is known to exist");
284            e.remove_by_output(out.1);
285            if !e.connections.is_empty() {
286                self.graph.add_edge(out.node().0, to.0, e);
287            }
288        }
289    }
290
291    /// Disconnect all outgoing connections from a node to another node's input
292    ///
293    /// Only used in WebAudio for disconnecting audio params
294    ///
295    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam>
296    pub fn disconnect_to(&mut self, node: NodeId, inp: PortId<InputPort>) {
297        let edge = self
298            .graph
299            .edges(node.0)
300            .find(|e| e.target() == inp.node().0)
301            .map(|e| e.id());
302        if let Some(edge) = edge {
303            let mut e = self
304                .graph
305                .remove_edge(edge)
306                .expect("Edge index is known to exist");
307            e.remove_by_input(inp.1);
308            if !e.connections.is_empty() {
309                self.graph.add_edge(node.0, inp.node().0, e);
310            }
311        }
312    }
313
314    /// Disconnect all outgoing connections from a node's output to another node's input
315    ///
316    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationnode-output-input>
317    /// <https://webaudio.github.io/web-audio-api/#dom-audionode-disconnect-destinationparam-output>
318    pub fn disconnect_output_between_to(
319        &mut self,
320        out: PortId<OutputPort>,
321        inp: PortId<InputPort>,
322    ) {
323        let edge = self
324            .graph
325            .edges(out.node().0)
326            .find(|e| e.target() == inp.node().0)
327            .map(|e| e.id());
328        if let Some(edge) = edge {
329            let mut e = self
330                .graph
331                .remove_edge(edge)
332                .expect("Edge index is known to exist");
333            e.remove_by_pair(out.1, inp.1);
334            if !e.connections.is_empty() {
335                self.graph.add_edge(out.node().0, inp.node().0, e);
336            }
337        }
338    }
339
340    /// Get the id of the destination node in this graph
341    ///
342    /// All graphs have a destination node, with one input port
343    pub fn dest_id(&self) -> NodeId {
344        self.dest_id
345    }
346
347    /// Add additional terminator nodes
348    pub fn add_extra_dest(&mut self, dest: NodeId) {
349        self.dests.push(dest);
350    }
351
352    /// Get the id of the AudioListener in this graph
353    ///
354    /// All graphs have a single listener, with no ports (but nine AudioParams)
355    ///
356    /// N.B. The listener actually has a single output port containing
357    /// its position data for the block, however this should
358    /// not be exposed to the DOM.
359    pub fn listener_id(&self) -> NodeId {
360        self.listener_id
361    }
362
363    /// For a given block, process all the data on this graph
364    pub fn process(&mut self, info: &BlockInfo) -> Chunk {
365        // DFS post order: Children are processed before their parent,
366        // which is exactly what we need since the parent depends on the
367        // children's output
368        //
369        // This will only visit each node once
370        let reversed = Reversed(&self.graph);
371
372        let mut blocks: SmallVec<[SmallVec<[Block; 1]>; 1]> = SmallVec::new();
373        let mut output_counts: SmallVec<[u32; 1]> = SmallVec::new();
374
375        let mut visit = DfsPostOrder::empty(reversed);
376
377        for dest in &self.dests {
378            visit.move_to(dest.0);
379
380            while let Some(ix) = visit.next(reversed) {
381                let mut curr = self.graph[ix].node.borrow_mut();
382
383                let mut chunk = Chunk::default();
384                chunk
385                    .blocks
386                    .resize(curr.input_count() as usize, Default::default());
387
388                // if we have inputs, collect all the computed blocks
389                // and construct a Chunk
390
391                // set up scratch space to store all the blocks
392                blocks.clear();
393                blocks.resize(curr.input_count() as usize, Default::default());
394
395                let mode = curr.channel_count_mode();
396                let count = curr.channel_count();
397                let interpretation = curr.channel_interpretation();
398
399                // all edges to this node are from its dependencies
400                for edge in self.graph.edges_directed(ix, Direction::Incoming) {
401                    let edge = edge.weight();
402                    for connection in &edge.connections {
403                        let mut block = connection
404                            .cache
405                            .borrow_mut()
406                            .take()
407                            .expect("Cache should have been filled from traversal");
408
409                        match connection.input_idx {
410                            PortIndex::Port(idx) => {
411                                blocks[idx as usize].push(block);
412                            },
413                            PortIndex::Param(param) => {
414                                // param inputs are downmixed to mono
415                                // https://webaudio.github.io/web-audio-api/#dom-audionode-connect-destinationparam-output
416                                block.mix(1, ChannelInterpretation::Speakers);
417                                curr.get_param(param).add_block(block)
418                            },
419                            PortIndex::Listener(_) => curr.set_listenerdata(block),
420                        }
421                    }
422                }
423
424                for (i, mut blocks) in blocks.drain(..).enumerate() {
425                    if blocks.is_empty() {
426                        if mode == ChannelCountMode::Explicit {
427                            // It's silence, but mix it anyway
428                            chunk.blocks[i].mix(count, interpretation);
429                        }
430                    } else if blocks.len() == 1 {
431                        chunk.blocks[i] = blocks.pop().expect("`blocks` had length 1");
432                        match mode {
433                            ChannelCountMode::Explicit => {
434                                chunk.blocks[i].mix(count, interpretation);
435                            },
436                            ChannelCountMode::ClampedMax => {
437                                if chunk.blocks[i].chan_count() > count {
438                                    chunk.blocks[i].mix(count, interpretation);
439                                }
440                            },
441                            // It's one channel, it maxes itself
442                            ChannelCountMode::Max => (),
443                        }
444                    } else {
445                        let mix_count = match mode {
446                            ChannelCountMode::Explicit => count,
447                            _ => {
448                                let mut max = 0; // max channel count
449                                for block in &blocks {
450                                    max = cmp::max(max, block.chan_count());
451                                }
452                                if mode == ChannelCountMode::ClampedMax {
453                                    max = cmp::min(max, count);
454                                }
455                                max
456                            },
457                        };
458                        let block = blocks.into_iter().fold(Block::default(), |acc, mut block| {
459                            block.mix(mix_count, interpretation);
460                            acc.sum(block)
461                        });
462                        chunk.blocks[i] = block;
463                    }
464                }
465
466                // actually run the node engine
467                let mut out = curr.process(chunk, info);
468
469                assert_eq!(out.len(), curr.output_count() as usize);
470                if curr.output_count() == 0 {
471                    continue;
472                }
473
474                // Count how many output connections fan out from each port
475                // This is so that we don't have to needlessly clone audio buffers
476                //
477                // If this is inefficient, we can instead maintain this data
478                // cached on the node
479                output_counts.clear();
480                output_counts.resize(curr.output_count() as usize, 0);
481                for edge in self.graph.edges(ix) {
482                    let edge = edge.weight();
483                    for conn in &edge.connections {
484                        if let PortIndex::Port(idx) = conn.output_idx {
485                            output_counts[idx as usize] += 1;
486                        } else {
487                            unreachable!()
488                        }
489                    }
490                }
491
492                // all the edges from this node go to nodes which depend on it,
493                // i.e. the nodes it outputs to. Store the blocks for retrieval.
494                for edge in self.graph.edges(ix) {
495                    let edge = edge.weight();
496                    for conn in &edge.connections {
497                        if let PortIndex::Port(idx) = conn.output_idx {
498                            output_counts[idx as usize] -= 1;
499                            // if there are no consumers left after this, take the data
500                            let block = if output_counts[idx as usize] == 0 {
501                                out[conn.output_idx].take()
502                            } else {
503                                out[conn.output_idx].clone()
504                            };
505                            *conn.cache.borrow_mut() = Some(block);
506                        } else {
507                            unreachable!()
508                        }
509                    }
510                }
511            }
512        }
513        // The destination node stores its output on itself, extract it.
514        self.graph[self.dest_id.0]
515            .node
516            .borrow_mut()
517            .destination_data()
518            .expect("Destination node should have data cached")
519    }
520
521    /// Obtain a mutable reference to a node
522    pub(crate) fn node_mut(&self, ix: NodeId) -> RefMut<'_, Box<dyn AudioNodeEngine>> {
523        self.graph[ix.0].node.borrow_mut()
524    }
525}
526
527impl Node {
528    pub fn new(node: Box<dyn AudioNodeEngine>) -> Self {
529        Node {
530            node: RefCell::new(node),
531        }
532    }
533}
534
535impl Edge {
536    pub fn new(input_idx: PortIndex<InputPort>, output_idx: PortIndex<OutputPort>) -> Self {
537        Edge {
538            connections: SmallVec::from_buf([Connection::new(input_idx, output_idx)]),
539        }
540    }
541}
542
543impl Connection {
544    pub fn new(input_idx: PortIndex<InputPort>, output_idx: PortIndex<OutputPort>) -> Self {
545        Connection {
546            input_idx,
547            output_idx,
548            cache: RefCell::new(None),
549        }
550    }
551}