-
Notifications
You must be signed in to change notification settings - Fork 118
Building Blocks
This guide will give you an overview of the key building blocks of Audiolet: Nodes, Inputs, Outputs, Parameters and Groups, and how they fit together to make Audiolet work.
AudioletNodes are the fundamental processing block in Audiolet. Each node represents a single DSP operations, from the simple (adding a constant value to every sample), to the less simple (generating a waveform, or applying a reverb effect). Nodes are connected together to form a network, directing audio through various stages of processing until it reaches the output and is played through your speakers. The network of nodes is fairly sophisticated, allowing one node to be connected to any number of other nodes, multiple nodes to connect to a single node, and complex routing including feedback paths.
Inside all nodes we have a number of objects. Each node contains at least one input or output allowing connections to and from the node, and most nodes contain parameters allowing you to change the characteristics of the node whilst it is processing audio. We will now take a closer look at each of the objects which you will commonly find within a node.
var saw = new Saw(audiolet, 200); // A node
var filter = new LowPassFilter(audiolet, 400); // A second node
saw.connect(filter); // Connect input 0 of the saw wave to input 0 of the filter
AudioletInputs and AudioletOutputs are how nodes connect with the outside world. Audio samples are received from any nodes connected to each input, processed, and the results are written to the outputs. The inputs and outputs themselves are contained within two arrays (unsurprisingly called this.inputs and this.outputs) within the node. Generally you will never interact directly with an input or an output, instead using the methods contained within the node for connection and disconnection.
If more than one node is connected to an input, then the samples taken from their outputs are summed before being processed by the node. If an output is connected to more than one input then the values in it's samples will be the same at each input, and will not be split in any way between the inputs.
AudioletParameters are the primary way of controlling nodes. For example, the LowPassFilter node contains a parameter called 'frequency', which is used to change the cutoff frequency for the filter. Each parameter is linked to a single input of the node, allowing you to use the output of a second node to control the parameter. This means that you could, for example, use a Sine node connected to the LowPassFilter's frequency input to act as an LFO for the cutoff frequency.
As well as being able to take values from their linked input, parameters also hold a static value, for when you only need occasional discrete changes to the parameter rather than a continuously changing value. This static value is set using the parameter's setValue function. Note that the dynamic value taken from the input always takes precedence over the static value as long as another node is connected to the input. The static value is used only if there is nothing connected.
// Filter frequency parameter has initial static value of 400hz
var filter = new LowPassFilter(audiolet, 400);
// Change cutoff to 200hz
filter.frequency.setValue(200);
// Oscillate cutoff between 100hz and 200hz every second
var sine = new Sine(audiolet, 1); // 1hz LFO frequency
var mulAdd = new MulAdd(audiolet, 50, 150); // Scale -1 to 1 -> 100 to 200
sine.connect(mulAdd);
// Connect to filter input 1 (linked to the frequency parameter)
mulAdd.connect(filter, 0, 1);
AudioletGroups are objects which behave in exactly the same way as nodes; you can connect to and from them, with audio being received from the inputs, generated or processed, and pushed to the outputs. The difference between groups and nodes is that where nodes contain a JavaScript function which processes the audio, groups contain a small network of other nodes to carry out the processing. This allows you to encapsulate complex pieces of audio processing involving many nodes, and have it operate as if it were a single node.
Inside groups you will find PassThroughNodes, used as an equivalent to inputs and outputs, and ParameterNodes, an equivalent to parameters.
var Group = function(audiolet) {
AudioletGroup.call(this, audiolet, 2, 1);
this.saw = new Saw(audiolet, 200);
this.filter = new LowPassFilter(audiolet, 400);
// First group input controls oscillator frequency
this.inputs[0].connect(this.saw);
// Second group input controls filter frequency
this.inputs[1].connect(this.filter, 0, 1);
// Connect up DSP network to group output
this.saw.connect(this.filter);
this.filter.connect(this.outputs[0]);
};
extend(Group, AudioletGroup);
var group = new Group(audiolet);
group.connect(this.audiolet.output); // Behaves like a regular node
PassThroughNodes are a specialised type of node designed to pass data received at their inputs directly to their outputs in the most efficient way possible. They can be used either for analysis nodes (where an extra output is used to carry the output of the analysis) or, more commonly, as the inputs and outputs of groups. This allows you to connect nodes from outside the group to nodes contained inside the group via the pass through node.
ParameterNodes are another specialised type of node designed to mimic the behaviour of parameters inside a group. Parameter nodes contain a parameter linked to their only input, and the node's output consists of whatever value the parameter holds. By connecting the input to one of the group's input pass through nodes and connecting the output to an input of the node you want to control, the parameter node will act identically to a parameter in a regular node. To allow the parameter's static value to be set you can call this.parameter = this.parameterNode.parameter in the group's constructor.
// Additive synth - one oscillator at frequency, one at twice frequency
var Group = function(audiolet) {
AudioletGroup.call(this, audiolet, 1, 1);
this.frequencyNode = new ParameterNode(audiolet);
this.frequency = this.frequencyNode.parameter;
this.frequencyMul = new Multiply(audiolet, 2);
this.osc1 = new Sine(audiolet);
this.osc2 = new Sine(audiolet);
// Connect group input to frequency parameter
this.inputs[0].connect(this.frequencyNode);
// Connect frequency parameter to oscillators' frequency inputs
this.frequencyNode.connect(this.osc1);
this.frequencyNode.connect(this.frequencyMul);
this.frequencyMul.connect(this.osc2);
};
extend(Group, AudioletGroup);
var group = new Group(audiolet);
// Set static frequency value to 200hz
group.frequency.setValue(200);
// Oscillate frequency between 100hz and 200hz every second
var sine = new Sine(audiolet, 1); // 1hz LFO frequency
var mulAdd = new MulAdd(audiolet, 50, 150); // Scale -1 to 1 -> 100 to 200
sine.connect(mulAdd);
// Connect to group input 0 (linked to the frequency parameter)
mulAdd.connect(group);