Routing & Selection
Routing & Selection
Every sound a device or scoring rule makes flows through two top-level blocks:
- A
routelists the candidate sounds an owner can make — one or moresourcesub-blocks, each a clip or a synth voice with a routingweight. - A
selectiondecides which candidate plays when the owner fires — random, weighted, round-robin, and so on.
Both are addressed to their owner with owners =, so the blocks live at the top level of your table rather than nested inside a device. This is the only way to attach sound to a device — a nested audio {} block under a device no longer parses.
The smallest example
A bumper that plays one clip on every hit needs a route naming the clip and a selection to fire it:
audio_clip "bumper_hit" {
file = "sounds/bumper_hit.wav"
volume = 0.8
}
device "switch" "bumper" "pop_bumper_1" {
id = 32
}
route {
owners = device.pop_bumper_1
source "audio_clip" "bumper_hit" { weight = 1 }
}
selection {
owners = device.pop_bumper_1
}With a single source and no selection_method, that one clip plays every time.
Owners
owners names who the route or selection serves. It takes a single reference or a bank — a list of references that share the same candidates and decision:
owners = device.pop_bumper_1 # one owner
owners = [device.pop_1, device.pop_2, device.pop_3] # a bankOwners are written bare (not quoted). Five kinds can own sound:
| Owner kind | Reference form | Fires when… |
|---|---|---|
device | device.<name> | the device switch/coil triggers |
score | score.<name> | the scoring rule awards |
modifier | modifier.<name> | the scoring modifier applies |
accumulator | accumulator.<name> | the accumulator advances |
event_handler | event_handler.<name> | the event handler runs |
Because scoring owners can drive audio too, a jackpot’s fanfare and a bumper’s pop are authored the same way.
Candidate sources
Each source inside a route is one candidate the owner can play. The block header is source "<kind>" "<name>", where kind is "audio_clip" or "synth":
route {
owners = device.drop_target_1
source "audio_clip" "target_1" { weight = 3 } # played most often
source "audio_clip" "target_2" { weight = 1 }
source "synth" "zap" {
weight = 1
pitch = 880
velocity = 0.4 + score / 200000 # synth-only payload
duration = "0.5s" # timed note-off
position { x = 0.5 y = 0.9 } # place the voice in space
}
mix { channel = "fx" } # optional mixer destination
}| Attribute | Applies to | Meaning |
|---|---|---|
weight | any source | Relative routing weight — higher is chosen more often |
pitch | "synth" only | Voice frequency in Hz (number or expression) |
velocity | "synth" only | Voice amplitude 0.0–1.0 (number or expression) |
duration | "synth" only | Timed note-off — hold the voice this long, then release ("0.5s") |
sustain | "synth" only | Hold the voice until the triggering switch opens; excludes duration |
position | "synth" only | Sub-block placing the voice in space: position { x = … y = … z = … } |
The four voice attributes — pitch, velocity, duration, sustain — and the position block are only valid on "synth" sources; an "audio_clip" source plays at its own declared volume/priority. duration and sustain are mutually exclusive: duration gives a fixed hold, sustain holds the note for as long as the owning switch stays closed and releases it on the open edge. An optional mix { channel = "<name>" } sub-block sends the route’s output to a named mixer channel.
Choosing a candidate
The selection block decides which source plays. With one candidate you can omit selection_method; with several, name one:
selection {
owners = device.drop_target_1
selection_method = "random"
no_repeat = 1 # never repeat the last 1 pick
condition = var.multiball_active == false
}| Attribute | Type | Meaning |
|---|---|---|
owners | reference | The owner(s) this decision serves |
selection_method | string | How a candidate is picked (see below); default random |
no_repeat | int | Suppress repeats — avoid replaying the last N picks |
shuffle_memory | int | For shuffle, how many recent picks the shuffle bag remembers |
clip_probability | expression | A live expression that biases the pick toward higher-weighted sources |
condition | expression | Gate — the owner stays silent unless this evaluates true |
condition and clip_probability are bare expressions over live game state (score, var.<name>, signal.<name>) — no ${…} interpolation.
Selection methods
| Method | Picks by… |
|---|---|
random | A weighted random draw each trigger (the default) |
weighted | Strict proportional weighting across sources |
rotate | Round-robin through the sources in order |
shuffle | A shuffle bag — every source before any repeats |
balanced | Evening out play counts over time |
probability | Driven by the clip_probability expression |
coordinated | Coordinated across the owners in a bank so they don’t collide |
Sharing candidates across a bank
When several owners should sound alike — a row of pop bumpers, a bank of drop targets — list them all in one owners bank. They share the candidate pool and the decision, and coordinated selection keeps them from all picking the same clip at once:
route {
owners = [device.pop_1, device.pop_2, device.pop_3]
source "audio_clip" "pop_a" { weight = 1 }
source "audio_clip" "pop_b" { weight = 1 }
source "audio_clip" "pop_c" { weight = 1 }
}
selection {
owners = [device.pop_1, device.pop_2, device.pop_3]
selection_method = "coordinated"
}Migrating from device audio {} blocks
A nested audio {} block under a device is now a parse error. To migrate:
- Move the block out of the device to a top-level
routekeyed byowners = device.<name>. - Turn each
clip/clipsentry into asource "audio_clip" "<name>" { weight = … }. - Add a
selectionfor the same owner; carryselection/selection_methodand anyconditiononto it.
Two capabilities from the old device-audio schema have no direct replacement: state-keyed variations maps (a route lists static candidates, not state-keyed alternates) and per-trigger volume/priority/delay overrides (each clip’s own declared volume/priority applies instead).
See also
- Audio & Sound — declaring clips, scoring audio, and levels
- Synth — building the synth patches a
"synth"source plays - Audio block reference — the full block/attribute tables