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:
| Input | Affects | Mapping of 0–1 |
|---|---|---|
| cutoff | filter frequency | exponential (≈20 Hz → 20 kHz) |
| amplitude | gain | linear |
| resonance | filter Q | linear (0 → 30) |
| pitch | detune | ±1200 cents |
| pan | stereo 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:
| Layer | Responsibility | Example |
|---|---|---|
| Runtime (backtick init) | Audio graph wiring | sitar → lpf → reverb → out |
| BPscript (grammar) | When and how parameters change | lpf.cutoff via polymetry |
| BP3 (engine) | Duration calculation and synchronization | melody + 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:
- Would duplicate the capabilities of each runtime — and do it worse
- Would limit possibilities to the constructions planned by BPscript
- 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
- Three scales: spec (permanent), CT (point-in-time
()), CV (continuous over a duration) - Cascade:
spec < CT < CV— the most specific layer wins - The runtime wires (backtick init), BPscript parameters over time (polymetry + CV)
- No patching — effects are controlled using existing mechanisms, zero extra words
- 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
Links in the series
- 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