Design: Coupled bodies. Related: Strings, bells and resonating objects.
Connect two ringing objects and play the object between them. A struck glass body can share its energy with a string-like body; a short-lived metal body can pull energy out of a long glass tail. Move the connection while the note rings, or excite it continuously with a gated sound.
glass = [[1, 2.8s, 1], [2.756, 1.8s, 0.44], [5.404, 950ms, 0.23]]
wire = [[1, 1.6s, 1], [2, 1.1s, 0.38], [3, 700ms, 0.22]]
connection = macro 0.3
object = patch {
source = strike 0.6 |> couple (freq pitch) glass wire {
ratio = 1.004, decay = 1, stretch = 0.02, position = 0.25,
coupling = connection, balance = 0.5
},
gain = vel * 1.8, voices = 5
}
t = play object "d4 ~ a4 f4"
Play and shape#
drive |> couple freq a b [ratio] [decay] [stretch] [position] [coupling] [balance] [contact] [pickup] is a signal processor. The required inputs are the drive, fundamental in Hz, and two mode tables. Named options work in ordinary records.
| Control | Default / range | What it changes |
|---|---|---|
ratio | 1 / 0.25–4 | B's fundamental relative to A |
decay | 1 / 0.05–4 | Both bodies' mode T60 multiplier; each result caps at 20 s |
stretch | 0 / −0.5–0.5 | Mode ratios become ratio^(1 + stretch) |
position | 0.23 / 0.02–0.98 | Which modes the drive excites on A |
coupling | 0.2 / 0–1 | Reciprocal internal energy exchange |
balance | 0.5 / 0–1 | A only at zero, B only at one; a linear pickup blend between |
contact | absent / 0.02–0.98 | Where the junction attaches along both bodies |
pickup | absent / 0.02–0.98 | Where the output listens along both bodies |
All six controls and the fundamental accept live control signals. Values smooth over 8 ms. Zero coupling leaves B unexcited and A independent; listening to B at zero coupling is intentionally silent. Balance changes the pickup, not the energy exchange. Mode levels are normalized together per body, so adding modes does not automatically add drive/pickup energy.
Each table is 1–64 [frequency ratio, T60, amplitude] rows or modalOf:
p = patch {
source = exciterOf "hand" 40 |> couple (freq pitch)
(modalOf "glass" 24) (modalOf "wire" 16) {
coupling = 0.5, balance = 0.7
}
}
Fits and extracted hands prepare before playback through the same sample analysis path as modal. A missing fit is silent until it arrives. Each note captures its own tables; replacing PCM affects later notes. Frequency is explicit Hz here: use (freq pitch) or a constant frequency. native belongs to the single-body fitted frequency API, and is not accepted by couple.
Noise, grains, samples, drum synths and other signals can drive A. Gate a sustained hand before couple so the object's tail survives the hand's release. Stereo drive retains separate left/right pairs; it is never downmixed. The pairs do not exchange energy across stereo channels.
A resonant sustained tone can accumulate much more energy than a short hand. Start continuous drives quietly and balance the raw instrument level before mastering. The junction is passive; it is not a limiter or loudness normalizer. Input gain is scaled by physical sample duration to preserve coherent-drive gain across sample rates. Drive samples are bounded to ±8 and non-finite input is treated as silence. Output retains the linear resonant response.
The instrument surface#
Open a couple node's Notebook inspector or its Rack panel. The two mode maps show authored frequency ratios, amplitude (height), and decay (line width). The connector represents coupling strength. This is a model map, not live energy telemetry or a plot of the coupled system's eigenfrequencies. Fitted or computed tables that cannot be resolved from source are identified without invented bars. When controls are expressions, the map explicitly shows base modes and leaves the live values in their authored expressions.
Drag literal controls; one drag has one Undo. A control owned by an expression or shared options record opens its source instead of replacing it with a number. Both body labels navigate to their authored table slot. The same Rust surface and source transactions run in desktop and browser builds.
Model and boundaries#
Each body has complex modal states in normalized energy coordinates. A mode rotates at its frequency and contracts at its T60. The junction acts on the normalized sum of the modes in each body using a symmetric unitary rotation; it modifies internal state before pickup. A one-mode identical pair at 440 Hz and full coupling has poles at 385 and 495 Hz. Energy travels A→B→A; a lossy neighbor changes the driven body's decay.
Without contact, contact weights are fixed 1/sqrt(mode count). With it (production-gaps AM4, Coupled Physical Modeling Stage 7) mode n attaches by sin(π·n·contact), renormalised to unit length every time the contact moves (a vector that vanishes falls back to uniform) — the junction is unitary only for a unit vector, so a sliding attachment keeps the pair passive (tested under continuous motion at 44.1/48/96 kHz). At the middle the even modes sit on a node and stop crossing: measured, B's second mode falls from 4.0·10⁻⁴ to 8.7·10⁻⁶ at contact = 0.5. pickup weights each mode's output by √2·sin(π·n·pickup). Both are smoothed like the other controls and rebuilt on the 16-sample coefficient clock; absent, the node, its IR and its sound are the shipped pair bit for bit. The inspector does not draw them yet. There is no mode matching: unequal counts and unrelated mode ratios are supported. position weights external excitation as sin(π·modeIndex·position); it does not move the junction. This is a musical coupled-mode model, not measured contact geometry, a finite-element solid, or a general waveguide junction. Table T60/color alone does not identify a real object's mass or attachment points.
Per-sample zero-input energy cannot grow: both damping steps contract, and the junction preserves norm. Frequency rotation and junction are Strang-split; coefficient updates use an internal 16-sample clock. Modes taper out between 0.43 and 0.48 times sample rate. Fixed storage covers 64 modes per body, with a retained pair pool prepared for each source in each voice; retrigger and heterogeneous slot reuse reuse it. A conservative 30-second release budget covers live T60 changes and drive release, with the ordinary engine edge fade.
Cross-track coupling and arbitrary networks of existing resonators remain separate work. Use one couple per playable object.
Songs#
Copper / Glass exposes the tapped glass motif, adds its independent reply, then changes connection, spacing and pickup through a sparse held-note section before the full return. The Water Between prints its own hand, extracts its attack and transfers the melody toward the unstruck body over a dub form. Both are portable original arrangements in the song library. Their only musical source inspirations are album-tagged Glass Parliament and The Cistern Remembers Rain. Verification and preview files: Coupled Bodies 2026 09 09.