LoopIT

RALGOL language reference

neuroConn GmbH

2026-06-05

Introduction

This is the reference for the RALGOL onboard scripting language: the syntax, and the function libraries available to a script. It complements the scripting guide, which is the tutorial; this document is the lookup table.

A note on scope: the device validates every script when you deploy it, checking that each function and field you use is actually available on that device. The functions listed here are the ones the standard libraries provide, but a specific device may ship a different or extended set. Treat this list as representative, and let the device’s deploy-time check be the authority.

Syntax

Script structure

A script has an interface block declaring its own fields and a script block that runs once per cycle. An optional requires {} block lists external variables (it is otherwise inferred).

1w interface {
  1w emit unsigned ticks;
} ral;

script {
  std::add(ral.ticks, 1) -> ral.ticks;
};

Interface block

Sizes are given in words (1w = 32 bits) or bits (12b). A field is at most 32 bits and may not cross a word boundary; the whole interface is a whole number of words. An execution-engine interface is named ral.

Field types:

Type Meaning
unsigned / signed Integer, with an optional {unit = ...} and {valid = ...}.
bool One-bit true/false.
enum { k = v, ... } Named integer states.
oneof Tagged union — one region decoded differently per mode.
reserved Padding; not exposed.

A field may also be an array, e.g. 4b unsigned channels[1..8].

Units and valid ranges:

  • {unit = 0.001 A} — the integer is interpreted in these physical units.
  • {valid = (0, [1:2000])} — allowed values are 0, or 1 through 2000. The set is a parenthesised list of single values and [low:high] (or [low:step:high]) ranges.

Field flags:

Flag Meaning
emit Stream the field continuously over LSL.
protected Readable by clients, not writable from outside the script.
hidden Not shown in discovery (still usable if the name is known).
persistent Value survives across script reloads (shareable between scripts).
const Fixed configuration value, set once.

Script block

Statements run top to bottom each cycle and end with ;.

  • Assignment expr -> target; stores the value on the left into the variable on the right.
  • Structured binding [a, b] -> [x, y]; assigns several values at once.
  • Function calls use namespace::name(args) and nest: std::add(1, std::add(2, 3)). The operators + - * / are shorthand for std::add, std::subtract, std::multiply, std::divide.
  • Literals are integers (true/false stand for 1/0). There are no floating-point literals; fractional quantities are handled by field units and the x1000 scaling convention (below).
  • Comments are // to end of line, or /* ... */.

Variables

A variable is module.index.field; the index counts instances from zero (ads.0.voltage_chan_1). For your own interface, ral.field is short for ral.0.field. Array elements and slices use [i], [a..b], [..].

Control flow

There are no loops (the script is the loop). Three if forms, each closed by fi;:

  • Truish — runs when the expression is non-zero: if (expr) : ... fi;
  • Equalityif (expr == N) : ... fi;
  • Switch-like — integer cases with is, optional else (which must be last):
1w interface {
  1w reserved;
} ral;

script {
  if (dio.0.digout_1)
    is 0: 1 -> dio.0.digout_1;
    is 1: 0 -> dio.0.digout_1;
  fi;
};

Stateful instances

Primitives that persist across cycles are declared once in a prolog {} block (which must be first in script {}) and bound to an @-name; methods use @name::method():

1w interface {
  1w emit signed average_x1000;
} ral;

script {
  prolog {
    let ringbuffer(64) -> @window;
  };
  @window::append(ads.0.voltage_chan_1);
  @window::mova() -> ral.average_x1000;
};

Library conventions

A few conventions apply across the function libraries:

  • Integer ABI. All arguments and return values are 64-bit integers. There is no floating-point type in a script.
  • x1000 scaling. Functions whose natural result is fractional return it scaled by 1000 (three decimal places). Angles are in milli-radians or milli-degrees, frequencies in millihertz, and so on; the description notes the scaling where it applies.
  • Variadic functions flatten their arguments. Where a signature shows values..., you may pass several scalars and/or array slices; they are flattened into one sequence. For example std::mean(ral.buffer[1..8]) averages the eight elements.
  • Element-wise vs. reducing. Some variadic functions reduce to a single value (sum, mean); others return one value per input (abs, sqrt, sort).
  • Integer math. Mathematical functions operate on integers and truncate, e.g. std::sqrt(10) is 3.

In the tables below, a signature like name(a, b) -> 1 means two arguments and one return value; name(values...) -> 1 is variadic reducing to one; name(values...) -> n returns as many values as it received.

std — standard library

Comparison

Each returns 1 (true) or 0 (false).

Signature Description
std::gt(a, b) a greater than b
std::lt(a, b) a less than b
std::ge(a, b) a greater than or equal
std::le(a, b) a less than or equal
std::eq(a, b) a equal to b
std::ne(a, b) a not equal to b

Logic

