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, steady, 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() selects shared-beat synchronization; entrained(), steady() and flow() select acoustic participation. Steady keeps its own due time and uses heard context only for sound or skip. These timing presets can re-attack; explicit sustain composes a living Tone with their opportunities. See Rhythm.
  • cycles(n) expresses duration in rhythmic cycles.

The lower-level once(), pulse(rate_hz), while_alive(), and adaptive duration controls are available when the presets do not express the intended gesture. Start with the presets; use explicit timing only when the piece needs it.

Release is not death

release(population) is a terminal script decision: later patches on that Population are ignored, its current Voices enter their release envelopes, and the Population closes. Ecological death is a runtime result: one living Voice exhausts its energy, after which the Population’s respawn policy may replace it. A section or play scope also releases the Populations it created when the scope ends.

The next chapter explains Voice and Landscape — Sound–Environment Feedback.

Explicit sustain() plus metric() combines held sound and acoustic beat synchronization. With free-pitch GateSnap/Rearticulate, omitted tie attacks at a beat with a pending adopted destination and extends other beats; write tie(0) for every beat. Fixed pitch and Glide still attack each crossing, and sustained entrained/steady/flow keeps omitted tie at zero. See rhythm.

Voice and Landscape — Sound–Environment Feedback

A Voice routed to the habitat bus changes the Landscape. The changed Landscape then affects placement, movement, and viability. This chapter traces that runtime feedback path and the energy, death, and respawn processes it supports.

Voice bodies
    |-- presentation bus --> listener / Listener Twin
    |
    `-- habitat bus --> Landscape --> placement, movement, viability
                              ^                         |
                              `------ new sound <------'

By default every voice feeds both buses, so the audience and the ecology share the same physical event. Routing can split them deliberately; see Routing and the Listener Twin.

From sound to terrain

The Landscape analyzes the habitat bus in log-frequency space and computes:

  • roughness potential: interference and beating within critical bands;
  • harmonicity potential: periodicity and support for virtual roots;
  • Consonance Field: the combined terrain used to evaluate candidate frequencies.

A voice changes the spectrum on the habitat bus, which changes these scans. That is why consonance(...).peak() does not return a fixed scale degree: its answer depends on what is sounding now.

Score, level, mass, and density

Several representations are derived from the same terrain. They are related, but they are not interchangeable.

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.

Range bounds must be finite and within the synthesizer’s 1–20000 Hz range; reversed bounds are accepted. Initial placement stays inside those bounds, including jitter and all-zero or fully occupied fallbacks. Field targets use the intersection with the analysis band (currently 55–8000 Hz). If there is no intersection, placement falls back to log-uniform sampling in the requested range because no field evidence is available there. random() samples the requested range directly. Placement does not constrain subsequent seek_consonance() movement to that range.

let anchor = harmonic()
    .brain("drone")
    .amp(0.06)
    .sustain()
    .anchor();

let voice = harmonic()
    .amp(0.04)
    .sustain();

section("field placement", || {
    place(anchor, at(110.0));
    wait(1.0);

    place(voice, consonance(110.0).peak().range(1.0, 4.0).count(6).spacing(0.9));
    wait(6.0);
});

A target with no modifier is a density cloud: not “random but harmonic” but a normalized distribution derived from the consonance model, well-defined inside the range. Placement is independent of behavior — enter at dissonance and anchor() for a held cluster, or enter at dissonance and seek_consonance() for a resolution gesture.

let cloud = harmonic().amp(0.035).sustain();

place(cloud, consonance(90.0, 1200.0).count(10).spacing(0.8));
wait(8.0);

Placement tension: tension(τ)

Without tension, Consonance Placement uses its ordinary realization: .peak() chooses the strongest peak, while the default density produces the ordinary Consonance cloud. tension(τ) biases that Placement toward a field-score step below the maximum. τ ∈ [0, 1] is the tension degree; 0 leaves the ordinary realization unchanged, while larger values target progressively weaker, metastable steps (target = L_max − τ·(L_max − L_min) over the range, in field score). It is the placement twin of movement’s search temperature — the dial for how resolved a spawn should sit — and it reads the field’s score directly, so the degree rides the terrain’s own scale rather than the count of peaks that happen to be sounding. With .peak() it snaps to the nearest step; as a cloud it concentrates the distribution around the target.

let tense = harmonic().amp(0.035).sustain();

// A metastable step below the strongest peak — placed, not resolved.
place(tense, consonance(110.0, 1200.0).peak().tension(0.4).count(6).spacing(0.8));
wait(6.0);

The field-agnostic placements are random(min_hz, max_hz) (log-uniform) and the geometric at(hz) and line(start_hz, end_hz).

Naming frequencies: ratios of a root

at(hz) and line(start_hz, end_hz) take absolute frequencies, and the canonical idiom for filling them in is to name one sounding root in Hz and derive every other pitch as a ratio of it: root_hz * 1.5 for a fifth, root_hz * 4.0/3.0 for a fourth, root_hz * 2.0 for a register lift. A ratio is the physical quantity the field itself reads, so writing intervals this way keeps the thinking in frequency relationships.

Writing an equal-tempered decimal instead — 146.83 for a D3 — still runs, but it re-imports the twelve-tone symbol grid through the back door: the number stands in for a note name rather than describing a relationship to a sounding partial. Prefer ratios of a root that is actually sounding in the scene.

let root_hz = 110.0;
let voice = harmonic().amp(0.04).sustain();

place(voice, at(root_hz * 1.5));
wait(2.0);

Consonance Movement: seek_consonance

Use seek_consonance() when voices should actively seek better field positions. It sets free hill-climb movement with glide defaults. Use glide(tau_sec) when the musical thought is “same movement idea, slower or faster pitch motion”.

let mover = harmonic()
    .amp(0.045)
    .sustain()
    .seek_consonance()
    .glide(0.35)
    .avoid_neighbors(0.6)
    .global_peaks(8, 70.0)
    .ratio_candidates(5);

place(mover, consonance(80.0, 900.0).count(8));
wait(12.0);

avoid_neighbors(strength) adds crowding repulsion so movers spread out instead of collapsing onto the same peak.

The opposite of movement is anchor(): an anchored voice holds its pitch and only deforms the terrain for others. freq() sets an anchored pitch; use anchor() to freeze strategy-placed voices at their settled position.

at(hz) holds a fixed pitch unless the final PopulationSpec explicitly requests seek_consonance() and remains free. For that moving Voice, it sets only the initial position, just as line(hz, hz) does, and keeps the placement count. For example, place(mover, at(220.0)) starts the mover defined above at 220 Hz without cancelling its movement or glide settings. It specifies initial body pitch, not a guaranteed first audible pitch or a promise of movement.

Builder order matters when changing the pitch policy:

Final builder orderMeaning of at(hz)
seek_consonance() then freq()Fixed pitch
freq() then seek_consonance()Initial position
seek_consonance() then anchor()Fixed pitch
anchor() then seek_consonance()Initial position

Without explicit movement, at() still fixes pitch. A live Population’s freq() still locks pitch during performance.

How movement lands is resolved from phonation: sustained movers glide, re-attacking movers (pulse(), metric(), entrained(), steady(), flow()) snap to their new pitch at each onset. pitch_apply_mode("gate_snap") and pitch_apply_mode("glide") override that choice.

For sustained movers without a participation clock, pitch_apply_mode("rearticulate") gives each newly adopted pitch target a fresh attack. The old Tone releases while the new Tone starts directly at the adopted pitch, at the sample when movement begins. Rejected proposals, unchanged targets and intermediate glide updates do not create notes. The proposal interval controls opportunities to choose a target, not a fixed note clock; rearticulation does not wait for a beat.

let moving = harmonic().sustain().seek_consonance()
    .pitch_apply_mode("rearticulate");
let voices = place(moving, line(220.0, 440.0).count(3));
wait(12.0);
voices.pitch_apply_mode("glide");
wait(12.0);

A live switch between glide and rearticulate preserves the sounding Tone and does not trigger a note by itself. Unrendered rearticulation requests are cancelled by that switch; the new mode handles subsequent adoptions. The mode also belongs to the Population template used for replacements. glide(tau) sets the sliding time constant in glide mode and below a rearticulation boundary. Anchored Voices are unaffected by rearticulate.

Rearticulate requires free pitch and Hold phonation. Combining free-pitch Gated phonation with it is a script error, checked on the final placement spec regardless of builder order; an incompatible live call also fails before any state is changed. Use sustain(), or choose gate_snap or glide with Gated phonation. Hold without participation does not offer a separate same-pitch attack request. Sustained participation can re-attack its current body pitch at a sounded opportunity; autonomous per-note selection is not provided.

Choosing how pitch changes become notes

These choices distinguish target adoption from a participation opportunity. The boundary is a composer parameter of the founder Voice’s articulation, not a universal auditory threshold. Values such as 50 or 100 cents are examples; the composer chooses them.

IntentionExplicit formWhen an attack occurs
Legatosustain().pitch_apply_mode("glide")Initial onset; subsequent pitch movement slides
Every adopted targetsustain().pitch_apply_mode("rearticulate")Each adoption, with the boundary omitted or zero
Small moves slidesustain().pitch_apply_mode("rearticulate").rearticulate_above(50)Adoption strictly above 50 cents from the last sounded onset
Sustained participation with slidingsustain().entrained().pitch_apply_mode("glide")A sounded opportunity at its current body pitch; other movement slides
Sustained participation with jumpssustain().flow().pitch_apply_mode("gate_snap")A sounded opportunity consumes the latest pending target
Sustained participation with a movement boundarysustain().entrained().pitch_apply_mode("rearticulate").rearticulate_above(50)Small adoptions slide; large ones wait for a sounded opportunity

Entrained, steady and flow can each be used in any participation row. Steady keeps only due-time opportunities, including with Glide. See Rhythm for their clocks.

intrinsic_rate(hz) sets the body’s initial scale in any participation row, independently of the pitch-application choice. It does not command an onset interval or place a shared beat. proposal_interval(seconds) still controls pitch proposals, not that bodily scale. See Setting the body’s intrinsic rate.

Without participation, a positive rearticulate_above(cents) measures net displacement from the last actual onset, not accumulated path length. At or below the boundary, the existing Tone glides with glide(tau); above it, a new Tone attacks. That choice lasts until the next adoption. Exact Log2Space bin centers use their bin-distance in cents; off-grid endpoints use the continuous log2 distance, without rounding the target or adding an epsilon. Earlier legal attacks in the same output hop provide the reference, and cancelled requests never replace the last materialized onset.

Without participation, the boundary defaults to zero, retaining every-adoption rearticulation. Negative and nonfinite values become zero; no upper limit is imposed. Changing the boundary live causes no attack and applies at the next adoption. A call outside Rearticulate warns and is ignored. Builder order is independent at placement. Replacement Voices inherit the boundary, with no inherited onset-pitch memory.

