Home

Awesome

EEL2 is a language that powers REAPER's JSFX, LiveProgVST, and is also used by JamesDSP. This document is purely for the core EEL2 functionality.


Basic Language Attributes

The core of EEL2 has many similarities to C but is distictly different. Some notable qualities of this language are:


Operator reference

Listed from highest precedence to lowest (but one should use parentheses whenever there is doubt!):

z=x[y];
x[y]=z;

If y is non-zero, executes and returns z, otherwise executes and returns x (or 0.0 if ': x' is not specified).

Note that the expressions used can contain multiple statements within parentheses, such as:

x % 5 ? (
f += 1;
x *= 1.5;
) : (
f = max(3,f);
x = 0;
);

Some key notes about the above, especially for C programmers:

z = (a = 5; b = 3; a+b;); // z will be set to 8, for example
a < 5 ? b = 6; // if a is less than 5, set b to 6
a < 5 ? b = 6 : c = 7; // if a is less than 5, set b to 6, otherwise set c to 7
a < 5 ? ( // if a is less than 5, set b to 6 and c to 7
b = 6;
c = 7; );
(a < 5 ? b : c) = 8; // if a is less than 5, set b to 8, otherwise set c to 8

Simple math functions


Loops

Looping is supported in EEL2 via the following functions:

loop(32,
    r += b;
    b = var * 1.5;
    );

Evaluates the first parameter once in order to determine a loop count. If the loop count is less than 1, the second parameter is not evaluated. Implementations may choose to limit the number of iterations a loop is permitted to execute (usually such limits are in the millions and should rarely be encountered).

The first parameter is only evaluated once (so modifying it within the code will have no effect on the number of loops). For a loop of indeterminate length, see while() below.

while(
a += b;
b *= 1.5;
a < 1000; // as long as a is below 1000, we go again.
);

Evaluates the first parameter until the last statement in the code block evaluates to zero.

Implementations may choose to limit the number of iterations a loop is permitted to execute (usually such limits are in the millions and should rarely be encountered).

while ( a < 1000 ) (
a += b;
b *= 1.5;
);

Evaluates the parameter, and if nonzero, evaluates the following code block, and repeats. This is similar to a C style while() construct.

Implementations may choose to limit the number of iterations a loop is permitted to execute (usually such limits are in the millions and should rarely be encountered).


User defined functions and namespace pseudo-objects

EEL2 supports user defined functions, as well as some basic object style data access.

Functions can be defined anywhere in top level code (i.e. not within an existing () block, but before or after existing code). Functions are not able to be called recursively -- this is enforced by functions only being able to call functions that are declared before the current function, and functions not being able to call themselves. Functions may have 0 to 40 parameters. To define a function, use the following syntax:

function getSampleRate()
(
  srate; // return srate
);

function mySine(x)
(
  // taylor approximation
  x - (x^3)/(3*2) + (x^5)/(5*4*3*2) - (x^7)/(7*6*5*4*3*2) + (x^9)/(9*8*7*6*5*4*3*2);
);

function calculateSomething(x y)
(
  x += mySine(y);
  x/y;
);

Which would then be callable from other code, such as:

y = mySine($pi * 18000 / getSampleRate());
z = calculateSomething(1,2);

Note that the parameters for functions are private to the function, and will not affect global variables. If you need more private variables for a function, you can declare additional variables using a local() statement between the function declaration and the body of the function. Variables declared in the local() statement will be local to that function, and persist across calls of the function. Example:

function mySine(x) local(lastreq lastvalue)
(
  lastreq != x ? (
  lastreq = x; // save last input
  // taylor approximation
  lastvalue = x - (x^3)/(3*2) + (x^5)/(5*4*3*2) - (x^7)/(7*6*5*4*3*2) + (x^9)/(9*8*7*6*5*4*3*2);
  );
  lastvalue; // result of function is cached value
);

In the above example, mySine() will cache the last value used and not perform the calculation if the cached value is available. Note that the local variables are initialized to 0, which happens to work for this demonstration but if it was myCosine(), additional logic would be needed.

