# EEL2

EEL2 is a language that powers REAPER’s JSFX, Video Processors, ReaScript, and other functionality, and is also used by OSCII-bot. This document is purely for the core EEL2 functionality — each use of EEL2 will define functions specific to its context.

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

Variables do not need to be declared, are by default global, and are all double-precision floating point.

Variable names are NOT case sensitive, so a and A refer to the same variable.

Variable names may begin with a _, a-z, or A-Z, and can contain numbers after one of those characters.

The maximum variable name length is by default 127 characters.

Variable names can also contain . characters, though this is used for namespaced pseudo-objects.

There are a few predefined constant variables: $pi, $phi, and $e.

Parentheses “(” and “)” can be used to clarify precidence, contain parameters for functions, and collect multiple statements into a single statement.

A semicolon “;” is used to separate statements from eachother (including within parentheses).

A virtual local address space of about 8 million words (queryable at runtime via __memtop()) can be accessed via brackets “[” and “]”.

A shared global address space of at least 1 million words, accessed via gmem[]. These words are shared between all script instances (the implementation can choose to partition separate instances depending on necessity).

Shared global named variables, accessible via the “_global.” prefix. These variables are shared between all script instances.

User definable functions, which can define private variables, parameters, and also can optionally access namespaced instance variables. Recursion is NOT supported.

Numbers are in normal decimal, however if you prefix a ‘$x’ or ‘0x’ to them, they will be hexadecimal (e.g. $x90, 0xDEADBEEF, etc).

You may specify the ASCII value of a character using $’c’ or ‘c’ (where c is the character). Multibyte characters are also supported using ‘abc’.

If you wish to generate a mask of 1 bits in integer, you can use $~X, for example $~7 is 127, $~8 is 255, $~16 is 65535, etc.

Comments can be specified using:

// comments to end of line

/* comments block of code that span lines or be part of a line */

top Operator reference

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

[ ]

z=x[y];

x[y]=z;

Brackets are used to index memory. The sum of the value to the left of the brackets and the value within the brackets is used to index memory. If a value in the brackets is omitted then only the value to the left of the brackets is used.

z=gmem[y];

gmem[y]=z;

If ‘gmem’ is specified as the left parameter to the brackets, then the global shared buffer is used.

!value — returns the logical NOT of the parameter (if the parameter is 0.0, returns 1.0, otherwise returns 0.0).

-value — returns value with a reversed sign (-1 * value).

+value — returns value unmodified.

base ^ exponent — returns the first parameter raised to the power of the second parameter. This is also available the function pow(x,y)

numerator % denominator — converts the absolute values of numerator and denominator to integers (may be 32-bit or 64-bit integers depending on platform/OS/etc), returns the remainder of numerator divided by denominator.

value << shift_amt -- converts both values to 32 bit integers, bitwise left shifts the first value by the second. Note that shifts by more than 32 or less than 0 produce undefined results. value >> shift_amt — converts both values to 32 bit integers, bitwise right shifts the first value by the second, with sign-extension (negative values of y produce non-positive results). Note that shifts by more than 32 or less than 0 produce undefined results.

value / divisor — divides two values and returns the quotient.

value * another_value — multiplies two values and returns the product.

value – another_value — subtracts two values and returns the difference.

value + another_value — adds two values and returns the sum.

Note: the relative precedence of |, &, and ~ are equal, meaning a mix of these operators is evaluated left-to-right (which is different from other languages and may not be as expected). Use parentheses when mixing these operators.

a | b — converts both values to integers (may be 32-bit or 64-bit integers depending on platform/OS/etc), and returns bitwise OR of values.

a & b — converts both values to integers (may be 32-bit or 64-bit integers depending on platform/OS/etc), and returns bitwise AND of values.

a ~ b — converts both values to integers (may be 32-bit or 64-bit integers depending on platform/OS/etc), bitwise XOR the values.

value1 == value2 — compares two values, returns 1 if difference is less than 0.00001, 0 if not.

value1 === value2 — compares two values, returns 1 if exactly equal, 0 if not.

value1 != value2 — compares two values, returns 0 if difference is less than 0.00001, 1 if not.

value1 !== value2 — compares two values, returns 0 if exactly equal, 1 if not.

value1 < value2 -- compares two values, returns 1 if first parameter is less than second.
value1 > value2 — compares two values, returns 1 if first parameter is greater than second.

value1 <= value2 -- compares two values, returns 1 if first is less than or equal to second.
value1 >= value2 — compares two values, returns 1 if first is greater than or equal to second.

Note: the relative precedence of || and && are equal, meaning a mix of these operators is evaluated left-to-right (which is different from other languages and may not be as expected). Use parentheses when mixing these operators.

y || z — returns logical OR of values. If ‘y’ is nonzero, ‘z’ is not evaluated.

y && z — returns logical AND of values. If ‘y’ is zero, ‘z’ is not evaluated.

y ? z — how conditional branching is done — similar to C’s if/else

y ? z : x

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;

);

