Runtime Bindings

expressions runtime bindings

Runtime Bindings

Some audio attributes accept a binding rather than a plain number: the value you configure can be a literal, a direct lookup of a property on the triggering event, or a full expression evaluated against live game state. The form is chosen automatically from what you write, so the simple cases stay simple and the dynamic cases are available when you need them.

Where Bindings Apply

ConsumerBinding attributes
source "synth" inside a route blockpitch, velocity
effect blocks inside synth_effectsfeedback, wet_level (reverb); low_gain, mid_gain, high_gain, low_freq, high_freq (equalizer); threshold, ratio, makeup_gain (compressor)

Only these attributes are bindings. On a synth source, the sibling attributes weight, duration, sustain, and the position block take literal values only. And the same effect block written inside a mixer channel or bus takes literal numbers — expression parameters are accepted only in a top-level synth_effects block.

synth_trigger is retired. Bindings used to be written on a synth_trigger block. That block is no longer authorable — a .cade file containing one fails to load with an error pointing at the replacement. Author the voice as a top-level route with a source "synth" instead; pitch and velocity expressions carry over verbatim. See Synthesis.

Binding Forms

A binding attribute accepts three forms. You do not declare which one you are using — Cade picks the form from the value.

Literal — a fixed number. No work happens at trigger time beyond returning the value.

route {
  owners = [event_handler.bumper_hit_sound]

  source "synth" "bumper_hit" {
    weight   = 1
    pitch    = 880
    velocity = 1.0
  }
}

Event-property lookupfrom:event.<field> reads a numeric property off the triggering event. This is the one binding form that must be quoted, since from:event.velocity is not valid bare syntax.

route {
  owners = [event_handler.bumper_hit_sound]

  source "synth" "bumper_hit" {
    weight   = 1
    pitch    = "from:event.pitch"
    velocity = "from:event.velocity"
  }
}

from:event. is the only lookup prefix — there is no from:var. or from:signal.. To read a variable, write it directly as an expression.

Expression — anything that is not a literal or a from: lookup is evaluated as an expression against live game state. Write the expression bare, as everywhere else in the config; a fully quoted expression parses to the same thing and is accepted for back-compat.

route {
  owners = [score.jackpot]

  source "synth" "chime" {
    weight   = 1
    pitch    = 440 + (score / 1000000) * 220
    velocity = min(var.bumper_count / 5, 1.0)
  }
}

Effect parameters take the same forms:

synth_effects "ball_fx" {
  effect "reverb" {
    feedback  = var.tension * 0.5   # expression
    wet_level = 0.3                 # literal
  }
}

Expression Namespace

Inside an expression-form binding, the following identifiers are available.

IdentifierValue
scoreThe active player’s current score. A user variable named score cannot shadow this built-in — score is a reserved name.
var.<name>The named game variable’s current value.
event.<field>A numeric property on the triggering event, for example event.velocity.

signal.<name> is recognized by the binding syntax but is not yet available in a running table — no shipped host supplies signal state to the audio binding context, so a binding that references signal.<name> fails at trigger time and the sound is skipped. Gate on signals with a condition on the owning handler instead.

For the operators, comparisons, ternary, and math functions usable inside these expressions, see Console Expressions. Note that binding arithmetic is floating-point — unlike the integer-first arithmetic of scoring expressions — so score / 1000000 keeps its fractional part here.

Evaluation Model

A binding is compiled once and then resolved on every trigger. At resolution, every identifier reflects state at that exact instant:

  • score is the active player’s score right now.
  • var.<name> is the variable’s current value.
  • event.<field> reads from the event that caused this trigger.

Literal and event-property bindings do effectively no work at trigger time. Expression bindings cost a full evaluation, so for parameters on a hot path prefer a literal or an event-property lookup unless the parameter genuinely needs cross-cutting context — for example scaling velocity by a current multiplier.

Error Handling

Bindings fail closed. When a binding cannot resolve, the value is not substituted with zero — the trigger is skipped and a warning is logged, so the sound simply does not play.

ConditionWhen surfacedResult
Malformed expression syntax in a route’s pitch / velocitycade validate and config loadValidation error naming the synth and the field
Malformed expression syntax in a synth_effects parameterConfig applyThe effects block is skipped with a logged warning
Reference to an undefined variable, or to signal.<name>Trigger timeTrigger skipped, warning logged
from:event.<field> naming a field the event does not carryTrigger timeTrigger skipped, warning logged
Expression producing a non-numeric resultTrigger timeTrigger skipped, warning logged

Only syntax is checked ahead of time. A binding that references a variable you later renamed still validates cleanly and only goes quiet at runtime — so if a sound stops playing after a rename, check the binding’s identifiers first.