| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // A class that tracks all the nodes and edges of an audio graph. |
| export class GraphView extends Common.Object { |
| /** |
| * @param {!Protocol.WebAudio.GraphObjectId} contextId |
| */ |
| constructor(contextId) { |
| super(); |
| |
| this.contextId = contextId; |
| |
| /** @type {!Map<!Protocol.WebAudio.GraphObjectId, !WebAudio.GraphVisualizer.NodeView>} */ |
| this._nodes = new Map(); |
| /** @type {!Map<!Protocol.WebAudio.GraphObjectId, !WebAudio.GraphVisualizer.EdgeView>} */ |
| this._edges = new Map(); |
| |
| /** |
| * For each node ID, keep a set of all out-bound edge IDs. |
| * @type {!Platform.Multimap<!Protocol.WebAudio.GraphObjectId, string>} |
| */ |
| this._outboundEdgeMap = new Platform.Multimap(); |
| |
| /** |
| * For each node ID, keep a set of all in-bound edge IDs. |
| * @type {!Platform.Multimap<!Protocol.WebAudio.GraphObjectId, string>} |
| */ |
| this._inboundEdgeMap = new Platform.Multimap(); |
| |
| // Use concise node label to replace the long UUID. |
| // Each graph has its own label generator so that the label starts from 0. |
| this._nodeLabelGenerator = new WebAudio.GraphVisualizer.NodeLabelGenerator(); |
| |
| /** |
| * For each param ID, save its corresponding node Id. |
| * @type {!Map<!Protocol.WebAudio.GraphObjectId, !Protocol.WebAudio.GraphObjectId>} |
| */ |
| this._paramIdToNodeIdMap = new Map(); |
| } |
| |
| /** |
| * Add a node to the graph. |
| * @param {!WebAudio.GraphVisualizer.NodeCreationData} data |
| */ |
| addNode(data) { |
| const label = this._nodeLabelGenerator.generateLabel(data.nodeType); |
| const node = new WebAudio.GraphVisualizer.NodeView(data, label); |
| this._nodes.set(data.nodeId, node); |
| this._notifyShouldRedraw(); |
| } |
| |
| /** |
| * Remove a node by id and all related edges. |
| * @param {!Protocol.WebAudio.GraphObjectId} nodeId |
| */ |
| removeNode(nodeId) { |
| this._outboundEdgeMap.get(nodeId).forEach(edgeId => this._removeEdge(edgeId)); |
| this._inboundEdgeMap.get(nodeId).forEach(edgeId => this._removeEdge(edgeId)); |
| this._nodes.delete(nodeId); |
| this._notifyShouldRedraw(); |
| } |
| |
| /** |
| * Add a param to the node. |
| * @param {!WebAudio.GraphVisualizer.ParamCreationData} data |
| */ |
| addParam(data) { |
| const node = this.getNodeById(data.nodeId); |
| if (!node) { |
| console.error(`AudioNode should be added before AudioParam`); |
| return; |
| } |
| node.addParamPort(data.paramId, data.paramType); |
| this._paramIdToNodeIdMap.set(data.paramId, data.nodeId); |
| this._notifyShouldRedraw(); |
| } |
| |
| /** |
| * Remove a param. |
| * @param {!Protocol.WebAudio.GraphObjectId} paramId |
| */ |
| removeParam(paramId) { |
| // Only need to delete the entry from the param id to node id map. |
| this._paramIdToNodeIdMap.delete(paramId); |
| // No need to remove the param port from the node because removeParam will always happen with |
| // removeNode(). Since the whole Node will be gone, there is no need to remove port individually. |
| } |
| |
| /** |
| * Add a Node-to-Node connection to the graph. |
| * @param {!WebAudio.GraphVisualizer.NodesConnectionData} edgeData |
| */ |
| addNodeToNodeConnection(edgeData) { |
| const edge = new WebAudio.GraphVisualizer.EdgeView(edgeData, WebAudio.GraphVisualizer.EdgeTypes.NodeToNode); |
| this._addEdge(edge); |
| } |
| |
| /** |
| * Remove a Node-to-Node connection from the graph. |
| * @param {!WebAudio.GraphVisualizer.NodesDisconnectionData} edgeData |
| */ |
| removeNodeToNodeConnection(edgeData) { |
| if (edgeData.destinationId) { |
| // Remove a single edge if destinationId is specified. |
| const {edgeId} = WebAudio.GraphVisualizer.generateEdgePortIdsByData( |
| /** @type {!WebAudio.GraphVisualizer.NodesDisconnectionDataWithDestination} */ (edgeData), |
| WebAudio.GraphVisualizer.EdgeTypes.NodeToNode); |
| this._removeEdge(edgeId); |
| } else { |
| // Otherwise, remove all outgoing edges from source node. |
| this._outboundEdgeMap.get(edgeData.sourceId).forEach(edgeId => this._removeEdge(edgeId)); |
| } |
| } |
| |
| /** |
| * Add a Node-to-Param connection to the graph. |
| * @param {!WebAudio.GraphVisualizer.NodeParamConnectionData} edgeData |
| */ |
| addNodeToParamConnection(edgeData) { |
| const edge = new WebAudio.GraphVisualizer.EdgeView(edgeData, WebAudio.GraphVisualizer.EdgeTypes.NodeToParam); |
| this._addEdge(edge); |
| } |
| |
| /** |
| * Remove a Node-to-Param connection from the graph. |
| * @param {!WebAudio.GraphVisualizer.NodeParamDisconnectionData} edgeData |
| */ |
| removeNodeToParamConnection(edgeData) { |
| const {edgeId} = |
| WebAudio.GraphVisualizer.generateEdgePortIdsByData(edgeData, WebAudio.GraphVisualizer.EdgeTypes.NodeToParam); |
| this._removeEdge(edgeId); |
| } |
| |
| /** |
| * @param {!Protocol.WebAudio.GraphObjectId} nodeId |
| * @return {?WebAudio.GraphVisualizer.NodeView} |
| */ |
| getNodeById(nodeId) { |
| return this._nodes.get(nodeId); |
| } |
| |
| /** @return {!Map<!Protocol.WebAudio.GraphObjectId, !WebAudio.GraphVisualizer.NodeView>} */ |
| getNodes() { |
| return this._nodes; |
| } |
| |
| /** @return {!Map<!Protocol.WebAudio.GraphObjectId, !WebAudio.GraphVisualizer.EdgeView>} */ |
| getEdges() { |
| return this._edges; |
| } |
| |
| /** |
| * @param {!Protocol.WebAudio.GraphObjectId} paramId |
| * @return {?Protocol.WebAudio.GraphObjectId} |
| */ |
| getNodeIdByParamId(paramId) { |
| return this._paramIdToNodeIdMap.get(paramId); |
| } |
| |
| /** |
| * Add an edge to the graph. |
| * @param {!WebAudio.GraphVisualizer.EdgeView} edge |
| */ |
| _addEdge(edge) { |
| const sourceId = edge.sourceId; |
| // Do nothing if the edge already exists. |
| if (this._outboundEdgeMap.hasValue(sourceId, edge.id)) { |
| return; |
| } |
| |
| this._edges.set(edge.id, edge); |
| this._outboundEdgeMap.set(sourceId, edge.id); |
| this._inboundEdgeMap.set(edge.destinationId, edge.id); |
| |
| this._notifyShouldRedraw(); |
| } |
| |
| /** |
| * Given an edge id, remove the edge from the graph. |
| * Also remove the edge from inbound and outbound edge maps. |
| * @param {string} edgeId |
| */ |
| _removeEdge(edgeId) { |
| const edge = this._edges.get(edgeId); |
| if (!edge) { |
| return; |
| } |
| |
| this._outboundEdgeMap.delete(edge.sourceId, edgeId); |
| this._inboundEdgeMap.delete(edge.destinationId, edgeId); |
| |
| this._edges.delete(edgeId); |
| this._notifyShouldRedraw(); |
| } |
| |
| _notifyShouldRedraw() { |
| this.dispatchEventToListeners(Events.ShouldRedraw, this); |
| } |
| } |
| |
| /** @enum {symbol} */ |
| export const Events = { |
| ShouldRedraw: Symbol('ShouldRedraw') |
| }; |
| |
| /* Legacy exported object */ |
| self.WebAudio = self.WebAudio || {}; |
| |
| /* Legacy exported object */ |
| WebAudio = WebAudio || {}; |
| |
| /* Legacy exported object */ |
| WebAudio.GraphVisualizer = WebAudio.GraphVisualizer || {}; |
| |
| /** |
| * @constructor |
| */ |
| WebAudio.GraphVisualizer.GraphView = GraphView; |
| |
| /** @enum {symbol} */ |
| WebAudio.GraphVisualizer.GraphView.Events = Events; |