ui-builder.js — UI Components

Generates the per-module parameter control cards, handles canvas drawing for waveform and envelope visualisation, and synchronises lock states.

Module Sections

buildModuleSection(container, mod, params, lockedParams, onChange)

Creates a card for one module. Each card contains:

┌─────────────────────────────────────────────┐
│  Module Label  [🔓] [🎲]      ┌──────────┐ │
│  Param Label  ═══●═════  val  [🔓]      │ canvas │
│  Param Label  ══○═══════ val  [🔓]      │  (if   │
│  ...                                    │  mod.  │
│                                         │canvasId│
│                                         └────────┘ │
└─────────────────────────────────────────────┘
  • Section lock button (🔓/🔒) toggles all params in that section
  • Randomise button (🎲) randomises unlocked params and calls onChange
  • Each param row has label, control (range/select/checkbox), value display, lock
  • Optional inline canvas to the right of controls when mod.canvasId is set. Set mod.canvasExternal = true to suppress inline canvas creation — the canvas is placed in the HTML template instead and drawn by doRender() via its ID.

Supports three control types: - type: 'range'<input type="range"> + value <span> - type: 'select'<select> with options - type: 'checkbox'<input type="checkbox">

For select params, set ui: 'pills' to render the options as clickable pill buttons instead of a dropdown:

pitchCurve: { type: 'select', options: ['exponential', 'linear'], default: 'exponential', label: 'Curve', ui: 'pills' }

syncModuleUI(container, modules, params)

Syncs UI controls ([data-key] elements) with current param values. Used after randomise or preset/template load. Builds a param-def lookup from modules for proper formatValue rendering (units, note names).

syncLockUI(container, lockedParams)

Updates all per-param lock buttons ([data-lock-key]) and section lock buttons (.section-lock) to reflect the current lockedParams state. Used when a template is applied or locks are cleared programmatically.

Canvas Drawing

drawEnvelopeCurve(c, fn, colour)

Draws a normalised envelope curve on a 2D canvas context. fn(t) receives t in [0, 1] and returns a value in [0, 1]. The curve is drawn with: - Theme-aware background (dark/light) - Light horizontal grid lines - Filled area under the curve at reduced opacity - Stroked curve at full opacity - "time" label at bottom-right

drawWaveform(c, samples)

Draws a PCM waveform as a centred line graph. samples is a Float32Array. Down-samples to match canvas width (buffer width, not CSS width). Auto-scales to the peak sample so the waveform always fills the full canvas height regardless of signal level (uses 95% of height, leaving 5% padding at top and bottom, clamped to ±1.0 to prevent clipping). Theme colours are applied by the caller.

isDarkTheme() → boolean

Returns true when data-bs-theme attribute is "dark".

formatValue(pd, val) → string

Formats a param value using its definition: - Appends unit string (Hz, s, dB) where present - Hz values show nearest note: "220 Hz (A3)" - Non-unit values get 2 decimal places - Log-scale Hz params (marked log: true) show 2 decimal places for fine control at low rates (e.g. "0.75 Hz")

Slider types

Property Effect
log: true Maps slider position logarithmically for fine control at low end of range
step: <number> Explicit step size (default: (max-min)/200)

Section Info Popovers

Modules can include a description field. When present, an ⓘ button is appended to the section title bar. Clicking it opens a Bootstrap popover with the description text. The popover is dismissed by clicking outside or pressing Escape.

{
  id: 'osc',
  label: 'Osc',
  description: 'Two main oscillators with FM routing, pulse width modulation, per-oscillator wavefolding, and stereo spread.',
  params: { ... },
}

Param Label Tooltips

Each param definition can include a description field. When present, the param's label <span> gets a native HTML title attribute, showing the description as a tooltip on hover.

osc1Fold: { min: 0, max: 1, default: 0, label: 'Fold1',
  description: 'Sine-based wavefolder on Osc1 adds harmonic richness before the filter.' }