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

Consonance Field — A Terrain for Evaluating Pitch

Conchordal’s perception core (the Landscape) listens to the habitat bus, transforms it into log-frequency space, and computes two potentials: roughness (sensory dissonance from amplitude fluctuations within critical bands) and harmonicity (periodicity and template matching). Their combination is the Consonance Field: an evaluation terrain over frequency that placement, movement, prediction, and survival all read from.

Because the field is computed from the habitat bus, every voice routed to that bus deforms the terrain for every other voice. A presentation-only voice does not. That feedback loop — not a chord chart — is where harmony comes from. See Voice and Landscape — Sound–Environment Feedback for the relationship between potential, score, level, mass, and density.

Placing into the field: consonance, dissonance, edge, gap

A field-relative placement names a target — a region of the field — and is realized either as a cloud (the default) or as a deterministic extremum with .peak(). The targets are:

  • consonance — high-consonance positions (harmonic centers, fusion).
  • dissonance — low-consonance positions (tension, clusters, color).
  • edge — the consonance/dissonance boundary (the metastable middle).
  • gap — empty registers (fill the room, avoid masking).

consonance(root) takes a harmonic window around a root (multiples via range()); every target also takes an absolute (min_hz, max_hz) range.

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

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

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

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

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

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

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

Placement tension: tension(τ)

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

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

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

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

Naming frequencies: ratios of a root

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

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

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

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

Consonance Movement: seek_consonance

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

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

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

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

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

How movement lands is resolved from phonation: sustained movers glide, re-attacking movers (pulse(), metric(), entrained(), flow()) snap to their new pitch at each onset. Override with pitch_apply_mode() when a script needs the other behavior. Mechanism-level controls (pitch_core() and the hill-climb / peak-sampler tuning in the API Reference) remain available for research scripts; prefer seek_consonance() and glide() in curated work.

Consonance Viability and Respawn

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

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

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

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

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

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

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

Landscape-aware timbre

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

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

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

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

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