← Back to index · Previous: Control flow
The emit system lets temporal functions output named values alongside their main return value. Catch blocks let process blocks react to those emitted events.
Use emit name = expression inside a lambda block to
publish a named side-channel value. Emitted values persist across ticks
and are readable from outside.
step_counter(dt=100ms) = count |> {
init: { count = 0 emit step = 0 }
emit step = 3
count = count + step
}Here, step_counter has: - A main output:
count (what the caller sees when using the instance as a
value) - An emitted value: step (accessible via the
:: operator)
Emitted variables can be initialized in the init block
to set a default value before the first tick:
init: { emit finished = _ } // starts as rest (no event)A common pattern is emitting a trigger when a condition is met:
countdown(dt=100ms) = remaining |> {
init: { remaining = 10 emit finished = _ }
remaining = remaining - 1
emit finished = (remaining == 0) ? _; !
}finished is _ (rest) on most ticks, and
! (trigger) on the tick where remaining hits
zero.
:: accessorThe :: operator reads an emitted value from a temporal
instance.
Syntax: instance::emitted_name
process: {
c = step_counter()
print("count:", c, "step:", c::step)
doubled = c::step * 2
print("doubled step:", doubled)
}c is the main output of the instance.
c::step reads the emitted step value.
The :: operator is also used for module namespace access
(osc::send), but the evaluator distinguishes these by
context.
A catch block reacts to an emitted value becoming
truthy. It is a standalone statement inside a process block that names
the instance and signal using :: — consistent with how
:: reads emitted values elsewhere.
Syntax:
catch instance::signal_name: {
// handler statements
}
countdown(dt=100ms) = remaining |> {
init: { remaining = 10 emit finished = _ }
remaining = remaining - 1
emit finished = (remaining == 0) ? _; !
}
process catch_demo, dur=3s: {
timer = countdown()
print("remaining:", timer)
catch timer::finished: {
print("Countdown complete!")
}
}On each tick, remaining is printed. When
finished emits a trigger (!), the catch
handler fires once and prints "Countdown complete!".
tracker(dt=50ms) = pos |> {
init: { pos = 0 emit halfway = _ emit done = _ }
pos = pos + 1
emit halfway = (pos == 50) ? _; !
emit done = (pos == 100) ? _; !
}
process: {
t = tracker()
catch t::halfway: {
print("halfway there")
}
catch t::done: {
print("finished")
}
}The instance expression can be an inline call rather than a named binding. The instance is created when the process starts and lives for the lifetime of the process (or until it is removed by hot reload).
process: {
// No binding needed — the counter instance is anonymous
catch counter_n(4)::done: {
print("caught after 4 ticks")
stop
}
}The signal name resolves against the anonymous instance’s
emitted_ map. If no matching emitted name is found, the
instance’s main return value is used (so
catch metro(dt=500ms)::tick: {} triggers whenever the
metro’s trigger output is live).
Lifetime rule: the anonymous instance is alive as
long as the process block that contains the catch is alive
— it is cancelled automatically when the process stops or is
hot-reloaded away.
| Main output | Emitted values | |
|---|---|---|
| Access | Use instance as value: c |
Use :: accessor: c::step |
| Per function | Exactly one | Any number |
| Declared with | Output variable before \|> |
emit name = expr |
| Re-evaluated | Every tick | Every tick |
| External visibility | Always | Always (via ::) |
The main output is what makes the instance usable in expressions
(440 * (1 + osc * 0.1)). Emitted values are for metadata,
events, and side-channel signals.