EEL2 also supports relative namespaces on global variables, allowing for pseudo object style programming. Accessing the relative namespace is accomplished either by using a "this." prefix for variable/function names, or by using the instance() declaration in the function definition for variable names:

function set_foo(x) instance(foo)
(
  foo = x;
);
// or
function set_foo(x)
(
  this.foo = x;
);

whatever.set_foo(32); // whatever.foo = 32;
set_foo(32); // set_foo.foo = 32;

function test2()
(
  this.set_foo(32);
);
whatever.test2(); // whatever.foo = 32

Additionally functions can use the "this.." prefix for navigating up the namespace hierarchy, such as:

function set_par_foo(x)
(
  this..foo = x;
);
a.set_par_foo(1); // sets foo (global) to 1
a.b.set_par_foo(1); // sets  a.foo to 1

Advanced Mathematics, Optimization, Linear algebra and Signal Processing Functions

Short time Fourier transform

Compute STFT on equispace sampled signal, following function's state variables occupy virtual local address space

Example:

fftsize = 1024;
stftIndexLeft = 0;
stftStructLeft = 50;
requiredSamples = stftInit(stftIndexLeft, stftStructLeft);
memreq = stftCheckMemoryRequirement(stftIndexLeft, fftsize, 4, 1.5);
// Fill up buffer
...
// Perform forward transform
spectralLen = stftForward(inBufLeft, stftIndexLeft, stftStructLeft, 1);
// Modify spectrum
...
// Perform inverse transform
error = stftBackward(inBufLeft, stftIndexLeft, stftStructLeft, 1);

The STFT provided also provide windowing, so your code is not required to window the overlapped results, but simply fill up the buffer. See the example codes for more information.

Fast Fourier transform

buffer=0;
fft(buffer, 512);
fft_permute(buffer, 512);
buffer[32]=0;
fft_ipermute(buffer, 512);
ifft(buffer, 512);
// need to scale output by 1/512.0, too.

Performs a FFT (or inverse in the case of ifft()) on the data in the local memory buffer at the offset specified by the first parameter. The size of the FFT is specified by the second parameter, which must be 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, or 32768. The outputs are permuted, so if you plan to use them in-order, call fft_permute(buffer, size) before and fft_ipermute(buffer,size) after your in-order use. Your inputs or outputs will need to be scaled down by 1/size, if used.

Note that the FFT/IFFT require real/imaginary input pairs (so a 256 point FFT actually works with 512 items).

Note that the FFT/IFFT must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly.

The fft_real()/ifft_real() variants operate on a set of size real inputs, and produce size/2 complex outputs. The first output pair is DC,nyquist. Normally this is used with fft_permute(buffer,size/2).

Note that the convolution must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly.

Fractional delay line

Delay input signal by arbitrary amount

Example:

ptr1 = 0;
// Initialize
req = fractionalDelayLineInit(ptr1, 1024);
// Set delay
fractionalDelayLineSetDelay(ptr1, 15.1);
// Performing some sample-by-sample processing
delayedOutput = fractionalDelayLineProcess(ptr1, input);

The fractional delay can be change anytime during processing. See the example codes for more information.

Direct form FIR Filter

Perform FIR filtering on purely time domain

Example:

coefficients = 0;
// Import coefficients from string
hLen = importFLTFromStr("0.0446871259733802,0.207161817163176,0.333544729155861,0.333544729155861,0.207161817163176,0.0446871259733802,-0.0538264189367632,-0.05743465917334062", coefficients);
ptr1 = coefficients + hLen;
req = FIRInit(ptr1, hLen);
// Processing
output = FIRProcess(ptr1, input, coefficients);

Real time FFT convolution

Perform FIR filtering on frequency domain with non-uniform partitioned convolution algorithm

Example:

// Initialization
requiredSamples = 1024;
h1 = 0;
impulseResponseLength = importFLTFromStr("IMPULSE1", h1);
h2 = h1 + impulseResponseLength;
impulseResponseLength = importFLTFromStr("IMPULSE2", h2);
h3 = h2 + impulseResponseLength;
impulseResponseLength = importFLTFromStr("IMPULSE3", h3);
h4 = h3 + impulseResponseLength;
impulseResponseLength = importFLTFromStr("IMPULSE4", h4);
convId = Conv1DInit(requiredSamples, impulseResponseLength, h1, h2, h3, h4);
inBufLeft = h4 + impulseResponseLength;
outBufLeft = inBufLeft + requiredSamples;
inBufRight = outBufLeft + requiredSamples;
outBufRight = inBufRight + requiredSamples;
// Processing
inBufLeft[bufpos] = spl0;
spl0 = outBufLeft[bufpos];
inBufRight[bufpos] = spl1;
spl1 = outBufRight[bufpos];
bufpos += 1;
bufpos >= requiredSamples ?
(
  Conv1DProcess(convId, inBufLeft, inBufRight);
  idx = 0;
  loop(requiredSamples,
  outBufLeft[idx] = inBufLeft[idx];
  outBufRight[idx] = inBufRight[idx];
  idx+=1);
  bufpos = 0;
);

Butterworth subband transform

Decompose signal to N channels time domain subbands

Example:

iirBPS = 0;
// Initialize 3 bands
reqSize = IIRBandSplitterInit(iirBPS, 48000, 4000, 12000); // Specify 2 cut-off frequencies
kDelta = iirBPS + reqSize;
kDelta[0] = 1; // Kronecker delta
sigLen = 1024;
low = 0;
mid = 0;
high = 0;
// Processing
// ...
IIRBandSplitterProcess(iirBPS, kDelta[idx], low, mid, high);
// ...
// Reset states
IIRBandSplitterClearState(iirBPS);

Constant-Q polyphase filterbank

Decompose signal to N channels decimated time domain subbands with logarithmic centre frequencies

Example:

// Initialization
N = 8;
m = 3;
pfb1 = 0;
memSize = InitPolyphaseFilterbank(srate, N, m, pfb1, pfb2);
PolyphaseFilterbankChangeWarpingFactor(pfb1, srate, 0.99);
phaseCorrFilt = pfb2 + memSize;
corrFiltLen = PolyphaseFilterbankGetPhaseCorrector(pfb1, 0.5, phaseCorrFilt);
i = 0;
loop(corrFiltLen, 
//printf("%1.7f,", phaseCorrFilt[i]);
i += 1);
Sk = phaseCorrFilt + corrFiltLen;
PolyphaseFilterbankGetDecimationFactor(pfb1, Sk);
subbandOutputLeft = Sk + N;
subbandOutputRight = subbandOutputLeft + N;
decimationCnt = subbandOutputRight + N;
eqGain = decimationCnt + N;
eqGain[0] = 1;
eqGain[1] = 0;
eqGain[2] = 1;
eqGain[3] = 0;
eqGain[4] = 1;
eqGain[5] = 0;
eqGain[6] = 1;

// Processing
PolyphaseFilterbankAnalysisStereo(pfb1, pfb2, subbandOutputLeft, subbandOutputRight, decimationCnt, spl0, spl1);
// Modify subbands
i = 0;
loop(N, 
decimationCnt[i] == Sk[i] ? (
subbandOutputLeft[i] = subbandOutputLeft[i] * eqGain[i];
subbandOutputRight[i] = subbandOutputRight[i] * eqGain[i];
);
i += 1);
PolyphaseFilterbankSynthesisStereo(pfb1, pfb2, subbandOutputLeft, subbandOutputRight, y1, y2);
spl0 = y1;
spl1 = y2;

// Reset states
PolyphaseFilterbankClearState(iirBPS);

The subband transform supposed work on time series. See the example codes for more information.

Autoregressive model

Decompose signal to N channels time domain subbands, following function's state variables occupy virtual local address space

Example:

