S14) Sounds, specs, and effects
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 — point-in-time overrides
CT (Control Table) are the runtime () qualifiers (S3). They change a parameter at a specific point — the new value persists until the next () that replaces it.
Sa(vel:120) // velocity 120 for this note
{A B C}(vel:80, pan:0.3) // velocity 80 and left pan for the group
CV — continuous modulation
CV (Control Voltage) are temporal objects that modify a parameter over a duration: ADSR envelopes, LFOs, ramps (S3). A CV is declared with a target and a transport, then placed in the grammar like any other temporal object:
env1(Phrase1, webaudio) = filter.adsr(10, 200, 0.5, 300)
// │ │ │ │
// │ │ │ └─ parameters (attack, decay, sustain, release)
// │ │ └─ object type in the lib
// │ └─ transport (output)
// └─ target (input: terminal, sequence, sub-grammar, scene)
S -> { Phrase1 , env1 } // env1 modulates the target during its duration
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.
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
- CT (Control Table): Point-in-time override via
()— persists until the next() - CV (Control Voltage): Continuous modulation over a duration (ADSR, LFO, ramp) — declared as
name(target, transport) = lib.type(args) - 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 (coming soon) — Transposition beyond equal temperament