Signature Description
std::and(a, b) logical AND (1/0)
std::or(a, b) logical OR
std::not(a) logical NOT
std::xor(a, b) bitwise/logical XOR
std::nand(a, b) NOT AND
std::nor(a, b) NOT OR

Arithmetic

Signature Description
std::add(a, b) a + b (also the + operator)
std::subtract(a, b) a - b (also -)
std::multiply(a, b) a x b (also *)
std::divide(a, b) a / b; errors on division by zero (also /)
std::modulo(a, b) remainder a mod b; returns 0 if b is 0
std::power(a, b) a raised to the power b
std::abs(values...) -> n absolute value, element-wise
std::sqrt(values...) -> n integer square root, element-wise
std::sign(values...) -> n -1, 0, or 1 per element
std::exp(values...) -> n e^x, element-wise (integer)
std::log(values...) -> n natural log, element-wise (integer)
std::log10(values...) -> n base-10 log, element-wise (integer)
std::gcd(a, b) greatest common divisor
std::lcm(a, b) least common multiple
std::factorial(n) n! (memoised)
std::clamp(x, lo, hi) x limited to the range [lo, hi]
std::is_even(values...) -> n 1 if even, else 0, per element
std::is_odd(values...) -> n 1 if odd, else 0, per element
std::is_prime(n) 1 if n is prime, else 0

Statistics (reduce a sequence to one value)

Signature Description
std::sum(values...) sum of all elements
std::prod(values...) product of all elements
std::min(values...) minimum
std::max(values...) maximum
std::range(values...) max - min
std::mean(values...) arithmetic mean
std::median(values...) median
std::variance(values...) population variance
std::std_dev(values...) population standard deviation
std::sum_of_squares(values...) sum of each element squared
std::rms(values...) root mean square
std::skewness(values...) skewness
std::kurtosis(values...) excess kurtosis (- 3)
std::harmonic_mean(values...) harmonic mean
std::geometric_mean(values...) geometric mean
std::iqr(values...) interquartile range (Q3 - Q1)

Vector operations (return a sequence)

Signature Description
std::sort(values...) -> n ascending sort
std::reverse(values...) -> n reverse order
std::mode(values...) -> n most frequent value(s)
std::zscore(values...) -> n standardised score per element
std::assign(a, b) assign (exposed form of the built-in assignment)

Data generation

Signature Description
std::random(size, max) sequence of size random integers in [0, max]
std::iota(start, end) sequence start, start+1, …, end

mntg — montage helpers

Signature Description
mntg::bipolarize(a, b) bipolar derivation a - b
mntg::laplacian(c, n1, n2, n3, n4) surface Laplacian: centre - mean of 4 neighbours

rgu — phase utilities

For phase-locked stimulation. Angles in milli-radians (mRad) or milli-degrees (mdeg), frequencies in millihertz (mHz), all x1000.

Signature Description
rgu::phase_wrap_deg(phi_mRad) wrap a phase into [0, 360000) and convert mRad -> mdeg
rgu::delta_phase_deg(freq_mHz, latency_samples, rate_hz) phase-advance correction for the system’s per-sample latency, in mdeg
rgu::phase_in_window(phi_mDeg, lo_mDeg, hi_mDeg) 1 if the phase is inside the window [lo, hi) (handles wrap-around), else 0
rgu::sine_wave(t_ms, freq_mHz, phase_mRad) sine value: sin(2*pi*t*freq + phase) x 1000 + 1000

pst — phase-specific stimulation

Specialised real-time helpers. EEGSyncTMS is an experimental, hardware-specific state machine; its argument units are still being finalised.

Signature Description
pst::sine_wave(t_ms, freq_mHz, phase_mRad) sine value (same as rgu::sine_wave)
pst::EEGSyncTMS(ch0, ch1, trigger, trigger_number, trigger_frequency, trigger_phase) EEG phase-synchronised TMS trigger; returns 1 when it fires

ringbuffer — rolling-window analysis

Declared with let ringbuffer(N) -> @name; in the prolog and used through the method-call form @name::method(...). Analysis results are scaled x1000.

Method Description
@buf::append(sample) push one sample into the window
@buf::reset() empty the window, keep the storage
@buf::clear() release the storage
@buf::mova() moving average over the window (x1000)
@buf::sd() sample standard deviation, Bessel-corrected (x1000)
@buf::fft(bin) magnitude of a single DFT bin (x1000)
@buf::fft_phase(bin) phase of a single DFT bin, in milli-radians (x1000)

io — diagnostics

Signature Description
io::print(x) write a value to the device log (for debugging)

harvard — study helpers

Signature Description
harvard::trialduration(bsl_cycles, stim_cycles, dead_cycles, m_off_freq, s_off_freq, i_freq) estimated trial duration for a stimulation protocol

References

  • The scripting guide (tutorial) and the device-specific function list are available from neuroConn.
LoopIT documentation · neuroConn