r_ = 0;
// Define time domain signal r_
inLen = importFLTFromStr("ARRAY OF SAMPLE", r_);
order = 31;
getPredictionState = 1;
memReq = arburgCheckMemoryRequirement(order, getPredictionState);
burg = r_ + inLen;
predictionCoefficients = burg + memReq;
reflectionCoefficient = predictionCoefficients + (order + 1);
arburgTrainModel(burg, order, getPredictionState, r_, inLen);
arburgGetPredictionReflectionCoeff(burg, predictionCoefficients, reflectionCoefficient);
printf("predictionCoefficients: ");
idx = 0;
loop((order + 1),
printf("%1.8f, ", predictionCoefficients[idx]);
idx += 1;
);
printf("\nreflectionCoefficient: \n");
idx = 0;
loop((order + 1),
printf("%1.8f, ", reflectionCoefficient[idx]);
idx += 1;
);
printf("\nBackward regression: \n");
idx = 0;
loop(1000,
printf("%1.8f, ", arburgPredictBackward(burg));
idx += 1;
);
printf("\nForward regression: \n");
idx = 0;
loop(1000,
printf("%1.8f, ", arburgPredictForward(burg));
idx += 1;
);

** Miscellaneous

** Linear algebra

** Optimization

Following are constrained optimization, altough ordinary least squares(OLS) is a type of optimization, the built-in routines are included in Linear algebra section. The actual usage is somehow similar to the Matlab counterparts.

The function description is not intuitive, for actual use, please check out the examples. Important The solution computed by the optimization algorithm is not identical to Matlab counterpart, but the solution is quite close, the optimization routine author had use the routine on optimization-based digital filter design, the design result is almost identical to the filter design Matlab routines.


Strings

Strings can be specified as literals using quotes, such as "This is a test string". Much of the syntax mirrors that of C: you must escape quotes with backslashes to put them in strings ("He said "hello, world" to me"), multiple literal strings will be automatically concatenated by the compiler. Unlike C, quotes can span multiple lines. Internal strings lookup was implemented in a growable container.

Strings are always referred to by a number, so one can reference a string using a normal EEL2 variable:

x = "hello world";
printf("%s\n", x);
printf("Val = %f", 3.4488);

String functions

Many standard C printf() modifiers can be used, including:

+ %.10s = string, but only print up to 10 characters
+ %-10s = string, left justified to 10 characters
+ %10s = string, right justified to 10 characters
+ %+f = floating point, always show sign
+ %.4f = floating point, minimum of 4 digits after decimal point
+ %10d = integer, minimum of 10 digits (space padded)
+ %010f = integer, minimum of 10 digits (zero padded)

The printf will print the value to stdout if the Host application is a CLI application, the string will be printed to logcat if the Host application have no GUI and not necessary limit to CLI application, however, if Host application have a GUI, for example, a executable or VST plugin, author had implement a string circular buffer to simulate the way CLI behave.

For these you can use simplified regex-style wildcards:

* = match 0 or more characters
*? = match 0 or more characters, lazy
+ = match 1 or more characters
+? = match 1 or more characters, lazy
? = match one character

Examples:

match("*blah*", "this string has the word blah in it") == 1
match("*blah", "this string ends with the word blah") == 1

You can also use format specifiers to match certain types of data, and optionally put that into a variable:

%s means 1 or more chars
%0s means 0 or more chars
%5s means exactly 5 chars
%5-s means 5 or more chars
%-10s means 1-10 chars
%3-5s means 3-5 chars.
%0-5s means 0-5 chars.
%x, %d, %u, and %f are available for use similarly
%c can be used, but can't take any length modifiers
Use uppercase (%S, %D, etc) for lazy matching 

The variables can be specified as additional parameters to match(), or directly within {} inside the format tag (in this case the variable will always be a global variable):

match("*%4d*","some four digit value is 8000, I say",blah)==1 && blah == 8000
match("*%4{blah}d*","some four digit value is 8000, I say")==1 && blah == 8000

Misc Utility Memory Utility

For example, if the user changed a parameter on your effect halving your memory requirements, you should use the lowest indices possible, and call this function with the highest index you are using plus 1, i.e. if you are using 128,000 items, you should call freembuf(128001); If you are no longer using any memory, you should call freembuf(0);

Note that calling this does not guarantee that the memory is freed or cleared, it just provides a hint that it is OK to free it.

Stack A small (approximately 32768 item) user stack is available for use in code: