Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

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(), and noise().
  • 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), plus random(), at(), and line().
  • 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 (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 by rhai-lsp
  • rhai-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 each section("name", || { ... }), so later records can be grouped by the most recent marker.
  • spawn / respawn / death — population turnover per voice: spawn and respawn carry the entry frequency (and, for respawn, the parent voice); death carries configured nominal endurance, energy-depletion time, observable lifetime_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 (including alive_count: 0 while awaiting replacement), mean frequency, Consonance Field score and level, and frequency entropy over time.
  • listener_state — the ListenerTwin’s four perceptual levels (stability_level, resolvability_level, tension_level, and attention_level), beat/subdivision/measure tracking, and analysis lag.
  • rhythm_observation — instantaneous global Kuramoto and environment rhythm state. rhythm_summary reports 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.

QuestionMain controlsMeaning
What sounds?sine, harmonic, modal, brightness, modesThe founder Voice body and spectrum.
What kind of life?brain(name): entrain, seq, droneWhether articulation participates in ecology, follows an authored life, or persists as terrain.
When does it sound?sustain, repeat, metric, entrained, flowPhonation and onset timing.
Where does pitch go?Placement, anchor, seek_consonance, temperatureFounder entry position and later movement.
How does the population persist?endurance, recovery, viability, respawnVoice 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(), and flow() 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.

RepresentationRangeUsed for
potentialkernel-dependentRaw roughness and harmonicity output.
field scoreunbounded real valueComparing positions, hill-climb, and placement tension.
field level0..1Bounded behavior and viability signals.
density massnon-negativeWeight before normalization for stochastic placement.
density / PMFsums to 1 in the selected rangeSampling 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:

  1. endurance(seconds) establishes the nominal zero-fit lifetime.
  2. Each attack spends attack_cost_fraction.
  3. A consonant attack can restore up to attack_recharge_fraction.
  4. recovery(seconds) enables continuous recovery; the viability window determines how much of that recovery is available at the current pitch.
  5. At zero energy the voice dies and enters its release tail.
  6. 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 of consonance(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. accent emits 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.5 places 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, and beat_confidence;
  • subdivision_ratio and subdivision_confidence;
  • measure_hz, measure_ratio, and measure_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.01.0, default 0.0): at 0.0 the twin is report/UI-only and generation is unchanged. Above 0.0 it applies tension_pressure = tension_level * resolvability_level * coupling_strength as a transient pitch-exploration bonus only. It never sets target pitches or changes rhythm synchronization.
  • max_temperature_bonus (default 0.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

TypeDescription
PopulationSpecReusable population definition: founder voice traits plus lifecycle, respawn, and demographic rules.
PopulationPlaced population returned by place(); one stable handle across its voices, deaths, and respawn generations.
PlacementWhere voices enter the field: built by at(), consonance(), dissonance(), edge(), gap(), random(), or line().
ModePatternFrequency pattern for modal synthesis bodies; built by *_modes() constructors.
BusOne of the two mono output buses; combine with `
BusSetCombination of buses produced by `bus

Built-in Constants

ConstantTypeDescription
habitat_busBusAnalysis bus: NSGT -> landscape; what the ecology senses.
presentation_busBusPresentation 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() -> 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(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
  1. A Single Voice — one voice appears, holds its breath, and leaves.
  2. Constellation — six ways to enter: a line, the peaks, the density, the strain, the gaps, and chance.
  3. Gravity — the same root under two suns; the peaks listen to what is sounding, not to a chart.
  4. Tension — the voices settle onto consonance, grow restless and stray, then cool and settle again.
  5. Settling — scattered voices glide to where the field can hold them.
  6. Bells — struck bodies; the last bell lets the field choose its partials.
  7. Heartbeat — no external scaffold is imposed; the population locks into a shared pulse.
  8. Murmuration — a flock drifts into step, never commanded.
  9. Rain — time without a beat, falling along the field.
  10. Generations — voices live, starve, and are reborn where harmony can hold them.
  11. Autumn Cycle — a directed harmony; the season turns and comes home.
  12. 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.