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 gate occupies time. Its duration is calculated by BP3.
  • A trigger does not occupy time. It’s a punctual event — zero duration.
  • A cv occupies 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

  1. Three temporal types: gate (duration), trigger (instant), cv (continuous)
  2. The actor links everything: alphabet + scale + sounds + transport + eval
  3. @actor declares an actor with its properties
  4. Implicit resolution: if a symbol is only in one actor, no need to qualify it
  5. Explicit resolution: dot notation sitar1.Sa when multiple actors share the same alphabet
  6. () = runtime, [] = engine — two syntaxes, two destinations
  7. 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 eval field 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


Back to index