let moving = harmonic().sustain().seek_consonance()
    .pitch_apply_mode("rearticulate").rearticulate_above(50).glide(0.03);
let voices = place(moving, consonance(220.0, 660.0).count(2));
wait(3.0);
voices.rearticulate_above(100);
wait(3.0);
release(voices);

With a participation clock, Rearticulate applies the same distance measure to each adoption but uses the clock for attacks. At or below a positive boundary, movement starts gliding immediately without an attack and cancels any older pending target. Above it, an active glide stops, body pitch is held, and the latest target waits for a sounded opportunity. That opportunity consumes the target and attacks there; with tie omitted or zero, it re-attacks the current body pitch without a target, which may have changed during a glide. Waiting and skipping retain sound and pending state while existing glides continue. Both kinds of attack update the last actual onset reference. Same-sample adoption precedes the opportunity. No adoption-time candidate is added, and the boundary does not filter clock attacks. Zero or omission uses the GateSnap behavior, including zero-distance adoptions. See Rhythm for mode switching.

The positive-boundary sustained-participation form also accepts initial-only articulation_persistence(p) to retain Base/Connect across existing events. This changes policy inertia, not the movement boundary or a prescribed legato proportion. See Retaining an articulation policy.

For gate_snap/rearticulate participation, tie(p) can extend a living Tone without a pending target instead of re-attacking. It leaves pending-target attacks intact, including small GateSnap moves. See Extending opportunities.

Sustained synchronization uses the same pending-target rules with a different omission default: free-pitch GateSnap/Rearticulate extends pending-free beats like tie(1). Write tie(0) to attack every legal beat. Fixed pitch and Glide remain unchanged; entrained/steady/flow participation keeps omitted tie at zero. See Sustained synchronization.

Cutting notes and leaving a rest

For sustained participation, rest() adds a finite end to every sounded note and an intentional pause after its sound has finished. Supply the note length with cycles(n) or adaptive_duration() and its modifiers. This length, explicit sustain(), entrained(), steady() or flow(), and rest() are retained independently of builder order. No finite length is supplied implicitly. Explicit while_alive() clears the finite length; placement then fails unless another finite length follows.

The scheduled Off starts the normal release, leaving tails intact. The rest begins only after the renderer confirms that every Tone of that Voice’s source ID and generation has finished. rest() then prohibits new notes for one intrinsic bodily period; rest(periods) uses the composer’s chosen multiple of the period at that completion. A known period with rest(0) waits for the tails only. An unknown period keeps waiting, as does unfinished sound, without a fallback or timeout. Negative and nonfinite arguments become zero; finite nonnegative values have no upper limit.

intrinsic_rate(hz) changes this bodily period and the finite note-length grid together. The same cycles or duration_range and rest(periods) therefore produce different note lengths and rests in seconds at different bodily rates, independently of entrained cadence adaptation.

At the end of the rest, the existing participation decision can sound, wait or skip; the end itself does not force an attack. GateSnap retains the latest adopted target through sounding, release and rest, and consumes it at the next sounded opportunity. Rearticulate applies its small-adoption cancellation and large-target deferral during release and rest too. Its last actual onset remains the reference until the next sounded opportunity. Sliding changes body pitch after Off but never retunes released tails. Glide leaves released tails at their released pitch. Release, death and removal take precedence. Replacements inherit controls, with fresh performance state.

let notes = harmonic().sustain().entrained()
    .adaptive_duration().duration_range(0.2, 0.2).rest(2);
let voices = place(notes, at(330.0));
wait(3.0);
release(voices);

Here 0.2 cycles and two periods are example inputs, not recommended values. rest is initial-only, like the note-length controls. It is valid only with explicit sustain plus entrained/steady/flow and a finite note length; Glide, GateSnap and Rearticulate are all available. Incompatible placements fail before state changes. Without rest, the existing rules remain. This form ends every note; choosing to sustain or release each individual note is not implemented by this control.

Mechanism-level controls (pitch_core() and the hill-climb / peak-sampler tuning in the API Reference) remain available for research scripts; prefer seek_consonance() and glide() in curated work.

Consonance Viability and Respawn

Viability makes field fit matter over time. consonance_viability(low, high) defines the consonance window, while recovery(seconds) states how long a full-scale recharge takes: a voice in a well-fitting place is sustained, a voice in a poor place approaches its nominal endurance(seconds).

By default viability uses environment-relative scoring: a voice is evaluated against the field with its own footprint approximately removed. Use viability_scope("total") only when the compositional question is explicitly total-field viability.

Respawn closes the loop into an ecology: when voices die, replacements appear according to a respawn policy. respawn_consonance() selects high field-score peaks with a bias around an energy-weighted living parent; respawn_capacity(count) sets the maximum living membership (defaulting to, and never lower than, the founder count); respawn_settle(placement) adds that Placement to the replacement candidate pool rather than replacing the policy’s baseline candidate.

let settle = consonance(70.0, 1100.0).spacing(0.8);

let ecology = harmonic()
    .amp(0.04)
    .repeat()
    .pulse(1.5)
    .cycles(3)
    .seek_consonance()
    .glide(0.45)
    .endurance(8.0)
    .recovery(4.0)
    .attack_cost_fraction(0.017)
    .attack_recharge_fraction(0.70)
    .consonance_viability(0.32, 0.82)
    .respawn_consonance()
    .respawn_capacity(14)
    .respawn_settle(settle);

place(ecology, consonance(70.0, 1100.0).count(14));
wait(30.0);

The full lifecycle surface (time-domain endurance/recovery and normalized per-attack fractions) and the respawn policies are documented in the API Reference. Population — A Persistent Unit of Voices separates articulation, phonation, pitch behavior, and survival; the Voice and Landscape — Sound–Environment Feedback follows the complete energy and respawn cycle.

Landscape-aware timbre

The field can shape timbre as well as pitch. The modal() body takes a mode pattern. landscape_density_modes() deterministically chooses the strongest separated density-mass positions; landscape_peaks_modes() chooses the strongest separated local Field-level peaks. Thus a bell’s partials can sit where the terrain already supports them.

let shimmer_modes = landscape_density_modes()
    .count(10)
    .range(1.0, 5.5)
    .gamma(1.6)
    .spacing(0.7);

let shimmer = modal()
    .amp(0.025)
    .sustain()
    .seek_consonance()
    .modes(shimmer_modes)
    .brightness(0.7);

place(shimmer, consonance(200.0, 1600.0).count(4));
wait(8.0);

Mode constructors include harmonic_modes(), odd_modes(), power_modes(beta), stiff_string_modes(stiffness), custom_modes([ratios]), modal_table(name), landscape_density_modes(), and landscape_peaks_modes() — see the API Reference.

Rhythm: Evidence and Participation

A beat is a recurrent temporal reference inferred from sound. Sharing that reference does not require every Voice to attack at the same phase.

Timing contracts

A preset defines the relationship its timing policy keeps. An opportunity is distinct from an attack: lifecycle, phonation gates, finite duration, rest and articulation can prevent an onset or extend a living Tone.

PresetKeepsMay changeDoes not guaranteeMeaning of entrainment(x)
entrained()The intrinsic bodily rate, fixed during a Voice’s lifetimeThe participation period follows supported recurrence weighted by proximity to the bodily scale. Costs select a time near due, a wait up to one participation period, or a skipEqual onset intervals, permanently different onset rates between Voices, or alignment with a shared beat phaseInfluence of acoustic context on period adaptation and timing costs; omitted value 0.5
flow()The intrinsic scale of bounded, irregular renewal draws; no period adaptation to heard recurrenceRenewal intervals vary; acoustic costs can select a different time, wait, or skipA regular onset sequence, a beat, or an exact mean interval equal to the intrinsic periodInfluence of acoustic context on timing costs, not renewal-scale adaptation; omitted value 0.05
steady()The intrinsic period and later private due-time grid, with sample roundingContext selects sound or skip at due; execution constraints can remove onsetsAn attack count, a pitch outcome, or following another Voice’s timeInfluence of acoustic context on sound-or-skip costs; omitted value 0.5. It does not move due or adapt the period
metric()Attraction to the acoustically confirmed production beat, with the configured beat-phase offset; independent initial phase before confirmationEffective rate and phase follow the beat and its confidence. Before confirmation the clock free-runs; execution and articulation can withhold attacksThat a beat will be confirmed, exact phase coincidence, or independent noninteger tempo relationsConfidence-weighted attraction of rate and phase to the beat; omitted value 0.95

entrained(), steady() and flow() use local acoustic participation, while metric() uses beat synchronization. Setting entrainment(0) removes these acoustic timing influences; flow still draws irregular renewal intervals. The production meter has one beat reference and integer-ratio subdivision and measure layers; it does not implement a network of independent noninteger tempi.

Entrained period updates preserve the remaining fraction of a future, uncommitted cycle. New acoustic context does not move a committed onset time.

For entrained and flow timing, a causal observer reads past habitat audio. Each Voice also retains the sound it rendered, so the energy attributed to itself can be excluded from a predicted competitor. The Voice compares sounding near its due time, waiting up to a participation cycle, and omitting the current event. Overlap is integrated across an estimate of the planned hold and ADSR release. Successive voluntary omissions become more costly; an emitted onset resets that count. Only an emitted onset creates a pending context-memory update, which is applied when its matching actual audio is available. Waiting, skipping and an unexecuted plan do not count as an onset. Hold lengths remain tied to the intrinsic period, so cadence adaptation does not stretch each sound. The overlap preview uses the same hold duration. The three-band energy proxy is an engineering approximation, not a model of auditory masking or consonant fusion.

Optional raw T2 production recurrence

The top-level TOML key temporal_meter_t2 = true selects continuous raw habitat T2 recurrence for the existing production beat. It defaults to false, preserving the event-based meter. This is separate from the group accent/arrival settings under [temporal_t2]; raw recurrence needs none of their weights or period keys. T2 proposes recurrence and confirms frozen future component predictions. Actual habitat flux and executed onsets still supply phase and intensity. Initial seed confidence is zero. Missing observations retain the coordinate without inventing negative evidence; filter restarts break unsupported comparisons. Offline render uses fixed source/receive cuts, while playback consumes available packets.

metric() reads the same confidence-weighted beat interface. Confidence combines fast phase coherence with recurrence support; it does not guarantee correct phase. The ListenerTwin’s presentation meter and the private timing contracts of steady(), entrained() and flow() retain their existing mechanisms.

Keeping private opportunities: steady()

Missed or illegal steady opportunities advance by whole intrinsic periods rather than shifting the grid. The initial sustained Hold onset remains immediate, before that grid’s later opportunities. Costs and weights are unchanged. Arrival cost currently cannot change sound versus skip: there is only one candidate, and the skip comparison excludes arrival cost.

