Modal2D · Features Guide

A reference for the live tool: what every button does, what physics it represents, and where the prototype edges lie.

Modal synthesis viewer · 2D plane-stress FEM · CUDA GPU audio · Built in C++ / ImGui / OpenGL

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:

y(t) = Σ A_k · α_k · exp(−ζ_k ω_k t) · sin(ω_k √(1−ζ_k²) t)
  • 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:

K φ_k = λ_k M φ_k · ω_k = √λ_k

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).

Why 2D? Real instruments are 3D, but a flat 2D analogue captures most modal phenomena (mode shapes, frequency ordering, mass / stiffness trade-off) at a tiny fraction of the cost. The trade-off: plate bending is approximated, air-cavity coupling is missing, and absolute frequencies drift from real-instrument values. We tune relatively.

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.

Play

Pick a preset shape, strike it, hear it. The default. Use the number keys 1–9 for quick preset switching.

Create

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.

Test

Play the saved custom instrument. Notes appear with labels (e.g. C4, D#5) above each component.

Discover

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.

Refine

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.

ModeAxisSample pickerTracksBest for
Fine-WWBisection (or HF Newton)fundamental Most reliable. Always your default if W alone can hit the target.
Fine-Sizeuniform (W∝H)Bisectionfundamental When pitch should scale isotropically (e.g. round bells).
Fine-WHW, H alternatingBisection per axisfundamental Legacy 2D. Generally worse than Bayes-WH.
Fine-W+WAdaptive (bracket / near-best / gap scoring) loudest audible When the fundamental isn't perceptually dominant.
Fine-WBWSweep + multi-bracket bisectionfundamental Shapes with multiple plausible bracket roots.
Manual-W live WBisection / HF spectrum-pinned You click the exact mode you want to tune.
Bayes-W prototype WGaussian-process · Expected-Improvementfundamental or pinned Exhaustive coverage of W when the cost surface has multiple minima.
Bayes-WH prototype W, HGaussian-process · Expected-Improvementfundamental or pinned 2D pitch search. Hard for fundamental reasons (see §14).
Filter-W prototype WBisection 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:

df_k/dW = (1 / (4π ω_k)) · (φ_kT · (K' − λ_k M') · φ_k) / (ε · W)

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:

MAC(φ_a, φ_b) = |φ_a · φ_b|² / ((φ_a · φ_a) (φ_b · φ_b))

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 μ:

f_n = (n / 2L) · √(T / μ)

Modal2D supports this via a per-beam tension field and a geometric stiffness term added to the beam element when tension is non-zero:

K_total = K_bending + (T/L) · K_geometric

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.
Current limit: the UI slider only exposes uniform tension. Per-beam tensions (different strings on the same instrument) are supported by the API but not yet by the panel — that's the next step.

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.

Visual only: modal frequencies don't shift yet. The proper next step is a pre-stress eigensolve — solve 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:

α_ij = 1 / (1 + Q · (Δω / ω_avg)²)

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).

Mechanical vs sympathetic. A guitar where the string is mesh-connected to the box doesn't need either toggle — the joint eigensolve already produces modes that span both. Use these toggles for disjoint shapes (multi-shape Test instruments, multiple objects hanging in the same scene).

10 Audio chain

Each audio callback runs this pipeline. The post_process panel exposes the knobs:

  1. libmodal synthesis on GPU → raw modal sum.
  2. Air-cavity bandpass (if enabled) — parallel resonator at cavity_freq, mixed back at cavity_gain.
  3. Noise gate — HP-filtered amplitude envelope. Gate closes when amplitude drops below gate_low_thr (default 0.002), opens above gate_high_thr (default 0.012). HP cutoff 80 Hz so sub-audible modes don't keep the gate falsely open.
  4. Post-processing — master gain, soft-clip (tanh) saturation at drive, low-pass anti-alias filter at lp_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 via modal_set_damping. Use this for sustain control during play; never use the timeout for that.
  • Strike timeout only fires when fine_mode_ > 0 or 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.

SectionStatusWhat it does
Hellmann-Feynman accelerationactiveHF Newton step toggle for Fine-W / Manual-W. See §3.
Mode-shape correlation (MAC)activeAuto-retracks pinned mode across W changes in Manual-W.
Bayesian (W, H) sampleractiveGP heatmap of the cost surface, sample dots, best-so-far ring.
Mass-spring previewactive10×6 lattice eigensolve thumbnails + FEM A/B pairs. Updates as you drag W/H sliders.
Physical environmentprototypeAir-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.

2D approximation

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.

No air-cavity FEM

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.

2D Bayesian tuning is hard

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.

Uniform string tension only

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.

Beam materials = obj->mat

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.

Gravity is visual only

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.

Frequency-independent damping

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.

Isotropic materials only

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:

#NameWhat it is
0String1D beam chain — half-length W, cross-section H. Pure beam unless you add tension.
1Box-2sClosed box, 2 thick sides + 2 thin sides (default).
2Box-OpenU-shape (3 sides + open top). Bell-like.
3TriangleIsoceles, apex up. Bounding box 2hw × 2hh.
4HexagonRegular hexagon scaled to bounding box.
5Box-HardClosed box, all sides equal thickness (rigid box).
6Ring12-sided elliptical loop, semi-major W / semi-minor H.
7Composite multi-matClosed cavity, hard frame (left/right) + soft panels (top/bottom).
8Strung multi-matComposite + 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.