Skip to content
roomi fields

BPscript · S14 · Design

Sounds, Specs, and Effects: Mastering Sound Parameters

Master sound settings and the priority cascade without a complex patching language.

Published 21 May 2026 · Lire en français

How BPscript parameters sound without inventing a patching language

The runtime knows how to wire a filter. BPscript knows when the filter should open. Each does what it does best — and polymetry synchronizes them.

Where does this article fit?

After actors (S3) and time (S5), we understand how sound is parameterized. Not how it is produced (that’s the runtime’s job) — but how BPscript tells it what to do and when.


Three time scales

Each terminal in the grammar passes through three layers of parameters, from the most permanent to the most ephemeral:

Spec — sound properties

The spec (specification) defines the permanent properties of a terminal: timbre, sample, acoustic characteristics. Resolved only once, it does not change during the piece.

@actor sitar  alphabet:sargam  scale:sargam_22shruti  sounds:sitar_timbre  transport:webaudio

The sounds:sitar_timbre file contains the specs per terminal:

{
  "sa": { "wave": "sawtooth", "decay": 2000, "brightness": 0.7 },
  "re": { "wave": "sawtooth", "decay": 1800, "brightness": 0.6 },
  "dha": { "type": "percussion", "sample": "tabla_dha.wav", "decay": 300 }
}

CT — non-temporal controls

The runtime () qualifiers (S3) govern content. They come in two regimes:

  • containment (...): applies to its whole scope (the rule or the group), including notes written before it, and stops at its edge (confined);
  • flux !(...): flows forward in playing order, crossing edges, until the next control (behaviour inherited from BP3).
Sa(vel:120)                       // a per-note override
Bass -> C2 E2 G2 (wave:sawtooth)  // containment: all three notes, confined to Bass
S -> A !(vel:100) B               // flux: from here on, velocity 100 forward

Precedence, strongest first: per-note override > flux > containment > defaults. These controls are resolved once per terminal upstream (by the engine); the dispatcher reads them, it does not recompute them.

CV — continuous modulation

CV (Control Voltage) is the third temporal type: ADSR envelopes, low-frequency oscillators (LFOs), ramps (S3). A CV is declared descriptively — a name bound to a modulator from the mod library — then bound per note onto an input:

cv env1 : mod.adsr(attack:10, decay:200, sustain:0.5, release:300)

Bass -> C2 C2 C3 (cutoff: env1)   // env1 modulates each note's cutoff

The modulator produces a value normalised between 0 and 1, which Kanopi maps onto the physical range of the chosen input. Five inputs are recognised:

InputAffectsMapping of 0–1
cutofffilter frequencyexponential (≈20 Hz → 20 kHz)
amplitudegainlinear
resonancefilter Qlinear (0 → 30)
pitchdetune±1200 cents
panstereo position−1 → 1

The target of a binding can be a derivable symbol — a sibling voice the grammar resolves — letting one voice modulate another without re-derivation. A CV receives its duration from the grammar.

Cascade — priority

The three layers combine with a cascade (increasing priority) inspired by CSS:

spec  <  CT  <  CV
(most permanent)  →  (most specific)

A CV overrides a CT, which overrides a spec. If a parameter is not defined in a layer, the lower layer applies.

This three-strata cascade is the runtime layer. The timbre itself (a sound’s spec) resolves through a deeper cascade — engine default → scene defaults → alphabet → actor → inline — merged property by property, again CSS-style.

Example: a Sa with spec.vel=80, CT.vel=120, and a CV env(vel: ramp(40,127)) → the CV wins during its duration, then the CT takes over again.


Effects: the runtime wires, BPscript parameters

BPscript has no patching language. No concept of buses, chains, or >. Zero extra reserved words.

The separation is clear:

LayerResponsibilityExample
Runtime (backtick init)Audio graph wiringsitar → lpf → reverb → out
BPscript (grammar)When and how parameters changelpf.cutoff via polymetry
BP3 (engine)Duration calculation and synchronizationmelody + filter curve polymetry

Step 1 — The runtime defines the graph

The wiring is in the runtime. It knows how to do this natively:

`js:
  const lpf = ctx.createBiquadFilter();
  lpf.type = 'lowpass';
  lpf.frequency.value = 1000;
  sitar.connect(lpf).connect(ctx.destination);
`

Step 2 — BPscript controls parameters over time

Effect parameters are placed in polymetry — like a parallel voice to the melody. BP3 calculates the synchronization:

S -> { melodie , sitar.lpf.cutoff(ramp(200, 4000)) }

The melody plays in one voice, the filter curve in another. BP3 synchronizes them. The result: the filter opens progressively while the melody unfolds.

Why no patching?

Each runtime has its own wiring system (SuperCollider: SynthDefs + Bus, WebAudio: AudioNode.connect, Max: objects + patch cords). Inventing a patching DSL (Domain-Specific Language) in BPscript:

  1. Would duplicate the capabilities of each runtime — and do it worse
  2. Would limit possibilities to the constructions planned by BPscript
  3. Would add syntactic complexity for a questionable gain

Backticks are enough for wiring (it’s a one-time setup, not time-based). Polymetry is enough for control over time (it’s what BP3 does best). Together, they cover practical cases without a single extra word.


Key takeaways

  1. Three scales: spec (permanent), CT (point-in-time ()), CV (continuous over a duration)
  2. Cascade: spec < CT < CV — the most specific layer wins
  3. The runtime wires (backtick init), BPscript parameters over time (polymetry + CV)
  4. No patching — effects are controlled using existing mechanisms, zero extra words
  5. Polymetry synchronizes melody and effect parameters — it’s the same engine

Glossary

  • Spec: Permanent properties of a terminal (timbre, sample, decay) — resolved once at loading
  • Containment (...) / Flux !(...): two regimes of non-temporal control — containment confined to its scope, flux flowing forward; precedence override > flux > containment > defaults
  • CV (Control Voltage): Continuous modulation over a duration (ADSR, LFO, ramp) — declared cv name : mod.type(args), bound per note (input: name), 0–1 value mapped by the input
  • Cascade: Priority system between layers — the most specific layer overrides the others
  • Patching: Wiring of audio modules (filter → reverb → output) — managed by the runtime, not by BPscript

  • S3 — Types, actors, and attachments — where sounds are declared
  • S5 — Polymetry — how effects are synchronized with music
  • S9 — The pitch system — the pitch layer vs the sound layer
  • S10 — The dispatcher — how spec/CT/CV are resolved at runtime

Prerequisites: S3, S5 Reading time: 10 min Tags: #BPscript #sounds #effects #CV #cascade


Next article: S15 — Transposition beyond equal temperament


← Back to index