let voices = place(harmonic().steady().sustain().intrinsic_rate(2.0)
    .seek_consonance().pitch_apply_mode("gate_snap"), line(220.0, 330.0).count(2));
wait(6.0);
release(voices);

The last timing preset wins, so flow().steady() selects steady and steady().flow() selects flow; duration still follows its separate existing rules. Timing presets are initial-only. Either order with explicit sustain() keeps a Tone through skipped opportunities. Glide, GateSnap, Rearticulate, its movement boundary, tie (omitted zero), articulation_persistence and finite duration with rest retain their existing composition and priority. Even Glide adds no adoption-time candidate in this preset. Live pitch-mode changes retain the Tone without adding an onset. Replacements inherit the template’s policy and rate, with fresh phase, due, plan and performance state.

Director-level terrain

The director shapes the rhythmic terrain, symmetric to the consonance-field operations. These operations shape the production meter used by metric timing. They do not set the bodily rate or recurrence candidates of entrained/steady/flow participants:

  • meter_stability(value) — attractor depth in [0,1], default 0 (neutral): how readily a pulse forms and how strongly its reading favors a preferred tempo. The tempo-preference weight equals this depth. The preferred center is the center of temporal_basin, or 2 Hz in the default band. It only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input. This is a composer-set cultural prior of the modeled listener.
  • temporal_basin(min_hz, max_hz) — a continuous tempo region for the emergent beat (the time-axis analogue of consonance(min, max)). It seeds the beat at the center and confines adaptation to the band. At stability 0, it adds no center preference or restoring pull. Higher stability strengthens both the preferred reading and confidence-weighted center restoring. It shapes the terrain; it does not place a beat, and it never forces a measure. The same sanitized band center also sets the intrinsic starting rate of metric() Voices, independently of stability. Without a basin, that rate remains 2 Hz.

The basin is one initial condition for the whole scene. The final declaration in the script applies to every metric Population, through both place() and create(). A declaration after a Population or after wait() still replaces that same scene value; it does not change the basin during performance.

A body’s initial tempo is distinct from a beat confirmed by the listener. Metric Voices start with independent phases. Until sound corroborates a beat, confidence and shared-beat attraction remain zero. After confirmation, the existing coupling follows the heard beat. Respawn starts from the Population’s resolved template; it does not inherit the parent’s current clock rate. Entrained/steady/flow bodily priors and explicit pulse(rate_hz) are unaffected.

From a cold start, a regular 4 Hz input in the default band is read as a 4 Hz beat at stability 0, and as duple subdivision of a 2 Hz beat at stability 1. Depth also retains its effects on forcing and evidence presence; it is not an independent control of tempo weighting. Confidence remains zero until the first interval hypothesis is corroborated. Chance corroboration in irregular input can still produce temporary rises afterward.

Per-voice presets and modifiers

The Tier-1 presets take no rate argument: metric(), entrained(), steady(), and flow(). Configure them on a PopulationSpec before calling place() or create(). Their timing relationships and coupling defaults are listed above; intrinsic_rate(hz) sets the bodily scale of participating presets.

Do not confuse .entrained() with brain("entrain"). The preset controls onset timing; the brain selects the voice’s articulation life and metabolism. They are independent and may be used together. See Population — A Persistent Unit of Voices.

Per-voice modifiers refine these intentions:

  • entrainment(strength) — influence in [0,1], with the per-preset meanings and omission values in the contract table. Within a selected preset, it changes that influence rather than selecting another preset.
  • rhythm_role("beat"|"subdivision"|"accent"|"texture") — the voice’s metrical job. accent emits a stronger onset that drives the shared meter harder, so a recurring downbeat can seed an emergent measure.
  • microtiming(amount) — a signed metric beat-phase offset in [-0.5, 0.5]. A value of 0.5 targets the half-beat. It does not assign fixed positions to entrained/steady/flow participants.
  • measure_accent(amount) — weak onset emphasis following the detected measure, in [0,1] (default 0). The onset strength multiplier is 1 + 0.35 * amount * confidence * cos(phase). The phase origin follows observed accents, and no detected measure means no emphasis. It changes strength, not onset scheduling, and works independently of rhythm_role.
meter_stability(0.85);     // attractor depth: how readily a pulse forms
temporal_basin(1.8, 2.2);   // tempo region the emergent beat gravitates toward

let beat = harmonic()
    .metric()
    .rhythm_role("accent")  // a strong onset that drives the shared beat
    .cycles(2);

let entrained = harmonic()
    .entrained()
    .cycles(2);

let drift = harmonic()
    .flow()
    .cycles(1);

let offbeat = harmonic()
    .metric()
    .microtiming(0.5)       // a half-beat offset reads as syncopation
    .cycles(2);

place(beat, at(110.0));
place(entrained, consonance(110.0).peak().count(3));
place(drift, consonance(300.0, 1200.0).count(4));
place(offbeat, at(220.0));
wait(12.0);

Except for explicit sustain plus participation described below, calls on the same axis are last-write-wins: the last timing mode and the last duration mode determine the final behavior. cycles(n) counts the intrinsic bodily period for entrained/steady/flow and the effective beat period for metric timing; flow retains its existing threefold hold multiplier. Modifiers are remembered and applied when their matching preset is selected, so entrainment(0.8).metric() and metric().entrainment(0.8) are equivalent. The same applies to duration_range(...).adaptive_duration() and the reverse order.

Setting the body’s intrinsic rate

intrinsic_rate(hz) sets the founder Voices’ bodily scale for entrained(), steady() or flow(). Its period is 1/hz. This is a body initial condition, not an onset-interval command or a common beat. Entrained timing adapts its participation period to heard recurrence without changing the intrinsic value. Steady keeps fixed opportunities; flow uses that value for irregular renewal. All can omit events, and entrained/flow can wait; different intrinsic rates do not guarantee permanently different onset rates.

Set it on a PopulationSpec before place() or create(). Either order with the participation preset has the same meaning. It cannot be changed during performance. Replacements read the Population template, with fresh initial due and performance state rather than a parent’s adapted period, plan or articulation policy.

The same bodily period sets finite note lengths (cycles, duration_range), rest(periods) and the representative hold used for overlap prediction. Changing the rate therefore changes note lengths and rests in seconds together. Flow keeps its threefold hold multiplier; ADSR release remains in seconds. Explicit sustained notes without a finite duration stay alive until release.

let slow = harmonic().sustain().entrained().intrinsic_rate(0.5)
    .seek_consonance().pitch_apply_mode("gate_snap");
let quick = harmonic().intrinsic_rate(2.0).sustain().flow()
    .seek_consonance().pitch_apply_mode("gate_snap");
let a = place(slow, at(220.0));
let b = place(quick, at(330.0));
wait(6.0);
release(a);
release(b);

These rates are example inputs, not recommended values. A final entrained/steady/flow specification accepts finite values in 0.01..=200 Hz. Values outside that range or nonfinite values are script errors before placement. The range protects execution; it is not a musically recommended range. Flow’s renewal interval has separate existing limits of 0.035..=4 seconds, so extreme rates do not imply proportionally scaled intervals. Other final timing forms, including metric, pulse, repeat and once, warn and ignore this modifier even if its value is invalid. Omission keeps the existing 2 Hz path, and metric’s basin rule is unchanged.

rhythm_freq(hz) sets the Entrain articulation core oscillator and does not set this participation scale. proposal_interval(seconds) controls pitch proposals; it does not command an onset or change the body’s intrinsic rate.

Sustained sound with participation opportunities

Explicit sustain() plus entrained(), steady() or flow() keeps a Tone sounding while its Voice continues to consider participation opportunities. Either builder order has the same meaning: sustain().entrained() and entrained().sustain() are equivalent. At an opportunity, choosing to sound releases the old Tone and attacks a new Tone at the body’s pitch at that sample. Waiting or omitting an opportunity retains the ongoing Tone, so omission does not create a gap. An unchanged pitch can be re-attacked.

let p = place(
    harmonic().sustain().entrained().seek_consonance().glide(0.03),
    consonance(220.0, 660.0).count(1)
);
wait(3.0);
release(p);

With the default "glide" mode, a newly adopted pitch target also offers its sample as a candidate for an undecided, legal participation opportunity. The existing timing, acoustic-context and overlap costs choose among this candidate and the existing candidates; ties retain the existing candidate, and a committed plan is not moved. If the adoption is not selected for an attack, movement glides. An adoption and clock onset at the same sample produce one attack. A clock onset during a glide uses the current body pitch; it does not jump ahead to the unfinished target. Release, death and removal cancel pending requests. Emitted attacks enter the usual energy and acoustic paths once.

This uses the existing finite representative footprint for participation prediction; it does not compare an exact rendering of continuing against release plus a new attack. It does not add a phrase-boundary detector or a pitch-interval threshold.

For this combination the omitted pitch application mode resolves to "glide". Explicit "gate_snap" with free pitch instead holds one adopted target until the Voice chooses to sound at a participation opportunity. The old Tone then releases and a new Tone attacks at that target. A later adoption replaces the pending target. Waiting and omission retain the sounding pitch and the pending target; movement does not glide. With tie omitted or zero, an opportunity without a pending target re-attacks the same pitch. GateSnap offers no extra candidate at the adoption sample and uses the unchanged participation costs and skip rule. At the same sample, adoption precedes the opportunity; a future adoption cannot affect an earlier onset. Release, death and removal cancel the pending target.

let p = place(
    harmonic().sustain().flow().seek_consonance().pitch_apply_mode("gate_snap"),
    consonance(220.0, 660.0).count(1)
);
wait(3.0);
p.pitch_apply_mode("glide");
wait(3.0);
release(p);

Live Glide ↔ GateSnap ↔ Rearticulate switching preserves the sounding Tone and emits no extra attack. It cancels unrendered requests and the pending attack target. On returning to Glide, movement follows the controller’s retained target at the next control update. GateSnap and Rearticulate wait for subsequent adoptions. Calling the same mode again keeps its pending state. Replacement Voices inherit the mode, starting with no pending performance state. Timing presets remain initial-only.

With pitch_apply_mode("rearticulate"), rearticulate_above(cents) combines sliding and deferred jumps. A small adoption cancels old pending pitch and starts gliding; a large one stops sliding and defers the target to the next sounded opportunity. With tie omitted or zero, an opportunity without a target attacks at current body pitch. Waiting and skipping do not stop existing glides. The boundary uses the last actual onset pitch, updated by every attack, and does not suppress clock attacks. Zero or omission matches GateSnap. Unlike Hold without participation, Rearticulate here attacks at a sounded opportunity rather than at adoption. Boundary changes preserve the selected behavior until the next adoption. Mode changes clear unrendered requests, pending pitch and the selected glide without changing the Tone or the controller’s target. Builder order does not change this form; replacements inherit controls, not pending pitch, selected glide or last-onset memory.

