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 pagerenderToBuffer(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())
- Create
masterGainwith volume envelope (scheduleMasterVolume) - Backward loop over
engine.modules: processors →mod.build(), distortion →buildDistortionChain - Call
engine.render(ctx, params, stages, chainInput, dur)to create sources - Apply builtin modules (normalize)
- Return rendered buffer
Unified Path (engine has buildLiveChain + trigger, no render)
- Call
engine.buildLiveChain(ctx, params, stages)to build processor chain - Connect
chain.masterGaintoctx.destination - Call
engine.trigger(ctx, chain, params, 0, 1.0)to create sources at time 0 - Apply builtin modules (normalize)
- 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
#waveformcanvas → 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.renderand noengine.buildLiveChain→ promise rejection