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, 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(),entrained(), andflow()select regions of the shared-meter coupling continuum and imply re-attacking behavior.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.
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.
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. Voices placed with at() or given
freq() are anchored implicitly; use anchor() to freeze strategy-placed
voices at their settled position.
How movement lands is resolved from phonation: sustained movers glide,
re-attacking movers (pulse(), metric(), entrained(), flow()) snap to
their new pitch at each onset. Override with pitch_apply_mode() when a
script needs the other behavior. 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: One Coupling Continuum
Rhythm in conchordal is one coupling continuum on a shared emergent meter, not a set of independent clocks. The Community drives a single production meter (a coupled-oscillator beat); each voice is a phase oscillator that entrains its onset phase to that emergent beat with a coupling strength. There is no externally imposed grid — coherence (or its absence) emerges from how tightly each voice locks to the meter the Community itself drives.
The three rhythm “families” are just three regions of the continuum:
- metric: high coupling — a deep attractor, reads as a stable pulse.
- entrained: medium coupling — synchronization emerges over time, still drifts.
- flow: near-zero coupling — a free renewal process, non-metric texture.
Rhythm is part of the same ecology as consonance, viability, movement, and respawn: timing can affect survival, and survival reorganizes timing.
Director-level terrain
The director shapes the rhythmic terrain, symmetric to the consonance-field operations. These are soft priors, never a schedule — emergence still does the work:
meter_stability(value)— attractor depth in[0,1]: how readily a pulse forms. It only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input.temporal_basin(min_hz, max_hz)— the tempo region the emergent beat gravitates toward (the time-axis analogue ofconsonance(min, max)). It shapes the terrain; it does not place a beat, and it never forces a measure.
Per-voice presets and modifiers
The Tier-1 presets take no rate argument: metric(), entrained(), and
flow(). The tempo region is a property of the terrain, set once at the
director level, not per voice. Configure them on a PopulationSpec before
calling place().
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 where on the continuum a voice sits:
entrainment(strength)— coupling in[0,1], free (0) .. locked (1).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 beat-phase offset in[-0.5, 0.5].0.5places a voice a half-beat off, reading as syncopation.
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);
Calls on the same axis are last-write-wins: the last timing mode and the last
duration mode determine the final behavior. 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.
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
oscillator directly:
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: composed rhythm should come from the continuum and the terrain.
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. The samples’ design rule applies: scaffolding is inaudible
or embodied.
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 * resolvability_level * coupling_strengthas a transient pitch-exploration bonus only. 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.
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.
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 coupling continuum (metric(), entrained(), 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 coupling continuum (metric/entrained/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).
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().
High coupling to the shared emergent meter: deep attractor, stable pulse. Takes no rate argument: the tempo region is director-level terrain (temporal_basin).
entrained
entrained()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Medium coupling: synchronization emerges over time, still drifts.
flow
flow()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Near-zero coupling: free renewal process, non-metric texture.
entrainment
entrainment(strength)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Override coupling strength in 0-1 (free .. locked). Order-independent with presets: entrainment(0.8).metric() and metric().entrainment(0.8) are equivalent.
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.
microtiming
microtiming(amount)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Signed beat-phase offset in -0.5..0.5; 0.5 reads as syncopation.
cycles
cycles(n)
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Duration of n rhythm cycles.
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. Voices placed with at() or given freq() are anchored implicitly; 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. How pitch decisions land is resolved at placement unless pitch_apply_mode() was called: sustained voices glide, re-attacking voices (pulse(), metric(), entrained(), 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.
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. A soft prior: it only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input.
temporal_basin
temporal_basin(min_hz, max_hz)
Tempo region the emergent beat gravitates toward. The time-axis analogue of consonance(min, max): it shapes the terrain, does not place a beat, and never forces 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.
adaptive_duration
adaptive_duration()
Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().
Duration follows field support.
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 entrained() 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.
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.
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" or "glide": override how pitch decisions are applied. Without an explicit call, moving voices resolve this at placement from their phonation: sustained voices glide, re-attacking voices snap at onsets.
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.
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_k
set_roughness_k(value)
Roughness tolerance of the landscape.
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
This is an ordered set of twelve small demonstrations. Run and read them to see the instrument’s main capabilities one at a time. They are API and behavior samples, not musical works.
cargo run --release -- samples/01_a_single_voice.rhai
- 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 — no external scaffold is imposed; the population locks into a shared pulse.
- Murmuration — a flock drifts into step, never commanded.
- 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 — everything at once, bent into a single arc.
Samples 1–6 walk the consonance terrain (placement, gravity, tension,
movement, timbre); 7–9 walk the rhythm continuum one region at a time;
10 closes the loop into life; 11–12 combine several mechanisms. 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.
One craft rule holds across the path: scaffolding is inaudible or embodied. Terrain anchors sing to the habitat bus, not to the audience, unless the drone is itself the subject, as with the suns of Gravity. Pulse carriers get resonant bodies and lives of their own, or the pulse is left to condense from the colony.
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.
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.