The implicit WhileAlive default and while_alive() alone do not select this form. sustain() alone, entrained() alone and flow() alone retain their previous behavior. With pulse() or repeat(), sustain() still uses the existing last-write-wins rule. Explicit sustain() plus metric() selects sustained synchronization as described below. Without rest(), a later finite duration via cycles() or adaptive_duration() replaces the sustained form. With rest(), finite note length and sustained participation are retained independently, and each note releases before a pause measured from actual tail completion. See Cutting notes and leaving a rest.

Retaining an articulation policy

Within free-pitch sustained participation, articulation_persistence(p) lets a Voice keep a private articulation policy across several pitch adoptions and participation opportunities. It requires sustain() plus metric(), entrained(), steady() or flow(), pitch_apply_mode("rearticulate"), and a positive rearticulate_above(cents). The value is set before placement; it is not a live Population method.

let p = place(
    harmonic().sustain().entrained().seek_consonance()
        .pitch_apply_mode("rearticulate").rearticulate_above(50)
        .articulation_persistence(0.75),
    consonance(220.0, 660.0).count(1)
);
wait(3.0);
release(p);

The boundary and persistence in this example are comparison inputs, not recommended values. The private policy starts in Base, using the existing boundary and participation rules. Connect slides adopted targets of either size while a Tone lives, and intentionally withholds opportunities that the participation policy selected to sound. It extends the current Tone rather than recording a cost skip. It creates no new Tone, onset recharge, or onset-derived context learning.

Small adoptions and cost skips propose Connect; large adoptions and newly selected sounded opportunities propose Base. For 0 < p < 1, a proposal against the current policy keeps it with probability p and changes it with probability 1-p. Matching proposals and waiting hops do not draw again. A future planned opportunity keeps its decision until execution. Entering Connect cancels pending pitch; the last actual onset remains the reference, and cancelled adoptions never replay.

Persistence is policy inertia, not a legato proportion. It acts on Base as well as Connect. Raising p need not increase Connect time or decrease the number of attacks monotonically. Duration follows existing events, not a fixed number of seconds or beats. These retained intervals are not tied to audible cadence, contour, or rests, and do not infer phrase boundaries.

The default 0 and endpoint 1 both use the existing path without a private policy or extra random draws; at 1, birth in Base never changes. Finite inputs clamp to [0,1]; positive infinity becomes 1, negative infinity and NaN become 0. An unsupported final Voice specification warns and ignores the modifier, regardless of builder order. Replacements inherit the template value and start in Base with fresh random and performance state. A live mode change resets Base; repeating the same mode or changing only the boundary retains the policy. While the boundary is zero, the private path is suspended.

Finite Off, rest(), tail completion, gaps, release, death and removal take priority. When no Tone lives, Connect allows the next legal onset, including the initial onset and return from a rest. The policy survives individual notes and rests; it does not decide whether each note should sustain or release.

Extending opportunities without a new pitch destination

tie(p) sets the probability of extending a living Tone instead of re-attacking at a selected participation opportunity without a pending target. It is an initial composer parameter for free-pitch sustain() with metric(), entrained(), steady() or flow() and explicit gate_snap or rearticulate, including a zero boundary. Omitted tie resolves to 1 for sustained synchronization and to 0 for sustained entrained/steady/flow participation, where the Voice can skip opportunities by cost. Explicit tie(0) preserves pending-free attacks, tie(1) always extends eligible Tones, and intermediate values mix the two per opportunity. Fixed pitch and Glide remain outside tie and re-attack each metric crossing. These choices do not claim a musical optimum.

A pending target still jumps and attacks. Small adoptions below a positive boundary already glide without pending pitch, so those gliding Tones can extend too: the condition is not exact pitch equality. GateSnap makes small and even zero-distance adoptions pending, so they still attack. Tie reduces attacks to a thinned sequence of existing opportunities; it neither moves the opportunities nor changes pitch proposals, costs or skip. It is not an overall legato amount, a phrase switch, or a response to heard phrase boundaries.

let voices = place(harmonic().sustain().entrained()
    .pitch_apply_mode("rearticulate").rearticulate_above(50).tie(0.5),
    line(220.0, 330.0).count(2));
wait(3.0);
release(voices);

The boundary and intermediate probability in this example illustrate composition, not recommended settings. Extension creates no new Tone, onset metabolism or onset-context learning; the opportunity is consumed, without cost-skip learning or later replay. With articulation_persistence, Connect withholds first without sampling tie. In Base, pending targets attack; only pending-free opportunities sample tie. Tie does not change Base/Connect proposals or retention. Connect’s sliding of large moves and withholding of attacks is a separate policy.

Initial onsets and legal onsets after finite Off, tail completion and rest() are never blocked when no Tone lives. Release, death, removal and rest legality take priority. The parameter cannot change during performance. Switching to Glide suspends it; returning to an applicable mode resumes it. Builder order is independent at place and create; replacements inherit the template value with fresh random and performance state. Finite values clamp to [0,1], positive infinity becomes 1, negative infinity and NaN become 0; unsupported final forms warn and ignore the value. Zero has no tie state or draws, one has no draws, and intermediate values use a separate individual stream at execution only.

Explicit when/duration (Tier 2)

Below the presets sit explicit controls: once(), pulse(rate_hz), while_alive(), cycles(n), and adaptive_duration() (with duration_range, duration_curve, and shorten_on_drop for tuning).

Timing as survival (Tier 3)

Use rhythm_coupling_vitality(lambda_v, v_floor) and rhythm_reward(rho_t, "attack_phase_match") when timing should affect survival and reorganization, and rhythm_freq(freq_hz) to set the internal articulation oscillator directly. These are legacy articulation controls: Gated phonation disables autonomous attacks and applies ordinary consonance recharge to emitted onsets, without the phase-match reward:

let pulse_voice = harmonic()
    .repeat()
    .pulse(2.0)
    .cycles(2)
    .rhythm_freq(2.0)
    .rhythm_coupling_vitality(0.8, 0.4)
    .rhythm_reward(0.4, "attack_phase_match");

place(pulse_voice, at(165.0));
wait(8.0);

Scaffolds (research controls)

The scaffold functions impose an external pulse for comparison assays:

set_scaffold_off();
set_scaffold_shared(2.0);
set_scaffold_scrambled(2.0, 17);

They are useful for demos and assays. They are not the rhythm-composition abstraction: use them only when explicit synchronization is essential to the demo or assay. A dedicated beat carrier is not required for entrained/steady/flow participation.

Sustained synchronization

Explicit sustain() plus metric() keeps a live Tone and uses the existing coupling clock’s integer crossings as sounding opportunities. Either builder order has the same meaning. The shared beat is confirmed from sound; before confirmation the Voice free-runs with its existing basin-centered rate and independent phase. This combination does not place a beat from outside.

The first Hold onset is immediate. Later opportunities occur on every crossing. With free-pitch GateSnap/Rearticulate, the default attacks at a beat with a pending adopted destination and extends the living Tone on other beats, just like tie(1). Write tie(0) to re-attack on every legal beat. Fixed pitch and Glide still re-attack current body pitch at each crossing; Glide moves between attacks. GateSnap retains the latest adopted target for the next legal crossing. Rearticulate with boundary zero does the same; a positive boundary cancels pending pitch and glides small adoptions, while larger adoptions remain pending. Same-sample adoption precedes the crossing.

let wire = harmonic().brain("drone").sustain().metric().seek_consonance()
    .pitch_apply_mode("rearticulate");
let voices = place(wire, at(660.0));
wait(4.0);
release(voices);
wait(2.0);

For free-pitch GateSnap/Rearticulate, an explicit tie(p) uses its written probability on pending-free crossings. The omitted probability is resolved before placement and also applies while live mode switching enters the eligible range. Sustained entrained/steady/flow retains omission at zero; its cost policy can skip opportunities. No live tie setter is added. articulation_persistence retains Base/Connect as in participation, but only adoptions and sounding crossings offer proposals: synchronization has no cost skip or planned waiting. rest() combines explicit finite duration with tail completion and a gap; the clock keeps advancing and omitted crossings never replay. Role, measure emphasis and microtiming keep their existing meanings. Fixed pitch is accepted just as in sustained participation; tie and persistence remain limited to free pitch. Merely setting entrainment or a role does not select this combination.

Routing and the Listener Twin

Two buses

Each voice contributes to two independent mono buses:

  • presentation bus → cpal output / offline render / UI metering (the work as presented)
  • habitat bus → NSGT analysis → landscape (what the ALife ecology responds to)

By default both buses receive the voice. Use send() when a voice should feed only one side, or combine buses with |:

// Reference anchor: sensed by the ecology, absent from the presented sound.
let anchor = harmonic().brain("drone").send(habitat_bus);

// Presented decor that does not influence the ecology.
let decor = sine().send(presentation_bus);

// Explicitly both (the default).
let normal = harmonic().send(habitat_bus | presentation_bus);

place(anchor, at(110.0));
place(decor, at(880.0));
place(normal, consonance(110.0).peak().count(3));
wait(4.0);

Keeping the buses separate is a composition tool. A hidden anchor on send(habitat_bus) can shape how the population organizes without ever becoming audible, and presented decor on send(presentation_bus) can be heard without perturbing the ecology.

Both buses receiving every voice is the default because it is Direct Cognitive Coupling: what the listener hears and what the ecology senses are the same physical event. Splitting the buses is a deliberate departure. send(habitat_bus) casts terrain — the director’s work, shaping the field without being heard. send(presentation_bus) casts decor — heard, but outside the ecology’s world. Pitch-reference anchors are distinct from dedicated synchronization support: beat carriers or temporal scaffolds belong only in samples where explicit synchronization is essential, even when they feed only the habitat bus.

The Listener Twin

conchordal keeps a ListenerTwin: a listener-side model of the presented sound only. It never reads the habitat bus, so hidden scaffolds cannot create fake listener tension. Its listener_state report contains four perceptual levels:

  • stability_level: how stable / consonant the current audible sound is.
  • resolvability_level: whether a nearby audible state offers a plausible, more stable continuation.
  • tension_level: (1 - stability) * resolvability — unstable now, but with a reachable path toward improvement.
  • attention_level: presentation-derived onset / spectral-flux salience.

