Modal2D · Features Guide
A reference for the live tool: what every button does, what physics it represents, and where the prototype edges lie.
01 Foundation
Modal synthesis treats a vibrating object as a sum of decoupled oscillators
(modes). Each mode has a shape φ_k, an angular frequency
ω_k, and a damping ratio ζ_k. The audio after a
strike is the time-evolution of every mode, summed:
A_k= how strongly the strike excited mode k (depends on strike point and direction).α_k= how efficiently the mode radiates into the surrounding medium.exp(−ζ_k ω_k t)= the decay envelope. Modes with higherωdie faster at the sameζ.sin(...)= the actual oscillation at the mode's frequency.
Where the modes come from: FEM eigensolve
The shape is meshed into 2D constant-strain triangles (CST), with optional
1D Bernoulli beam elements for strings, ribs, and corner connectors. The
standard 2D plane-stress assembly gives sparse stiffness and mass matrices
K and M. The modes are the generalised eigenpairs:
We solve the lowest N modes (typically 24–120) using a sparse
Lanczos iteration. Every "Strike" hit projects an impulse onto each
eigenvector, sets A_k, and hands the result to the audio
synthesiser. The visual on screen is the same superposition rendered with
visual-time (which can be slowed for slow-motion inspection).
Elements supported
- CST triangle (default): 2 DOFs per node, plane-stress membrane modes. Drives every 2D shape from RECT to BELL_PENT.
- Beam edge (mixed mode): 3 DOFs per node when present (ux, uy, θ). Used by strings, boundary stiffeners, and bridge connectors. Supports axial pre-tension (§7).
- DKT plate bending (optional): 3 DOFs per node (w, θx, θy). Gives proper out-of-plane bending modes for bell-like shapes. Toggle in Physics → Plate.
02 App modes
The viewer is built around five top-level modes. The toolbar's mode buttons switch between them.
Pick a preset shape, strike it, hear it. The default. Use the number keys 1–9 for quick preset switching.
Build a single tuning component (one of 9 shapes), tune up to 8 notes one at a time, save the result as a custom instrument.
Play the saved custom instrument. Notes appear with labels (e.g.
C4, D#5) above each component.
Shape-first: tweak (W, H, comp_type) and record any clean note that the shape happens to produce. Chromatic grid view shows coverage across multiple octaves.
Click a filled cell in the chromatic grid to run Fine-W targeting that cell's chromatic frequency. Convergence writes back into the cell.
03 Tuning toolkit
Tuning a component means moving its (W, H, shell) parameters
until a tracked mode matches a target frequency. The toolbar has eight
different tuning algorithms; they all share the same state machine
(wait for strike → solve → read freq → decide next sample) and
differ in how the next sample is picked and which mode the
target is compared against.
| Mode | Axis | Sample picker | Tracks | Best for |
|---|---|---|---|---|
| Fine-W | W | Bisection (or HF Newton) | fundamental | Most reliable. Always your default if W alone can hit the target. |
| Fine-Size | uniform (W∝H) | Bisection | fundamental | When pitch should scale isotropically (e.g. round bells). |
| Fine-WH | W, H alternating | Bisection per axis | fundamental | Legacy 2D. Generally worse than Bayes-WH. |
| Fine-W+ | W | Adaptive (bracket / near-best / gap scoring) | loudest audible | When the fundamental isn't perceptually dominant. |
| Fine-WB | W | Sweep + multi-bracket bisection | fundamental | Shapes with multiple plausible bracket roots. |
| Manual-W live | W | Bisection / HF | spectrum-pinned | You click the exact mode you want to tune. |
| Bayes-W prototype | W | Gaussian-process · Expected-Improvement | fundamental or pinned | Exhaustive coverage of W when the cost surface has multiple minima. |
| Bayes-WH prototype | W, H | Gaussian-process · Expected-Improvement | fundamental or pinned | 2D pitch search. Hard for fundamental reasons (see §14). |
| Filter-W prototype | W | Bisection | loudest mode with decay ∈ [min, max] | Complex shapes where the fundamental decays too fast / too slow. |
Tuning aids that work across all modes
Threshold
Convergence triggers when |cents_to_target| < threshold.
Default 5c (≈ one cent below human pitch discrimination).
Tighter = more iterations, looser = faster but audibly imperfect.
Auto mode
Sticky toggle. When on, the tuner auto-strikes between iterations and automatically advances to the next saved note when each tune finishes. Lets you tune all 8 notes hands-free.
HF acceleration (Hellmann-Feynman) live
Active for Fine-W and Manual-W when the Experimental tab's HF toggle is
on (default on). Replaces the bisection mid-point with
a Newton step using df_k/dW computed analytically from the
current eigenvector + a single extra matrix-assembly (no eigensolve!) at
perturbed geometry:
The Newton step is bracket-clamped, so a wrong derivative (mode crossing, plateau) falls back to bisection cleanly. Typical effect: 10–15 iteration Fine-W collapses to 2–3 iterations.
Caveat: only the 2D CST path is supported. Plate-bending and mixed-mesh (beam-coupled) shapes auto-fall-back to bisection — their eigenvectors have been 2D-projected from the 3-DOF originals, so the rayleigh quotient against the unprojected K/M would be wrong.
MAC mode tracking live
Manual-W tracks "the same mode" across W changes by resampling each new solve's eigenvectors onto a fixed 20×20 grid and computing the Modal Assurance Criterion against a reference snapshot:
The arrow in the spectrum follows the highest-MAC mode every solve. The arrow's tint colour-codes the score: green ≥ 0.60 (clearly the same), yellow ≥ 0.30 (probably), red below (shape changed too much — re-pin from the spectrum). The "Mode N pinned" banner glows above the bars while a pin is active.
Flexible target
Toggle next to the octave drag in Create mode. When on, the 8-note table is anchored to the current shape's dominant frequency instead of an absolute C-octave. Useful when a shape's natural pitch lands far off a familiar octave — you tune the major scale starting wherever the shape sits.
Tune-drift compensation
Toggle in the spectrum's title bar, right next to the freq-drift slider. OFF (default): fine-tune targets the absolute raw modal Hz. ON: target is divided by the current shift factor so playback (raw × shift) lands on the requested chromatic Hz regardless of drift.
Spectrum click-to-pin
Left-click any bar in the spectrum (while in Create / Refine) to pin that mode. Manual-W, Bayes-W, and Bayes-WH will track the pinned mode instead of the fundamental. Right-click anywhere in the spectrum clears the pin.
04 Material & medium
The Material tab sets the structure's material properties (single-material scenes only — see §5):
E— Young's modulus [Pa]. Higher = stiffer = higher mode frequencies. Range varies from rubber ~1e7 to diamond ~1e12.ν— Poisson's ratio. Range 0–0.5. Affects how axial stress couples to transverse strain. Most metals ~0.3.ρ— density [kg/m³]. Higher = more inertial mass = lower frequencies. Air ~1, water ~1000, steel ~7800.ζ— damping ratio. Controls how fast modes decay (τ = 1 / (ζ ω)). 0.001 = bell-like ring, 0.05 = thud.
The Acoustic Medium selector below the material sliders
controls the surrounding fluid. Speed of sound + density together drive
the radiation efficiency α_k per mode. Vacuum-like (very low
density) silences output; helium speeds it up.
05 Multi-material live
Some presets bake a per-region material palette into the mesh. The "Composite" and "Strung" comp_types do this: hard frame (steel-like, E ≈ 200 GPa) on the left/right walls + soft panel (plywood-like, E ≈ 12 GPa) on the top/bottom. The classic resonance-cavity recipe: the hard frame defines the structural skeleton, the soft panels radiate.
The viewport overlays a translucent tint per material id with a corner legend ("hard frame", "soft panel", "string"). When multi-material is active the Material tab disables its single-material sliders and shows a notice — editing them wouldn't affect the per-element palette anyway.
API
libmodal exposes the underlying primitives if you write your own preset:
modal_set_element_materials(obj, palette, n_palette, ids, n_ids)— set the palette and per-triangle ids.modal_get_num_materials(obj)/modal_get_element_materials(obj, out, n)— query.modal_result_get_element_materials(res, out, n)— query from a const ModalResult* (for the renderer).
06 Air cavity prototype
Experimental tab → Physical environment → Air cavity. Real instruments have an enclosed air volume whose lowest acoustic mode (Helmholtz) couples to the surrounding shell. We approximate that as a parallel 2-pole state-variable bandpass on the structure audio:
- freq [30 Hz – 1.5 kHz] — bandpass peak. Guitar-sized boxes resonate around 80–130 Hz; smaller cavities go higher.
- Q [1 – 40] — resonance sharpness. Higher Q = ringier "body" tone.
- gain [0 – 2] — how much of the bandpass output mixes back into the final signal.
This is the lumped-element approximation. The full 2-domain FEM (acoustic pressure field in the cavity coupled to the shell at the wall) would derive the Helmholtz frequency from geometry instead of asking the user. Listed as planned in the Experimental tab footer.
07 String tension live
Real strings get their pitch from tension, not bending stiffness. A taut guitar string at length L and linear mass density μ:
Modal2D supports this via a per-beam tension field and a
geometric stiffness term added to the beam element when tension is non-zero:
When T·L² ≫ EI the geometric term dominates and the beam
behaves like a pure string. When T = 0 you get the legacy
pure-bending beam.
UI
post_process panel → String tension (N) slider. Range 0–500 N: 0 = legacy, 50–200 = guitar territory, 500+ = piano. Triggers a re-solve when changed.
API
modal_set_beam_tension_uniform(obj, T)— apply T to every beam.modal_set_beam_tensions(obj, T[], n)— per-beam variant for 8-string instruments.modal_get_beam_count(obj)— array sizing helper.
08 Gravity prototype
Experimental tab → Physical environment → Gravity. Toggle
+ magnitude slider (0–30 m/s², default 9.81). When enabled and
the shape has fixed boundary nodes (Physics → Boundary clamp
≥ 1-Fix), nodes farther from the highest fixed node sag downward
proportional to distance² × (g / 9.81). Catenary-like profile
drawn on top of the modal deformation, so the shape both hangs and
vibrates.
K · u_g = f_g
for the static deformation, compute the geometric stiffness K_geom(u_g),
eigensolve on (K + K_geom). That would surface the
pendulum mode at the low end of the spectrum for suspended instruments.
09 Resonance toggles
Two acoustic-coupling toggles live in the Appearance tab. Both work in every app mode, not just Test. They only have audible effect when the scene has disjoint shapes — for a single-mesh instrument the joint eigenmodes already cover the coupling.
Medium-FB
Routes acoustic pressure from the medium-propagation viz back into the structure as a body force. Cross-component resonance via the airborne pressure field. Subtle; works in any mode.
Sympathetic prototype
Modal-domain sympathetic resonance. When a component is struck, the strike impulse propagates to frequency-matched modes in other components weighted by a Lorentzian centred on the active mode's frequency:
Cross-component pairs with a Lorentzian weight above 2% get coupled energy at strike time. Mismatched modes stay silent (unlike Medium-FB, which couples everything via the spatial pressure field). Two sliders: Q (sharpness, 5–300, default 50) and strength (mix, 0–0.5, default 0.10).
10 Audio chain
Each audio callback runs this pipeline. The post_process panel exposes the knobs:
- libmodal synthesis on GPU → raw modal sum.
- Air-cavity bandpass (if enabled) — parallel resonator at
cavity_freq, mixed back atcavity_gain. - Noise gate — HP-filtered amplitude envelope. Gate closes when amplitude drops below
gate_low_thr(default 0.002), opens abovegate_high_thr(default 0.012). HP cutoff 80 Hz so sub-audible modes don't keep the gate falsely open. - Post-processing — master gain, soft-clip (tanh) saturation at
drive, low-pass anti-alias filter atlp_cutoff, peak auto-normalisation.
Other audio controls
- Search timeout (s) — clears all modal energy N seconds after the last strike, but only during fine-tune sessions. Play and Test get infinite sustain.
- Damping slider — extra
ζadded on top of the material's damping. Live-pushed to libmodal viamodal_set_damping. Use this for sustain control during play; never use the timeout for that. - Strike timeout only fires when
fine_mode_ > 0or Discover auto-explore is running.
11 Frequency drift
Slider in the spectrum's title bar (±48 semitones). Multiplies every mode
frequency by 2^(s/12) for playback (the audio synth applies
this on the GPU so decay scales correctly with the shifted frequency,
not the raw one).
Right-click to reset to 0. The toggle next to it (comp) is
tune-drift compensation — when ON, fine-tune
targets are divided by the drift factor so playback lands on the labelled
chromatic Hz regardless of drift.
12 Visualisation
The bottom row hosts three live visualisations:
Frequency spectrum
Log-frequency bar chart. Bar height = current modal amplitude. Green overlay = audible band (20 Hz – 20 kHz, shifted by the drift slider). Tick labels at 10, 100, 1k, 10k. Hover over the bars to see mode index and Hz; click to pin (in tuning contexts); right-click to clear pin.
Mode bars
Vertical bar chart of the lowest 24 modes by amplitude. Doubles as a mode selector for Chladni-pattern viz (click a mode → the viewport shows that mode's nodal pattern).
Waveform
Scrolling time-domain plot of the audio output. Lets you eyeball envelope behaviour: attack transient, sustain shape, gate closure.
Test-mode note labels
When playing a saved instrument, each component gets a C4,
D#5 etc. label rendered just below its bounding box (so the
label never overlaps the actual shape).
Material overlay
Translucent triangle tint per material id with a corner legend. Drawn whenever the active scene uses a per-element palette.
Gravity sag overlay
When the gravity toggle is on, each node gets a downward offset
proportional to (distance from highest fixed node)². Drawn
on top of the modal deformation, so the shape both hangs and vibrates.
13 Experimental tab
Lives in the right panel as a fifth tab. Houses prototype controls plus diagnostic visualisations for the more research-y features.
| Section | Status | What it does |
|---|---|---|
| Hellmann-Feynman acceleration | active | HF Newton step toggle for Fine-W / Manual-W. See §3. |
| Mode-shape correlation (MAC) | active | Auto-retracks pinned mode across W changes in Manual-W. |
| Bayesian (W, H) sampler | active | GP heatmap of the cost surface, sample dots, best-so-far ring. |
| Mass-spring preview | active | 10×6 lattice eigensolve thumbnails + FEM A/B pairs. Updates as you drag W/H sliders. |
| Physical environment | prototype | Air-cavity bandpass + gravity sag toggles. |
14 Limits & next steps
Honest list of what doesn't work the way you might expect, in roughly decreasing order of how much it matters for sound quality.
Real instruments are 3D. Our modes are 2D plane-stress, which captures mode shape topology and frequency ordering well but misses out-of-plane bending. Bell-like shapes sound thin without enabling plate-bending. Absolute Hz drifts a few percent from real instruments.
The current air-cavity prototype is a bandpass at a user-chosen frequency. Real instruments have a shape-dependent Helmholtz mode that couples bidirectionally to the surrounding shell. A second FEM domain for cavity pressure would close that gap.
Bayes-WH has structural problems: mode crossings make the cost function discontinuous, and 2D needs N² samples for the coverage 1D gets with N. Use Fine-W / Manual-W / Bayes-W when possible; Bayes-WH is a last resort. MAC-tracked Bayes-WH would help.
The UI applies one tension to every beam. Per-beam tensions are supported by the libmodal API; the UI just doesn't expose them yet. Blocks proper guitar / Er Hu instruments where each string has a different tension.
The multi-material palette currently only colours triangles. Beam
elements (strings, ribs) still draw their material from obj->mat.
When multi-material is active, the Material panel is disabled, so the
user can't edit that. Per-beam material assignment is the next step.
The catenary sag is a draw-time offset; the modes don't actually shift. Pre-stress eigensolve (K + K_geom) is the planned upgrade — gives proper frequency shifts and a pendulum mode for hanging instruments.
We use a single ζ for every mode. Real materials have
ζ(ω) = a + b·ω — high modes decay faster, low modes ring
longer. Adding this is small but would meaningfully improve instrument
tail realism.
Wood, carbon, and composites are orthotropic: stiffer along the grain
than across. Our material model has a single E; an
E_x, E_y split would matter for any wood instrument.
15 Tuning-component presets
The Create / Discover / Refine flows let you pick from these comp_types:
| # | Name | What it is |
|---|---|---|
| 0 | String | 1D beam chain — half-length W, cross-section H. Pure beam unless you add tension. |
| 1 | Box-2s | Closed box, 2 thick sides + 2 thin sides (default). |
| 2 | Box-Open | U-shape (3 sides + open top). Bell-like. |
| 3 | Triangle | Isoceles, apex up. Bounding box 2hw × 2hh. |
| 4 | Hexagon | Regular hexagon scaled to bounding box. |
| 5 | Box-Hard | Closed box, all sides equal thickness (rigid box). |
| 6 | Ring | 12-sided elliptical loop, semi-major W / semi-minor H. |
| 7 | Composite multi-mat | Closed cavity, hard frame (left/right) + soft panels (top/bottom). |
| 8 | Strung multi-mat | Composite + tensioned steel string above the top panel, bridge-connected. |
The Play-mode preset toolbar additionally exposes pre-built ensemble shapes: HARP_TUNED, GUITAR, BELL_TUNED, TUNED_BOXES_12, and so on — those bypass the Create tuning flow entirely.