Skip to content

Instantly share code, notes, and snippets.

@kn0ll
Last active October 10, 2015 04:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kn0ll/3636640 to your computer and use it in GitHub Desktop.
Save kn0ll/3636640 to your computer and use it in GitHub Desktop.
a directed tick-based "streaming" graph. used for an audio processing graph, for example.
// this is a directed graph data structure- with the unique property that it is
// intended to handle the flow of data upstream in a clock based system.
// streaming audio through a graph of nodes with variable inputs and outputs, for instance.
// an `Edge` behaves as a relation between two nodes.
// the edge is saved on both connected nodes for reference.
var Edge = function(fromNode, fromIndex, toNode, toIndex) {
this.fromNode = fromNode;
this.fromIndex = fromIndex;
this.toNode = toNode;
this.toIndex = toIndex;
};
// a `Node` is a node in a graph with x inputs and y outputs.
// - `inputs` is an object whose keys are input indexes, and values are edges.
// - `outputs` is an object whose keys are output indexes, and values are edges.
// - `data` is an object whose keys are input indexes, and values are written data.
var Node = function() {
this.inputs = {};
this.outputs = {};
this.data = {};
};
// `connect` stores a new `Edge` in the current node's outputs,
// and saves the same edge on the next node's inputs.
Node.prototype.connect = function(fromIndex, toIndex, node) {
var edge = new Edge(this, fromIndex, node, toIndex);
this.outputs[fromIndex] = edge;
node.inputs[toIndex] = edge;
};
// `inputsLength` returns the number of connected inputs.
Node.prototype.inputsLength = function() {
var foundInputs = 0;
for (var i in this.inputs) {
foundInputs++;
}
return foundInputs;
};
// `outputsLength` returns the number of connected outputs.
Node.prototype.outputsLength = function() {
var foundOutputs = 0;
for (var i in this.outputs) {
foundOutputs++;
}
return foundOutputs;
};
// `dataLength` returns the number of the inputs
// which have data written to them.
Node.prototype.dataLength = function() {
var foundData = 0;
for (var i in this.read()) {
foundData++;
}
return foundData;
};
// `findRootNodes` finds all nodes in the graph before
// this one which have no connected inputs. these are essentially the
// beginning nodes of the directed graph.
Node.prototype.findRootNodes = function() {
var nodes = [];
for (var i in this.inputs) {
var node = this.inputs[i].fromNode;
if (node.inputsLength()) {
nodes = nodes.concat(node.findRootNodes());
} else {
nodes.push(node);
}
}
return nodes;
};
// `send` takes an outputIndex and writes data
// to the input connected to the output associated with outputIndex
Node.prototype.send = function(outputIndex, data) {
var output = this.outputs[outputIndex];
output.toNode.write(output.toIndex, data);
};
// `write` stores input data on the node.
// once all connected nodes have written data to the inputs,
// we do one of two things: if the current node is an "end" node in the graph,
// we call `generate` is responsible for handling the final data. if the current node
// has outputs and is not and "end" node, it's responsibility is starting a new `tick`
// to send upstream.
Node.prototype.write = function(inputIndex, data) {
this.data[inputIndex] = data;
if (this.dataLength() == this.inputsLength()) {
if (this.outputsLength()) {
this.tick();
} else {
this.generate();
}
this.data = {};
}
};
// `read` returns the data currently written to the node's inputs
Node.prototype.read = function() {
return this.data;
};
// `tick` handles two explicit scenarios. if the current node
// has no connected outputs, and is thus an "end" node in the graph, it's
// job is to find all "beginning" nodes in the graph and trigger their `tick`-
// which will begin the flow of data upstream. however if the node has connected outputs,
// thus data to be sent, we call `generate` which will process and write the data to the following nodes.
Node.prototype.tick = function() {
if (!this.outputsLength()) {
var rootNodes = this.findRootNodes();
for (var i = 0; i < rootNodes.length; i++) {
rootNodes[i].tick();
}
} else {
for (var index in this.outputs) {
this.generate();
}
}
};
// `generate` is called on each node once all it's inputs have had data written to them.
// if the node is an "end" node, it should handle the data in some way. if it's not,
// it should process data and write it to the next node.
Node.prototype.generate = function() {
if (this.outputsLength()) {
for (var index in this.outputs) {
this.send(index, 'lol');
}
} else {
console.log('End node received data', this.read());
}
};
// a simple graph: [a] => [b] => [c]
var nodeA = new Node(),
nodeB = new Node(),
nodeC = new Node();
nodeA.connect(0, 0, nodeB);
nodeB.connect(0, 0, nodeC);
setInterval(function() {
console.log('triggering tick');
nodeC.tick();
}, 3000);
// a simple graph: [a] => [b] => [c]
var nodeA = new Node(),
nodeB = new Node(),
nodeC = new Node();
nodeA.connect(0, 0, nodeB);
nodeB.connect(0, 0, nodeC);
setInterval(function() {
console.log('triggering tick');
nodeC.tick();
}, 3000);
var Sine = Node.extend({
generate: function() {
var sin = Math.sin,
two_pi = 2 * Math.PI,
sampleRate = 44100;
var frequency = 440,
phase = this.phase || 0,
nextPhase = phase + (two_pi * frequency / sampleRate),
sine = sin(phase);
if (nextPhase > two_pi) {
this.phase = nextPhase % two_pi;
} else {
this.phase = nextPhase;
}
this.send(0, [sine, sine]);
}
});
var Gain = Node.extend({
generate: function() {
var level = 0.8,
samples = this.read()[0],
new_samples = [];
for (var i = 0; i < samples.length; i++) {
new_samples.push(samples[i] * level);
}
this.send(0, new_samples);
}
});
var Speaker = Node.extend({
generate: function() {
console.log('play noise', this.read()[0]);
}
});
// a simple graph: [a] => [b] => [c]
var sine = new Sine(),
gain = new Gain(),
speaker = new Speaker();
sine.connect(0, 0, gain);
gain.connect(0, 0, speaker);
setInterval(function() {
speaker.tick();
}, 100);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment