Overview of SNDC files
Nodes
A SNDC source file is a collection of nodes. Each node is an instance of a particular module. A node is declared like so:
nodeName: moduleName {
/* list of inputs */
}
Each module has a set of inputs and outputs, whose type can be either FLOAT
,
BUFFER
or STRING
. Of course this is an ongoing project and further data
types will be added in the future. The inputs are set like so:
inputName: inputValue;
Some inputs are required and some are optional depending on the module.
inputValue
can be either a literal value, or the output of a node above
the current node. Example:
newNode: myModule { /* node named "newNode", instance of "myModule" */
input: n.out; /* "input" is set to an output of a previous node named n */
freq: 440; /* "freq" is set to a literal float */
interp: "sine"; /* "interp" is set to a literal string */
}
Let’s try it out with the simplest SNDC file that produces an audible sound,
save the following script in a file named test.sndc
:
o: osc {
function: "sin";
duration: 3;
freq: 440;
}
Then, play it with:
sndc_play test.sndc
It should play a simple tone at 440Hz during 3 seconds.
Modules
A module is basically the “type” of a node. Modules are either built-in or can be SNDC files, as we will see later on.
In the previous example, we used the osc
module, but there are many more. To
get a full list of sndc
’s built-in modules, simply type:
$ sndc -l
To get the description and specification of a particular module, type:
$ sndc -h <module>
For instance, sndc -h osc
will give the following output:
[generator] osc - A generator for sine, saw and square waves
Inputs:
function [STRING] [REQUIRED]
waveform: 'sin', 'square', 'saw' or 'input'
waveform [BUFFER] [OPTIONAL]
buffer containing waveform, used when 'input' is specified in 'function'
freq [BUFFER | FLOAT] [REQUIRED]
frequency in Hz
amplitude [BUFFER | FLOAT] [OPTIONAL]
amplitude in unit
p_offset [FLOAT] [OPTIONAL]
period offset, in period (1. = full period)
a_offset [FLOAT] [OPTIONAL]
amplitude offset, a constant that gets added to the resulting signal
param [FLOAT] [OPTIONAL]
the wave parameter, relevant for 'square' and 'saw'
duration [FLOAT] [REQUIRED]
duration of resulting signal in seconds
sampling [FLOAT] [OPTIONAL]
sampling rate, def 44100Hz
interp [STRING] [OPTIONAL]
interpolation of the resulting buffer, 'step', 'linear' or 'sine'
Outputs:
out [BUFFER] output signal
From the first line, we learn that the osc
module is in the “generator”
category, which means that it generates signals and buffers. Categories are just
an informal way of ordering modules and don’t enforce anything on the module.
Then we have two lists, inputs and outputs, with their names used to reference
them in SNDC fils (function
, freq
, etc), their types (note that one input
can accept several types) and whether they are required or not. Not providing a
required input will result in sndc
returning an error.
Inputs are what you use within the node declaration, outputs are what you refer to in subsequent nodes' inputs.
A simple example: frequency-oscillating tone
The following SNDC file produces a sine tone with an oscillating frequency:
frequency: osc {
function: "sin";
freq: 1;
duration: 5;
amplitude: 50;
a_offset: 440;
}
tone: osc {
function: "sin";
freq: frequency.out;
duration: 5;
}
The first node is of type osc
, it will output a sinusoidal function of
frequency 1, amplitude 50 and centered around 440 (refer to sndc -h osc
for a
description of osc
’s inputs).
The second node is also an osc
, it will produce a sinusoidal signal but with a
variable frequency given by the output of the frequency
node. In the above
description of osc
we see indeed that freq
can be either FLOAT
or
BUFFER
.
Try it yourself by copy-pasting the above SNDC script in a file and playing it with:
$ sndc_play <file>
Processing
When sndc
is invoked on a SNDC file, it will process the nodes sequentially in
the same order as they appear in the file. The node will read their inputs and
produce/set their outputs at once. Nodes never modify their inputs (they do not
process their inputs “in-place” as the underlying data might be shared by other
nodes).
Once processed, the nodes' outputs become available as inputs of the successive nodes.
Finally, by default, sndc
will output the raw data contained in the last node,
from the node’s output name out
or will not output anything at all if no such
output exists.
Visualizing signals and buffers
Visualizing what’s in the buffers is critical to debug node assemblies. While
sndc
doesn’t come with a graphical user interface and hence cannot plot the
waveforms and other functions in buffers, it does have a way to easily export
buffer content to files that can be easily plotted using specialized software
like Gnuplot: the print
module.
$ sndc -h print
[debug] print - Prints input buffer to specified file for plotting/analysis
Inputs:
in [BUFFER] [REQUIRED]
input buffer
file [STRING] [OPTIONAL]
output file name
Outputs:
That module takes a buffer in the in
slot and a file name in the file
slot,
it doesn’t have any output but when processed, it will create a file of the
given name containing all values in text form, one value per line, preceded by
their sample number.
Example:
o: osc {
function: "sin";
freq: 440;
duration: 1;
}
p: print {
in: o.out;
file: "o.out";
}
Calling sndc
on this file won’t output anything, because print
is the last
node and it doesn’t have any output. However, it will create o.out
which you
can plot with gnuplot
:
gnuplot -e 'set xrange [0:200]; plot "o.out" with lines; pause -1'