S2) Three Words and Twenty-Four Symbols

The Complete BPscript Vocabulary — Dense, Not Simple

A language that reserves only three words, one might imagine it to be simple. It is not. BPscript is dense — and that is a deliberate choice.

Where Does This Article Fit In?

After the vision presented in S1, we delve into the language itself. This article is a map: it shows all the building blocks without yet assembling them. The following articles (S3 (coming soon) to S8) will explore each mechanism in detail.


Three Reserved Words

Even before knowing what sound to produce, a scheduler needs information: how does the event occupy time? Does it last? Is it instantaneous? Does its value change while it sounds? BPscript’s three — and only — reserved words answer this question. All the rest of the vocabulary (names of notes, runtimes, libraries) is defined by the user or by the libraries.

Word Relationship to Time Meaning
gate occupies a duration a held value, for the duration of the event
trigger instantaneous a punctual impulse, without duration
cv occupies a duration a value that varies continuously during the event

These three categories come from modular synthesis (the eurorack, a modular synthesizer format): a signal there is either a gate (gate — the sound lasts as long as the gate is open), a trigger (trigger — an instantaneous impulse), or a control voltage (CV, control voltage — a value that evolves over time). BPscript adopts this distinction because it accurately describes the three ways a musical event can occupy time. The details of these types are the subject of S3 (coming soon).


Twenty-Four Structural Symbols

Why symbols rather than keywords? Because a symbol is read at a glance and doesn’t burden the vocabulary to memorize: -> shows a derivation, ! shows an impulse. Beyond the three words, BPscript is therefore written with 24 symbols, grouped into six families according to what they are used to express.

Family 1 — Environment

 

@              directive (imports, global configuration)

 

The @ opens a directive: an instruction that applies to the entire scene, not to a specific moment. For example @actor sitar alphabet:sargam ..., @tempo:120, @mode:random. The most important is @actor, which links an alphabet, a scale, sounds, and an output (S3). When appended as a suffix to an element (C4@kick), the @ also applies a macro or a label.

Family 2 — Derivation

 

->             production rule — used when the engine generates (default direction)
<-             analysis rule — used when the engine recognizes a sequence (reduces right to left)
<>             bidirectional rule — valid in both phases (generation and analysis)

 

Arrows are the core of the language. S -> A B C reads as “the non-terminal S (intermediate symbol, rewritten by a rule) rewrites to A B C”. The direction of the arrow indicates in which phase the rule is valid: -> is a production rule, used when the engine generates a sequence (default direction); <- is an analysis rule, used when the engine recognizes a given sequence (it reduces the right side to the left); <> is a bidirectional rule, valid in both phases. These directions extend those of BP3 (B3).

Family 3 — Temporal Grouping

 

{ }            polymetry (multiple voices) or grouping
,              voice separator in a polymetric group
.              period separator (fragments of equal duration)

 

Curly braces {} group events in time. With a comma, they superimpose voices — this is polymetry (superposition of temporal flows, see B5) : {melodie, rythme}. Without a comma, it’s a simple grouping: {A B C}. The dot . divides a sequence into fragments of symbolically equal duration.

Family 4 — Metadata and Parameters

 

[ ]            engine instructions (on a symbol, a group, or a rule)
( )            declaration, call, runtime parameter, or context (depending on position)
:              binding (symbol:runtime, key:value)

 

Two notations, because there are two different recipients:

  • [] speaks to the engine: these are the instructions that BP3 must apply for derivation — [speed:2], [weight:3], [scan:right].
  • () speaks to the runtime: these are the parameters transmitted to the output that will produce the sound — (vel:120), (pan:0.5).

Square brackets [] apply to a symbol (A[/2]), a group ({A B}[speed:2]), or an entire rule. Parentheses () have four roles depending on their position: runtime parameter (Sa(vel:120)), declaration (gate note(pitch, vel:80)), macro call (accent(Sa)), or rule context ((A B) C -> D E). The : links a name to a value, or a symbol to its output.

Family 5 — Time and Silence

 

-              silence (occupies time, no sound)
_              prolongation (extends the previous event)
...            indeterminate rest (duration calculated by the engine)
~              tie (legato between two notes)

 

