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:

  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
  • 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


Back to index