y = z — assigns the value of ‘z’ to ‘y’. ‘z’ can be a variable or an expression.

y *= z — multiplies two values and stores the product back into ‘y’.

y /= divisor — divides two values and stores the quotient back into ‘y’.

y %= divisor — converts the absolute values of y and divisor to integers (may be 32-bit or 64-bit integers depending on platform/OS/etc), returns and sets y to the remainder of y divided by divisor.

base ^= exponent — raises first parameter to the second parameter-th power, saves back to ‘base’

y += z — adds two values and stores the sum back into ‘y’.

y -= z — subtracts ‘z’ from ‘y’ and stores the difference into ‘y’.

y |= z — converts both values to integer, and stores the bitwise OR into ‘y’

y &= z — converts both values to integer, and stores the bitwise AND into ‘y’

y ~= z — converts both values to integer, and stores the bitwise XOR into ‘y’

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

( and ) (vs { } ) — enclose multiple statements, and the value of that expression is the last statement within the block:

z = (

a = 5;

b = 3;

a+b;

); // z will be set to 8, for example

Conditional branching is done using the ? or ? : operator, rather than if()/else.

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; ); The ? and ?: operators can also be used as the lvalue of expressions: (a < 5 ? b : c) = 8; // if a is less than 5, set b to 8, otherwise set c to 8 top Simple math functions sin(angle) -- returns the Sine of the angle specified (specified in radians -- to convert from degrees to radians, multiply by $pi/180, or 0.017453) cos(angle) -- returns the Cosine of the angle specified (specified in radians). tan(angle) -- returns the Tangent of the angle specified (specified in radians). asin(x) -- returns the Arc Sine of the value specified (return value is in radians). acos(x) -- returns the Arc Cosine of the value specified (return value is in radians). atan(x) -- returns the Arc Tangent of the value specified (return value is in radians). atan2(x,y) -- returns the Arc Tangent of x divided by y (return value is in radians). sqr(x) -- returns the square of the parameter (similar to x*x, though only evaluating x once). sqrt(x) -- returns the square root of the parameter. pow(x,y) -- returns the first parameter raised to the second parameter-th power. Identical in behavior and performance to the ^ operator. exp(x) -- returns the number e (approx 2.718) raised to the parameter-th power. This function is significantly faster than pow() or the ^ operator log(x) -- returns the natural logarithm (base e) of the parameter. log10(x) -- returns the logarithm (base 10) of the parameter. abs(x) -- returns the absolute value of the parameter. min(x,y) -- returns the minimum value of the two parameters. max(x,y) -- returns the maximum value of the two parameters. sign(x) -- returns the sign of the parameter (-1, 0, or 1). rand(x) -- returns a psuedorandom number between 0 and the parameter. floor(x) -- rounds the value to the lowest integer possible (floor(3.9)==3, floor(-3.1)==-4). ceil(x) -- rounds the value to the highest integer possible (ceil(3.1)==4, ceil(-3.9)==-3). invsqrt(x) -- returns a fast inverse square root (1/sqrt(x)) approximation of the parameter. top Loops Looping is supported in EEL2 via the following functions: loop(count,code) 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(code) 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(condition) ( code ) 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). top Strings Note: Implementations may choose not to implement string functions 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. Implementations may choose to impose a soft limit on the size of each string: attempts to grow a string past the limit will result in the string not being modified. Strings are always refered to by a number, so one can reference a string using a normal EEL2 variable: x = "hello world"; gfx_drawstr(x); Literal strings are immutable (meaning they cannot be modified). If you wish to have mutable strings, you have three choices: You can use the fixed values of 0-1023 (implementations define the number of indexed strings available): x = 50; // string slot 50 strcpy(x, "hello "); strcat(x, "world"); gfx_drawstr(x); This mode is useful if you need to build or load a table of strings. You can use # to get an instance of a temporary string: x = #; strcpy(x, "hello "); strcat(x, "world"); gfx_drawstr(x); Note that the scope of these temporary instances is very limited and unpredictable, and their initial values are undefined. Finally, you can use named strings, which are the equivalent of normal variables: x = #myString; strcpy(x, "hello world"); The value of named strings is defined to be empty at script load, and to persist throughout the life of your script. There is also a shortcut to assign/append to named strings: #myString = "hello "; // same as strcpy(#myString, "hello "); #myString += "world"; // same as strcat(#myString, "world"); top String functions strlen(str) -- returns length of string strcpy(str, srcstr) -- copies srcstr into str, returns str strcat(str, srcstr) -- appends srcstr to str, returns str strcmp(str, str2) -- compares str to str2, case sensitive, returns -1, 0, or 1 stricmp(str, str2) -- compares str to str2, ignoring case, returns -1, 0, or 1 strncmp(str, str2, maxlen) -- compares str to str2 up to maxlen bytes, case sensitive, returns -1, 0, or 1 strnicmp(str, str2, maxlen) -- compares str to str2 up to maxlen bytes, ignoring case, returns -1, 0, or 1 strncpy(str, srcstr, maxlen) -- copies srcstr into str, but stops after maxlen bytes. returns str strncat(str, srcstr, maxlen) -- appends srcstr to str, but stops after maxlen bytes of srcstr have been read. returns str strcpy_from(str,srcstr, offset) -- copies srcstr to str, starting offset bytes into srcstr. returns str. strcpy_substr(str,srcstr, offset, maxlen) -- copies srcstr to str, starting offset bytes into srcstr, and up to maxlen bytes. if offset is less than 0, offset is from end of source string. If maxlen is less than 0, length is limited to output string length shortened by maxlen. returns str. str_getchar(str, offset[, type]) -- returns the data at byte-offset offset of str. if offset is negative, position is relative to end of string. Type defaults to signed char, but can be specified to read raw binary data in other formats (note the single quotes, these are single/multi-byte characters): 'c' - signed char 'cu' - unsigned char 's' - signed short 'S' - signed short, big endian 'su' - unsigned short 'Su' - unsigned short, big endian 'i' - signed int 'I' - signed int, big endian 'iu' - unsigned int 'Iu' - unsigned int, big endian 'f' - float 'F' - float, big endian 'd' - double 'D' - double, big endian str_setchar(str, offset, value[, type]) -- sets the value at byte-offset "offset" of str to value (which may be one or more bytes of data). If offset is negative, then offset is relative to end of the string. If offset is the length of the string, or between (-0.5,0.0), then the character (or multibyte value if type is specified) will be appended to the string. sprintf(str,format, ...) -- copies format to str, converting format strings: %% = % %s = string from parameter %d = parameter as integer %i = parameter as integer %u = parameter as unsigned integer %x = parameter as hex (lowercase) integer %X = parameter as hex (uppercase) integer %c = parameter as character %f = parameter as floating point %e = parameter as floating point (scientific notation, lowercase) %E = parameter as floating point (scientific notation, uppercase) %g = parameter as floating point (shortest representation, lowercase) %G = parameter as floating point (shortest representation, uppercase) 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) Values for format specifiers can be specified as additional parameters to sprintf, or within {} in the format specifier (such as %{varname}d, in that case a global variable is always used). match(needle, haystack, ...) -- search for needle in haystack matchi(needle, haystack, ...) -- search for needle in haystack (case insensitive) 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 top 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 top Advanced Functions FFT/MDCT Not available in all implementations, but common mdct(start_index, size), imdct(start_index, size) Example: mdct(0, 512); Performs a modified DCT (or inverse in the case of imdct()) on the data in the local memory buffer at the offset specified by the first parameter. The second parameter controls the size of the MDCT, and it MUST be one of the following: 64, 128, 256, 512, 1024, 2048, or 4096. The MDCT takes the number of inputs provided, and replaces the first half of them with the results. The IMDCT takes size/2 inputs, and gives size results. Note that the MDCT must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly. The MDCT/IMDCT provided also provide windowing, so your code is not required to window the overlapped results, but simply add them. See the example effects for more information. fft(start_index, size), ifft(start_index, size) fft_real(start_index, size), ifft_real(start_index, size) fft_permute(index,size), fft_ipermute(index,size) Example: 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). convolve_c(dest,src,size) Used to convolve two buffers, typically after FFTing them. convolve_c works with complex numbers. The sizes specify number of items (the number of complex number pairs). Note that the convolution must NOT cross a 65,536 item boundary, so be sure to specify the offset accordingly. Memory Utility freembuf(top) The freembuf() function provides a facility for you to notify the memory manager that you are no longer using a portion of the local memory buffer. 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. memcpy(dest,source,length) The memcpy() function provides the ability to quickly copy regions of the local memory buffer. If the buffers overlap and either buffer crosses a 65,536 item boundary, the results may be undefined. memset(dest,value,length) The memset() function provides the ability to quickly set a region of the local memory buffer to a particular value. mem_multiply_sum(buf1,buf2,length) Sums the products of length items of buf1 and buf2. If buf2 is exactly -1, then sums the squares of items in buf1. If buf2 is exactly -2 then sums the absolute values of buf1. If buf2 is exactly -3 then sums the values of buf1. If buf2 is another negative value, the result is undefined. mem_insert_shuffle(buf,len,value) Shuffles buf to the right by one element, inserting value as buf[0], and returning the previous buf[len-1]. __memtop() -- returns the maximum memory words available to the script (read/writes to __memtop()[-1] will succeed, but __memtop()[0] will not) Stack A small (approximately 4096 item) user stack is available for use in code: stack_push(value) Pushes value onto the user stack, returns a reference to the value. stack_pop(value) Pops a value from the user stack into value, or into a temporary buffer if value is not specified, and returns a reference to where the stack was popped. Note that no checking is done to determine if the stack is empty, and as such stack_pop() will never fail. stack_peek(index) Returns a reference to the item on the top of the stack (if index is 0), or to the Nth item on the stack if index is greater than 0. stack_exch(value) Exchanges a value with the top of the stack, and returns a reference to the parameter (with the new value).