Several ways to express absence or sustain, each with a distinct temporal semantics:

Symbol Name Effect
- silence no event, but time passes
_ prolongation the previous event continues, without a new attack
... indeterminate rest the engine calculates the duration that best balances the voices

The distinction matters for polymetry: Sa _ Re makes Sa last for two positions, while Sa - Re inserts a true silence between the two. The indeterminate rest ... is specific to BP3: it lets the polymetry algorithm choose the duration that yields the most regular structure (B13).

The tilde ~ ties two notes: Sa~ Re ~Sa indicates that Sa extends through Re without a new attack (a single attack at the beginning, a single release at the end).

Family 6 — Events and Pattern Filtering

 

!              simultaneity (events at the same instant) / outgoing trigger
<!             incoming trigger (waiting for an external signal)
?              capture (pattern filtering, with number)
|x|            homomorphism (bound variable)
$              master template (pattern definition)
&              slave template (reference to a pattern)
#              context negation
=              assignment (flag mutation in `[]`, macro definition)

 

These are the advanced mechanisms of the language. Each will have its dedicated article:

  • ! and <! → S7
  • ?, |x|, $, &, # → S8
  • flags (guards and mutations in []) → S6

Flag Operators

A composition is not necessarily a fixed sequence: it can make decisions and evolve during derivation. This state is carried by flags (BP3 conditional variables — see B4). Flag operators are divided into two families according to what they do.

Comparisontesting a flag, in a guard ([…] before the LHS):

 

==             equality test
!=             inequality test
>              greater than test
<              less than test
>=             greater than or equal to test
<=             less than or equal to test

 

Calculationmodifying a flag, in a mutation ([…] in the RHS):

 

+              increment        [flag+1]
-              decrement        [flag-1]
=              assignment       [flag=value]

 

This makes a total of nine operators: six for comparison, three for calculation. Two of them reuse a glyph encountered elsewhere — - also serves as silence, = also serves to define a macro — but these are indeed distinct operators: the - that decrements a flag has nothing to do with the - that marks a silence. The inventory of glyphs (the 24 symbols) and the inventory of operators are two independent counts.

Common to all: both guards and mutations live within square brackets [] — this represents the engine’s state, not a temporal event.

 

[count>3] A -> B C                      // guard: the rule exists if count > 3
S -> A B [count+1] C                    // mutation: increments count after B
[phase==1] S -> Sa Re Ga [phase=2] Pa   // guard + mutation: plays, then sets phase to 2

 

A guard determines whether a rule applies: the rule “exists” as long as the condition is true. This is not an imperative test executed line by line, but a condition for the rule’s existence within the grammar. Outside the context of flags, these operators are useless: BPscript has no general arithmetic. All calculation logic goes through backticks (S4).


Macros: Factoring a Gesture

When composing, one often rewrites the same gestures: an accent, an ornament, a combination of effects. A macro avoids copying them — the pattern is defined once, and called by its name. It is a textual substitution: the compiler replaces the call with the macro’s body, before any other analysis. The macro therefore knows neither the types nor the outputs; verification comes afterwards.

 

@macro accent(x) = x(vel:120)
@macro scene_a(x) = x!visual_glow!spotlight

S -> accent(Sa) scene_a(Re) Ga

// After expansion, the compiler sees:
// Sa(vel:120) Re!visual_glow!spotlight Ga

 

The accent(x) macro ignores that x will be a gate. The scene_a(x) macro ignores what visual_glow or spotlight are. It copies, period. This separation yields three independent steps, each with its own concern:

  1. Macros — text rewriting, without knowing the types
  2. Temporal types — gate / trigger / cv, verified at compilation
  3. Output (runtime) — resolved in the downstream layer

Few Words, Many Combinations

The language reserves only 3 words, but it adds 24 symbols, 7 flag operators, macros, backticks, and directives. Few words therefore does not mean few things to learn: the richness has shifted from vocabulary to combinatorics.

To place this intention among other live coding languages (on-stage live programming):

Language Vocabulary Syntax
Sonic Pi restricted, quick access close to Ruby
TidalCycles expressive pattern algebra close to Haskell
BPscript 3 words + 24 symbols inherited from BP3

