Introduction
Conchordal is a bio-acoustic instrument for generative composition. Rhai is its scenario language: it directs the instrument without reducing it to note scheduling. The central idea is shaping a perceptual consonance field, then letting populations of voices move, survive, and reorganize inside it.
A scenario script defines PopulationSpecs, places each one into the field as a stable Population, and shapes the terrain its Voices inhabit. Together those Populations form the runtime Community. Harmony emerges from psychoacoustics (roughness and harmonicity); rhythm emerges from coupled oscillators on a shared meter the Community itself drives. The script is a director, not a sequencer.
Conchordal v0.4.0 is an alpha release for researchers and developers who want to examine these concepts directly. Features are incomplete and may be unstable; composers and creators should wait for the beta release. The model is intentionally described in its own terms rather than hidden behind common music-production vocabulary.
How this book is organized
- Tutorial gets a first sound out, wires up your editor, and covers running, recording analysis, and replaying a performance: Quick Start, Editor Setup, and Performance.
- Concepts builds the model from a single living voice to the complete ecological and temporal structure: Population — A Persistent Unit of Voices, Voice and Landscape — Sound–Environment Feedback, Consonance Field — A Terrain for Evaluating Pitch, Rhythm, and Routing and the Listener Twin, followed by Timeline and Structure.
- Reference contains the generated API Reference and the Curated Samples listening path. The reference is split into four tiers: the Core API (enough for every curated sample), Experimental (candidate Core verbs under audition), Mechanism Tuning, and Research Controls. Start with Core and ignore the rest until a piece demands it. Registered signatures and tier membership are generated and checked against the engine; explanatory prose is maintained in the documentation registry.
Every rhai code block in this book is executed against the real script
engine by the test suite. This checks that the examples compile and run with
the current engine; it does not by itself prove that every explanation is
semantically correct.
Quick Start
Run a scenario script with the real-time instrument (release mode is recommended for real-time DSP):
cargo run --release -- samples/01_a_single_voice.rhai
Minimal Sound
place(sine().amp(0.08).sustain(), at(440.0));
wait(2.0);
place(population_spec, placement) creates a Population immediately at the
current script time. The returned handle can patch or release that living
population.
Basic Objects
- PopulationSpecs describe founder voices and population policy. Start one
with
sine(),harmonic(),modal(),saw(),square(), andnoise(). - Variants clone a specification with
variant(population_spec). - Placements decide where populations enter: field targets
consonance(),dissonance(),edge(),gap()(cloud by default,.peak()for the extremum), plusrandom(),at(), andline(). - Populations are the stable handles returned by
place(). Their founder voices exist from that boundary; live methods update the current voices. - Sections scope populations and release them automatically.
A Population is one stable handle for a placed population. A placement with .count(6)
creates six founder voices under that handle. If voices later die and respawn,
the Population remains the same while its members and generations change. See
Population — A Persistent Unit of Voices for the complete object and
lifecycle model.
let population_spec = harmonic()
.amp(0.08)
.sustain()
.brightness(0.35);
section("plain entry", || {
place(population_spec, line(220.0, 440.0).count(3));
wait(4.0);
});
Placing Into the Field
consonance(root_hz).peak() places voices at high Consonance Field positions
around a root. The field is shaped by what the system perceives — an anchor
changes where the peaks are.
let anchor = harmonic()
.brain("drone")
.amp(0.06)
.sustain()
.anchor();
let voice = harmonic()
.amp(0.04)
.sustain();
section("field placement", || {
place(anchor, at(110.0));
wait(1.0);
place(voice, consonance(110.0).peak().range(1.0, 4.0).count(6).spacing(0.9));
wait(6.0);
});
How the field works — and how voices move, survive, and respawn inside it — is the subject of Consonance Field — A Terrain for Evaluating Pitch.
Live Patching
Configure body, behavior, lifecycle, and respawn on a PopulationSpec before
calling place(). The returned Population exposes only operations that make
sense after placement: live patches and release. The
API Reference marks the two method sets.
let spec = harmonic().amp(0.04).sustain();
let population = place(
spec,
consonance(220.0).peak().count(3)
);
population.amp(0.02); // live patch on running voices
population.glide(0.8);
wait(3.0);
release(population);
A Complete Miniature
seed(7);
let anchor = harmonic()
.brain("drone")
.amp(0.05)
.sustain();
let colony = harmonic()
.amp(0.035)
.sustain()
.seek_consonance()
.glide(0.4)
.avoid_neighbors(0.6);
section("emergence", || {
place(anchor, at(110.0));
wait(2.0);
place(colony, consonance(90.0, 900.0).count(8).spacing(0.8));
wait(8.0);
});
Where to go next
- Editor Setup — completion, hover docs, and diagnostics for the whole scripting surface.
- Performance — reports, filtering, and
replaying a run with
--seed. - Population — A Persistent Unit of Voices — population specs, voices, brains, phonation, survival, and release.
- Voice and Landscape — Sound–Environment Feedback — how sound changes the terrain that changes the voices.
- Consonance Field — A Terrain for Evaluating Pitch — field, density, movement, viability, respawn.
- Rhythm — the coupling continuum and the director’s rhythmic terrain.
- Routing and the Listener Twin — what the ecology senses, what the audience hears, and what is observed.
- Timeline and Structure — placement boundaries, scopes, reusable gestures, and parallel branches.
- Curated Samples — the guided listening path.
Editor Setup (LSP)
Conchordal ships a Rhai LSP definition file describing its named callable
scripting API, including hover documentation. With a compatible client, it can
provide completion, hover, go-to-definition, and diagnostics for calls such as
place, harmonic, .brain(), and .send(habitat_bus). The bus-combining
| overloads are operators rather than named declarations and are therefore
not emitted into the definition file.
The two files that drive this are committed at the repo root:
Rhai.toml— workspace config picked up byrhai-lsprhai-defs/conchordal.d.rhai— auto-generated type/fn declarations with doc comments
The upstream rhai-lsp project labels itself experimental and incomplete, and does not recommend general use. Treat this setup as an optional development aid rather than guaranteed production tooling. If you still want to use it, install it directly from its git repository (it is not published on crates.io):
cargo install --git https://github.com/rhaiscript/lsp rhai-cli
This builds a binary named rhai with the lsp subcommand.
Then wire your editor:
VS Code
The official Rhai
extension currently provides syntax highlighting only. It does not launch
rhai-lsp. For LSP features in VS Code, use an LSP client/extension that can
launch this command from the conchordal workspace:
rhai lsp stdio --config Rhai.toml
Neovim 0.11+
vim.lsp.config("rhai", {
cmd = { "rhai", "lsp", "stdio", "--config", "Rhai.toml" },
filetypes = { "rhai" },
root_markers = { "Rhai.toml", ".git" },
})
vim.lsp.enable("rhai")
Helix
In ~/.config/helix/languages.toml:
[[language]]
name = "rhai"
scope = "source.rhai"
file-types = ["rhai"]
language-servers = ["rhai-lsp"]
[language-server.rhai-lsp]
command = "rhai"
args = ["lsp", "stdio", "--config", "Rhai.toml"]
Emacs (eglot)
(add-to-list 'eglot-server-programs
'(rhai-mode . ("rhai" "lsp" "stdio" "--config" "Rhai.toml")))
(add-hook 'rhai-mode-hook #'eglot-ensure)
Regenerating the definition file
The definition file and both the English and Japanese API references are
generated from the engine’s register_fn surface joined with the
documentation registry (src/scripting/docs.rs). If you pull a new conchordal
version and miss diagnostics, regenerate all three:
cargo run --bin gen_rhai_defs
CI tests fail whenever the committed artifacts are stale. This checks the registered signatures and generated text; editor behavior still depends on the experimental upstream server and the client configuration.
Performance
Quick Start explains how to write and run a minimal scenario. This chapter describes the actual performance workflow: starting the real-time instrument, ending a run, performing without the GUI, and then recording or replaying what happened.
Performing with the GUI
Start a scenario with the real-time instrument:
cargo run --release -- samples/10_generations.rhai
The scenario is compiled, the GUI opens, and playback begins automatically.
The script supplies the macro-timeline: place() introduces Populations,
wait() advances script time, and section() or play() releases the
Populations created inside its scope. During that timeline, Voice behavior,
the Landscape, and their feedback continue to evolve in real time.
The progress bar shows scenario time. While playback is running, the control at its right exits the performance; closing the window or pressing Ctrl-C also stops it. It is not a pause button, and the GUI is not a live script editor. To change the scenario, stop the run, edit the Rhai file, and run it again.
To prepare the GUI first and begin on a cue, add --wait-user-start. Press
Space or click the start control when ready:
cargo run --release -- samples/10_generations.rhai --wait-user-start
By default the GUI remains open when the scenario finishes, allowing the
final state to be inspected. Close it with the playback control or window
close button. Add --wait-user-exit=false when it should close automatically
at the end.
Performing without the GUI
--nogui starts immediately and exits when the scenario finishes. It still
plays through the default audio device:
cargo run --release -- samples/10_generations.rhai --nogui
Use --play=false only for a silent simulation or report-only run:
cargo run --release -- samples/10_generations.rhai --nogui --play=false --report run.jsonl
--nogui disables both wait-for-start and wait-for-exit. Offline WAV output
is a different operation provided by conchordal-render; the real-time
instrument itself never records audio.
Listen, inspect, revise
A scenario specifies which Populations enter, when they enter, and the policies that guide their behavior. It does not predetermine the exact pitches, rhythmic synchronization, survival, or respawn outcome of a performance; those emerge from interaction with the changing Landscape. Run the scenario, listen to the result, inspect what happened, adjust the script, and run it again.
conchordal (the instrument) never writes audio to disk, in any build
profile. A performance is designed as an ephemeral event that leaves no audio
inside the instrument after it ends.
Replaying a performance
Every run logs its seed:
scenario seed: 3821650944810716341 (replay with --seed 3821650944810716341)
Top-level samples start from a fresh, unseeded run every time so they expose the system’s variation rather than prescribe one fixed result. When a run is worth keeping, replay it exactly with the logged value:
cargo run --release -- samples/10_generations.rhai --seed 3821650944810716341
--seed works the same way on conchordal-render for turning a kept
performance into a WAV. A script-level seed(...) call always wins over the
flag, since it runs during script evaluation and overwrites whatever seed the
run started with — reach for it only when a script itself must be
reproducible regardless of how it is invoked.
Reports
Run with --report, pointing at a file to write:
cargo run --release -- samples/10_generations.rhai --report run.jsonl
--report is a flag on the conchordal instrument (it also works headless,
with --nogui). It is not available on conchordal-render, which renders
audio rather than observing a live run.
The file is JSON Lines — one record per line, tagged by type. The record
families are:
meta— the effective scenario seed, written first.scene_marker— the start of eachsection("name", || { ... }), so later records can be grouped by the most recent marker.spawn/respawn/death— population turnover per voice:spawnandrespawncarry the entry frequency (and, forrespawn, the parent voice);deathcarries configured nominal endurance, energy-depletion time, observablelifetime_sec(including the envelope tail), an early-life consonance snapshot (first_k_mean), and phase-locking at death (plv_at_death).onset— per-voice onset time, strength, frequency, phase-locking, and scaffold context.population_step— active Population size (includingalive_count: 0while awaiting replacement), mean frequency, Consonance Field score and level, and frequency entropy over time.listener_state— theListenerTwin’s four perceptual levels (stability_level,resolvability_level,tension_level, andattention_level), beat/subdivision/measure tracking, and analysis lag.rhythm_observation— instantaneous global Kuramoto and environment rhythm state.rhythm_summaryreports onset density, inter-onset-interval regularity, and burstiness globally and per Population; its Kuramoto summary is global only.listener_confidence_summary— peak and late-window beat confidence.dcc_pressure— listener-derived tension pressure and the pitch-temperature bonus applied by DCC.phonation_gate_open— when a voice’s phonation gate opens, and the consonance value it opened at.
Reading the raw JSONL answers narrow questions well: “when did Population 3’s
membership actually turn over?” or “what did tension_level do right after
that anchor entered?”
Reading a report
The stream is plain JSONL, so section-level questions are one filter away with any JSON tool. For example, every death with its lifetime and early-life consonance:
jq -c 'select(.type == "death")
| {time_sec, population_id, configured_endurance_sec,
energy_depletion_sec, lifetime_sec, first_k_mean}' run.jsonl
Bucket those by your scene_marker times and “did the colony starve in
section IV, and at what consonance?” becomes a direct read. There is no
bespoke digest tool: which summaries matter depends on the piece, and a
one-off filter (or a script) shaped to the question beats a fixed report
format.
The GUI
The GUI shows the same landscape and listener-twin state live, as it runs. Reports exist for reading after listening — for the moments you noticed something but couldn’t hold every number in your head at once.
Population — A Persistent Unit of Voices
The shortest useful mental model of conchordal is not a note followed by another note. It is a population definition becoming a persistent population of living voices:
PopulationSpec + Placement --place()--> Population --> Voice(s)
|
`-- later generations
Community = every Population sharing the runtime terrain
A PopulationSpec is a reusable pre-placement definition. It combines the
founder Voice defaults with policy that belongs to the population as a whole,
such as lifecycle, viability, and respawn. A Placement says where and how
many founder voices enter. place() combines them and immediately returns a
Population: one stable handle for the placed population, not one handle per
Voice. The runtime Community is the aggregate of all Populations that share
the Landscape.
let population_spec = harmonic()
.amp(0.035)
.sustain()
.respawn_capacity(6);
let population = place(
population_spec,
consonance(90.0, 900.0).count(6)
);
wait(3.0);
release(population);
Here population controls six founder Voices together. Reports distinguish
their shared population_id from each individual voice_id. If a Voice dies
and is replaced, its voice_id and generation change; the Population and its
population_id do not.
The placement boundary
place() is the only transition from definition to runtime. Configure all
initial-only properties on the PopulationSpec first. Once placed, a
Population exposes only live patches and release; it cannot be turned back
into a specification or have its founder policy rewritten.
let spec = harmonic()
.brightness(0.4)
.brain("entrain")
.endurance(8.0);
let population = place(spec, consonance(220.0).count(3));
population.amp(0.03); // Live patch at the current script time.
flush(); // Emit pending live patches without advancing time.
wait(2.0);
release(population);
wait(seconds) also emits pending live patches, then advances the script
cursor. Neither wait() nor flush() creates a deferred or draft Population:
the founder Voices were scheduled by place() itself.
The API Reference marks PopulationSpec methods as
initial-only and Population methods as live-patchable.
Five independent questions
A PopulationSpec answers several independent questions. Keeping them separate prevents one musical decision from being mistaken for another.
| Question | Main controls | Meaning |
|---|---|---|
| What sounds? | sine, harmonic, modal, brightness, modes | The founder Voice body and spectrum. |
| What kind of life? | brain(name): entrain, seq, drone | Whether articulation participates in ecology, follows an authored life, or persists as terrain. |
| When does it sound? | sustain, repeat, metric, entrained, steady, flow | Phonation and onset timing. |
| Where does pitch go? | Placement, anchor, seek_consonance, temperature | Founder entry position and later movement. |
| How does the population persist? | endurance, recovery, viability, respawn | Voice energy, death, and Population turnover. |
Calls on different rows compose. Calls on the same axis are generally last-write-wins; the API Reference identifies the exact builder behavior.
Articulation life: brain
brain(name) selects how a Voice lives while it is sounding:
brain("entrain")is the default living articulation. It can respond to consonance and rhythmic fit through the metabolism and lifecycle controls.brain("seq")is an authored event with a fixed life. It ignores field viability and metabolism.brain("drone")is undying until explicitly released. It is useful for terrain anchors and other persistent material.
This is separate from phonation timing. In particular:
brain("entrain")chooses a kind of life;.entrained()chooses medium coupling of repeated onsets to the shared meter.
The similar names describe different axes and may be used together.
let colony = harmonic()
.brain("entrain")
.entrained()
.cycles(2)
.seek_consonance()
.endurance(8.0)
.recovery(4.0)
.consonance_viability(0.30, 0.80);
place(colony, consonance(90.0, 900.0).count(5));
wait(8.0);
The brain does not choose the body, Placement, or pitch strategy. A drone may be audible or habitat-only; a living Voice may be anchored or moving; the same modal body may use any articulation life.
Phonation and duration
Phonation answers two questions: when an onset occurs, and how long that onset remains open.
sustain()holds while the Voice is alive.repeat()selects repeated phonation with defaults.metric()selects shared-beat synchronization;entrained(),steady()andflow()select acoustic participation. Steady keeps its own due time and uses heard context only for sound or skip. These timing presets can re-attack; explicit sustain composes a living Tone with their opportunities. See Rhythm.cycles(n)expresses duration in rhythmic cycles.
The lower-level once(), pulse(rate_hz), while_alive(), and adaptive
duration controls are available when the presets do not express the intended
gesture. Start with the presets; use explicit timing only when the piece needs
it.
Release is not death
release(population) is a terminal script decision: later patches on that
Population are ignored, its current Voices enter their release envelopes, and
the Population closes. Ecological death is a runtime
result: one living Voice exhausts its energy, after which the Population’s
respawn policy may replace it. A section or play scope also releases the
Populations it created when the scope ends.
The next chapter explains Voice and Landscape — Sound–Environment Feedback.
Explicit sustain() plus metric() combines held sound and acoustic beat synchronization. With free-pitch GateSnap/Rearticulate, omitted tie attacks at a beat with a pending adopted destination and extends other beats; write tie(0) for every beat. Fixed pitch and Glide still attack each crossing, and sustained entrained/steady/flow keeps omitted tie at zero. See rhythm.
Voice and Landscape — Sound–Environment Feedback
A Voice routed to the habitat bus changes the Landscape. The changed Landscape then affects placement, movement, and viability. This chapter traces that runtime feedback path and the energy, death, and respawn processes it supports.
Voice bodies
|-- presentation bus --> listener / Listener Twin
|
`-- habitat bus --> Landscape --> placement, movement, viability
^ |
`------ new sound <------'
By default every voice feeds both buses, so the audience and the ecology share the same physical event. Routing can split them deliberately; see Routing and the Listener Twin.
From sound to terrain
The Landscape analyzes the habitat bus in log-frequency space and computes:
- roughness potential: interference and beating within critical bands;
- harmonicity potential: periodicity and support for virtual roots;
- Consonance Field: the combined terrain used to evaluate candidate frequencies.
A voice changes the spectrum on the habitat bus, which changes these scans.
That is why consonance(...).peak() does not return a fixed scale degree: its
answer depends on what is sounding now.
Score, level, mass, and density
Several representations are derived from the same terrain. They are related, but they are not interchangeable.
| Representation | Range | Used for |
|---|---|---|
| potential | kernel-dependent | Raw roughness and harmonicity output. |
| field score | unbounded real value | Comparing positions, hill-climb, and placement tension. |
| field level | 0..1 | Bounded behavior and viability signals. |
| density mass | non-negative | Weight before normalization for stochastic placement. |
| density / PMF | sums to 1 in the selected range | Sampling a density cloud. |
.peak() selects an extremum. The default density placement samples a PMF, so
several voices can form a cloud around supported regions instead of collapsing
onto one bin. tension(degree) targets a field-score step below the strongest
peak. A viability window reads a bounded or environment-relative fit signal.
The suffixes in reports preserve the same distinction: for example,
mean_c_field_score and mean_c_field_level are different quantities.
Placement is not movement
Placement answers where a voice enters. Pitch behavior answers what happens after entry.
let fixed_strain = harmonic()
.amp(0.035)
.sustain()
.anchor();
let resolving_strain = harmonic()
.amp(0.035)
.sustain()
.seek_consonance()
.glide(0.4);
section("two responses to dissonance", || {
place(fixed_strain, dissonance(140.0, 900.0).count(3));
place(resolving_strain, dissonance(140.0, 900.0).count(3));
wait(6.0);
});
Both Populations enter a dissonant region. One holds it; the other treats it as a starting point for resolution.
Evaluate the environment, not the self
A voice contributes energy to the field it later evaluates. Without care, a
strong voice could appear viable merely because it hears its own footprint.
consonance_viability() therefore enables environment-relative evaluation by
default: the system approximately removes the voice’s own contribution before
judging its fit.
This is a survival rule, not a routing rule. The voice may still feed the
habitat bus and reshape the terrain for every other voice. Use
viability_scope("total") only when the intended question is explicitly
whether the voice fits the total field including itself.
Energy, death, and replacement
The ecological lifecycle belongs to brain("entrain"). Its energy is
normalized to 0..1:
endurance(seconds)establishes the nominal zero-fit lifetime.- Each attack spends
attack_cost_fraction. - A consonant attack can restore up to
attack_recharge_fraction. recovery(seconds)enables continuous recovery; the viability window determines how much of that recovery is available at the current pitch.- At zero energy the voice dies and enters its release tail.
- If a respawn policy exists, the Population may create a replacement.
let settlement = consonance(70.0, 1100.0).spacing(0.8);
let ecology = harmonic()
.brain("entrain")
.entrained()
.cycles(2)
.seek_consonance()
.endurance(8.0)
.recovery(4.0)
.attack_cost_fraction(0.017)
.attack_recharge_fraction(0.70)
.consonance_viability(0.32, 0.82)
.respawn_consonance()
.respawn_capacity(8)
.respawn_settle(settlement);
place(ecology, consonance(70.0, 1100.0).count(8));
wait(20.0);
Respawn policies answer different compositional questions:
respawn_random()creates no parent lineage. Candidates come from the Population’s original Placement and are weighted by the current scene score; it is not uniform random placement.respawn_hereditary(sigma_oct)selects a living parent by energy, proposes offspring near it, and keeps the candidate with the best current Field level.respawn_consonance()selects an energy-weighted living parent and chooses among high field-score peaks with a bias around that parent.respawn_capacity(n)bounds how many living members the Population maintains; without it, the founder count is the capacity, and an explicit value cannot be lower than that founder count.respawn_settle(placement)adds candidates from that Placement; the respawn policy’s own baseline still contributes one candidate.
Respawn preserves the Population and its population_id while individual
voice_id and generation values change. The report stream makes that
turnover observable.
Runtime feedback and human revision
Two different processes are involved:
- Runtime feedback is automatic: sound reshapes the terrain, which changes voice behavior and survival.
- Human revision happens between runs: run, listen, inspect a report, revise the scenario, and run again.
The practical run-and-revise procedure is described in Performance.
Consonance Field — A Terrain for Evaluating Pitch
Conchordal’s perception core (the Landscape) listens to the habitat bus, transforms it into log-frequency space, and computes two potentials: roughness (sensory dissonance from amplitude fluctuations within critical bands) and harmonicity (periodicity and template matching). Their combination is the Consonance Field: an evaluation terrain over frequency that placement, movement, prediction, and survival all read from.
Because the field is computed from the habitat bus, every voice routed to that bus deforms the terrain for every other voice. A presentation-only voice does not. That feedback loop — not a chord chart — is where harmony comes from. See Voice and Landscape — Sound–Environment Feedback for the relationship between potential, score, level, mass, and density.
Placing into the field: consonance, dissonance, edge, gap
A field-relative placement names a target — a region of the field — and is
realized either as a cloud (the default) or as a deterministic extremum with
.peak(). The targets are:
consonance— high-consonance positions (harmonic centers, fusion).dissonance— low-consonance positions (tension, clusters, color).edge— the consonance/dissonance boundary (the metastable middle).gap— empty registers (fill the room, avoid masking).
consonance(root) takes a harmonic window around a root (multiples via
range()); every target also takes an absolute (min_hz, max_hz) range.
Range bounds must be finite and within the synthesizer’s 1–20000 Hz range;
reversed bounds are accepted. Initial placement stays inside those bounds,
including jitter and all-zero or fully occupied fallbacks. Field targets use
the intersection with the analysis band (currently 55–8000 Hz). If there is
no intersection, placement falls back to log-uniform sampling in the requested
range because no field evidence is available there. random() samples the
requested range directly. Placement does not constrain subsequent
seek_consonance() movement to that range.
let anchor = harmonic()
.brain("drone")
.amp(0.06)
.sustain()
.anchor();
let voice = harmonic()
.amp(0.04)
.sustain();
section("field placement", || {
place(anchor, at(110.0));
wait(1.0);
place(voice, consonance(110.0).peak().range(1.0, 4.0).count(6).spacing(0.9));
wait(6.0);
});
A target with no modifier is a density cloud: not “random but harmonic” but
a normalized distribution derived from the consonance model, well-defined inside
the range. Placement is independent of behavior — enter at dissonance and
anchor() for a held cluster, or enter at dissonance and seek_consonance()
for a resolution gesture.
let cloud = harmonic().amp(0.035).sustain();
place(cloud, consonance(90.0, 1200.0).count(10).spacing(0.8));
wait(8.0);
Placement tension: tension(τ)
Without tension, Consonance Placement uses its ordinary realization:
.peak() chooses the strongest peak, while the default density produces the
ordinary Consonance cloud. tension(τ) biases that Placement toward a
field-score step below the maximum. τ ∈ [0, 1] is the tension degree;
0 leaves the ordinary realization unchanged, while larger values target
progressively weaker, metastable steps
(target = L_max − τ·(L_max − L_min) over the range, in field score). It is the
placement twin of movement’s search temperature — the dial for how resolved a
spawn should sit — and it reads the field’s score directly, so the degree rides
the terrain’s own scale rather than the count of peaks that happen to be
sounding. With .peak() it snaps to the nearest step; as a cloud it concentrates
the distribution around the target.
let tense = harmonic().amp(0.035).sustain();
// A metastable step below the strongest peak — placed, not resolved.
place(tense, consonance(110.0, 1200.0).peak().tension(0.4).count(6).spacing(0.8));
wait(6.0);
The field-agnostic placements are random(min_hz, max_hz) (log-uniform) and the
geometric at(hz) and line(start_hz, end_hz).
Naming frequencies: ratios of a root
at(hz) and line(start_hz, end_hz) take absolute frequencies, and the
canonical idiom for filling them in is to name one sounding root in Hz and
derive every other pitch as a ratio of it: root_hz * 1.5 for a fifth,
root_hz * 4.0/3.0 for a fourth, root_hz * 2.0 for a register lift. A ratio
is the physical quantity the field itself reads, so writing intervals this way
keeps the thinking in frequency relationships.
Writing an equal-tempered decimal instead — 146.83 for a D3 — still runs,
but it re-imports the twelve-tone symbol grid through the back door: the
number stands in for a note name rather than describing a relationship to a
sounding partial. Prefer ratios of a root that is actually sounding in the
scene.
let root_hz = 110.0;
let voice = harmonic().amp(0.04).sustain();
place(voice, at(root_hz * 1.5));
wait(2.0);
Consonance Movement: seek_consonance
Use seek_consonance() when voices should actively seek better field
positions. It sets free hill-climb movement with glide defaults. Use
glide(tau_sec) when the musical thought is “same movement idea, slower or
faster pitch motion”.
let mover = harmonic()
.amp(0.045)
.sustain()
.seek_consonance()
.glide(0.35)
.avoid_neighbors(0.6)
.global_peaks(8, 70.0)
.ratio_candidates(5);
place(mover, consonance(80.0, 900.0).count(8));
wait(12.0);
avoid_neighbors(strength) adds crowding repulsion so movers spread out
instead of collapsing onto the same peak.
The opposite of movement is anchor(): an anchored voice holds its pitch and
only deforms the terrain for others. freq() sets an anchored pitch; use
anchor() to freeze strategy-placed voices at their settled position.
at(hz) holds a fixed pitch unless the final PopulationSpec explicitly requests
seek_consonance() and remains free. For that moving Voice, it sets only the
initial position, just as line(hz, hz) does, and keeps the placement count.
For example, place(mover, at(220.0)) starts the mover defined above at 220 Hz
without cancelling its movement or glide settings. It specifies initial body
pitch, not a guaranteed first audible pitch or a promise of movement.
Builder order matters when changing the pitch policy:
| Final builder order | Meaning of at(hz) |
|---|---|
seek_consonance() then freq() | Fixed pitch |
freq() then seek_consonance() | Initial position |
seek_consonance() then anchor() | Fixed pitch |
anchor() then seek_consonance() | Initial position |
Without explicit movement, at() still fixes pitch. A live Population’s
freq() still locks pitch during performance.
How movement lands is resolved from phonation: sustained movers glide,
re-attacking movers (pulse(), metric(), entrained(), steady(), flow()) snap to
their new pitch at each onset. pitch_apply_mode("gate_snap") and
pitch_apply_mode("glide") override that choice.
For sustained movers without a participation clock, pitch_apply_mode("rearticulate") gives each newly
adopted pitch target a fresh attack. The old Tone releases while the new Tone
starts directly at the adopted pitch, at the sample when movement begins.
Rejected proposals, unchanged targets and intermediate glide updates do not
create notes. The proposal interval controls opportunities to choose a target,
not a fixed note clock; rearticulation does not wait for a beat.
let moving = harmonic().sustain().seek_consonance()
.pitch_apply_mode("rearticulate");
let voices = place(moving, line(220.0, 440.0).count(3));
wait(12.0);
voices.pitch_apply_mode("glide");
wait(12.0);
A live switch between glide and rearticulate preserves the sounding Tone and
does not trigger a note by itself. Unrendered rearticulation requests are
cancelled by that switch; the new mode handles subsequent adoptions. The mode
also belongs to the Population template used for replacements. glide(tau)
sets the sliding time constant in glide mode and below a rearticulation boundary. Anchored Voices are
unaffected by rearticulate.
Rearticulate requires free pitch and Hold phonation. Combining free-pitch
Gated phonation with it is a script error, checked on the final placement spec
regardless of builder order; an incompatible live call also fails before any
state is changed. Use sustain(), or choose gate_snap or glide with Gated
phonation. Hold without participation does not offer a separate same-pitch attack request.
Sustained participation can re-attack its current body pitch at a sounded opportunity;
autonomous per-note selection is not provided.
Choosing how pitch changes become notes
These choices distinguish target adoption from a participation opportunity. The boundary is a composer parameter of the founder Voice’s articulation, not a universal auditory threshold. Values such as 50 or 100 cents are examples; the composer chooses them.
| Intention | Explicit form | When an attack occurs |
|---|---|---|
| Legato | sustain().pitch_apply_mode("glide") | Initial onset; subsequent pitch movement slides |
| Every adopted target | sustain().pitch_apply_mode("rearticulate") | Each adoption, with the boundary omitted or zero |
| Small moves slide | sustain().pitch_apply_mode("rearticulate").rearticulate_above(50) | Adoption strictly above 50 cents from the last sounded onset |
| Sustained participation with sliding | sustain().entrained().pitch_apply_mode("glide") | A sounded opportunity at its current body pitch; other movement slides |
| Sustained participation with jumps | sustain().flow().pitch_apply_mode("gate_snap") | A sounded opportunity consumes the latest pending target |
| Sustained participation with a movement boundary | sustain().entrained().pitch_apply_mode("rearticulate").rearticulate_above(50) | Small adoptions slide; large ones wait for a sounded opportunity |
Entrained, steady and flow can each be used in any participation row. Steady keeps only due-time opportunities, including with Glide. See Rhythm for their clocks.
intrinsic_rate(hz) sets the body’s initial scale in any participation row,
independently of the pitch-application choice. It does not command an onset interval
or place a shared beat. proposal_interval(seconds) still controls pitch proposals,
not that bodily scale. See Setting the body’s intrinsic rate.
Without participation, a positive rearticulate_above(cents) measures net displacement from the last actual onset,
not accumulated path length. At or below the boundary, the existing Tone glides
with glide(tau); above it, a new Tone attacks. That choice lasts until the next
adoption. Exact Log2Space bin centers use their bin-distance in cents; off-grid
endpoints use the continuous log2 distance, without rounding the target or adding
an epsilon. Earlier legal attacks in the same output hop provide the reference,
and cancelled requests never replace the last materialized onset.
Without participation, the boundary defaults to zero, retaining every-adoption rearticulation. Negative and nonfinite values become zero; no upper limit is imposed. Changing the boundary live causes no attack and applies at the next adoption. A call outside Rearticulate warns and is ignored. Builder order is independent at placement. Replacement Voices inherit the boundary, with no inherited onset-pitch memory.
let moving = harmonic().sustain().seek_consonance()
.pitch_apply_mode("rearticulate").rearticulate_above(50).glide(0.03);
let voices = place(moving, consonance(220.0, 660.0).count(2));
wait(3.0);
voices.rearticulate_above(100);
wait(3.0);
release(voices);
With a participation clock, Rearticulate applies the same distance measure to each adoption but uses the clock for attacks. At or below a positive boundary, movement starts gliding immediately without an attack and cancels any older pending target. Above it, an active glide stops, body pitch is held, and the latest target waits for a sounded opportunity. That opportunity consumes the target and attacks there; with tie omitted or zero, it re-attacks the current body pitch without a target, which may have changed during a glide. Waiting and skipping retain sound and pending state while existing glides continue. Both kinds of attack update the last actual onset reference. Same-sample adoption precedes the opportunity. No adoption-time candidate is added, and the boundary does not filter clock attacks. Zero or omission uses the GateSnap behavior, including zero-distance adoptions. See Rhythm for mode switching.
The positive-boundary sustained-participation form also accepts initial-only
articulation_persistence(p) to retain Base/Connect across existing events.
This changes policy inertia, not the movement boundary or a prescribed legato
proportion. See Retaining an articulation policy.
For gate_snap/rearticulate participation, tie(p) can extend a living Tone without
a pending target instead of re-attacking. It leaves pending-target attacks intact,
including small GateSnap moves. See Extending opportunities.
Sustained synchronization uses the same pending-target rules with a different
omission default: free-pitch GateSnap/Rearticulate extends pending-free beats
like tie(1). Write tie(0) to attack every legal beat. Fixed pitch and Glide
remain unchanged; entrained/steady/flow participation keeps omitted tie at zero. See
Sustained synchronization.
Cutting notes and leaving a rest
For sustained participation, rest() adds a finite end to every sounded note
and an intentional pause after its sound has finished. Supply the note length
with cycles(n) or adaptive_duration() and its modifiers. This length, explicit
sustain(), entrained(), steady() or flow(), and rest() are retained independently of
builder order. No finite length is supplied implicitly. Explicit while_alive()
clears the finite length; placement then fails unless another finite length follows.
The scheduled Off starts the normal release, leaving tails intact. The rest begins
only after the renderer confirms that every Tone of that Voice’s source ID and
generation has finished. rest() then prohibits new notes for one intrinsic bodily
period; rest(periods) uses the composer’s chosen multiple of the period at that
completion. A known period with rest(0) waits for the tails only. An unknown period
keeps waiting, as does unfinished sound, without a fallback or timeout. Negative
and nonfinite arguments become zero; finite nonnegative values have no upper limit.
intrinsic_rate(hz) changes this bodily period and the finite note-length grid
together. The same cycles or duration_range and rest(periods) therefore produce
different note lengths and rests in seconds at different bodily rates, independently
of entrained cadence adaptation.
At the end of the rest, the existing participation decision can sound, wait or skip; the end itself does not force an attack. GateSnap retains the latest adopted target through sounding, release and rest, and consumes it at the next sounded opportunity. Rearticulate applies its small-adoption cancellation and large-target deferral during release and rest too. Its last actual onset remains the reference until the next sounded opportunity. Sliding changes body pitch after Off but never retunes released tails. Glide leaves released tails at their released pitch. Release, death and removal take precedence. Replacements inherit controls, with fresh performance state.
let notes = harmonic().sustain().entrained()
.adaptive_duration().duration_range(0.2, 0.2).rest(2);
let voices = place(notes, at(330.0));
wait(3.0);
release(voices);
Here 0.2 cycles and two periods are example inputs, not recommended values.
rest is initial-only, like the note-length controls. It is valid only with explicit
sustain plus entrained/steady/flow and a finite note length; Glide, GateSnap and
Rearticulate are all available. Incompatible
placements fail before state changes. Without rest, the existing rules remain.
This form ends every note; choosing to sustain or release each individual note is
not implemented by this control.
Mechanism-level controls (pitch_core() and
the hill-climb / peak-sampler tuning in the
API Reference) remain available for research scripts;
prefer seek_consonance() and glide() in curated work.
Consonance Viability and Respawn
Viability makes field fit matter over time. consonance_viability(low, high)
defines the consonance window, while recovery(seconds) states how long a
full-scale recharge takes: a voice in a well-fitting place is sustained, a
voice in a poor place approaches its nominal endurance(seconds).
By default viability uses environment-relative scoring: a voice is
evaluated against the field with its own footprint approximately removed. Use
viability_scope("total") only when the compositional question is explicitly
total-field viability.
Respawn closes the loop into an ecology: when voices die, replacements appear
according to a respawn policy. respawn_consonance() selects high field-score
peaks with a bias around an energy-weighted living parent;
respawn_capacity(count) sets the maximum living membership (defaulting to,
and never lower than, the founder count); respawn_settle(placement) adds
that Placement to the replacement candidate pool rather than replacing the
policy’s baseline candidate.
let settle = consonance(70.0, 1100.0).spacing(0.8);
let ecology = harmonic()
.amp(0.04)
.repeat()
.pulse(1.5)
.cycles(3)
.seek_consonance()
.glide(0.45)
.endurance(8.0)
.recovery(4.0)
.attack_cost_fraction(0.017)
.attack_recharge_fraction(0.70)
.consonance_viability(0.32, 0.82)
.respawn_consonance()
.respawn_capacity(14)
.respawn_settle(settle);
place(ecology, consonance(70.0, 1100.0).count(14));
wait(30.0);
The full lifecycle surface (time-domain endurance/recovery and normalized per-attack fractions) and the respawn policies are documented in the API Reference. Population — A Persistent Unit of Voices separates articulation, phonation, pitch behavior, and survival; the Voice and Landscape — Sound–Environment Feedback follows the complete energy and respawn cycle.
Landscape-aware timbre
The field can shape timbre as well as pitch. The modal() body takes a mode
pattern. landscape_density_modes() deterministically chooses the strongest
separated density-mass positions; landscape_peaks_modes() chooses the
strongest separated local Field-level peaks. Thus a bell’s partials can sit
where the terrain already supports them.
let shimmer_modes = landscape_density_modes()
.count(10)
.range(1.0, 5.5)
.gamma(1.6)
.spacing(0.7);
let shimmer = modal()
.amp(0.025)
.sustain()
.seek_consonance()
.modes(shimmer_modes)
.brightness(0.7);
place(shimmer, consonance(200.0, 1600.0).count(4));
wait(8.0);
Mode constructors include harmonic_modes(), odd_modes(),
power_modes(beta), stiff_string_modes(stiffness),
custom_modes([ratios]), modal_table(name), landscape_density_modes(),
and landscape_peaks_modes() — see the
API Reference.
Rhythm: Evidence and Participation
A beat is a recurrent temporal reference inferred from sound. Sharing that reference does not require every Voice to attack at the same phase.
Timing contracts
A preset defines the relationship its timing policy keeps. An opportunity is distinct from an attack: lifecycle, phonation gates, finite duration, rest and articulation can prevent an onset or extend a living Tone.
| Preset | Keeps | May change | Does not guarantee | Meaning of entrainment(x) |
|---|---|---|---|---|
entrained() | The intrinsic bodily rate, fixed during a Voice’s lifetime | The participation period follows supported recurrence weighted by proximity to the bodily scale. Costs select a time near due, a wait up to one participation period, or a skip | Equal onset intervals, permanently different onset rates between Voices, or alignment with a shared beat phase | Influence of acoustic context on period adaptation and timing costs; omitted value 0.5 |
flow() | The intrinsic scale of bounded, irregular renewal draws; no period adaptation to heard recurrence | Renewal intervals vary; acoustic costs can select a different time, wait, or skip | A regular onset sequence, a beat, or an exact mean interval equal to the intrinsic period | Influence of acoustic context on timing costs, not renewal-scale adaptation; omitted value 0.05 |
steady() | The intrinsic period and later private due-time grid, with sample rounding | Context selects sound or skip at due; execution constraints can remove onsets | An attack count, a pitch outcome, or following another Voice’s time | Influence of acoustic context on sound-or-skip costs; omitted value 0.5. It does not move due or adapt the period |
metric() | Attraction to the acoustically confirmed production beat, with the configured beat-phase offset; independent initial phase before confirmation | Effective rate and phase follow the beat and its confidence. Before confirmation the clock free-runs; execution and articulation can withhold attacks | That a beat will be confirmed, exact phase coincidence, or independent noninteger tempo relations | Confidence-weighted attraction of rate and phase to the beat; omitted value 0.95 |
entrained(), steady() and flow() use local acoustic participation, while
metric() uses beat synchronization. Setting entrainment(0) removes these
acoustic timing influences; flow still draws irregular renewal intervals.
The production meter has one beat reference and integer-ratio subdivision and
measure layers; it does not implement a network of independent noninteger tempi.
Entrained period updates preserve the remaining fraction of a future, uncommitted cycle. New acoustic context does not move a committed onset time.
For entrained and flow timing, a causal observer reads past habitat audio. Each Voice also retains the sound it rendered, so the energy attributed to itself can be excluded from a predicted competitor. The Voice compares sounding near its due time, waiting up to a participation cycle, and omitting the current event. Overlap is integrated across an estimate of the planned hold and ADSR release. Successive voluntary omissions become more costly; an emitted onset resets that count. Only an emitted onset creates a pending context-memory update, which is applied when its matching actual audio is available. Waiting, skipping and an unexecuted plan do not count as an onset. Hold lengths remain tied to the intrinsic period, so cadence adaptation does not stretch each sound. The overlap preview uses the same hold duration. The three-band energy proxy is an engineering approximation, not a model of auditory masking or consonant fusion.
Optional raw T2 production recurrence
The top-level TOML key temporal_meter_t2 = true selects continuous raw habitat
T2 recurrence for the existing production beat. It defaults to false, preserving
the event-based meter. This is separate from the group accent/arrival settings
under [temporal_t2]; raw recurrence needs none of their weights or period keys.
T2 proposes recurrence and confirms frozen future component predictions. Actual
habitat flux and executed onsets still supply phase and intensity. Initial seed
confidence is zero. Missing observations retain the coordinate without inventing
negative evidence; filter restarts break unsupported comparisons. Offline render
uses fixed source/receive cuts, while playback consumes available packets.
metric() reads the same confidence-weighted beat interface. Confidence combines
fast phase coherence with recurrence support; it does not guarantee correct phase.
The ListenerTwin’s presentation meter and the private timing contracts of
steady(), entrained() and flow() retain their existing mechanisms.
Keeping private opportunities: steady()
Missed or illegal steady opportunities advance by whole intrinsic periods rather than shifting the grid. The initial sustained Hold onset remains immediate, before that grid’s later opportunities. Costs and weights are unchanged. Arrival cost currently cannot change sound versus skip: there is only one candidate, and the skip comparison excludes arrival cost.
let voices = place(harmonic().steady().sustain().intrinsic_rate(2.0)
.seek_consonance().pitch_apply_mode("gate_snap"), line(220.0, 330.0).count(2));
wait(6.0);
release(voices);
The last timing preset wins, so flow().steady() selects steady and
steady().flow() selects flow; duration still follows its separate existing
rules. Timing presets are initial-only. Either order with explicit sustain()
keeps a Tone through skipped opportunities. Glide, GateSnap, Rearticulate, its
movement boundary, tie (omitted zero), articulation_persistence and finite
duration with rest retain their existing composition and priority. Even Glide
adds no adoption-time candidate in this preset. Live pitch-mode changes retain
the Tone without adding an onset. Replacements inherit the template’s policy
and rate, with fresh phase, due, plan and performance state.
Director-level terrain
The director shapes the rhythmic terrain, symmetric to the consonance-field operations. These operations shape the production meter used by metric timing. They do not set the bodily rate or recurrence candidates of entrained/steady/flow participants:
meter_stability(value)— attractor depth in[0,1], default0(neutral): how readily a pulse forms and how strongly its reading favors a preferred tempo. The tempo-preference weight equals this depth. The preferred center is the center oftemporal_basin, or 2 Hz in the default band. It only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input. This is a composer-set cultural prior of the modeled listener.temporal_basin(min_hz, max_hz)— a continuous tempo region for the emergent beat (the time-axis analogue ofconsonance(min, max)). It seeds the beat at the center and confines adaptation to the band. At stability0, it adds no center preference or restoring pull. Higher stability strengthens both the preferred reading and confidence-weighted center restoring. It shapes the terrain; it does not place a beat, and it never forces a measure. The same sanitized band center also sets the intrinsic starting rate ofmetric()Voices, independently of stability. Without a basin, that rate remains 2 Hz.
The basin is one initial condition for the whole scene. The final declaration
in the script applies to every metric Population, through both place() and
create(). A declaration after a Population or after wait() still replaces
that same scene value; it does not change the basin during performance.
A body’s initial tempo is distinct from a beat confirmed by the listener.
Metric Voices start with independent phases. Until sound corroborates a beat,
confidence and shared-beat attraction remain zero. After confirmation, the
existing coupling follows the heard beat. Respawn starts from the Population’s
resolved template; it does not inherit the parent’s current clock rate.
Entrained/steady/flow bodily priors and explicit pulse(rate_hz) are unaffected.
From a cold start, a regular 4 Hz input in the default band is read as a 4 Hz
beat at stability 0, and as duple subdivision of a 2 Hz beat at stability 1.
Depth also retains its effects on forcing and evidence presence; it is not an
independent control of tempo weighting. Confidence remains zero until the first
interval hypothesis is corroborated. Chance corroboration in irregular input
can still produce temporary rises afterward.
Per-voice presets and modifiers
The Tier-1 presets take no rate argument: metric(), entrained(), steady(), and
flow(). Configure them on a PopulationSpec before calling place() or
create(). Their timing relationships and coupling defaults are listed above;
intrinsic_rate(hz) sets the bodily scale of participating presets.
Do not confuse .entrained() with brain("entrain"). The preset controls
onset timing; the brain selects the voice’s articulation life and metabolism.
They are independent and may be used together. See
Population — A Persistent Unit of Voices.
Per-voice modifiers refine these intentions:
entrainment(strength)— influence in[0,1], with the per-preset meanings and omission values in the contract table. Within a selected preset, it changes that influence rather than selecting another preset.rhythm_role("beat"|"subdivision"|"accent"|"texture")— the voice’s metrical job.accentemits a stronger onset that drives the shared meter harder, so a recurring downbeat can seed an emergent measure.microtiming(amount)— a signed metric beat-phase offset in[-0.5, 0.5]. A value of0.5targets the half-beat. It does not assign fixed positions to entrained/steady/flow participants.measure_accent(amount)— weak onset emphasis following the detected measure, in[0,1](default0). The onset strength multiplier is1 + 0.35 * amount * confidence * cos(phase). The phase origin follows observed accents, and no detected measure means no emphasis. It changes strength, not onset scheduling, and works independently ofrhythm_role.
meter_stability(0.85); // attractor depth: how readily a pulse forms
temporal_basin(1.8, 2.2); // tempo region the emergent beat gravitates toward
let beat = harmonic()
.metric()
.rhythm_role("accent") // a strong onset that drives the shared beat
.cycles(2);
let entrained = harmonic()
.entrained()
.cycles(2);
let drift = harmonic()
.flow()
.cycles(1);
let offbeat = harmonic()
.metric()
.microtiming(0.5) // a half-beat offset reads as syncopation
.cycles(2);
place(beat, at(110.0));
place(entrained, consonance(110.0).peak().count(3));
place(drift, consonance(300.0, 1200.0).count(4));
place(offbeat, at(220.0));
wait(12.0);
Except for explicit sustain plus participation described below, calls on the same axis are last-write-wins: the last timing mode and the last
duration mode determine the final behavior. cycles(n) counts the intrinsic
bodily period for entrained/steady/flow and the effective beat period for metric timing;
flow retains its existing threefold hold multiplier. Modifiers are remembered and
applied when their matching preset is selected, so entrainment(0.8).metric()
and metric().entrainment(0.8) are equivalent. The same applies to
duration_range(...).adaptive_duration() and the reverse order.
Setting the body’s intrinsic rate
intrinsic_rate(hz) sets the founder Voices’ bodily scale for entrained(), steady() or
flow(). Its period is 1/hz. This is a body initial condition, not an
onset-interval command or a common beat. Entrained timing adapts its participation
period to heard recurrence without changing the intrinsic value. Steady keeps fixed opportunities; flow uses that
value for irregular renewal. All can omit events, and entrained/flow can wait;
different intrinsic rates do not guarantee permanently different onset rates.
Set it on a PopulationSpec before place() or create(). Either order with the
participation preset has the same meaning. It cannot be changed during performance.
Replacements read the Population template, with fresh initial due and performance
state rather than a parent’s adapted period, plan or articulation policy.
The same bodily period sets finite note lengths (cycles, duration_range),
rest(periods) and the representative hold used for overlap prediction. Changing
the rate therefore changes note lengths and rests in seconds together. Flow keeps
its threefold hold multiplier; ADSR release remains in seconds. Explicit sustained
notes without a finite duration stay alive until release.
let slow = harmonic().sustain().entrained().intrinsic_rate(0.5)
.seek_consonance().pitch_apply_mode("gate_snap");
let quick = harmonic().intrinsic_rate(2.0).sustain().flow()
.seek_consonance().pitch_apply_mode("gate_snap");
let a = place(slow, at(220.0));
let b = place(quick, at(330.0));
wait(6.0);
release(a);
release(b);
These rates are example inputs, not recommended values. A final entrained/steady/flow
specification accepts finite values in 0.01..=200 Hz. Values outside that range
or nonfinite values are script errors before placement. The range protects
execution; it is not a musically recommended range. Flow’s renewal interval has
separate existing limits of 0.035..=4 seconds, so extreme rates do not imply
proportionally scaled intervals. Other final timing forms, including metric,
pulse, repeat and once, warn and ignore this modifier even if its value is invalid.
Omission keeps the existing 2 Hz path, and metric’s basin rule is unchanged.
rhythm_freq(hz) sets the Entrain articulation core oscillator and does not set
this participation scale. proposal_interval(seconds) controls pitch proposals;
it does not command an onset or change the body’s intrinsic rate.
Sustained sound with participation opportunities
Explicit sustain() plus entrained(), steady() or flow() keeps a Tone sounding while
its Voice continues to consider participation opportunities. Either builder order
has the same meaning: sustain().entrained() and entrained().sustain() are equivalent.
At an opportunity, choosing to sound releases the old Tone and attacks a new Tone
at the body’s pitch at that sample. Waiting or omitting an opportunity retains the
ongoing Tone, so omission does not create a gap. An unchanged pitch can be re-attacked.
let p = place(
harmonic().sustain().entrained().seek_consonance().glide(0.03),
consonance(220.0, 660.0).count(1)
);
wait(3.0);
release(p);
With the default "glide" mode, a newly adopted pitch target also offers its sample as a candidate for an undecided,
legal participation opportunity. The existing timing, acoustic-context and overlap
costs choose among this candidate and the existing candidates; ties retain the
existing candidate, and a committed plan is not moved. If the adoption is not
selected for an attack, movement glides. An adoption and clock onset at the same
sample produce one attack. A clock onset during a glide uses the current body
pitch; it does not jump ahead to the unfinished target. Release, death and removal
cancel pending requests. Emitted attacks enter the usual energy and acoustic paths once.
This uses the existing finite representative footprint for participation prediction; it does not compare an exact rendering of continuing against release plus a new attack. It does not add a phrase-boundary detector or a pitch-interval threshold.
For this combination the omitted pitch application mode resolves to "glide".
Explicit "gate_snap" with free pitch instead holds one adopted target until the
Voice chooses to sound at a participation opportunity. The old Tone then releases
and a new Tone attacks at that target. A later adoption replaces the pending target.
Waiting and omission retain the sounding pitch and the pending target; movement
does not glide. With tie omitted or zero, an opportunity without a pending target re-attacks the same
pitch. GateSnap offers no extra candidate at the adoption sample and uses the
unchanged participation costs and skip rule. At the same sample, adoption precedes
the opportunity; a future adoption cannot affect an earlier onset. Release, death
and removal cancel the pending target.
let p = place(
harmonic().sustain().flow().seek_consonance().pitch_apply_mode("gate_snap"),
consonance(220.0, 660.0).count(1)
);
wait(3.0);
p.pitch_apply_mode("glide");
wait(3.0);
release(p);
Live Glide ↔ GateSnap ↔ Rearticulate switching preserves the sounding Tone and emits no extra attack. It cancels unrendered requests and the pending attack target. On returning to Glide, movement follows the controller’s retained target at the next control update. GateSnap and Rearticulate wait for subsequent adoptions. Calling the same mode again keeps its pending state. Replacement Voices inherit the mode, starting with no pending performance state. Timing presets remain initial-only.
With pitch_apply_mode("rearticulate"), rearticulate_above(cents) combines
sliding and deferred jumps. A small adoption cancels old pending pitch and starts
gliding; a large one stops sliding and defers the target to the next sounded
opportunity. With tie omitted or zero, an opportunity without a target attacks at current body pitch.
Waiting and skipping do not stop existing glides. The boundary uses the last actual
onset pitch, updated by every attack, and does not suppress clock attacks. Zero or
omission matches GateSnap. Unlike Hold without participation, Rearticulate here
attacks at a sounded opportunity rather than at adoption. Boundary changes preserve
the selected behavior until the next adoption. Mode changes clear unrendered
requests, pending pitch and the selected glide without changing the Tone or the
controller’s target. Builder order does not change this form; replacements inherit
controls, not pending pitch, selected glide or last-onset memory.
The implicit WhileAlive default and while_alive() alone do not select this form.
sustain() alone, entrained() alone and flow() alone retain their previous
behavior. With pulse() or repeat(), sustain() still uses the
existing last-write-wins rule. Explicit sustain() plus metric() selects sustained
synchronization as described below. Without rest(), a later finite duration via
cycles() or adaptive_duration() replaces the sustained form. With rest(),
finite note length and sustained participation are retained independently, and
each note releases before a pause measured from actual tail completion. See
Cutting notes and leaving a rest.
Retaining an articulation policy
Within free-pitch sustained participation, articulation_persistence(p) lets a Voice
keep a private articulation policy across several pitch adoptions and participation
opportunities. It requires sustain() plus metric(), entrained(), steady() or flow(),
pitch_apply_mode("rearticulate"), and a positive rearticulate_above(cents).
The value is set before placement; it is not a live Population method.
let p = place(
harmonic().sustain().entrained().seek_consonance()
.pitch_apply_mode("rearticulate").rearticulate_above(50)
.articulation_persistence(0.75),
consonance(220.0, 660.0).count(1)
);
wait(3.0);
release(p);
The boundary and persistence in this example are comparison inputs, not recommended values. The private policy starts in Base, using the existing boundary and participation rules. Connect slides adopted targets of either size while a Tone lives, and intentionally withholds opportunities that the participation policy selected to sound. It extends the current Tone rather than recording a cost skip. It creates no new Tone, onset recharge, or onset-derived context learning.
Small adoptions and cost skips propose Connect; large adoptions and newly selected
sounded opportunities propose Base. For 0 < p < 1, a proposal against the current
policy keeps it with probability p and changes it with probability 1-p.
Matching proposals and waiting hops do not draw again. A future planned opportunity
keeps its decision until execution. Entering Connect cancels pending pitch; the
last actual onset remains the reference, and cancelled adoptions never replay.
Persistence is policy inertia, not a legato proportion. It acts on Base as well
as Connect. Raising p need not increase Connect time or decrease the number of
attacks monotonically. Duration follows existing events, not a fixed number of
seconds or beats. These retained intervals are not tied to audible cadence,
contour, or rests, and do not infer phrase boundaries.
The default 0 and endpoint 1 both use the existing path without a private
policy or extra random draws; at 1, birth in Base never changes. Finite inputs
clamp to [0,1]; positive infinity becomes 1, negative infinity and NaN become
0. An unsupported final Voice specification warns and ignores the modifier,
regardless of builder order. Replacements inherit the template value and start
in Base with fresh random and performance state. A live mode change resets Base;
repeating the same mode or changing only the boundary retains the policy.
While the boundary is zero, the private path is suspended.
Finite Off, rest(), tail completion, gaps, release, death and removal take priority.
When no Tone lives, Connect allows the next legal onset, including the initial
onset and return from a rest. The policy survives individual notes and rests; it
does not decide whether each note should sustain or release.
Extending opportunities without a new pitch destination
tie(p) sets the probability of extending a living Tone instead of re-attacking
at a selected participation opportunity without a pending target. It is an
initial composer parameter for free-pitch sustain() with metric(), entrained(), steady() or
flow() and explicit gate_snap or rearticulate, including a zero boundary.
Omitted tie resolves to 1 for sustained synchronization and to 0 for sustained
entrained/steady/flow participation, where the Voice can skip opportunities by cost.
Explicit tie(0) preserves pending-free attacks, tie(1) always extends eligible
Tones, and intermediate values mix the two per opportunity. Fixed pitch and Glide
remain outside tie and re-attack each metric crossing. These choices do not claim
a musical optimum.
A pending target still jumps and attacks. Small adoptions below a positive boundary already glide without pending pitch, so those gliding Tones can extend too: the condition is not exact pitch equality. GateSnap makes small and even zero-distance adoptions pending, so they still attack. Tie reduces attacks to a thinned sequence of existing opportunities; it neither moves the opportunities nor changes pitch proposals, costs or skip. It is not an overall legato amount, a phrase switch, or a response to heard phrase boundaries.
let voices = place(harmonic().sustain().entrained()
.pitch_apply_mode("rearticulate").rearticulate_above(50).tie(0.5),
line(220.0, 330.0).count(2));
wait(3.0);
release(voices);
The boundary and intermediate probability in this example illustrate composition,
not recommended settings. Extension creates no new Tone, onset metabolism or
onset-context learning; the opportunity is consumed, without cost-skip learning
or later replay. With articulation_persistence, Connect withholds first without
sampling tie. In Base, pending targets attack; only pending-free opportunities
sample tie. Tie does not change Base/Connect proposals or retention. Connect’s
sliding of large moves and withholding of attacks is a separate policy.
Initial onsets and legal onsets after finite Off, tail completion and rest()
are never blocked when no Tone lives. Release, death, removal and rest legality
take priority. The parameter cannot change during performance. Switching to
Glide suspends it; returning to an applicable mode resumes it. Builder order
is independent at place and create; replacements inherit the template value
with fresh random and performance state. Finite values clamp to [0,1], positive
infinity becomes 1, negative infinity and NaN become 0; unsupported final
forms warn and ignore the value. Zero has no tie state or draws, one has no draws,
and intermediate values use a separate individual stream at execution only.
Explicit when/duration (Tier 2)
Below the presets sit explicit controls: once(), pulse(rate_hz),
while_alive(), cycles(n), and adaptive_duration() (with
duration_range, duration_curve, and shorten_on_drop for tuning).
Timing as survival (Tier 3)
Use rhythm_coupling_vitality(lambda_v, v_floor) and
rhythm_reward(rho_t, "attack_phase_match") when timing should affect
survival and reorganization, and rhythm_freq(freq_hz) to set the internal
articulation oscillator directly. These are legacy articulation controls:
Gated phonation disables autonomous attacks and applies ordinary consonance
recharge to emitted onsets, without the phase-match reward:
let pulse_voice = harmonic()
.repeat()
.pulse(2.0)
.cycles(2)
.rhythm_freq(2.0)
.rhythm_coupling_vitality(0.8, 0.4)
.rhythm_reward(0.4, "attack_phase_match");
place(pulse_voice, at(165.0));
wait(8.0);
Scaffolds (research controls)
The scaffold functions impose an external pulse for comparison assays:
set_scaffold_off();
set_scaffold_shared(2.0);
set_scaffold_scrambled(2.0, 17);
They are useful for demos and assays. They are not the rhythm-composition abstraction: use them only when explicit synchronization is essential to the demo or assay. A dedicated beat carrier is not required for entrained/steady/flow participation.
Sustained synchronization
Explicit sustain() plus metric() keeps a live Tone and uses the existing
coupling clock’s integer crossings as sounding opportunities. Either builder order
has the same meaning. The shared beat is confirmed from sound; before confirmation
the Voice free-runs with its existing basin-centered rate and independent phase.
This combination does not place a beat from outside.
The first Hold onset is immediate. Later opportunities occur on every crossing.
With free-pitch GateSnap/Rearticulate, the default attacks at a beat with a pending
adopted destination and extends the living Tone on other beats, just like tie(1).
Write tie(0) to re-attack on every legal beat. Fixed pitch and Glide still
re-attack current body pitch at each crossing; Glide moves between attacks.
GateSnap retains the latest adopted target for the next legal crossing. Rearticulate with boundary zero does the
same; a positive boundary cancels pending pitch and glides small adoptions, while
larger adoptions remain pending. Same-sample adoption precedes the crossing.
let wire = harmonic().brain("drone").sustain().metric().seek_consonance()
.pitch_apply_mode("rearticulate");
let voices = place(wire, at(660.0));
wait(4.0);
release(voices);
wait(2.0);
For free-pitch GateSnap/Rearticulate, an explicit tie(p) uses its written
probability on pending-free crossings. The omitted probability is resolved before
placement and also applies while live mode switching enters the eligible range.
Sustained entrained/steady/flow retains omission at zero; its cost policy can skip
opportunities. No live tie setter is added.
articulation_persistence retains Base/Connect as in participation, but only
adoptions and sounding crossings offer proposals: synchronization has no cost
skip or planned waiting. rest() combines explicit finite duration with tail
completion and a gap; the clock keeps advancing and omitted crossings never replay.
Role, measure emphasis and microtiming keep their existing meanings. Fixed pitch
is accepted just as in sustained participation; tie and persistence remain limited
to free pitch. Merely setting entrainment or a role does not select this combination.
Routing and the Listener Twin
Two buses
Each voice contributes to two independent mono buses:
- presentation bus → cpal output / offline render / UI metering (the work as presented)
- habitat bus → NSGT analysis → landscape (what the ALife ecology responds to)
By default both buses receive the voice. Use send() when a voice should feed
only one side, or combine buses with |:
// Reference anchor: sensed by the ecology, absent from the presented sound.
let anchor = harmonic().brain("drone").send(habitat_bus);
// Presented decor that does not influence the ecology.
let decor = sine().send(presentation_bus);
// Explicitly both (the default).
let normal = harmonic().send(habitat_bus | presentation_bus);
place(anchor, at(110.0));
place(decor, at(880.0));
place(normal, consonance(110.0).peak().count(3));
wait(4.0);
Keeping the buses separate is a composition tool. A hidden anchor on
send(habitat_bus) can shape how the population organizes without ever
becoming audible, and presented decor on send(presentation_bus) can be heard
without perturbing the ecology.
Both buses receiving every voice is the default because it is Direct
Cognitive Coupling: what the listener hears and what the ecology senses are
the same physical event. Splitting the buses is a deliberate departure.
send(habitat_bus) casts terrain — the director’s work, shaping the field
without being heard. send(presentation_bus) casts decor — heard, but outside
the ecology’s world. Pitch-reference anchors are distinct from dedicated
synchronization support: beat carriers or temporal scaffolds belong only in
samples where explicit synchronization is essential, even when they feed
only the habitat bus.
The Listener Twin
conchordal keeps a ListenerTwin: a listener-side model of the presented
sound only. It never reads the habitat bus, so hidden scaffolds cannot create
fake listener tension. Its listener_state report contains four perceptual
levels:
stability_level: how stable / consonant the current audible sound is.resolvability_level: whether a nearby audible state offers a plausible, more stable continuation.tension_level:(1 - stability) * resolvability— unstable now, but with a reachable path toward improvement.attention_level: presentation-derived onset / spectral-flux salience.
It also contains the listener-side meter estimate:
beat_hz,beat_phase, andbeat_confidence;subdivision_ratioandsubdivision_confidence;measure_hz,measure_ratio, andmeasure_confidence.
generated_frame_id, analysis_frame_id, and analysis_lag_frames make the
perceptual delay explicit. They matter when comparing a report event with the
sound that caused it.
There is no scripting verb for the twin. It is observed, not commanded: when
you run with reporting enabled it emits listener_state records, and the GUI
shows the same state. Use it to check whether the tension you hear matches
what the twin reports before coupling it back into generation.
DCC: coupling the twin back
That optional coupling is DCC, configured in config.toml (or the file
selected with --config), not in script:
[dcc]
# Listener pressure is report/UI-only by default.
# coupling_strength = 0.0
# max_temperature_bonus = 0.10
coupling_strength(0.0–1.0, default0.0): at0.0the twin is report/UI-only and generation is unchanged. Above0.0it appliestension_pressure = tension_level * coupling_strengthas a transient pitch-exploration bonus only. Listener tension already includes resolvability, so the coupler does not apply it again. It never sets target pitches or changes rhythm synchronization.max_temperature_bonus(default0.10): ceiling on that transient bonus.
Raise coupling_strength gradually, and only after the reported
listener_state looks musically legible. When coupling is active,
dcc_pressure report records show both the pressure and the resulting
temperature bonus.
Timeline and Structure
A scenario does not schedule individual audio samples. It advances a script cursor, places Populations at that cursor, and describes when they are patched or released.
place, wait, and flush
place() schedules founder Voices immediately at the current cursor and
returns their stable Population handle. wait(seconds) emits pending live
patches and moves the cursor forward. flush() emits pending live patches
without moving time.
let spec = harmonic()
.amp(0.04)
.brightness(0.35)
.sustain();
let population = place(spec, consonance(220.0).count(3));
population.amp(0.025); // Live patch at the same script time.
flush(); // Emit it; the cursor does not move.
wait(2.0); // Advance two seconds.
release(population);
The semantic boundary is place(), not wait() or flush(). Initial body,
behavior, lifecycle, and respawn settings belong to PopulationSpec; only
live patches and release belong to Population.
release, section, and play
release(population) is explicit. section(name, callback) and
play(callback, ...) create scopes and automatically release Populations
created inside them when the callback returns. Scope exit emits any pending
live patches before the automatic release.
Use section for named form. Its name becomes a scene_marker in reports.
Use play for a reusable gesture that takes arguments.
let gesture = |root_hz, duration_sec| {
place(
harmonic().amp(0.035).sustain(),
consonance(root_hz).peak().count(3)
);
wait(duration_sec);
};
section("two gestures", || {
play(gesture, 110.0, 2.0);
play(gesture, 165.0, 2.0);
});
The Populations created by each play are released at the end of that call.
The outer section provides the report marker and owns anything created
directly inside it.
Parallel timelines
parallel([callbacks]) forks several cursors from the current time. Every
branch starts together. After all branches are described, the main cursor
continues at the end of the longest branch. Each branch is also a scope: its
Populations are released when that branch returns.
section("overlap", || {
parallel([
|| {
place(sine().amp(0.05).sustain(), at(220.0));
wait(3.0);
},
|| {
wait(1.0);
place(harmonic().amp(0.03).sustain(), at(330.0));
wait(1.0);
}
]);
});
The first branch ends three seconds after the fork. The second ends two seconds after it, so the main cursor advances by three seconds. Their Voices overlap only for the interval in which both branch scopes are active.
Script time and runtime behavior
The script cursor decides when control events occur. It does not predict the
exact state of the terrain at those times. Movement, coupled rhythm, death,
and respawn continue inside the runtime while wait() advances the scenario.
This distinction is central:
- The script authors macro-structure.
- The Community produces moment-to-moment behavior.
- Reports show what actually happened during the authored interval.
Use the workflow in Performance to inspect the
result and revise the scenario. Use seed(...) or --seed only when a
particular realization must be replayed.
API Reference
This page is generated from the engine’s registered scripting surface joined with the documentation registry (src/scripting/docs.rs). A CI test fails whenever this page drifts from the engine. Regenerate with:
cargo run --bin gen_rhai_defs
Builder methods return their receiver and are chainable. Integer and float literals are interchangeable wherever both overloads are registered; the generated LSP definitions (rhai-defs/conchordal.d.rhai) list the exact overloads.
The surface is split into four tiers. Core API is the curated composing surface — it is enough for every curated sample. Experimental contains candidate Core verbs under audition. Mechanism Tuning adjusts the mechanisms behind the core verbs when a piece needs a behavior the core surface does not express. Research Controls exist for studying the instrument, not composing with it.
Types
| Type | Description |
|---|---|
PopulationSpec | Reusable population definition: founder voice traits plus lifecycle, respawn, and demographic rules. |
Population | Placed population returned by place(); one stable handle across its voices, deaths, and respawn generations. |
Placement | Where voices enter the field: built by at(), consonance(), dissonance(), edge(), gap(), random(), or line(). |
ModePattern | Frequency pattern for modal synthesis bodies; built by *_modes() constructors. |
Bus | One of the two mono output buses; combine with ` |
BusSet | Combination of buses produced by `bus |
Built-in Constants
| Constant | Type | Description |
|---|---|---|
habitat_bus | Bus | Analysis bus: NSGT -> landscape; what the ecology senses. |
presentation_bus | Bus | Presentation bus: cpal output, recording, UI metering; what the listener hears. |
Core API
The curated composing surface. These verbs are enough for every curated sample.
Population Specs
A PopulationSpec defines founder voice traits and population-level lifecycle rules. Constructors return a PopulationSpec; builder methods refine it; place() creates a Population.
sine
sine() -> PopulationSpec
PopulationSpec whose founder voices use a pure sine body.
harmonic
harmonic() -> PopulationSpec
PopulationSpec whose founder voices use a harmonic-series body.
modal
modal() -> PopulationSpec
PopulationSpec whose founder voices use a resonator-based modal body; shape it with modes().
saw
saw() -> PopulationSpec
Harmonic-body preset with brightness 0.85.
square
square() -> PopulationSpec
Harmonic-body preset with brightness 0.65.
noise
noise() -> PopulationSpec
Harmonic-body preset with full brightness and maximal spectral motion (jitter).
variant
variant(population_spec) -> PopulationSpec
Clone a PopulationSpec so the copy can be modified independently.
Placement
Placements decide where voices enter the frequency field. Constructors return a Placement; modifiers refine it before it is passed to place().
at
at(freq_hz) -> Placement
Place at a fixed frequency in Hz, or start an explicitly moving voice here. With explicit seek_consonance() intent and a final free pitch mode, at(hz) sets only the initial position, using the same placement as line(hz, hz) and preserving the founder count. Otherwise it locks pitch as before. A later freq() or anchor() restores the lock; a later seek_consonance() restores movement. This specifies the initial body pitch, not a guaranteed first audible pitch or a promise that movement will occur.
consonance
consonance(root_hz) -> Placement
consonance(min_hz, max_hz) -> Placement
Target consonance maxima. Density cloud by default; .peak() for the extremum. consonance(root) takes a harmonic window around a root (default 1x-4x, set multiples with range()); consonance(min, max) takes an absolute range. Default spacing is 1.0 ERB.
dissonance
dissonance(min_hz, max_hz) -> Placement
Target consonance minima (tension, clusters). Density cloud by default; .peak() for the most dissonant point.
edge
edge(min_hz, max_hz) -> Placement
Target the consonance/dissonance boundary (C near its midpoint). The metastable region between fusion and beating. Density band by default; .peak() for the point closest to the boundary.
gap
gap(min_hz, max_hz) -> Placement
Target empty registers (low subjective intensity); fill the room. Density cloud by default; .peak() for the emptiest position.
peak
peak()
Applies to: Placement.
Realize a field target as its deterministic extremum.
density
density()
Applies to: Placement.
Realize a field target as a stochastic cloud (the default).
tension
tension(degree)
Applies to: Placement.
Bias Consonance placement toward a field-score step below the in-range maximum (degree 0..1). Consonance placement only. 0 leaves ordinary Consonance placement unchanged: peak() selects the strongest peak, while the default density remains the ordinary Consonance cloud. Higher values target a weaker, more tense step (target = L_max - degree*(L_max - L_min) over the range, in field_score). Pairs with peak() (nearest step, sharp) or density (a broader cloud around it).
random
random(min_hz, max_hz) -> Placement
Log-uniform random placement inside a frequency range.
line
line(start_hz, end_hz) -> Placement
Linear interpolation of positions between two frequencies.
count
count(n)
Applies to: Placement.
Positive number of founder voices to place (default 1).
range
range(min_mul, max_mul)
Applies to: Placement.
Multiplier range relative to the root; only valid on consonance(root).
spacing
spacing(min_erb)
Applies to: Placement.
Minimum ERB distance between placed voices; valid on field placements.
Timeline & Populations
place() creates a Population at the current script time. wait() advances the cursor; flush() emits pending live patches without advancing it; scopes release their populations automatically.
place
place(population_spec, placement) -> Population
Create a Population at the current script time. place() is the sole Population boundary. Configure initial-only properties on the PopulationSpec first; the returned Population exposes only live patches and release.
wait
wait(seconds)
Emit pending live patches, then advance the timeline cursor.
flush
flush()
Emit pending live patches without advancing time.
release
release(population)
Close a live Population; its voices enter their release phase and fade out. Release is terminal at the scripting surface: later live patches on the same Population are ignored.
section
section(name, callback)
Named scope; populations placed inside are released when the callback returns.
play
play(callback)
play(callback, arg1)
play(callback, arg1, arg2)
play(callback, arg1, arg2, arg3)
play(callback, [args])
Scoped callback execution with automatic population release. Accepts 0-3 positional arguments, or an array of arguments.
parallel
parallel(callbacks)
Run an array of closures on parallel timelines from the current cursor. Each branch starts at the current cursor; the cursor advances to the latest branch end.
seed
seed(value)
Set the random seed for reproducible scenarios.
temporal_mode
temporal_mode(name)
Select off or observe for new temporal cognition before playback. Declare once at top level before events or time advance. Default off preserves existing behavior. Observe currently tracks ordered NSGT evidence on both buses; relation inference remains unknown and no new action pressure is applied. Participate is rejected until implemented and calibrated. Run a new scenario to change mode.
Body & Timbre
Sound body parameters of a voice: level, spectrum, detuning, and envelope.
amp
amp(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Amplitude in 0.0-1.0.
freq
freq(hz)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Frequency lock in Hz; implies an anchored pitch (see anchor()).
brightness
brightness(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Spectral brightness in 0.0-1.0 (harmonic/modal bodies).
spread
spread(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Detuning spread.
unison
unison(count)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Number of unison detuning copies.
modes
modes(pattern)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Set the mode pattern of a modal body. See the Mode Patterns section for constructors and modifiers.
adsr
adsr(attack_sec, decay_sec, sustain_level, release_sec)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
ADSR amplitude envelope.
Phonation & Rhythm
When and how long a voice sounds. Tier 1 picks a region on the rhythm timing presets (metric(), entrained(), steady(), flow()); Tier 2 sets explicit when/duration; Tier 3 is expert tuning. Calls on the same axis are last-write-wins, and modifiers are remembered and applied when their matching preset is selected.
brain
brain(name)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Articulation life: "entrain" (default), "seq", or "drone". Selects how the voice lives while sounding; orthogonal to the rhythm timing presets (metric/entrained/steady/flow). entrain is a living articulation whose vitality responds to consonance and rhythm fit, subject to the metabolism economy. seq holds for a fixed lifetime, ignoring the field and metabolism. drone is undying, sustaining forever with a slow sway – useful as terrain material.
sustain
sustain()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Sustain phonation mode (default). Explicit sustain() plus entrained(), steady() or flow(), in either order, retains a Tone while the Voice considers participation. With glide (the omitted mode), clock attacks use current body pitch; entrained/flow can offer one legal adoption-time candidate with unchanged costs, otherwise it glides. Steady evaluates only its own due time and never adds an adoption-time candidate. Explicit gate_snap on free pitch holds the latest adopted target until a sounded clock opportunity, then jumps and attacks; wait/skip retains pitch and pending target, and no adoption-time candidate is offered. Rearticulate with a positive rearticulate_above boundary glides small adoptions, cancelling older pending pitch, and defers large ones to a sounded opportunity. Zero or omission matches gate_snap; clock attacks are not filtered by the boundary. Implicit WhileAlive and while_alive() alone do not enable the combination. Pulse and repeat retain their last-write-wins rule. Explicit sustain() plus metric(), in either order, keeps a live Tone and uses the existing coupling-clock integer crossings as opportunities. For free-pitch gate_snap/rearticulate, omitted tie means tie(1): beats with a pending adopted target attack, while other beats extend the living Tone. Explicit tie(0) re-attacks every legal crossing; explicit intermediate values are unchanged. Fixed pitch and glide still re-attack at each crossing. Omitted tie for sustained entrained/steady/flow remains zero, where participation can skip opportunities by cost. No adoption-time opportunity or participation cost policy is added. The shared beat is confirmed from sound; before confirmation the clock free-runs from its existing basin-centered rate and independent phase. It does not place an external beat. Fixed pitch is supported as in sustained participation; tie and persistence remain free-pitch controls. With rest(), finite duration and sustained participation are retained independently of builder order; every sounded note releases at that duration and waits for its tails before the rest. tie(p) can extend a living Tone at pending-free gate_snap/rearticulate opportunities; it leaves pending-target attacks intact.
rest
rest()
rest(periods)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Release each finite participation note, then leave a rest after all its tails finish. Requires explicit sustain() plus metric(), entrained(), steady() or flow(), and an explicit finite duration from cycles() or adaptive_duration(). These choices are retained independently of builder order. The normal Off releases the note without cutting its tails. After every Tone of the same source ID and generation has actually finished, wait periods times the intrinsic bodily period measured at that completion; rest() uses one period. For entrained/steady/flow, intrinsic_rate(hz) changes this period and therefore the rest in seconds, independently of cadence adaptation; metric retains its existing basin-centered intrinsic rate and ignores intrinsic_rate. With a known period, rest(0) waits only for tail completion. Unknown periods and unfinished tails keep waiting, without a fallback or timeout. Finite nonnegative periods have no upper cap; negative or nonfinite values sanitize to zero. Expiry permits sounding again but never forces an onset. Participation costs/candidates/skip are unchanged; metric keeps advancing its crossings during the gap and never replays suppressed ones. GateSnap retains the latest adopted target through sounding, tails and rest, consuming it at the next sounded opportunity. Glide does not update released tails. Rearticulate processes adoptions during tails and rest: small ones cancel pending pitch and glide the body, large ones defer the latest target, using the last actual onset as reference until the next sounded opportunity. Released tails never move. Missing finite duration and other timing forms fail at final placement before writes; all three pitch application modes are available; explicit while_alive() clears finite intent. Initial-only, like cycles() and duration_range(). Replacement Voices inherit controls, starting with fresh performance state. Without rest(), existing behavior is unchanged. Every note in this form has a finite end; it does not select sustain versus release per note. tie(p) cannot block a legal onset without a Tone after the gap, and never delays finite Off or tail completion.
repeat
repeat()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Repeated/pulsed phonation mode.
metric
metric()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Confidence-weighted rate and phase attraction to the acoustically confirmed beat. Takes no rate argument. With temporal_basin, its intrinsic starting rate is the sanitized band center; without a basin it is 2 Hz. This initial rate is independent of meter_stability. Every metric Population created by place or create reads the scene’s final basin, including calls after wait; repeated basin declarations use the last value, not timed changes. Initial phases remain independent. The omitted entrainment is 0.95. Beat confidence weights attraction of both effective rate and phase, with microtiming setting the beat-phase offset. Beat confirmation itself, exact phase coincidence and independent noninteger tempo relations are not guaranteed. Each clock crossing is an onset opportunity; lifecycle, phonation gates, rest and articulation determine whether an On is emitted. Attraction follows positive confidence in a beat confirmed from sound; the starting rate is not a confirmed beat. Respawn uses the Population template, not the parent’s current clock rate. entrained, steady, flow, and explicit pulse(rate_hz) are outside this initialization rule. Explicit sustain() plus metric(), in either builder order, retains a Tone and uses every coupling-clock integer crossing as an onset opportunity. For free-pitch gate_snap/rearticulate, omitted tie means tie(1): beats with a pending adopted target attack, while other beats extend the living Tone. Explicit tie(0) re-attacks every legal crossing; explicit intermediate values are unchanged. Fixed pitch and glide still re-attack at each legal crossing. Omitted tie for sustained entrained/steady/flow remains zero, where participation can skip opportunities by cost. Glide, deferred pitch, movement boundary, persistence and finite-duration rest share the sustained articulation resolver. Synchronization has no participation cost skip or planned waiting. Before acoustic beat confirmation it free-runs; this preset does not place a beat from outside.
entrained
entrained()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Adaptive acoustic participation with a lifetime-fixed bodily scale. Uses past habitat audio and its own rendered sound. Keeps the intrinsic bodily rate fixed during a Voice’s lifetime; it defaults to 2 Hz and intrinsic_rate(hz) sets its creation value. The participation period can adapt to supported recurrence weighted by proximity to that scale. Its omitted entrainment is 0.5 and controls period adaptation and acoustic timing costs. Costs compare sounding near due, waiting up to one participation period and skipping the event. Adaptation preserves the remaining fraction of a future uncommitted cycle; new acoustic context does not move a committed onset time. Only emitted onsets create pending context-memory updates, applied when their matching actual audio is observed. Waiting, skipping and unexecuted plans do not create an onset experience. Equal onset intervals, permanently different onset rates between Voices and alignment with a shared beat phase are not guaranteed. With explicit sustain(), in either builder order, sounding re-attacks the current body pitch by default; explicit gate_snap/rearticulate may use tie(p) to extend a living Tone without pending pitch. Waiting or skipping retains the ongoing Tone. Omitted pitch application resolves to glide. With rest(), an explicit finite duration ends each note before the tail-completion rest. Without explicit sustain(), finite gated phonation is unchanged.
steady
steady()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Fixed private due times; acoustic context selects sound or skip. A separate non-flow participation preset. It keeps its intrinsic period fixed and evaluates only its own due time, without adapting or moving that time toward another Voice or a shared beat. Heard acoustic context and executed-onset memory affect only whether an opportunity sounds or is skipped; pitch and the number of onsets are not guaranteed. The omitted coupling is 0.5, as for entrained(); entrainment(strength) keeps the same acoustic-context meaning. intrinsic_rate(hz) sets the creation pace, otherwise 2 Hz. Later sounded onsets occupy the private due-time grid, subject to sample rounding, omissions and execution constraints. Missed or illegal opportunities advance by whole intrinsic periods without shifting that grid. No cost term or weight is changed. The current arrival cost is excluded from the skip test, so with one candidate it cannot change sound versus skip. Timing presets follow the existing last-preset-wins rule. With sustain(), in either builder order, the first legal Hold On remains immediate and a skipped opportunity keeps the ongoing Tone. GateSnap, Glide, Rearticulate, rearticulate_above, tie (omitted 0), articulation_persistence, finite duration and rest retain the participation contracts. Replacement Voices inherit the Population template’s policy and start with fresh phase, due, plans and memory.
flow
flow()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Irregular bodily renewal with acoustic timing choices. Keeps its intrinsic renewal scale fixed during a Voice’s lifetime. Renewal draws produce irregular intervals; acoustic costs let the policy choose another time, wait or skip. The omitted entrainment is 0.05; it weights those acoustic costs without adapting the renewal scale. A regular onset sequence, a beat and an exact mean onset interval equal to the intrinsic period are not guaranteed. This is a separate irregular preset, not steady() with renewal added. Unlike entrained(), it does not adapt that scale to an observed recurrence. intrinsic_rate(hz) sets its creation value, otherwise 2 Hz. Hold lengths also use the intrinsic period. Renewal intervals retain their existing 0.035..=4 second limits, so extreme rates do not produce proportionally scaled intervals. With explicit sustain(), in either builder order, sounding re-attacks the current body pitch by default; explicit gate_snap/rearticulate may use tie(p) to extend a living Tone without pending pitch. Waiting or skipping retains the ongoing Tone. Omitted pitch application resolves to glide. With rest(), an explicit finite duration ends each note before the tail-completion rest. Without explicit sustain(), finite gated phonation is unchanged.
intrinsic_rate
intrinsic_rate(hz)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Set the intrinsic bodily rate at creation for entrained()/steady()/flow(), in Hz. Sets the Population’s founder Voices’ bodily scale, not an onset-interval command or a shared beat. The intrinsic period is 1/hz and remains fixed during life; entrained() adapts its participation period to heard recurrence, steady() keeps fixed private opportunities, and flow() retains an irregular renewal scale. Sounding, waiting and omission still use the existing policy. This same bodily period sets cycles(), duration_range(), rest(periods) and the representative hold used for overlap prediction, so note lengths and rests in seconds change together; flow retains its threefold hold multiplier. Omission preserves the existing 2 Hz path. Initial-only: put it before place() or create(), in either order with entrained()/steady()/flow(). Replacements read the Population template and start with fresh phase, due, plan and policy state; they do not inherit a parent’s adapted period. On the final entrained()/steady()/flow() specification, nonfinite values or values outside 0.01..=200 Hz are script errors before placement. These are execution protection limits, not a musically recommended range. Other final timing forms, including metric(), pulse(), repeat() and once(), warn and ignore the modifier even if its value is invalid. Flow renewal intervals separately retain their existing 0.035..=4 second limits, so extreme rates do not imply proportionally scaled onset intervals. Distinct from rhythm_freq(), the Entrain articulation core oscillator, and proposal_interval(), the pitch proposal interval.
entrainment
entrainment(strength)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Preset-specific acoustic timing influence or beat-rate and phase attraction. Order-independent with presets: entrainment(0.8).metric() and metric().entrainment(0.8) are equivalent. Within a selected preset it changes the timing influence, not the preset. For entrained(), it weights acoustic period adaptation and timing costs; for flow(), acoustic timing costs while the renewal scale stays fixed; for steady(), sound-or-skip costs without adapting the period or moving due. For metric(), beat confidence weights its attraction of both effective rate and phase toward the confirmed beat. Omitted values are 0.95 for metric(), 0.5 for entrained()/steady() and 0.05 for flow(). Zero removes these acoustic timing influences; flow still draws irregular renewal intervals. Without an already coupled timing specification, a bare call initializes Coupled timing with its existing defaults.
rhythm_role
rhythm_role(name)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Metrical job: "beat", "subdivision", "accent", or "texture". accent emits a stronger onset that drives the shared meter harder, so a recurring downbeat can seed an emergent measure.
measure_accent
measure_accent(amount)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Weak onset emphasis following the emergent measure, amount 0-1 (default 0). Scales onset strength by 1 + 0.35 * amount * measure confidence * cos(measure phase). Zero phase follows observed accents. With no detected measure, strength stays unchanged. Does not schedule onsets or impose a grouping. Order-independent with metric/entrained/steady/flow presets.
microtiming
microtiming(amount)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Signed beat-phase offset for metric timing, in -0.5..0.5. A value of 0.5 offsets the synchronized target by half a beat. Does not place entrained/steady/flow participants at fixed offsets.
cycles
cycles(n)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Duration of n rhythm cycles. For entrained/steady/flow, duration uses the intrinsic bodily period set by intrinsic_rate(hz), or 2 Hz when omitted, independently of acoustic cadence adaptation. Metric timing uses its effective beat period. Flow retains its threefold hold multiplier. With rest(), this finite duration is retained independently of sustain() and the participation preset, and each sounded note releases at its scheduled end.
tie
tie(p)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Extend a living Tone with probability p at an opportunity without pending pitch. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Initial-only composer parameter for free-pitch sustain() plus metric()/entrained()/steady()/flow() with gate_snap or rearticulate, including a zero boundary. At execution of a selected legal opportunity, pending pitch attacks as before; without pending pitch, a living Tone extends with probability p and re-attacks current body pitch with 1-p. No living Tone means a legal initial or post-rest onset is never withheld. Omission resolves before placement to one for free-pitch sustained metric and zero for sustained entrained/steady/flow, independent of builder order at place/create. Explicit zero bypasses tie state and random draws; one always extends eligible Tones without drawing. Fixed pitch and glide still re-attack at each metric crossing. The omitted metric probability is retained while glide suspends its use, so entering gate_snap/rearticulate live applies the same default without a new setter. Intermediate values use a dedicated individual random stream, once per eligible executed opportunity, not waiting hops; existing pitch, participation, body and articulation_persistence streams are untouched. Extension creates no new Tone, onset metabolism or onset-context learning, and consumes the opportunity without cost-skip learning or later replay. Small boundary-limited glides are eligible too: pending-free does not mean exactly equal pitch. GateSnap retains small and zero-distance adoptions as pending, so those still attack. This is not an overall legato amount: attacks become a thinned opportunity sequence; choices are per opportunity, not phrases or heard boundaries. With articulation_persistence, Connect vetoes first without a tie draw, while Base applies tie only without pending pitch; extension does not change the policy or its proposals. Finite Off, rest, tails/gaps, release, death and removal take priority. Live glide suspends tie; returning to an applicable mode resumes it, without an attack from switching alone. Finite inputs clamp to 0..1; positive infinity becomes one, negative infinity and NaN become zero. Unsupported explicit tie settings warn and ignore the modifier; omission emits no ignored-tie warning. Resolution is independent of builder order at place/create. Replacements inherit the template probability, not the parent’s RNG position, pending target or actual-onset memory. There is no live tie setter or alias. Example intermediate values illustrate composition, not recommended settings.
articulation_persistence
articulation_persistence(p)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Retain a Voice’s private articulation policy across pitch adoptions and sustained clock opportunities. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Initial-only, for free-pitch sustain() plus metric()/entrained()/steady()/flow(), rearticulate and a positive rearticulate_above boundary. The private policy starts in Base (the existing boundary and participation behavior); Connect glides all adoptions while a Tone lives and intentionally withholds sounded opportunities to extend it. Small adoptions and participation cost skips propose Connect; large adoptions and newly selected sounded opportunities propose Base. For 0 < p < 1, only an opposing proposal draws from a dedicated individual random stream: retain with probability p, switch with 1-p. A planned opportunity freezes its decision until execution; waiting hops do not draw again. Entering Connect cancels pending pitch but retains the last actual onset; cancelled adoptions never replay. Intentional withholding is distinct from cost skip and creates no Tone, onset metabolism or onset-context learning. Rest, finite Off, tails/gaps and lifecycle constraints take priority; Connect cannot block a legal onset when no Tone lives. Zero (the default) and one bypass the private state and RNG and retain the existing path. Finite values clamp to 0..1, positive infinity becomes one, negative infinity and NaN become zero. Unsupported final specs warn and ignore the value, independent of builder order. Replacements inherit the template parameter, not policy, RNG, pending decisions or last-onset memory, and start in Base. Live mode changes reset Base; the same mode and boundary-only changes retain the policy. A zero boundary suspends the private path. This is policy inertia, not a legato proportion: increasing p need not monotonically increase Connect time or reduce attacks. Retention is measured in existing events, not seconds or beats, and is not tied to heard cadence, contour, rests or phrase boundaries. Example values are comparisons, not recommendations. With tie(p), Connect vetoes first without a tie trial; Base uses tie only without pending pitch. Tie never changes policy proposals or retention.
Pitch Movement
How a voice moves through the consonance field. seek_consonance(), glide(), and temperature() (movement tension) form the curated surface; the rest tunes the hill-climb and peak-sampler mechanisms.
anchor
anchor()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Hold the voice at its pitch: an anchored voice never moves. at() anchors implicitly unless the final spec explicitly seeks consonance and remains free; in that case it sets only the initial position. freq() still sets an anchored pitch. A later anchor() or freq() overrides movement, while a later seek_consonance() restores it. Use anchor() to hold strategy-placed voices (consonance(), dissonance(), …) at their settled position, or to state the intent explicitly.
seek_consonance
seek_consonance()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Free hill-climb movement toward better field positions, with glide defaults. With this explicit intent and a final free pitch mode, at(hz) sets only the initial position. A later freq() or anchor() restores the lock; calling seek_consonance() after either restores movement. How pitch decisions land is resolved at placement unless pitch_apply_mode() was called: sustained voices glide, re-attacking voices (pulse(), metric(), entrained(), steady(), flow()) snap at onsets.
glide
glide(tau_sec)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Pitch glide time constant in seconds. Sets the sliding time constant, without changing pitch apply mode. It controls glide mode and movement at or below a positive rearticulate_above boundary; gate_snap does not slide.
rearticulate_above
rearticulate_above(cents)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Choose sliding or rearticulation by distance from the last sounded onset pitch. For free-pitch Hold without coupled opportunities, a positive boundary measures net displacement from the last actual ToneSpec onset to the adopted target: strictly above attacks, at or below glides with glide(tau). Selection persists until the next adoption. Earlier legal onsets in the same output hop provide the reference; cancelled requests do not replace the last actual onset. Exact Log2Space centers use bin-distance in cents, otherwise continuous log2 distance is used without rounding or epsilon. Without sustained coupled timing, zero retains every-adoption rearticulation. With explicit sustain plus metric/entrained/steady/flow, a small adoption cancels older pending pitch and glides immediately; a large adoption stops the glide, holds body pitch and defers one latest target to the next sounded opportunity. At a sounded opportunity without pending pitch, explicit tie(0) attacks at current body pitch; omission extends in free-pitch sustained metric and retains attacks in entrained/steady/flow. Other tie(p) values may extend its living Tone; either attack updates the reference. Waiting/skipping retain sound and pending pitch while existing glides continue. Zero or omission matches GateSnap, including zero-distance adoptions. The boundary does not suppress clock attacks and adds no adoption-time candidate. Negative or nonfinite values become zero; no upper limit is imposed. A live boundary change emits no attack and applies at the next adoption. Outside rearticulate, a call warns and is ignored. Builder order is independent at placement; replacement Voices inherit the boundary but not the onset-pitch memory. The composer chooses the boundary; 50 or 100 cents are examples, not recommendations. Initial-only articulation_persistence(p) can retain a private Base/Connect policy within the positive-boundary sustained-participation form; the boundary alone never suppresses clock attacks.
temperature
temperature(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Search temperature shared by both pitch cores: 0 settles greedily, higher is more exploratory. The single stochastic-search knob. At 0 the hill-climb is greedy (move only on a clear improvement, otherwise stay) and the peak sampler takes the argmax candidate. Higher values let the hill-climb accept downhill moves (Metropolis) and soften the peak sampler’s candidate choice. Musically the movement-tension dial; its placement twin is tension().
Neighbor Awareness
How a voice perceives other voices and its own spectral footprint when evaluating the field.
avoid_neighbors
avoid_neighbors(strength)
avoid_neighbors(strength, sigma_cents)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Crowding repulsion from neighboring voices. With one argument the repulsion width uses the default; the two-argument form sets it explicitly in cents.
Lifecycle & Viability
Time-domain survival and recovery. endurance() states nominal zero-fit lifetime; recovery() states full-scale continuous recovery time. consonance_viability() defines the recovery window, with environment-relative scoring by default.
endurance
endurance(seconds)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Nominal survival time at zero field fit, excluding the release tail. Energy is normalized to 0-1. The runtime drain rate is derived at spawn; actual lifetime varies with field fit, attacks, recovery, and release time.
recovery
recovery(seconds)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Time to refill normalized energy from empty at full viability. Optional. If omitted, continuous recovery is disabled. This does not govern the separate per-attack economy.
attack_cost_fraction
attack_cost_fraction(value)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Fraction of normalized energy spent by a full-strength attack.
attack_recharge_fraction
attack_recharge_fraction(value)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Maximum normalized energy restored by a fully consonant attack.
sustain_drive
sustain_drive(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Continuous drive level for sustained voices.
consonance_viability
consonance_viability(low, high)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Consonance window used for viability scoring. Enables environment-relative scoring by default: a voice is evaluated against the field with its own footprint approximately removed (see viability_scope()).
dissonance_penalty
dissonance_penalty(value)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
How strongly good field fit extends life beyond zero-fit endurance. The zero-fit endurance remains fixed; the penalty changes how much longer a well-fit voice survives.
Respawn
Population turnover. A respawn policy decides where replacements appear when voices die; capacity and acceptance thresholds shape ecology-scale behavior.
respawn_random
respawn_random()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Respawn without parent inheritance. Candidates come from the Population’s original Placement and, when set, respawn_settle() adds alternative candidates. The current scene score weights the final choice, so this is not uniform random placement.
respawn_hereditary
respawn_hereditary(sigma_oct)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Respawn near an energy-weighted living parent, with frequency variance in octaves. Candidates share one selected parent. The candidate with the highest current Consonance Field level is used.
respawn_consonance
respawn_consonance()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Respawn from high field-score peaks, biased around an energy-weighted living parent. When living members exist, one parent is selected by energy. Candidate peaks come from the current Consonance Field and are weighted by scene score and their relation to the parent; without a usable peak, the policy falls back to the parent or original Placement.
respawn_capacity
respawn_capacity(count)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Maximum number of living members in the Population. Defaults to the number of founder Voices created by place() and cannot be lower than that founder count.
respawn_settle
respawn_settle(placement)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Add a Placement source to the replacement candidate pool. The policy’s own baseline still supplies one candidate; this Placement supplies the remaining alternatives. Requires a strategy-bearing placement: consonance(), dissonance(), edge(), gap(), random(), or line() (not at()).
respawn_min_c_level
respawn_min_c_level(level)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Minimum consonance level for respawn acceptance.
respawn_background_death_rate
respawn_background_death_rate(rate)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Background turnover rate per second.
Mode Patterns
Frequency relationships for modal() bodies. Constructors return a ModePattern; modifiers are chainable. Landscape-aware patterns deterministically select supported positions from the live field.
harmonic_modes
harmonic_modes() -> ModePattern
Harmonic series: f, 2f, 3f, …
odd_modes
odd_modes() -> ModePattern
Odd harmonics: f, 3f, 5f, …
power_modes
power_modes(beta) -> ModePattern
Power law: f * n^beta.
stiff_string_modes
stiff_string_modes(stiffness) -> ModePattern
Stiffness-adjusted harmonics.
custom_modes
custom_modes(ratios) -> ModePattern
Custom frequency ratios from an array.
modal_table
modal_table(name) -> ModePattern
Named mode table lookup. Falls back to harmonic_modes() with a warning if the name is unknown.
landscape_density_modes
landscape_density_modes() -> ModePattern
Modes selected from the strongest live landscape density-mass positions. Selection is deterministic: after each strongest position is chosen, nearby weights are suppressed according to spacing(). Falls back to harmonic modes when live landscape data yields no usable position.
landscape_peaks_modes
landscape_peaks_modes() -> ModePattern
Modes selected from the strongest local peaks of the live Consonance Field level. Selection is deterministic and enforces the ERB separation from spacing(). Falls back to harmonic modes when live landscape data yields no usable peak.
count
count(n)
Applies to: ModePattern.
Number of modes.
range
range(min_mul, max_mul)
Applies to: ModePattern.
Frequency range; only valid on landscape_*_modes().
spacing
spacing(min_erb)
Applies to: ModePattern.
Minimum ERB distance between modes; only valid on landscape_*_modes().
gamma
gamma(g)
Applies to: ModePattern.
Density sharpening exponent; only valid on landscape_density_modes().
jitter
jitter(cents)
Applies to: ModePattern.
Randomize mode frequencies by up to the given cents.
seed
seed(value)
Applies to: ModePattern.
Random seed for jittered patterns (>= 0).
Routing
Each voice contributes to two independent mono buses: the presentation bus (the work as heard) and the habitat bus (what the ecology senses through NSGT analysis). By default a voice feeds both.
send
send(bus)
Applies to: PopulationSpec only.
Route the voice to specific buses; accepts a Bus or a BusSet. PopulationSpec only. send(habitat_bus) makes a voice sensed by the ecology but absent from the presented sound; send(presentation_bus) is heard without perturbing the ecology; send(habitat_bus | presentation_bus) feeds both (the default).
Director & Global Parameters
Scene-global terrain shaping and research controls. Director verbs are soft priors: they shape the terrain and never schedule a beat or force a measure.
meter_stability
meter_stability(value)
Attractor depth in 0-1: how readily a pulse forms. Default 0 (neutral). A soft prior: it only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input. It also weights the tempo preference toward the center of temporal_basin, or 2 Hz in the default beat band. The preference weight equals the depth, clamped to [0,1]; it is part of the modeled listener’s enculturation. Higher depth favors an in-band tactus nearer that center, including an integer subdivision reading of faster input.
temporal_basin
temporal_basin(min_hz, max_hz)
Set the beat’s tempo band and metric Voices’ initial rate. The time-axis analogue of consonance(min, max): it seeds the beat at the band center and confines tempo adaptation to the band. At meter_stability(0), the band adds no center preference or restoring pull; with higher stability, candidate preference and confidence-weighted center restoring grow with the depth. The same sanitized center sets the intrinsic starting rate of metric() Voices, even at stability 0; without a basin that rate is 2 Hz. entrained()/steady()/flow() retain their separate bodily rate, 2 Hz unless intrinsic_rate(hz) is supplied, and explicit pulse(rate_hz) retains its rate. This is one scene initial condition: the last basin declaration applies to every metric Population, regardless of its position before or after place, create, or wait. It is not an in-performance change. Independent Voice phases and acoustic confirmation remain required; this does not place a beat or force a measure.
Experimental
Candidate core verbs under audition: composing surface by intent, but with research-grade stability until validated.
Phonation & Rhythm
phonate_when_viable
phonate_when_viable()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Withhold the first onset until perceived consonance is viable. Silently settles into the field until consonance reaches the low bound of the voice’s consonance-viability window (consonance_viability(), or always-viable if that window was never set), then phonates normally and never re-gates. A voice that never settles dies unheard and the respawn policy replaces it – there is no timeout parameter, the energy economy is the timeout.
Mechanism Tuning
Fine-grained control over the mechanisms behind the core verbs. Defaults are calibrated; reach for these when a piece needs a specific behavior the core surface does not express.
Phonation & Rhythm
once
once()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Single trigger.
pulse
pulse(rate_hz)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Pulse at an explicit rate in Hz.
while_alive
while_alive()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Hold/sustain until release. Explicit while_alive() clears any finite duration retained for rest(); placement with rest() then fails unless a later finite duration is supplied.
adaptive_duration
adaptive_duration()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Duration follows field support. With rest(), retain this finite duration independently of sustain() and entrained()/steady()/flow(), in any builder order. duration_range(), duration_curve() and shorten_on_drop() tune the retained rule.
pulse_lock
pulse_lock(depth)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Low-level pulse phase weighting in 0-1.
social
social(coupling)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Social coupling for participation or pulse(), in 0-1.
duration_range
duration_range(min_cycles, max_cycles)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Adaptive duration range in rhythm cycles. Also tunes the finite adaptive duration retained for rest(). For entrained()/steady()/flow(), cycles use the bodily period set by intrinsic_rate(hz), independently of cadence adaptation; changing the bodily rate changes this duration in seconds. Initial-only, like cycles() and rest().
duration_curve
duration_curve(k, x0)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Adaptive duration curve parameters.
shorten_on_drop
shorten_on_drop(gain)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Shorten adaptive duration when field support drops.
rhythm_freq
rhythm_freq(freq_hz)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Internal theta/rhythm oscillator frequency. Sets the Entrain articulation core oscillator; it does not set the entrained()/steady()/flow() participation scale. Use intrinsic_rate(hz) for that bodily rate. This control’s existing meaning is unchanged.
rhythm_coupling_vitality
rhythm_coupling_vitality(lambda_v, v_floor)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Vitality-modulated rhythm coupling.
rhythm_reward
rhythm_reward(rho_t, metric)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Energy reward for timing fit; metric is "attack_phase_match" or "none".
Pitch Movement
pitch_smooth
pitch_smooth(tau_sec)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Pitch smoothing time constant in seconds.
pitch_apply_mode
pitch_apply_mode(name)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
"gate_snap", "glide" or "rearticulate": choose how adopted pitch targets are applied. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Choose gate_snap, glide or rearticulate. Without an explicit call, moving sustained voices resolve to glide and Gated voices to gate_snap. Free-pitch Hold without coupled opportunities rearticulate couples each changed adopted target to an attack at its sample, releasing the old Tone; a positive rearticulate_above(cents) instead glides at or below the distance boundary from the last actual onset. Rejected or unchanged targets and proposal-free updates cause no attack. Glide(tau) controls sliding, including below that boundary. With explicit sustain() plus metric()/entrained()/steady()/flow(), the omitted mode is glide: clock attacks use current body pitch and unselected movement glides. Explicit gate_snap on free pitch holds one latest adopted target for the next sounded opportunity, jumps there and attacks; wait/skip retains pitch and pending state, and no extra adoption-time candidate is offered. Without a pending target the clock re-attacks the current pitch unless tie(p) extends the living Tone. With explicit sustained participation, rearticulate measures each adopted target from the last actual onset using the current Log2Space: at or below a positive boundary it cancels old pending pitch and glides immediately; above it, it stops the glide and holds one latest target for the next sounded opportunity. Without pending pitch, omitted tie extends the living Tone in sustained metric; sustained entrained/steady/flow keeps omission at zero. Explicit tie(0) re-attacks current body pitch, while other explicit values retain their probability. Fixed pitch and glide still re-attack each metric crossing, and the omission default applies whenever a live mode switch enters the eligible range. Every actual attack updates onset memory. Same-sample adoption precedes opportunity; no adoption-time candidate is added. Zero or omission matches gate_snap. Hold without coupled opportunities attacks at adoption; sustained participation attacks only at sounded opportunities. Rest retains this adoption policy through tails and gaps without retuning released tails. Live three-mode changes preserve the Tone, emit no attack and cancel unrendered requests, pending attacks and selected glides. Boundary changes preserve the selected behavior until the next adoption. Returning to glide follows the controller’s retained target at the next control update; gate_snap and rearticulate wait for later adoptions. Repeating the same mode retains pending state. Replacement Voices inherit controls, not performance state. Initial-only articulation_persistence(p) adds initial-only policy retention within positive-boundary sustained participation; actual live mode changes reset it to Base, while the same mode and boundary-only updates retain it. Anchored Voices remain locked. Free-pitch Gated rearticulate remains a script error at final placement and before live writes, independent of builder order; use Hold with sustain(), including explicit sustain plus metric/entrained/steady/flow, or choose glide/gate_snap for Gated phonation.
landscape_weight
landscape_weight(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Weight of the landscape objective.
neighbor_step_cents
neighbor_step_cents(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Step size for neighbor exploration in cents.
move_cost
move_cost(coeff)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Cost multiplier for pitch changes.
move_cost_exp
move_cost_exp(exp)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Exponent for the move cost.
proposal_interval
proposal_interval(seconds)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Proposal generation interval in seconds. Controls opportunities to propose a pitch target, not the bodily participation rate set by intrinsic_rate(hz). A proposal or adoption is not necessarily a sounded onset.
tessitura_gravity
tessitura_gravity(value)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Gravity toward the tessitura center.
window_cents
window_cents(width)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Peak-sampler search window width in cents.
top_k
top_k(count)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Number of top candidates kept by the peak sampler.
sigma_cents
sigma_cents(spread)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Gaussian spread of the peak sampler in cents.
random_candidates
random_candidates(count)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Number of random candidates considered.
global_peaks
global_peaks(count)
global_peaks(count, min_sep_cents)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Global field peaks as movement candidates, with optional minimum separation.
ratio_candidates
ratio_candidates(count)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Ratio-based movement candidates; 0 disables.
Neighbor Awareness
crowding_target
crowding_target(same_visible, other_visible)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Which voices are visible to crowding: own population, other populations (booleans).
leave_self_out
leave_self_out(enabled)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Subtract the voice’s own spectral contribution when evaluating the field.
leave_self_out_harmonics
leave_self_out_harmonics(count)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
Number of harmonics used for approximate self-subtraction.
Lifecycle & Viability
viability_scope
viability_scope(name)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
"environment" (default) or "total" viability scoring scope. Use "total" only when the selection question should include the voice’s own contribution.
Director & Global Parameters
set_roughness_aversion
set_roughness_aversion(weight)
How much the modeled listener minds roughness (default 1.0). Scales the configured roughness terms of consonance valuation (field and density), clamped to [0, 100]; non-finite values are ignored. 0 ignores roughness in valuation. It acts as aversion, with larger values penalizing roughness more, when the configured kernel penalizes roughness at every harmonicity (b <= 0 and b + c <= 0, true for the defaults). Roughness itself, as sensed and displayed, does not change.
set_global_coupling
set_global_coupling(value)
Voice interaction strength.
Research Controls
For studying the instrument, not composing with it. Normal composing never touches this tier, and it has the weakest stability guarantee: entries may change or disappear as their research questions settle.
Pitch Movement
pitch_core
pitch_core(name)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
"hill_climb" or "peak_sampler". Research control.
move_cost_time_scale
move_cost_time_scale(name)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
"legacy"/"integration_window" or "proposal"/"proposal_interval".
Neighbor Awareness
leave_self_out_mode
leave_self_out_mode(name)
Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.
"approx"/"approx_harmonics" or "exact"/"exact_scan".
Lifecycle & Viability
selection_approx_loo
selection_approx_loo(enabled)
Applies to: PopulationSpec only.
Override environment-relative viability scoring; research/reference control. PopulationSpec only. Use only for older reference assays that need the previous implementation-level control.
Director & Global Parameters
set_pitch_objective
set_pitch_objective(name)
"consonance"/"positive" or "dissonance"/"negative". Research control.
set_control_update_mode
set_control_update_mode(name)
"snapshot_phased"/"snapshot" (default) or "sequential_rotating"/"sequential". Research control.
set_scaffold_off
set_scaffold_off()
Disable the external rhythm scaffold. Scaffolds are external comparison controls for demos and assays, not the rhythm-composition abstraction.
set_scaffold_shared
set_scaffold_shared(freq_hz)
Shared external scaffold pulse.
set_scaffold_scrambled
set_scaffold_scrambled(freq_hz, seed)
Per-voice scrambled scaffold pulse.
Samples
Sixteen samples in two groups, kept together in samples/. Samples 1–12
introduce the instrument’s capabilities one at a time: API and behavior
demonstrations, not musical works. Samples 13–16 are longer scenes written
as compositions combining those capabilities. Run and read both groups.
cargo run --release -- samples/01_a_single_voice.rhai
Capability demonstrations (1–12)
- A Single Voice — one voice appears, holds its breath, and leaves.
- Constellation — six ways to enter: a line, the peaks, the density, the strain, the gaps, and chance.
- Gravity — the same root under two suns; the peaks listen to what is sounding, not to a chart.
- Tension — the voices settle onto consonance, grow restless and stray, then cool and settle again.
- Settling — scattered voices glide to where the field can hold them.
- Bells — struck bodies; the last bell lets the field choose its partials.
- Heartbeat — a dedicated downbeat Voice supports an explicit synchronization demonstration.
- Murmuration — a flock without a conductor: each voice keeps its own pace and leans toward the recurrences it hears; no shared beat is targeted.
- Rain — time without a beat, falling along the field.
- Generations — voices live, starve, and are reborn where harmony can hold them.
- Autumn Cycle — a directed harmony; the season turns and comes home.
- Emergence and Resolution — a colony heats up and settles, with rhythmic input from its own onsets and no dedicated beat carrier.
Composed scenes (13–16)
- Three Windows — three lines attack on pitch adoption and leave by their own doors.
- Stone and Wire — two metrical voices turn while an independent line crosses.
- Folded Air — a companion rests and returns, changing articulation across sections.
- Open Court — a denser crowd fills the court around two independent paths.
Reading and running
Samples 1–6 walk the consonance terrain (placement, gravity, tension,
movement, timbre). Samples 7–9 introduce three timing intentions: coupling
to a shared beat (metric()), participation informed by heard sound without
targeting shared-beat phase (entrained()), and non-metric clustered
renewal (flow()). Sample 10 closes the loop into life; 11–12 combine
several mechanisms. Samples 13–16
combine adoption-driven attacks, metrical voices with a crossing line, a resting
companion with changing articulation, and a denser crowd. All samples
are compile-checked by the test suite, so they always match the current API.
Top-level samples intentionally do not call seed(...): each run starts from
a fresh scenario seed. Research assays keep fixed seeds so comparisons remain
reproducible.
Terrain anchors generally feed the habitat bus without being heard; in Gravity, the sounding drone is itself the subject. Heartbeat includes a dedicated beat carrier because synchronization is the demonstration’s subject. The other top-level samples have no dedicated beat carrier.
Research assays
samples/research/ holds comparison fixtures — heredity/selection ablations,
external-scaffold rhythm controls, and mechanism studies. They study the
instrument rather than play it, and are not part of the path.
Dedicated synchronization support is limited to demonstrations or assays
where explicit synchronization is essential. Sending that support only to
the habitat bus does not make it suitable for other samples.
Offline rendering
The conchordal instrument never writes audio to disk — performances are
ephemeral by design. For offline WAV rendering use the separate
conchordal-render binary, which shares the core engine but is not the
instrument.