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.

  1. Static component properties (e.g. name, description, icon, ports etc) are defined by XML elements.
  2. 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) {… `

Version: 1.0.13-SNAPSHOT. Last Published: 2023-11-12.