The idea is not to promise simplicity, but to reduce what needs to be remembered: three words, and symbols that are visually readable. -> is an arrow, ! an impulse, ... a wait, {} a grouping. The difficulty is not in the vocabulary; it is in how these building blocks are combined — like in chess, where six types of pieces suffice for immense complexity.


A First Complete Example

Here is a minimal BPscript scene that utilizes most of the building blocks:

 

@alphabet.raga:supercollider          // directive: import the raga, linked to SC
@tempo:60                             // directive: global tempo

[phase==1] S -> alap S                // phase 1: play the ālāp, then restart
[phase==2] S -> jor S                 // phase 2: play the jor
[phase==3] S -> jhala                 // phase 3: conclude with the jhālā

alap  -> Sa _ Re _ Ga _ [phase=2]            // the ālāp: slow, then switches to phase 2
jor   -> {Sa Re Ga Pa}[speed:2] [phase=3]    // the jor: faster, then phase 3
jhala -> {Sa Re Ga Pa Dha Ni Sa}[speed:4]    // the jhālā: fast, terminal

 

What can be recognized here:

  • @ — global directives (library, tempo)
  • [flag==N] — conditional guards
  • -> — derivation rules
  • _ — prolongation (Sa lasts two positions)
  • [phase=2] — flag mutation (phase switch)
  • {} — temporal grouping
  • [speed:2] — local engine instruction (speed doubled)

Thirteen lines, three phases, a complete raga. This is not a fixed sequence: the structure is derived, conditioned, and branched.


Key Takeaways

  1. 3 reserved wordsgate, trigger, cv: they declare how an event occupies time
  2. 24 structural symbols in 6 families: environment, derivation, grouping, metadata, time/silence, events/patterns
  3. 9 flag operators — six for comparison (guards) and three for calculation (+, -, =, mutations); - and = reuse a glyph seen elsewhere, but remain distinct operators
  4. ! is exclusively temporal (simultaneous events); flag guards and mutations are written within []
  5. Macros are a textual substitution: they ignore types and outputs, which are verified after expansion
  6. Dense, not simple — few words, visual symbols, a richness that comes from combinatorics
  7. No for, no while — all calculation logic goes through backticks

To Go Further

  • Bel, B. & Kippen, J. (1992): “Modelling Music with Grammars” — the BP3 syntax from which BPscript inherits. PDF Link
  • ISO 14977 (1996): EBNF standard — the notation used to formally specify BPscript (S12)

Glossary

  • Reserved Word: a word that the language reserves for itself — the user cannot redefine it or use it as a name
  • Structural Symbol: a character (or group of characters) with a fixed syntactic meaning
  • Directive: an @… instruction that configures the entire scene, outside the temporal flow
  • Non-terminal: an intermediate symbol, rewritten by a rule (as opposed to a terminal, a final symbol)
  • Derivation: successive application of grammar rules to generate a sequence
  • Guard: a condition that determines if a rule is active — the rule “exists” when the guard is true
  • Flag: a BP3 conditional variable that guides the application of rules and can change during derivation
  • Macro: textual substitution — the compiler replaces the call with the macro’s body before any analysis
  • Polymetry: superposition of voices with potentially irrational duration ratios, denoted {voice1, voice2}
  • Runtime: the downstream execution environment that consumes the timestamped sequence and produces sound
  • Live coding: the practice of writing or modifying code live on stage

Links in the Series

  • S1 — BPscript: A Modern Language for the Bol Processor — The Vision
  • S3 — Types, Actors, and Bindings — Types in Detail
  • S4 — Backticks — The Bridge to Native Languages
  • S5 — Structuring Time — Polymetry, Silences, Speed
  • S6 — Composing with Conditions — Flags and Traversal
  • S7 — The Shared Instant — Simultaneity ! and <!
  • S8 — Patterns, Captures, and Templates — Advanced Mechanisms
  • S12 — BPscript’s EBNF — The Complete Formal Specification

Prerequisite: S1
Reading time: 12 min
Tags: #BPscript #syntax #gate #trigger #cv


Next article: S3 — Types, Actors, and Bindings


Back to index