engine.js — Orchestrator & Renderer

The central module that wires everything together. Exports two main functions:

  • initSynth(engine) — async, bootstraps the full UI + audio pipeline on a page
  • renderToBuffer(engine, params, stages) — headless render for WAV export

initSynth(engine)

Called once per page load.

1. Load Default Preset

Fetches /static/js/synth/presets/{engine.id}/default.json. If found, populates params and pipeline.stages from the file. Also captures locked section from the preset (stored as factoryLocked). Falls back to engine.defaults.

2. Build Param UI

Iterates engine.modules and for each: - type === 'distortion' → creates container, calls buildDistortionUI() - All others with params → calls buildModuleSection() for card + controls + lock buttons

3. Preset Management

Creates a PresetManager(engine.presetsKey, engine.id). buildListUI() loads built-in presets from manifest.json and user presets from localStorage. The resetToDefaults() function restores factory params + factory locks (including pipeline.masterLocked for distortion).

4. Keyboard Shortcuts

  • Space: clicks #play-btn (if it exists) — preview the rendered buffer
  • Tab: clicks #randomise-btn

5. Return Value

return { params, lockedParams, pipeline, doRender };

Page bootstraps (e.g. init-juno.js) access the param state and trigger re-renders programmatically.

renderToBuffer(engine, params, stages) → Promise

Headless render for WAV export and waveform preview. Uses OfflineAudioContext and supports two paths:

Legacy Path (engine has render())

  1. Create masterGain with volume envelope (scheduleMasterVolume)
  2. Backward loop over engine.modules: processors → mod.build(), distortion → buildDistortionChain
  3. Call engine.render(ctx, params, stages, chainInput, dur) to create sources
  4. Apply builtin modules (normalize)
  5. Return rendered buffer

Unified Path (engine has buildLiveChain + trigger, no render)

  1. Call engine.buildLiveChain(ctx, params, stages) to build processor chain
  2. Connect chain.masterGain to ctx.destination
  3. Call engine.trigger(ctx, chain, params, 0, 1.0) to create sources at time 0
  4. Apply builtin modules (normalize)
  5. Return rendered buffer

Factory Lock Support

When default.json includes a locked section, it's captured as factoryLocked and applied on initial page load and when "Default" is clicked. This includes: - locked.params — individual locked params restored and re-locked - locked.distortion.masterLocked — distortion master lock state

Signal Chain Wiring (Legacy Path)

ctx = new OfflineAudioContext(1, length, 48000)
masterGain = ctx.createGain()
masterMod.schedule(masterGain, params, dur)  // volume ADSR
masterGain.connect(ctx.destination)

chainInput = masterGain
for (i = modules.length - 1; i >= 0; i--):
  if mod.type === 'processor':
    result = mod.build(ctx, params, chainInput, dur)
    chainInput = result && result.input ? result.input : result
  if mod.type === 'distortion':
    chainInput = buildDistortionChain(ctx, stages, chainInput)

engine.render(ctx, params, stages, chainInput, dur)

State

All mutable state is scoped to the initSynth closure:

Variable Type Description
params {} Flat param key-value store
lockedParams {} { key: true/false } lock state
pipeline DistortionPipeline Distortion stage array + locks
factoryLocked {} Locked params from default.json
activePreset string Current preset name (null = Default)
renderedBuffer AudioBuffer Last rendered buffer for playback
renderedBlob Blob Last rendered WAV blob for download

Error Handling

  • Missing #waveform canvas → early return with console error
  • Missing #param-sliders → early return with console error
  • OfflineAudioContext unsupported → promise rejection, error shown on canvas
  • Render failure → error message drawn on waveform canvas
  • No engine.render and no engine.buildLiveChain → promise rejection