It also contains the listener-side meter estimate:

  • beat_hz, beat_phase, 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.0–1.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 * coupling_strength as a transient pitch-exploration bonus only. Listener tension already includes resolvability, so the coupler does not apply it again. It never sets target pitches or changes rhythm synchronization.
  • max_temperature_bonus (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, or start an explicitly moving voice here. With explicit seek_consonance() intent and a final free pitch mode, at(hz) sets only the initial position, using the same placement as line(hz, hz) and preserving the founder count. Otherwise it locks pitch as before. A later freq() or anchor() restores the lock; a later seek_consonance() restores movement. This specifies the initial body pitch, not a guaranteed first audible pitch or a promise that movement will occur.

consonance

consonance(root_hz) -> Placement
consonance(min_hz, max_hz) -> Placement

Target consonance maxima. Density cloud by default; .peak() for the extremum. consonance(root) takes a harmonic window around a root (default 1x-4x, set multiples with range()); consonance(min, max) takes an absolute range. Default spacing is 1.0 ERB.

dissonance

dissonance(min_hz, max_hz) -> Placement

Target consonance minima (tension, clusters). Density cloud by default; .peak() for the most dissonant point.

edge

edge(min_hz, max_hz) -> Placement

Target the consonance/dissonance boundary (C near its midpoint). The metastable region between fusion and beating. Density band by default; .peak() for the point closest to the boundary.

gap

gap(min_hz, max_hz) -> Placement

Target empty registers (low subjective intensity); fill the room. Density cloud by default; .peak() for the emptiest position.

peak

peak()

Applies to: Placement.

Realize a field target as its deterministic extremum.

density

density()

Applies to: Placement.

Realize a field target as a stochastic cloud (the default).

tension

tension(degree)

Applies to: Placement.

Bias Consonance placement toward a field-score step below the in-range maximum (degree 0..1). Consonance placement only. 0 leaves ordinary Consonance placement unchanged: peak() selects the strongest peak, while the default density remains the ordinary Consonance cloud. Higher values target a weaker, more tense step (target = L_max - degree*(L_max - L_min) over the range, in field_score). Pairs with peak() (nearest step, sharp) or density (a broader cloud around it).

random

random(min_hz, max_hz) -> Placement

Log-uniform random placement inside a frequency range.

line

line(start_hz, end_hz) -> Placement

Linear interpolation of positions between two frequencies.

count

count(n)

Applies to: Placement.

Positive number of founder voices to place (default 1).

range

range(min_mul, max_mul)

Applies to: Placement.

Multiplier range relative to the root; only valid on consonance(root).

spacing

spacing(min_erb)

Applies to: Placement.

Minimum ERB distance between placed voices; valid on field placements.

Timeline & Populations

place() creates a Population at the current script time. wait() advances the cursor; flush() emits pending live patches without advancing it; scopes release their populations automatically.

place

place(population_spec, placement) -> Population

Create a Population at the current script time. place() is the sole Population boundary. Configure initial-only properties on the PopulationSpec first; the returned Population exposes only live patches and release.

wait

wait(seconds)

Emit pending live patches, then advance the timeline cursor.

flush

flush()

Emit pending live patches without advancing time.

release

release(population)

Close a live Population; its voices enter their release phase and fade out. Release is terminal at the scripting surface: later live patches on the same Population are ignored.

section

section(name, callback)

Named scope; populations placed inside are released when the callback returns.

play

play(callback)
play(callback, arg1)
play(callback, arg1, arg2)
play(callback, arg1, arg2, arg3)
play(callback, [args])

Scoped callback execution with automatic population release. Accepts 0-3 positional arguments, or an array of arguments.

parallel

parallel(callbacks)

Run an array of closures on parallel timelines from the current cursor. Each branch starts at the current cursor; the cursor advances to the latest branch end.

seed

seed(value)

Set the random seed for reproducible scenarios.

temporal_mode

temporal_mode(name)

Select off or observe for new temporal cognition before playback. Declare once at top level before events or time advance. Default off preserves existing behavior. Observe currently tracks ordered NSGT evidence on both buses; relation inference remains unknown and no new action pressure is applied. Participate is rejected until implemented and calibrated. Run a new scenario to change mode.

Body & Timbre

Sound body parameters of a voice: level, spectrum, detuning, and envelope.

amp

amp(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Amplitude in 0.0-1.0.

freq

freq(hz)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Frequency lock in Hz; implies an anchored pitch (see anchor()).

brightness

brightness(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Spectral brightness in 0.0-1.0 (harmonic/modal bodies).

spread

spread(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Detuning spread.

unison

unison(count)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Number of unison detuning copies.

modes

modes(pattern)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Set the mode pattern of a modal body. See the Mode Patterns section for constructors and modifiers.

adsr

adsr(attack_sec, decay_sec, sustain_level, release_sec)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

ADSR amplitude envelope.

Phonation & Rhythm

When and how long a voice sounds. Tier 1 picks a region on the rhythm timing presets (metric(), entrained(), steady(), flow()); Tier 2 sets explicit when/duration; Tier 3 is expert tuning. Calls on the same axis are last-write-wins, and modifiers are remembered and applied when their matching preset is selected.

brain

brain(name)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Articulation life: "entrain" (default), "seq", or "drone". Selects how the voice lives while sounding; orthogonal to the rhythm timing presets (metric/entrained/steady/flow). entrain is a living articulation whose vitality responds to consonance and rhythm fit, subject to the metabolism economy. seq holds for a fixed lifetime, ignoring the field and metabolism. drone is undying, sustaining forever with a slow sway – useful as terrain material.

sustain

sustain()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Sustain phonation mode (default). Explicit sustain() plus entrained(), steady() or flow(), in either order, retains a Tone while the Voice considers participation. With glide (the omitted mode), clock attacks use current body pitch; entrained/flow can offer one legal adoption-time candidate with unchanged costs, otherwise it glides. Steady evaluates only its own due time and never adds an adoption-time candidate. Explicit gate_snap on free pitch holds the latest adopted target until a sounded clock opportunity, then jumps and attacks; wait/skip retains pitch and pending target, and no adoption-time candidate is offered. Rearticulate with a positive rearticulate_above boundary glides small adoptions, cancelling older pending pitch, and defers large ones to a sounded opportunity. Zero or omission matches gate_snap; clock attacks are not filtered by the boundary. Implicit WhileAlive and while_alive() alone do not enable the combination. Pulse and repeat retain their last-write-wins rule. Explicit sustain() plus metric(), in either order, keeps a live Tone and uses the existing coupling-clock integer crossings as opportunities. For free-pitch gate_snap/rearticulate, omitted tie means tie(1): beats with a pending adopted target attack, while other beats extend the living Tone. Explicit tie(0) re-attacks every legal crossing; explicit intermediate values are unchanged. Fixed pitch and glide still re-attack at each crossing. Omitted tie for sustained entrained/steady/flow remains zero, where participation can skip opportunities by cost. No adoption-time opportunity or participation cost policy is added. The shared beat is confirmed from sound; before confirmation the clock free-runs from its existing basin-centered rate and independent phase. It does not place an external beat. Fixed pitch is supported as in sustained participation; tie and persistence remain free-pitch controls. With rest(), finite duration and sustained participation are retained independently of builder order; every sounded note releases at that duration and waits for its tails before the rest. tie(p) can extend a living Tone at pending-free gate_snap/rearticulate opportunities; it leaves pending-target attacks intact.

rest

rest()
rest(periods)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Release each finite participation note, then leave a rest after all its tails finish. Requires explicit sustain() plus metric(), entrained(), steady() or flow(), and an explicit finite duration from cycles() or adaptive_duration(). These choices are retained independently of builder order. The normal Off releases the note without cutting its tails. After every Tone of the same source ID and generation has actually finished, wait periods times the intrinsic bodily period measured at that completion; rest() uses one period. For entrained/steady/flow, intrinsic_rate(hz) changes this period and therefore the rest in seconds, independently of cadence adaptation; metric retains its existing basin-centered intrinsic rate and ignores intrinsic_rate. With a known period, rest(0) waits only for tail completion. Unknown periods and unfinished tails keep waiting, without a fallback or timeout. Finite nonnegative periods have no upper cap; negative or nonfinite values sanitize to zero. Expiry permits sounding again but never forces an onset. Participation costs/candidates/skip are unchanged; metric keeps advancing its crossings during the gap and never replays suppressed ones. GateSnap retains the latest adopted target through sounding, tails and rest, consuming it at the next sounded opportunity. Glide does not update released tails. Rearticulate processes adoptions during tails and rest: small ones cancel pending pitch and glide the body, large ones defer the latest target, using the last actual onset as reference until the next sounded opportunity. Released tails never move. Missing finite duration and other timing forms fail at final placement before writes; all three pitch application modes are available; explicit while_alive() clears finite intent. Initial-only, like cycles() and duration_range(). Replacement Voices inherit controls, starting with fresh performance state. Without rest(), existing behavior is unchanged. Every note in this form has a finite end; it does not select sustain versus release per note. tie(p) cannot block a legal onset without a Tone after the gap, and never delays finite Off or tail completion.

repeat

repeat()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Repeated/pulsed phonation mode.

metric

metric()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Confidence-weighted rate and phase attraction to the acoustically confirmed beat. Takes no rate argument. With temporal_basin, its intrinsic starting rate is the sanitized band center; without a basin it is 2 Hz. This initial rate is independent of meter_stability. Every metric Population created by place or create reads the scene’s final basin, including calls after wait; repeated basin declarations use the last value, not timed changes. Initial phases remain independent. The omitted entrainment is 0.95. Beat confidence weights attraction of both effective rate and phase, with microtiming setting the beat-phase offset. Beat confirmation itself, exact phase coincidence and independent noninteger tempo relations are not guaranteed. Each clock crossing is an onset opportunity; lifecycle, phonation gates, rest and articulation determine whether an On is emitted. Attraction follows positive confidence in a beat confirmed from sound; the starting rate is not a confirmed beat. Respawn uses the Population template, not the parent’s current clock rate. entrained, steady, flow, and explicit pulse(rate_hz) are outside this initialization rule. Explicit sustain() plus metric(), in either builder order, retains a Tone and uses every coupling-clock integer crossing as an onset opportunity. For free-pitch gate_snap/rearticulate, omitted tie means tie(1): beats with a pending adopted target attack, while other beats extend the living Tone. Explicit tie(0) re-attacks every legal crossing; explicit intermediate values are unchanged. Fixed pitch and glide still re-attack at each legal crossing. Omitted tie for sustained entrained/steady/flow remains zero, where participation can skip opportunities by cost. Glide, deferred pitch, movement boundary, persistence and finite-duration rest share the sustained articulation resolver. Synchronization has no participation cost skip or planned waiting. Before acoustic beat confirmation it free-runs; this preset does not place a beat from outside.

entrained

entrained()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Adaptive acoustic participation with a lifetime-fixed bodily scale. Uses past habitat audio and its own rendered sound. Keeps the intrinsic bodily rate fixed during a Voice’s lifetime; it defaults to 2 Hz and intrinsic_rate(hz) sets its creation value. The participation period can adapt to supported recurrence weighted by proximity to that scale. Its omitted entrainment is 0.5 and controls period adaptation and acoustic timing costs. Costs compare sounding near due, waiting up to one participation period and skipping the event. Adaptation preserves the remaining fraction of a future uncommitted cycle; new acoustic context does not move a committed onset time. Only emitted onsets create pending context-memory updates, applied when their matching actual audio is observed. Waiting, skipping and unexecuted plans do not create an onset experience. Equal onset intervals, permanently different onset rates between Voices and alignment with a shared beat phase are not guaranteed. With explicit sustain(), in either builder order, sounding re-attacks the current body pitch by default; explicit gate_snap/rearticulate may use tie(p) to extend a living Tone without pending pitch. Waiting or skipping retains the ongoing Tone. Omitted pitch application resolves to glide. With rest(), an explicit finite duration ends each note before the tail-completion rest. Without explicit sustain(), finite gated phonation is unchanged.

steady

steady()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Fixed private due times; acoustic context selects sound or skip. A separate non-flow participation preset. It keeps its intrinsic period fixed and evaluates only its own due time, without adapting or moving that time toward another Voice or a shared beat. Heard acoustic context and executed-onset memory affect only whether an opportunity sounds or is skipped; pitch and the number of onsets are not guaranteed. The omitted coupling is 0.5, as for entrained(); entrainment(strength) keeps the same acoustic-context meaning. intrinsic_rate(hz) sets the creation pace, otherwise 2 Hz. Later sounded onsets occupy the private due-time grid, subject to sample rounding, omissions and execution constraints. Missed or illegal opportunities advance by whole intrinsic periods without shifting that grid. No cost term or weight is changed. The current arrival cost is excluded from the skip test, so with one candidate it cannot change sound versus skip. Timing presets follow the existing last-preset-wins rule. With sustain(), in either builder order, the first legal Hold On remains immediate and a skipped opportunity keeps the ongoing Tone. GateSnap, Glide, Rearticulate, rearticulate_above, tie (omitted 0), articulation_persistence, finite duration and rest retain the participation contracts. Replacement Voices inherit the Population template’s policy and start with fresh phase, due, plans and memory.

flow

flow()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Irregular bodily renewal with acoustic timing choices. Keeps its intrinsic renewal scale fixed during a Voice’s lifetime. Renewal draws produce irregular intervals; acoustic costs let the policy choose another time, wait or skip. The omitted entrainment is 0.05; it weights those acoustic costs without adapting the renewal scale. A regular onset sequence, a beat and an exact mean onset interval equal to the intrinsic period are not guaranteed. This is a separate irregular preset, not steady() with renewal added. Unlike entrained(), it does not adapt that scale to an observed recurrence. intrinsic_rate(hz) sets its creation value, otherwise 2 Hz. Hold lengths also use the intrinsic period. Renewal intervals retain their existing 0.035..=4 second limits, so extreme rates do not produce proportionally scaled intervals. With explicit sustain(), in either builder order, sounding re-attacks the current body pitch by default; explicit gate_snap/rearticulate may use tie(p) to extend a living Tone without pending pitch. Waiting or skipping retains the ongoing Tone. Omitted pitch application resolves to glide. With rest(), an explicit finite duration ends each note before the tail-completion rest. Without explicit sustain(), finite gated phonation is unchanged.

intrinsic_rate

intrinsic_rate(hz)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Set the intrinsic bodily rate at creation for entrained()/steady()/flow(), in Hz. Sets the Population’s founder Voices’ bodily scale, not an onset-interval command or a shared beat. The intrinsic period is 1/hz and remains fixed during life; entrained() adapts its participation period to heard recurrence, steady() keeps fixed private opportunities, and flow() retains an irregular renewal scale. Sounding, waiting and omission still use the existing policy. This same bodily period sets cycles(), duration_range(), rest(periods) and the representative hold used for overlap prediction, so note lengths and rests in seconds change together; flow retains its threefold hold multiplier. Omission preserves the existing 2 Hz path. Initial-only: put it before place() or create(), in either order with entrained()/steady()/flow(). Replacements read the Population template and start with fresh phase, due, plan and policy state; they do not inherit a parent’s adapted period. On the final entrained()/steady()/flow() specification, nonfinite values or values outside 0.01..=200 Hz are script errors before placement. These are execution protection limits, not a musically recommended range. Other final timing forms, including metric(), pulse(), repeat() and once(), warn and ignore the modifier even if its value is invalid. Flow renewal intervals separately retain their existing 0.035..=4 second limits, so extreme rates do not imply proportionally scaled onset intervals. Distinct from rhythm_freq(), the Entrain articulation core oscillator, and proposal_interval(), the pitch proposal interval.

entrainment

entrainment(strength)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Preset-specific acoustic timing influence or beat-rate and phase attraction. Order-independent with presets: entrainment(0.8).metric() and metric().entrainment(0.8) are equivalent. Within a selected preset it changes the timing influence, not the preset. For entrained(), it weights acoustic period adaptation and timing costs; for flow(), acoustic timing costs while the renewal scale stays fixed; for steady(), sound-or-skip costs without adapting the period or moving due. For metric(), beat confidence weights its attraction of both effective rate and phase toward the confirmed beat. Omitted values are 0.95 for metric(), 0.5 for entrained()/steady() and 0.05 for flow(). Zero removes these acoustic timing influences; flow still draws irregular renewal intervals. Without an already coupled timing specification, a bare call initializes Coupled timing with its existing defaults.

rhythm_role

rhythm_role(name)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Metrical job: "beat", "subdivision", "accent", or "texture". accent emits a stronger onset that drives the shared meter harder, so a recurring downbeat can seed an emergent measure.

measure_accent

measure_accent(amount)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Weak onset emphasis following the emergent measure, amount 0-1 (default 0). Scales onset strength by 1 + 0.35 * amount * measure confidence * cos(measure phase). Zero phase follows observed accents. With no detected measure, strength stays unchanged. Does not schedule onsets or impose a grouping. Order-independent with metric/entrained/steady/flow presets.

microtiming

microtiming(amount)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Signed beat-phase offset for metric timing, in -0.5..0.5. A value of 0.5 offsets the synchronized target by half a beat. Does not place entrained/steady/flow participants at fixed offsets.

cycles

cycles(n)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Duration of n rhythm cycles. For entrained/steady/flow, duration uses the intrinsic bodily period set by intrinsic_rate(hz), or 2 Hz when omitted, independently of acoustic cadence adaptation. Metric timing uses its effective beat period. Flow retains its threefold hold multiplier. With rest(), this finite duration is retained independently of sustain() and the participation preset, and each sounded note releases at its scheduled end.

tie

tie(p)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Extend a living Tone with probability p at an opportunity without pending pitch. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Initial-only composer parameter for free-pitch sustain() plus metric()/entrained()/steady()/flow() with gate_snap or rearticulate, including a zero boundary. At execution of a selected legal opportunity, pending pitch attacks as before; without pending pitch, a living Tone extends with probability p and re-attacks current body pitch with 1-p. No living Tone means a legal initial or post-rest onset is never withheld. Omission resolves before placement to one for free-pitch sustained metric and zero for sustained entrained/steady/flow, independent of builder order at place/create. Explicit zero bypasses tie state and random draws; one always extends eligible Tones without drawing. Fixed pitch and glide still re-attack at each metric crossing. The omitted metric probability is retained while glide suspends its use, so entering gate_snap/rearticulate live applies the same default without a new setter. Intermediate values use a dedicated individual random stream, once per eligible executed opportunity, not waiting hops; existing pitch, participation, body and articulation_persistence streams are untouched. Extension creates no new Tone, onset metabolism or onset-context learning, and consumes the opportunity without cost-skip learning or later replay. Small boundary-limited glides are eligible too: pending-free does not mean exactly equal pitch. GateSnap retains small and zero-distance adoptions as pending, so those still attack. This is not an overall legato amount: attacks become a thinned opportunity sequence; choices are per opportunity, not phrases or heard boundaries. With articulation_persistence, Connect vetoes first without a tie draw, while Base applies tie only without pending pitch; extension does not change the policy or its proposals. Finite Off, rest, tails/gaps, release, death and removal take priority. Live glide suspends tie; returning to an applicable mode resumes it, without an attack from switching alone. Finite inputs clamp to 0..1; positive infinity becomes one, negative infinity and NaN become zero. Unsupported explicit tie settings warn and ignore the modifier; omission emits no ignored-tie warning. Resolution is independent of builder order at place/create. Replacements inherit the template probability, not the parent’s RNG position, pending target or actual-onset memory. There is no live tie setter or alias. Example intermediate values illustrate composition, not recommended settings.

articulation_persistence

articulation_persistence(p)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Retain a Voice’s private articulation policy across pitch adoptions and sustained clock opportunities. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Initial-only, for free-pitch sustain() plus metric()/entrained()/steady()/flow(), rearticulate and a positive rearticulate_above boundary. The private policy starts in Base (the existing boundary and participation behavior); Connect glides all adoptions while a Tone lives and intentionally withholds sounded opportunities to extend it. Small adoptions and participation cost skips propose Connect; large adoptions and newly selected sounded opportunities propose Base. For 0 < p < 1, only an opposing proposal draws from a dedicated individual random stream: retain with probability p, switch with 1-p. A planned opportunity freezes its decision until execution; waiting hops do not draw again. Entering Connect cancels pending pitch but retains the last actual onset; cancelled adoptions never replay. Intentional withholding is distinct from cost skip and creates no Tone, onset metabolism or onset-context learning. Rest, finite Off, tails/gaps and lifecycle constraints take priority; Connect cannot block a legal onset when no Tone lives. Zero (the default) and one bypass the private state and RNG and retain the existing path. Finite values clamp to 0..1, positive infinity becomes one, negative infinity and NaN become zero. Unsupported final specs warn and ignore the value, independent of builder order. Replacements inherit the template parameter, not policy, RNG, pending decisions or last-onset memory, and start in Base. Live mode changes reset Base; the same mode and boundary-only changes retain the policy. A zero boundary suspends the private path. This is policy inertia, not a legato proportion: increasing p need not monotonically increase Connect time or reduce attacks. Retention is measured in existing events, not seconds or beats, and is not tied to heard cadence, contour, rests or phrase boundaries. Example values are comparisons, not recommendations. With tie(p), Connect vetoes first without a tie trial; Base uses tie only without pending pitch. Tie never changes policy proposals or retention.

Pitch Movement

How a voice moves through the consonance field. seek_consonance(), glide(), and temperature() (movement tension) form the curated surface; the rest tunes the hill-climb and peak-sampler mechanisms.

anchor

anchor()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Hold the voice at its pitch: an anchored voice never moves. at() anchors implicitly unless the final spec explicitly seeks consonance and remains free; in that case it sets only the initial position. freq() still sets an anchored pitch. A later anchor() or freq() overrides movement, while a later seek_consonance() restores it. Use anchor() to hold strategy-placed voices (consonance(), dissonance(), …) at their settled position, or to state the intent explicitly.

seek_consonance

seek_consonance()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Free hill-climb movement toward better field positions, with glide defaults. With this explicit intent and a final free pitch mode, at(hz) sets only the initial position. A later freq() or anchor() restores the lock; calling seek_consonance() after either restores movement. How pitch decisions land is resolved at placement unless pitch_apply_mode() was called: sustained voices glide, re-attacking voices (pulse(), metric(), entrained(), steady(), flow()) snap at onsets.

glide

glide(tau_sec)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Pitch glide time constant in seconds. Sets the sliding time constant, without changing pitch apply mode. It controls glide mode and movement at or below a positive rearticulate_above boundary; gate_snap does not slide.

rearticulate_above

rearticulate_above(cents)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Choose sliding or rearticulation by distance from the last sounded onset pitch. For free-pitch Hold without coupled opportunities, a positive boundary measures net displacement from the last actual ToneSpec onset to the adopted target: strictly above attacks, at or below glides with glide(tau). Selection persists until the next adoption. Earlier legal onsets in the same output hop provide the reference; cancelled requests do not replace the last actual onset. Exact Log2Space centers use bin-distance in cents, otherwise continuous log2 distance is used without rounding or epsilon. Without sustained coupled timing, zero retains every-adoption rearticulation. With explicit sustain plus metric/entrained/steady/flow, a small adoption cancels older pending pitch and glides immediately; a large adoption stops the glide, holds body pitch and defers one latest target to the next sounded opportunity. At a sounded opportunity without pending pitch, explicit tie(0) attacks at current body pitch; omission extends in free-pitch sustained metric and retains attacks in entrained/steady/flow. Other tie(p) values may extend its living Tone; either attack updates the reference. Waiting/skipping retain sound and pending pitch while existing glides continue. Zero or omission matches GateSnap, including zero-distance adoptions. The boundary does not suppress clock attacks and adds no adoption-time candidate. Negative or nonfinite values become zero; no upper limit is imposed. A live boundary change emits no attack and applies at the next adoption. Outside rearticulate, a call warns and is ignored. Builder order is independent at placement; replacement Voices inherit the boundary but not the onset-pitch memory. The composer chooses the boundary; 50 or 100 cents are examples, not recommendations. Initial-only articulation_persistence(p) can retain a private Base/Connect policy within the positive-boundary sustained-participation form; the boundary alone never suppresses clock attacks.

temperature

temperature(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Search temperature shared by both pitch cores: 0 settles greedily, higher is more exploratory. The single stochastic-search knob. At 0 the hill-climb is greedy (move only on a clear improvement, otherwise stay) and the peak sampler takes the argmax candidate. Higher values let the hill-climb accept downhill moves (Metropolis) and soften the peak sampler’s candidate choice. Musically the movement-tension dial; its placement twin is tension().

Neighbor Awareness

How a voice perceives other voices and its own spectral footprint when evaluating the field.

avoid_neighbors

avoid_neighbors(strength)
avoid_neighbors(strength, sigma_cents)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Crowding repulsion from neighboring voices. With one argument the repulsion width uses the default; the two-argument form sets it explicitly in cents.

Lifecycle & Viability

Time-domain survival and recovery. endurance() states nominal zero-fit lifetime; recovery() states full-scale continuous recovery time. consonance_viability() defines the recovery window, with environment-relative scoring by default.

endurance

endurance(seconds)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Nominal survival time at zero field fit, excluding the release tail. Energy is normalized to 0-1. The runtime drain rate is derived at spawn; actual lifetime varies with field fit, attacks, recovery, and release time.

recovery

recovery(seconds)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Time to refill normalized energy from empty at full viability. Optional. If omitted, continuous recovery is disabled. This does not govern the separate per-attack economy.

attack_cost_fraction

attack_cost_fraction(value)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Fraction of normalized energy spent by a full-strength attack.

attack_recharge_fraction

attack_recharge_fraction(value)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Maximum normalized energy restored by a fully consonant attack.

sustain_drive

sustain_drive(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Continuous drive level for sustained voices.

consonance_viability

consonance_viability(low, high)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Consonance window used for viability scoring. Enables environment-relative scoring by default: a voice is evaluated against the field with its own footprint approximately removed (see viability_scope()).

dissonance_penalty

dissonance_penalty(value)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

How strongly good field fit extends life beyond zero-fit endurance. The zero-fit endurance remains fixed; the penalty changes how much longer a well-fit voice survives.

Respawn

Population turnover. A respawn policy decides where replacements appear when voices die; capacity and acceptance thresholds shape ecology-scale behavior.

respawn_random

respawn_random()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Respawn without parent inheritance. Candidates come from the Population’s original Placement and, when set, respawn_settle() adds alternative candidates. The current scene score weights the final choice, so this is not uniform random placement.

respawn_hereditary

respawn_hereditary(sigma_oct)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Respawn near an energy-weighted living parent, with frequency variance in octaves. Candidates share one selected parent. The candidate with the highest current Consonance Field level is used.

respawn_consonance

respawn_consonance()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Respawn from high field-score peaks, biased around an energy-weighted living parent. When living members exist, one parent is selected by energy. Candidate peaks come from the current Consonance Field and are weighted by scene score and their relation to the parent; without a usable peak, the policy falls back to the parent or original Placement.

respawn_capacity

respawn_capacity(count)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Maximum number of living members in the Population. Defaults to the number of founder Voices created by place() and cannot be lower than that founder count.

respawn_settle

respawn_settle(placement)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Add a Placement source to the replacement candidate pool. The policy’s own baseline still supplies one candidate; this Placement supplies the remaining alternatives. Requires a strategy-bearing placement: consonance(), dissonance(), edge(), gap(), random(), or line() (not at()).

respawn_min_c_level

respawn_min_c_level(level)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Minimum consonance level for respawn acceptance.

respawn_background_death_rate

respawn_background_death_rate(rate)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Background turnover rate per second.

Mode Patterns

Frequency relationships for modal() bodies. Constructors return a ModePattern; modifiers are chainable. Landscape-aware patterns deterministically select supported positions from the live field.

harmonic_modes

harmonic_modes() -> ModePattern

Harmonic series: f, 2f, 3f, …

odd_modes

odd_modes() -> ModePattern

Odd harmonics: f, 3f, 5f, …

power_modes

power_modes(beta) -> ModePattern

Power law: f * n^beta.

stiff_string_modes

stiff_string_modes(stiffness) -> ModePattern

Stiffness-adjusted harmonics.

custom_modes

custom_modes(ratios) -> ModePattern

Custom frequency ratios from an array.

modal_table(name) -> ModePattern

Named mode table lookup. Falls back to harmonic_modes() with a warning if the name is unknown.

landscape_density_modes

landscape_density_modes() -> ModePattern

Modes selected from the strongest live landscape density-mass positions. Selection is deterministic: after each strongest position is chosen, nearby weights are suppressed according to spacing(). Falls back to harmonic modes when live landscape data yields no usable position.

landscape_peaks_modes

landscape_peaks_modes() -> ModePattern

Modes selected from the strongest local peaks of the live Consonance Field level. Selection is deterministic and enforces the ERB separation from spacing(). Falls back to harmonic modes when live landscape data yields no usable peak.

count

count(n)

Applies to: ModePattern.

Number of modes.

range

range(min_mul, max_mul)

Applies to: ModePattern.

Frequency range; only valid on landscape_*_modes().

spacing

spacing(min_erb)

Applies to: ModePattern.

Minimum ERB distance between modes; only valid on landscape_*_modes().

gamma

gamma(g)

Applies to: ModePattern.

Density sharpening exponent; only valid on landscape_density_modes().

jitter

jitter(cents)

Applies to: ModePattern.

Randomize mode frequencies by up to the given cents.

seed

seed(value)

Applies to: ModePattern.

Random seed for jittered patterns (>= 0).

Routing

Each voice contributes to two independent mono buses: the presentation bus (the work as heard) and the habitat bus (what the ecology senses through NSGT analysis). By default a voice feeds both.

send

send(bus)

Applies to: PopulationSpec only.

Route the voice to specific buses; accepts a Bus or a BusSet. PopulationSpec only. send(habitat_bus) makes a voice sensed by the ecology but absent from the presented sound; send(presentation_bus) is heard without perturbing the ecology; send(habitat_bus | presentation_bus) feeds both (the default).

Director & Global Parameters

Scene-global terrain shaping and research controls. Director verbs are soft priors: they shape the terrain and never schedule a beat or force a measure.

meter_stability

meter_stability(value)

Attractor depth in 0-1: how readily a pulse forms. Default 0 (neutral). A soft prior: it only deepens the basin for a real periodicity; it never fabricates a beat from non-metric input. It also weights the tempo preference toward the center of temporal_basin, or 2 Hz in the default beat band. The preference weight equals the depth, clamped to [0,1]; it is part of the modeled listener’s enculturation. Higher depth favors an in-band tactus nearer that center, including an integer subdivision reading of faster input.

temporal_basin

temporal_basin(min_hz, max_hz)

Set the beat’s tempo band and metric Voices’ initial rate. The time-axis analogue of consonance(min, max): it seeds the beat at the band center and confines tempo adaptation to the band. At meter_stability(0), the band adds no center preference or restoring pull; with higher stability, candidate preference and confidence-weighted center restoring grow with the depth. The same sanitized center sets the intrinsic starting rate of metric() Voices, even at stability 0; without a basin that rate is 2 Hz. entrained()/steady()/flow() retain their separate bodily rate, 2 Hz unless intrinsic_rate(hz) is supplied, and explicit pulse(rate_hz) retains its rate. This is one scene initial condition: the last basin declaration applies to every metric Population, regardless of its position before or after place, create, or wait. It is not an in-performance change. Independent Voice phases and acoustic confirmation remain required; this does not place a beat or force a measure.

Experimental

Candidate core verbs under audition: composing surface by intent, but with research-grade stability until validated.

Phonation & Rhythm

phonate_when_viable

phonate_when_viable()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Withhold the first onset until perceived consonance is viable. Silently settles into the field until consonance reaches the low bound of the voice’s consonance-viability window (consonance_viability(), or always-viable if that window was never set), then phonates normally and never re-gates. A voice that never settles dies unheard and the respawn policy replaces it – there is no timeout parameter, the energy economy is the timeout.

Mechanism Tuning

Fine-grained control over the mechanisms behind the core verbs. Defaults are calibrated; reach for these when a piece needs a specific behavior the core surface does not express.

Phonation & Rhythm

once

once()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Single trigger.

pulse

pulse(rate_hz)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Pulse at an explicit rate in Hz.

while_alive

while_alive()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Hold/sustain until release. Explicit while_alive() clears any finite duration retained for rest(); placement with rest() then fails unless a later finite duration is supplied.

adaptive_duration

adaptive_duration()

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Duration follows field support. With rest(), retain this finite duration independently of sustain() and entrained()/steady()/flow(), in any builder order. duration_range(), duration_curve() and shorten_on_drop() tune the retained rule.

pulse_lock

pulse_lock(depth)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Low-level pulse phase weighting in 0-1.

social

social(coupling)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Social coupling for participation or pulse(), in 0-1.

duration_range

duration_range(min_cycles, max_cycles)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Adaptive duration range in rhythm cycles. Also tunes the finite adaptive duration retained for rest(). For entrained()/steady()/flow(), cycles use the bodily period set by intrinsic_rate(hz), independently of cadence adaptation; changing the bodily rate changes this duration in seconds. Initial-only, like cycles() and rest().

duration_curve

duration_curve(k, x0)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Adaptive duration curve parameters.

shorten_on_drop

shorten_on_drop(gain)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Shorten adaptive duration when field support drops.

rhythm_freq

rhythm_freq(freq_hz)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Internal theta/rhythm oscillator frequency. Sets the Entrain articulation core oscillator; it does not set the entrained()/steady()/flow() participation scale. Use intrinsic_rate(hz) for that bodily rate. This control’s existing meaning is unchanged.

rhythm_coupling_vitality

rhythm_coupling_vitality(lambda_v, v_floor)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Vitality-modulated rhythm coupling.

rhythm_reward

rhythm_reward(rho_t, metric)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

Energy reward for timing fit; metric is "attack_phase_match" or "none".

Pitch Movement

pitch_smooth

pitch_smooth(tau_sec)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Pitch smoothing time constant in seconds.

pitch_apply_mode

pitch_apply_mode(name)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

"gate_snap", "glide" or "rearticulate": choose how adopted pitch targets are applied. The sustained metric combination uses shared-beat crossings, including crossings without adoptions; before acoustic confirmation it free-runs. Unlike participation, synchronization has no cost skip or committed plan. Choose gate_snap, glide or rearticulate. Without an explicit call, moving sustained voices resolve to glide and Gated voices to gate_snap. Free-pitch Hold without coupled opportunities rearticulate couples each changed adopted target to an attack at its sample, releasing the old Tone; a positive rearticulate_above(cents) instead glides at or below the distance boundary from the last actual onset. Rejected or unchanged targets and proposal-free updates cause no attack. Glide(tau) controls sliding, including below that boundary. With explicit sustain() plus metric()/entrained()/steady()/flow(), the omitted mode is glide: clock attacks use current body pitch and unselected movement glides. Explicit gate_snap on free pitch holds one latest adopted target for the next sounded opportunity, jumps there and attacks; wait/skip retains pitch and pending state, and no extra adoption-time candidate is offered. Without a pending target the clock re-attacks the current pitch unless tie(p) extends the living Tone. With explicit sustained participation, rearticulate measures each adopted target from the last actual onset using the current Log2Space: at or below a positive boundary it cancels old pending pitch and glides immediately; above it, it stops the glide and holds one latest target for the next sounded opportunity. Without pending pitch, omitted tie extends the living Tone in sustained metric; sustained entrained/steady/flow keeps omission at zero. Explicit tie(0) re-attacks current body pitch, while other explicit values retain their probability. Fixed pitch and glide still re-attack each metric crossing, and the omission default applies whenever a live mode switch enters the eligible range. Every actual attack updates onset memory. Same-sample adoption precedes opportunity; no adoption-time candidate is added. Zero or omission matches gate_snap. Hold without coupled opportunities attacks at adoption; sustained participation attacks only at sounded opportunities. Rest retains this adoption policy through tails and gaps without retuning released tails. Live three-mode changes preserve the Tone, emit no attack and cancel unrendered requests, pending attacks and selected glides. Boundary changes preserve the selected behavior until the next adoption. Returning to glide follows the controller’s retained target at the next control update; gate_snap and rearticulate wait for later adoptions. Repeating the same mode retains pending state. Replacement Voices inherit controls, not performance state. Initial-only articulation_persistence(p) adds initial-only policy retention within positive-boundary sustained participation; actual live mode changes reset it to Base, while the same mode and boundary-only updates retain it. Anchored Voices remain locked. Free-pitch Gated rearticulate remains a script error at final placement and before live writes, independent of builder order; use Hold with sustain(), including explicit sustain plus metric/entrained/steady/flow, or choose glide/gate_snap for Gated phonation.

landscape_weight

landscape_weight(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Weight of the landscape objective.

neighbor_step_cents

neighbor_step_cents(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Step size for neighbor exploration in cents.

move_cost

move_cost(coeff)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Cost multiplier for pitch changes.

move_cost_exp

move_cost_exp(exp)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Exponent for the move cost.

proposal_interval

proposal_interval(seconds)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Proposal generation interval in seconds. Controls opportunities to propose a pitch target, not the bodily participation rate set by intrinsic_rate(hz). A proposal or adoption is not necessarily a sounded onset.

tessitura_gravity

tessitura_gravity(value)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Gravity toward the tessitura center.

window_cents

window_cents(width)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Peak-sampler search window width in cents.

top_k

top_k(count)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Number of top candidates kept by the peak sampler.

sigma_cents

sigma_cents(spread)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Gaussian spread of the peak sampler in cents.

random_candidates

random_candidates(count)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Number of random candidates considered.

global_peaks

global_peaks(count)
global_peaks(count, min_sep_cents)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Global field peaks as movement candidates, with optional minimum separation.

ratio_candidates

ratio_candidates(count)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Ratio-based movement candidates; 0 disables.

Neighbor Awareness

crowding_target

crowding_target(same_visible, other_visible)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Which voices are visible to crowding: own population, other populations (booleans).

leave_self_out

leave_self_out(enabled)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Subtract the voice’s own spectral contribution when evaluating the field.

leave_self_out_harmonics

leave_self_out_harmonics(count)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

Number of harmonics used for approximate self-subtraction.

Lifecycle & Viability

viability_scope

viability_scope(name)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

"environment" (default) or "total" viability scoring scope. Use "total" only when the selection question should include the voice’s own contribution.

Director & Global Parameters

set_roughness_aversion

set_roughness_aversion(weight)

How much the modeled listener minds roughness (default 1.0). Scales the configured roughness terms of consonance valuation (field and density), clamped to [0, 100]; non-finite values are ignored. 0 ignores roughness in valuation. It acts as aversion, with larger values penalizing roughness more, when the configured kernel penalizes roughness at every harmonicity (b <= 0 and b + c <= 0, true for the defaults). Roughness itself, as sensed and displayed, does not change.

set_global_coupling

set_global_coupling(value)

Voice interaction strength.

Research Controls

For studying the instrument, not composing with it. Normal composing never touches this tier, and it has the weakest stability guarantee: entries may change or disappear as their research questions settle.

Pitch Movement

pitch_core

pitch_core(name)

Applies to: PopulationSpec only. Initial-only: configure the PopulationSpec before place().

"hill_climb" or "peak_sampler". Research control.

move_cost_time_scale

move_cost_time_scale(name)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

"legacy"/"integration_window" or "proposal"/"proposal_interval".

Neighbor Awareness

leave_self_out_mode

leave_self_out_mode(name)

Applies to: PopulationSpec and Population. Live-patchable: updates running voices in a Population.

"approx"/"approx_harmonics" or "exact"/"exact_scan".

Lifecycle & Viability

selection_approx_loo

selection_approx_loo(enabled)

Applies to: PopulationSpec only.

Override environment-relative viability scoring; research/reference control. PopulationSpec only. Use only for older reference assays that need the previous implementation-level control.

Director & Global Parameters

set_pitch_objective

set_pitch_objective(name)

"consonance"/"positive" or "dissonance"/"negative". Research control.

set_control_update_mode

set_control_update_mode(name)

"snapshot_phased"/"snapshot" (default) or "sequential_rotating"/"sequential". Research control.

set_scaffold_off

set_scaffold_off()

Disable the external rhythm scaffold. Scaffolds are external comparison controls for demos and assays, not the rhythm-composition abstraction.

set_scaffold_shared

set_scaffold_shared(freq_hz)

Shared external scaffold pulse.

set_scaffold_scrambled

set_scaffold_scrambled(freq_hz, seed)

Per-voice scrambled scaffold pulse.

Samples

Sixteen samples in two groups, kept together in samples/. Samples 1–12 introduce the instrument’s capabilities one at a time: API and behavior demonstrations, not musical works. Samples 13–16 are longer scenes written as compositions combining those capabilities. Run and read both groups.

cargo run --release -- samples/01_a_single_voice.rhai

Capability demonstrations (1–12)

  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 — a dedicated downbeat Voice supports an explicit synchronization demonstration.
  8. Murmuration — a flock without a conductor: each voice keeps its own pace and leans toward the recurrences it hears; no shared beat is targeted.
  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 — a colony heats up and settles, with rhythmic input from its own onsets and no dedicated beat carrier.

Composed scenes (13–16)

  1. Three Windows — three lines attack on pitch adoption and leave by their own doors.
  2. Stone and Wire — two metrical voices turn while an independent line crosses.
  3. Folded Air — a companion rests and returns, changing articulation across sections.
  4. Open Court — a denser crowd fills the court around two independent paths.

Reading and running

Samples 1–6 walk the consonance terrain (placement, gravity, tension, movement, timbre). Samples 7–9 introduce three timing intentions: coupling to a shared beat (metric()), participation informed by heard sound without targeting shared-beat phase (entrained()), and non-metric clustered renewal (flow()). Sample 10 closes the loop into life; 11–12 combine several mechanisms. Samples 13–16 combine adoption-driven attacks, metrical voices with a crossing line, a resting companion with changing articulation, and a denser crowd. All samples are compile-checked by the test suite, so they always match the current API. Top-level samples intentionally do not call seed(...): each run starts from a fresh scenario seed. Research assays keep fixed seeds so comparisons remain reproducible.

Terrain anchors generally feed the habitat bus without being heard; in Gravity, the sounding drone is itself the subject. Heartbeat includes a dedicated beat carrier because synchronization is the demonstration’s subject. The other top-level samples have no dedicated beat carrier.

Research assays

samples/research/ holds comparison fixtures — heredity/selection ablations, external-scaffold rhythm controls, and mechanism studies. They study the instrument rather than play it, and are not part of the path. Dedicated synchronization support is limited to demonstrations or assays where explicit synchronization is essential. Sending that support only to the habitat bus does not make it suitable for other samples.

Offline rendering

The conchordal instrument never writes audio to disk — performances are ephemeral by design. For offline WAV rendering use the separate conchordal-render binary, which shares the core engine but is not the instrument.