Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Visamp Docs

Expressions

Expressions compute values. Visript supports a full expression system with arithmetic, comparison, boolean logic, arrays, and function calls.

Literals

42          // Integer
3.14        // Float
"hello"     // String
true        // Boolean
false       // Boolean

Arithmetic

1 + 2       // 3
10 - 3      // 7
4 * 5       // 20
10 / 3      // 3.333... (division is always float)
10.0 / 3.0  // 3.333...
10 \ 3      // 3 (integer division)
10 % 3      // 1 (modulus keeps integers)

Mixed types are promoted to float:

1 + 2.5     // 3.5 (integer promoted to float)

Division always gives a float

+, - and * keep two integers as an integer, but / always produces a float1 / 2 is 0.5, not 0.

This matters because a lot of what you divide is an integer without looking like one. $TIME_MS and $FRAME_COUNT are integers, so under truncating division something like

on_frame {
  // Would step 0, 1, 2, 3 … one whole radian every 5 seconds
  angle = math::sin(rad: $TIME_MS / 5000)
}

would snap between whole values instead of moving smoothly.

% is left alone, because integer modulus is usually what you want:

on_frame {
  if $FRAME_COUNT % 60 == 0 {
    flash = true
  }
}

Integer division with \

When you want a whole number, use \. It divides and throws away the fraction, always giving an integer:

let columns = $WIDTH \ 100     // how many 100px columns fit
let bucket  = i \ 8            // group an index into eights

It accepts floats as well as integers — $WIDTH and $HEIGHT are floats, and requiring a conversion first would defeat the point:

7.5 \ 2     // 3

\ truncates toward zero, so -7 \ 2 is -3. If you want it to round down instead, use math::floor:

math::floor(value: -7 / 2)    // -4

Dividing by zero is an error, the same as with /.

Comparison

1 == 1      // true
1 != 2      // true
3 < 5       // true
5 > 3       // true
3 <= 3      // true
5 >= 4      // true

Boolean Logic

true && false   // false
true || false   // true
!true           // false

&& and || short-circuit: if the left side settles the answer, the right side is never worked out at all. That is what lets one side guard the other:

on_frame {
  // The division only happens when n is non-zero
  if n != 0 && total \ n > 5 {
    flash = true
  }
}

Both sides must be booleans. 1 && true is an error rather than treating non-zero as true — a number is not a truth value here.

Bitwise Operators

&, | and ^ work on the bits of whole numbers:

6 & 3       // 2   — bits set in both
6 | 3       // 7   — bits set in either
6 ^ 3       // 5   — bits set in exactly one

They are useful for packing several on/off flags into one property, or for cycling through a power-of-two range:

prop flags = 0

on_frame {
  flags = flags | 4          // turn a flag on
  if (flags & 4) > 0 {       // test it — note the parentheses
    pulse = 1.0
  }
}

Whole numbers only. 6.5 & 3 is an error rather than a silent truncation, since a bit pattern is not a meaningful notion for a float — use value \ 1 to truncate, or math::floor(value: value) \ 1 to round down and convert.

Note that & is a different operator from &&, and | from ||. The doubled forms are the boolean ones.

String Operations

"hello" + " " + "world"   // "hello world"

Arrays

[1, 2, 3]
[[100.0, 200.0], [300.0, 400.0]]

Indexing

Read a single element with [...], counting from zero:

let first = audio::detect::get_spectrum()[0]
let bass  = audio::detect::get_spectrum()[4]

The index can be any whole-number expression, and indexing chains:

let v = audio::detect::get_spectrum()[i * 2]
let y = points[1][0]

Reading past the end gives 0 rather than failing. That is deliberate: an index may be outside an array’s bounds, so an error would break every audio-reactive script the moment it fell silent. A negative index also reads as 0.

The index must have integer type. Use value \ 1 to truncate or math::floor(value: value) \ 1 to round down:

let v = audio::detect::get_spectrum()[$WIDTH \ 40]

Grouping

Use parentheses to control precedence:

(1 + 2) * 3    // 9
1 + (2 * 3)    // 7

Color Constructors

color::rgb(r: 1.0, g: 0.5, b: 0.0)
color::hsl(h: 0.5, s: 0.8, l: 0.5)

See Color Constructors for details.

Function Calls

my_func(x: 1.0, y: 2.0)

See Functions for details.

Operator Precedence

From lowest to highest. This follows C, which is what most languages use:

  1. || (logical or)
  2. && (logical and)
  3. | (bitwise or)
  4. ^ (bitwise xor)
  5. & (bitwise and)
  6. ==, !=
  7. <, <=, >, >=
  8. +, -
  9. *, /, \, %
  10. !, - (unary)
  11. [...] (indexing)
  12. () (grouping)

One consequence is worth knowing, because it surprises people in every language that inherits it: the bitwise operators bind more loosely than ==. So

flags & 4 == 4

groups as flags & (4 == 4), which is a type error rather than the test you meant. Parenthesise when you mix them:

(flags & 4) == 4

Assignment is a statement, not an operator, so it does not appear here. See Variables & Assignment for =, the compound forms and ++.

Creating and updating arrays

Engine 2.4.0 adds array::filled(count:, value:) and indexed assignment:

on_init {
  let values = array::filled(count: 4, value: -1)
  values[0] = 10
  values[1] += 2

  let rows = array::filled(count: 2, value: [0, 0])
  rows[0][1] = 7
}

Both constructor arguments are required. count must be a finite whole number from 0 to 65,536; value is evaluated once and copied into each element. Empty arrays ([]) are supported. Nested arrays are independent copies, so changing one row leaves the others unchanged. Assigning an array to another variable or passing it into a function also copies it; return the modified array to update the caller’s value.

Writes support =, +=, -=, *=, /= and %=. Each index is evaluated once, from left to right, followed by the right-hand expression. A write index must have integer type and be within the existing bounds. To convert a float use value \ 1 (or math::floor(value: value) \ 1 to round down). A failed bounds check or arithmetic operation leaves the target element unchanged and reports the statement’s source location. Writes never grow arrays. Audio snapshots such as audio::detect::get_spectrum() remain read-only; copy selected samples into an array created with array::filled or a literal.

The constructor also limits the total copied value size to 1,000,000 units: array nodes and scalar values each count as one, and strings additionally count by UTF-8 bytes. Copying consumes the existing execution budget, including when constructors are nested or called repeatedly in a loop. Large nested values can therefore reach the budget before the element-count limit.