S3) Types, Actors, and Bindings
How BPscript Knows What Takes Time, Where to Send It, and How Often
In Eurorack, each cable carries a signal type: gate, trigger, or CV. BPscript does the same with its symbols. But a cable goes somewhere — and that “somewhere” is defined by the actor.
Where Does This Article Fit In?
We saw in S2 that BPscript has three type-words: gate, trigger, cv. Here we understand why these three, and how the actor links each symbol to its complete context: which alphabet, which scale, which output.
Three Temporal Types
The inspiration comes from the modular world (Eurorack, analog synthesis). In a modular synthesizer, three types of signals circulate:
| Signal | Behavior | Musical Analogy |
|---|---|---|
| Gate | High voltage as long as the key is pressed | A sustained note — beginning and end |
| Trigger | Brief, almost instantaneous pulse | A snare drum hit — you trigger it, it doesn’t “last” |
| CV | Continuously varying voltage | A pitch bend, a crescendo — the value changes |
BPscript borrows this trichotomy because it precisely answers the question the scheduler must ask: does it occupy time?
- A
gateoccupies time. Its duration is calculated by BP3. - A
triggerdoes not occupy time. It’s a punctual event — zero duration. - A
cvoccupies time AND its value changes during the duration.
The Problem: An Alphabet Is Not Enough
Imagine a scene with two musicians. They play the same notes (Sa, Re, Ga…) but:
- go to different outputs (Web Audio vs MIDI channel 3)
- use different tunings (22 shruti vs 12-TET)
- may have different octave conventions
A simple alphabet → runtime binding (@alphabet.raga:sc) is not enough to distinguish two instruments sharing the same vocabulary. An entity is needed that groups all the resolution layers of a symbol. This is the actor.
The Actor: Linking Everything Together
The actor is the unit that links all resolution layers of a symbol:
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor sitar2 alphabet:sargam scale:sargam_12TET transport:midi(ch:3)
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
@actor lights alphabet:dmx_cues transport:dmx
An actor = alphabet + scale + sounds + transport (+ optionally eval for backticks).
| Key | Mandatory | Role | Example |
|---|---|---|---|
alphabet |
yes | Available symbol names | alphabet:sargam |
scale |
no | How degrees become frequencies (via temperament) | scale:sargam_22shruti |
sounds |
no | Terminal definitions: timbre, percussions, samples | sounds:tabla_perc |
transport |
yes | Where to send events | transport:webaudio |
eval |
no | Which REPL evaluates backticks | eval:sclang |
If scale is omitted → no frequency resolution (percussions, DMX, lights).
If sounds is omitted → the transport applies its default rendering.
If eval is omitted → no REPL is associated: the backticks for this actor then remain without an evaluator (you must declare eval to use them).
Resolution: Implicit or Explicit
Explicit Resolution — The Dot Notation actor.terminal
In the rules, a terminal is qualified by its actor via dot notation:
sitar1.Sa // Sa resolved via sitar1 (sargam + 22 shruti + webaudio)
sitar2.Sa // same note, different actor (sargam + 12-TET + midi ch3)
tabla.tin // tin resolved via tabla (tabla + midi ch10)
lights.spot // spot resolved via lights (dmx)
Implicit Resolution — When It’s Obvious
If a symbol appears in only one actor, BPscript resolves it automatically:
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
Sa Re Ga Pa // → sitar1 (only actor with sa, re, ga, pa)
tin ta ke dha // → tabla (only actor with tin, ta, ke, dha)
No need to write sitar1. everywhere — the compiler infers.
If a symbol appears in multiple actors without explicit dot notation → error:
@actor sitar1 alphabet:sargam ...
@actor sitar2 alphabet:sargam ...
Sa Re Ga Pa // Error: 'Sa' is in sitar1 AND sitar2 — specify the actor
Block Import
An @actor with an alphabet imports all symbols from that alphabet, linked to that actor:
@actor sitar1 alphabet:sargam scale:sargam_22shruti transport:webaudio
// Sa, Re, Ga, Ma, Pa, Dha, Ni → automatically declared as gate, linked to sitar1
// No need for "gate sitar1.Sa" for each note
Individual override possible via an explicit declaration:
trigger dha:sitar1 // override: dha is a trigger in this actor, not a gate
Parameters: () Runtime vs [] Engine
Two syntaxes for two destinations (S5):
| Syntax | Destination | Examples |
|---|---|---|
[] |
BP3 Engine | [speed:2], [weight:50], [tempo:2] |
() |
Runtime (downstream layer) | (vel:80), (wave:sawtooth), (filter:300) |
Parentheses () transport parameters to the runtime — BPscript does not interpret them; it compiles them into opaque controls (_script(CT n)) that the downstream runtime consumes:
Sa(vel:120) // symbol: vel=120 for this note
{A B C}(vel:80) // group: vel=80 for the entire group
S -> A B (vel:120) // rule: vel=120 at the end of RHS
Square brackets [] are instructions for the engine:
{A B C}[speed:2] // doubled speed (engine)
S -> A B C [weight:3] // rule weight (engine)
The distinction is fundamental: [] changes the engine’s behavior (speed, weight, mode), () changes the rendering by the runtime (velocity, pan, instrument).
CV: Continuous Values
Some gestures are neither a sustained note nor a pulse: a filter opening, a crescendo, a vibrato. This is what cv carries — a value that evolves during its duration. CV objects are declared at the top of the scene and used in the grammar as ordinary symbols. The signature is name(target, transport) = lib.type(args):
// CV declaration — ADSR envelope targeting the filter, evaluated by SC
env1(filter, sc) = filter.adsr(10, 100, 0.7, 200)
// Usage in a rule
S -> env1 Sa Re Ga Pa
CVs cover several types: ADSR envelopes, LFOs (low-frequency oscillator), ramps (linear interpolation). They receive their duration from the grammar and are resolved in the downstream layer (S10).
Frequency Resolution Is Downstream
Important point: Unlike writing BP3 grammars, here we use it only as a scheduler, so BP3 does not process the scheduled objects, the sounds. The compiler only transmits opaque names prefixed bol (bolSa, bolRe…) to it. BP3 derives the temporal structure; it knows neither which actor, nor which tuning, nor which output.
The translation of a symbol into hertz occurs in the downstream layer (the runtime), via a 6-layer pitch system:
Layer 0 — Actor : which resolution context (sitar1 vs sitar2)?
1 — Alphabet : which degree (Sa = degree 1, Ga = degree 3)?
2 — Octaves : which register (saptak)?
3 — Temperament : which ratio for this degree (5/4, 2^(4/12))?
4 — Tuning : which fundamental, which pitch standard?
5 — Resolver : the final calculation in Hz
This is why the same Ga can produce two different frequencies depending on the actor:
sitar1 : Ga (registre médian) → 327.03 Hz (22 shruti, ratio 5/4)
sitar2 : Ga (registre médian) → 329.63 Hz (12-TET, ratio 2^(4/12))
Same symbol, same register, different frequencies — because the tuning (layers 3-4) differs. The details of the 6-layer pitch system are in S9.
A Complete Example
// Actors
@actor sitar alphabet:sargam scale:sargam_22shruti transport:webaudio
@actor tabla alphabet:tabla sounds:tabla_perc transport:midi(ch:10)
@actor lights alphabet:dmx_cues transport:dmx
// Inits
`js: // Web Audio setup`
// Composition — actors resolve automatically
S -> { melodie, rythme, eclairage }
melodie -> Sa Re Ga(vel:120) Pa // → sitar (only one with these notes)
rythme -> tin ta ke dha // → tabla (only one with these bols)
eclairage -> -!spot _ _ -!fade // → lights (only one with spot, fade)
Three instruments, three actors, three destinations — a single grammar. Resolution is implicit because each symbol belongs to only one actor.
Key Takeaways
- Three temporal types:
gate(duration),trigger(instant),cv(continuous) - The actor links everything: alphabet + scale + sounds + transport + eval
@actordeclares an actor with its properties- Implicit resolution: if a symbol is only in one actor, no need to qualify it
- Explicit resolution: dot notation
sitar1.Sawhen multiple actors share the same alphabet ()= runtime,[]= engine — two syntaxes, two destinations- Frequency is resolved downstream — BP3 only sees opaque names
bol…; the 6 pitch layers run in the runtime
Glossary
- Actor: A binding unit that links an alphabet, a scale, sounds, a transport, and an evaluator — the complete resolution context of a symbol
- Gate: A signal that remains active for a duration — in BPscript, a symbol that occupies time
- Trigger: An instantaneous pulse of zero duration
- CV (Control Voltage): A value that varies continuously over a duration (ADSR, LFO, ramp)
- Resolver: The last of the 6 pitch layers (downstream) that translates a symbol into a frequency
- Binding: A link between a symbol and its actor — implicit (inferred) or explicit (dot notation)
- Transport: Output protocol (Web Audio, MIDI, OSC, DMX) declared in the actor
- Implicit resolution: The compiler infers the actor of a symbol when there is no ambiguity
Links in the Series
- S2 — The 3 words and 24 symbols
- S4 (coming soon) — Backticks — the actor’s
evalfield determines which REPL evaluates - S5 —
[]engine vs()runtime in detail - S9 — The 6-layer pitch system — what the resolver does
- S10 — The pipeline — how actors go through compilation
Prerequisites: S2
Reading time: 14 min
Tags: #BPscript #actors #gate #trigger #cv #tuning
Next article: S4 (coming soon) — Backticks: inline JS and REPLs