123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- // Import as different name `coreEmit`, so we don't conflict
- // with the global `window` listener itself.
- const { emit: coreEmit } = require("sdk/event/core");
- /**
- * Representational wrapper around AudioNodeActors. Adding and destroying
- * AudioNodes should be performed through the AudioNodes collection.
- *
- * Events:
- * - `connect`: node, destinationNode, parameter
- * - `disconnect`: node
- */
- const AudioNodeModel = Class({
- extends: EventTarget,
- // Will be added via AudioNodes `add`
- collection: null,
- initialize: function (actor) {
- this.actor = actor;
- this.id = actor.actorID;
- this.type = actor.type;
- this.bypassable = actor.bypassable;
- this._bypassed = false;
- this.connections = [];
- },
- /**
- * Stores connection data inside this instance of this audio node connecting
- * to another node (destination). If connecting to another node's AudioParam,
- * the second argument (param) must be populated with a string.
- *
- * Connecting nodes is idempotent. Upon new connection, emits "connect" event.
- *
- * @param AudioNodeModel destination
- * @param String param
- */
- connect: function (destination, param) {
- let edge = findWhere(this.connections, { destination: destination.id, param: param });
- if (!edge) {
- this.connections.push({ source: this.id, destination: destination.id, param: param });
- coreEmit(this, "connect", this, destination, param);
- }
- },
- /**
- * Clears out all internal connection data. Emits "disconnect" event.
- */
- disconnect: function () {
- this.connections.length = 0;
- coreEmit(this, "disconnect", this);
- },
- /**
- * Gets the bypass status of the audio node.
- *
- * @return Boolean
- */
- isBypassed: function () {
- return this._bypassed;
- },
- /**
- * Sets the bypass value of an AudioNode.
- *
- * @param Boolean enable
- * @return Promise
- */
- bypass: function (enable) {
- this._bypassed = enable;
- return this.actor.bypass(enable).then(() => coreEmit(this, "bypass", this, enable));
- },
- /**
- * Returns a promise that resolves to an array of objects containing
- * both a `param` name property and a `value` property.
- *
- * @return Promise->Object
- */
- getParams: function () {
- return this.actor.getParams();
- },
- /**
- * Returns a promise that resolves to an object containing an
- * array of event information and an array of automation data.
- *
- * @param String paramName
- * @return Promise->Array
- */
- getAutomationData: function (paramName) {
- return this.actor.getAutomationData(paramName);
- },
- /**
- * Takes a `dagreD3.Digraph` object and adds this node to
- * the graph to be rendered.
- *
- * @param dagreD3.Digraph
- */
- addToGraph: function (graph) {
- graph.addNode(this.id, {
- type: this.type,
- label: this.type.replace(/Node$/, ""),
- id: this.id,
- bypassed: this._bypassed
- });
- },
- /**
- * Takes a `dagreD3.Digraph` object and adds edges to
- * the graph to be rendered. Separate from `addToGraph`,
- * as while we depend on D3/Dagre's constraints, we cannot
- * add edges for nodes that have not yet been added to the graph.
- *
- * @param dagreD3.Digraph
- */
- addEdgesToGraph: function (graph) {
- for (let edge of this.connections) {
- let options = {
- source: this.id,
- target: edge.destination
- };
- // Only add `label` if `param` specified, as this is an AudioParam
- // connection then. `label` adds the magic to render with dagre-d3,
- // and `param` is just more explicitly the param, ignoring
- // implementation details.
- if (edge.param) {
- options.label = options.param = edge.param;
- }
- graph.addEdge(null, this.id, edge.destination, options);
- }
- },
- toString: () => "[object AudioNodeModel]",
- });
- /**
- * Constructor for a Collection of `AudioNodeModel` models.
- *
- * Events:
- * - `add`: node
- * - `remove`: node
- * - `connect`: node, destinationNode, parameter
- * - `disconnect`: node
- */
- const AudioNodesCollection = Class({
- extends: EventTarget,
- model: AudioNodeModel,
- initialize: function () {
- this.models = new Set();
- this._onModelEvent = this._onModelEvent.bind(this);
- },
- /**
- * Iterates over all models within the collection, calling `fn` with the
- * model as the first argument.
- *
- * @param Function fn
- */
- forEach: function (fn) {
- this.models.forEach(fn);
- },
- /**
- * Creates a new AudioNodeModel, passing through arguments into the AudioNodeModel
- * constructor, and adds the model to the internal collection store of this
- * instance.
- *
- * Emits "add" event on instance when completed.
- *
- * @param Object obj
- * @return AudioNodeModel
- */
- add: function (obj) {
- let node = new this.model(obj);
- node.collection = this;
- this.models.add(node);
- node.on("*", this._onModelEvent);
- coreEmit(this, "add", node);
- return node;
- },
- /**
- * Removes an AudioNodeModel from the internal collection. Calls `delete` method
- * on the model, and emits "remove" on this instance.
- *
- * @param AudioNodeModel node
- */
- remove: function (node) {
- this.models.delete(node);
- coreEmit(this, "remove", node);
- },
- /**
- * Empties out the internal collection of all AudioNodeModels.
- */
- reset: function () {
- this.models.clear();
- },
- /**
- * Takes an `id` from an AudioNodeModel and returns the corresponding
- * AudioNodeModel within the collection that matches that id. Returns `null`
- * if not found.
- *
- * @param Number id
- * @return AudioNodeModel|null
- */
- get: function (id) {
- return findWhere(this.models, { id: id });
- },
- /**
- * Returns the count for how many models are a part of this collection.
- *
- * @return Number
- */
- get length() {
- return this.models.size;
- },
- /**
- * Returns detailed information about the collection. used during tests
- * to query state. Returns an object with information on node count,
- * how many edges are within the data graph, as well as how many of those edges
- * are for AudioParams.
- *
- * @return Object
- */
- getInfo: function () {
- let info = {
- nodes: this.length,
- edges: 0,
- paramEdges: 0
- };
- this.models.forEach(node => {
- let paramEdgeCount = node.connections.filter(edge => edge.param).length;
- info.edges += node.connections.length - paramEdgeCount;
- info.paramEdges += paramEdgeCount;
- });
- return info;
- },
- /**
- * Adds all nodes within the collection to the passed in graph,
- * as well as their corresponding edges.
- *
- * @param dagreD3.Digraph
- */
- populateGraph: function (graph) {
- this.models.forEach(node => node.addToGraph(graph));
- this.models.forEach(node => node.addEdgesToGraph(graph));
- },
- /**
- * Called when a stored model emits any event. Used to manage
- * event propagation, or listening to model events to react, like
- * removing a model from the collection when it's destroyed.
- */
- _onModelEvent: function (eventName, node, ...args) {
- if (eventName === "remove") {
- // If a `remove` event from the model, remove it
- // from the collection, and let the method handle the emitting on
- // the collection
- this.remove(node);
- } else {
- // Pipe the event to the collection
- coreEmit(this, eventName, node, ...args);
- }
- },
- toString: () => "[object AudioNodeCollection]",
- });
|