Documentation
Scripting
Introduction
One of the design objectives was ability to export designs into a variety of ways. For example, the design may be exported as VST plugin or VST instrument, in-browser effect, for implementation on a small CPU (e.g. STM32). And, we don’t want to support different environments by having a virtual machine - this just doesn’t cut it in terms of performance, memory footprint, startup time etc.
In order to achieve these goals, components had to be written in such a way that allows the export function to work without having separate component implementations for every language and for every environment.
Scripting is not fully exposed to end users yet. We are working hard on this.
Component model
We use XML to define component model. There are essentially two kinds of things we need to define for a component.
- Static component properties (e.g. name, description, icon, ports etc) are defined by XML elements.
- Component code for initialisation, execution and de-initialisation is defined in the payload of the special init, exec and end XML elements.
It’s all wrapped in a single XML file. Let’s look at an example.
<?xml version="1.0" encoding="UTF-8" ?> <component name="Noise" description="Noise generator" category="Primitives" package="dsp.graph.components.scripted"> <outputs> <output label="Out" name="out" description="The noisy output "/> </outputs> <controls> <control displayMode="POT" label="Amp" max="1.0" min="0.0" name="amp" /> </controls> <exec> $out = (float) ( $amp * (2 * #random() - 1) ); </exec> </component>
This defines the Noise component with a single output and a single control port. It has no data and no initialisation, and exec function does some calculation.
Defining ports
Input and output ports are defined in a single XML element (either input or output) with following attributes:
Attribute | Description |
---|---|
label | Port label. |
name | Port name - use in your script when you want to read input or write to output |
description | A short text description, usually shown in port tooltip |
Defining control ports
Control ports require additional information. They are defined with a control element which has following attributes:
Attribute | Description |
---|---|
label | Port label in GUI. |
name | Port name - use in your script when you want to read control value |
description | A short text description, usually shown in port tooltip |
min | Port minimum value |
max | Port maximum value |
def | port default value. Ports will be initialised to this value unless in PORT display mode |
displayMode | Either POT, PORT or SWITCH |
Additionally, control XML element can have children as follows:
Child XML element | Description |
---|---|
change | Can contain a script to be executed when control value is changed |
value | For control ports in SWITCH mode only, this allows us to define switch values and labels. |
Let’s look at another example to see how this works
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <component name="sample" description="sample component" package="dsp.script"> <data> int $buffLen; float $someFloat; float[] $someFloatArray; </data> <inputs> <input name="a" label="a" description="An input port"/> <input name="b"/> </inputs> <controls> <control displayMode="POT" label="Amp" max="1.0" min="0.0" name="amp" /> <control displayMode="SWITCH" label="Range" max="2.0" min="0.0" name="range"> <value val="0" label="Low"/> <value val="1" label="Mid"/> <value val="2" label="High"/> <change> switch($intValue) { case 0: { someFloat = 2; } case 1: { someFloat = 3; } default: { someFloat = 5; } } </change> </control> </controls> <outputs> <output label="a+b" name="out"/> </outputs> <init> $someFloat = 1; $buffLen = (int)($sampleRate / 2); $someFloatArray = #makeFloat(32); $amp = 1; </init> <exec> $out = $amp * #math:sin(6.28 * ( $a + $b )); </exec> <end> </end> </component>
Component code - scripting
The philosophy behind the scripting “language” (it really isn’t a formal language) is that we use the lowest common denominator between Java, C, C++ and JavaScript. With the following quirks:
Quirk | Description | When to use | Example |
---|---|---|---|
$ prefix on all variables | regardless of scope, all variables must be prefixed with $ sign | Always | $amp refering to value of control port named amp |
Variable injection | Ports are injected as variables named using either name or label attribute in port definition. Before your script is executed, appropriate declarations and assignments are injected. After your script completes resulting values are assigned back to ports. | Always | $out = $amp assigns output port out with value of control port amp. |
# prefix on all functions | regardless of scope, all functions must be prefixed with # sign | Always | #makeArray() referring to special function makeArray |
Special variables | There are a few special variables available. See section on special variables below. | Access to context data e.g. sample rate | $sampleRate will be injected in all scripts and will hold current context sample rate |
Functions | For user defined or built-in functions. See documentation section on functions | General purpose functions | #base:rms() referring to function rms in package base |
Special functions | There are a few special functions available. See section on special functions. | array allocation, math functions random | #makeArray() referring to special function makeArray |
Data Types
Script variables can be primitive types common for C and Java - specifically int, float, double and array types int[] and float[]. Note - when translating to C, array references are automatically converted to pointers, and memory allocation is automatically handled.
In addition to above, function return can be void - meaning that function does not return a value. See functions documentation for more info.
So, supported data types are:
Type name | Description | Example declaration |
---|---|---|
int | Integer number. Bit size is platform dependent. Components, functions and scripts shipping with Spektar assume 32-bit minimum. | int $myInt = 10; |
int[] | Integer array. In Java this will be reference, in C this will be a pointer type. Either way, before using the array it must be initialised with #makeInt(int size). | int[] $myInt; |
float | Floating point number. Components, functions and scripts shipping with Spektar assume 32-bit floats. | float $myFloat = 10.f; |
float[] | Float array. In Java this will be reference, in C this will be a pointer type. Either way, before using the array it must be initialised with #makeFloat(int size). | float[] $myFloat; |
double | Double precision number. Bit size is platform dependent. | double $d; |
Additional types are built-in types for essential DSP data structures.
Type name | Description |
---|---|
buffer | Float buffer. This type behaves as a struct with following fields: |
struct buffer { float* buffer; int len; int channels; int sampleRate; }
Example:
buffer $b = #makeBuffer(); $b.len = 32; $b.buffer = #makeFloat( $b.len );
Type name | Description |
---|---|
buffer8 | Byte buffer. This type behaves as a struct with following fields: |
struct buffer8 { uint8_t* data; long int len; }
Example:
buffer8 $b = #makeBuffer8();
Type name | Description |
---|---|
resource | Resource info and content. This type behaves as a struct with following fields: |
struct resource { int id; char name[256]; uint8_t type; char resolved[256]; int size; }
Example:
resource $r = #makeResource();
Structs
Structs are an experimental feature presently supported only within a function’s tribe (can’t be used across tribes for the moment).
Here is an example struct:
/** First test struct. @member floatVal A float value @member intVal An integer value @member intArray An int array value */ struct test1 { float $floatVal; int $intVal; int[] $intArray; }
Struct definition must be in a file of it’s own and must have an extension .str. Structs belong to tribes, based on their file location. Currently, structs can only hold basic types (int, float, double, int[] and float[]).
Component code - declarations
The data section allows us to define variables in the scope of this component. Same rules apply as for scripts, however mind that you cannot initialise values here. These are just declarations, any initialisation should occur in the init section.
If we put this into the context of translated code, for Java, these delarations will be
Special variables
Following special variables are available in scripts.
Variable | Description | Example |
---|---|---|
sampleRate | Current context sample rate. | $buffLen = (int)($sampleRate / 2); |
value | Injected for controls change script to indicate control current value | ` | |intValue| Injected for controlschangescript |switch(intValue) {… ` |