Week 7 — Plugin GUI


The Evergreen State College · Summer 2026 · 12 credits
Student: Travis Inskeep · Faculty Sponsor: Jessica Carey

Eidolon · Independent Learning ContractWK·07

Goal: deliver a plugin GUI that exposes all 400 engine parameters across three named surfaces. Done: the Front/Detail/Back editor is implemented, approved, and host-tested on 2026-08-18 in two of its four plugin formats, VST3 in Ableton Live 12.3.2 and Bitwig and CLAP in Bitwig; it builds in all four; it is a working foundation and an interim compromise, not the production-final interaction hierarchy. Open: release acceptance still covers engine performance, preset and event transitions, default output level, audible crackle and pop sources, and continued UX/UI and legibility refinement.

The signal path

The Front surface shows the signal path. The instrument runs three oscillators into a pair of wavefolders, through two cascaded filters, into a voltage-controlled amplifier. Envelopes and LFOs can drive any parameter in that chain through the modulation matrix on the Back surface. The six Front panels follow that route left to right: 01 OSCILLATORS (bone), 02 WAVEFOLDERS (vermillion), 03 FILTERS (slate), 04 VCA plus NOISE (ochre), 05 EFFECTS (teal), 06 OUTPUT (neutral). Each panel carries a DETAIL affordance that opens the full-canvas workspace for that module.

Controls are conventional: rotaries, sliders, dropdowns, and explicit ON/ACTIVE/BYPASSED states with typed values visible. Front owns 82 parameters. The implementation carries 72 to 73 simultaneous controls at 1600 by 1200, rather than the 65 in the approved design specification. The specification reached 65 partly by hiding eight effects controls that are alternatives to nothing; hiding them in the live editor would make them unreachable, so reachability took precedence.

Eidolon Front surface at 1600 by 1200, showing six numbered signal-path panels labeled 01 OSCILLATORS in bone, 02 WAVEFOLDERS in vermillion, 03 FILTERS in slate, 04 VCA plus NOISE in ochre, 05 EFFECTS in teal, and 06 OUTPUT in neutral, each carrying a DETAIL affordance and conventional rotary and slider controls with typed values
Screenshot of current implementation: Front surface at 1600 by 1200. Six numbered panels cover 82 controls using conventional rotaries, sliders, and explicit ON/ACTIVE/BYPASSED states with typed values visible. Each panel carries a DETAIL affordance that opens the full-canvas workspace.

Module workspace

Selecting a panel from the Front opens the Detail surface: a full-canvas workspace for that module. Detail carries 246 controls. Its membership is derived from the live AudioProcessorValueTreeState rather than authored by hand, so a parameter added to the engine cannot silently fall out of the editor. Its presentation is authored: 73 semantic subgroups with no alphabetical wall. Eight stepped-float categoricals appear as labelled choices built from the consuming DSP tables rather than continuous knobs.

Eidolon Detail surface at 1600 by 1200, showing a full-canvas selected-module workspace with grouped controls, typed values, named choices, and 73 semantic subgroups across 246 parameters
Screenshot of current implementation: Detail surface at 1600 by 1200. The full-canvas workspace opens for one selected signal-path panel and exposes its 246 controls in 73 semantic subgroups with typed values and named choices.

The modulation matrix

The Back surface is the full 24-slot modulation matrix as a dedicated third surface. Each slot exposes a source, a target, and a signed depth that sets both amount and direction. Back carries 72 controls. The 12 sources and 20 current targets reflect the live engine state. A target is a parameter a voice can offset where it consumes it, so the list holds exactly the 20 that have such a site: cutoff and resonance on both filters, and eight controls on each of the two wavefolders. The effects rack, the master section, and the console sit after the voices are summed, where no single voice can reach them, so they are not targets at all. The interface states that boundary directly.

Eidolon Back surface at 1600 by 1200, showing the full 24-slot modulation matrix as a dedicated surface with source, target, and signed-depth editing for 72 controls
Screenshot of current implementation: Back surface at 1600 by 1200. The dedicated matrix surface exposes all 24 modulation slots with source, target, and signed-depth editing across 72 controls.

Responsive operation

The editor has two supported native sizes. At 1600 by 1200 the six Front panels lay out at full target size. At 960 by 720 the same six panels remain and all three surfaces remain reachable; the dense panels run rotaries at the 32 px minimum floor rather than 40 px, because a 15 px label plus a 32 px target exactly fills the panel cell at that width.

The responsive layout adapts the control geometry to the available space rather than scaling one fixed canvas. Both sizes expose all 400 controls across Front, Detail, and Back.

Eidolon Front surface at 960 by 720, showing the same six numbered signal-path panels at the narrower native size with rotary controls at the 32 px minimum floor
Screenshot of current implementation: Front surface at 960 by 720. The six panels remain fully operable at the narrower native size; rotaries run at the 32 px minimum floor to fit the available cell width.

Verification

Host automation updates stored values and is reflected in all connected controls. Editor close and reopen, and full state serialization, restore all parameter values, bypass states, matrix slot contents, and all eight LFO mode and Division pairs. All 400 controls are keyboard focusable and report names, roles, and current values. The instrument carries 401 parameters in total: the 400 host-automatable controls plus voices, a non-automatable session ceiling placed in the header beside the preset selector, belonging to no surface deliberately. This keeps the ownership census accurate. This is state v12; sessions saved before v12 load at the previous ceiling of 8 and keep their sound.

The editor loads in the VST3, AU, CLAP, and Standalone plugin formats; VST3 was validated by load test in Ableton Live 12.3.2 and Bitwig, CLAP by load test in Bitwig, AU validation returned SUCCEEDED with attribution to the specific build not established due to contended install custody, and the Standalone artifact is a valid arm64 Mach-O that was not launched on a live audio device. The verification pass covers keyboard operation, focus visibility, minimum hit regions, host gestures, state restoration, and both supported layouts. Operational text is at least 15 px, headings at least 18 px, minimum touch targets 32 by 32 px. Eleven captures, zero collisions. Three deterministic capture runs returned byte-identical results. All 34 factory programs render byte-identically to the pre-GUI engine: the editor changes operation without changing audio. The suite stood at 318 Catch2 cases for this pass: 317 passed plus one expected Release-only skip in Debug with 2,221,771 assertions, and 318 of 318 in Release with 2,221,773 assertions. It has since grown to 323 cases as later work added tests. RTSan clean. The editor was visually and operationally tested in Ableton Live 12.3.2 (VST3) and Bitwig (VST3, CLAP) on 2026-08-18; the restored-solve integrated binary awaits deliberate installation and final host acceptance alongside the open engine work.

The CLAP build comes from the same tree as the others. One juce_add_plugin target emits VST3, AU, and Standalone, and CLAP is produced beside them by clap-juce-extensions, a bridge that wraps the same processor rather than reimplementing it. One consequence runs through everything that follows: the formats share a single processBlock, so a format cannot change the sound by itself. What can differ is how a host reaches the parameters, stores the state, and calls the threads, and that is where the comparison is worth making.

Two of the differences are friction in the bridge rather than gaps in the standard. The first is parameter range. CLAP carries an explicit minimum and maximum for every parameter, but the bridge normalizes each value to the range zero to one unless told otherwise, which made values read wrongly in a CLAP host while the same build was correct as VST3. Setting CLAP_USE_JUCE_PARAMETER_RANGES to ALL in the build configuration passes the real ranges through, and that is how Eidolon builds. The second is parameter identity. CLAP requires a stable integer id that must never change, JUCE identifies a parameter by a string, and the bridge hashes one into the other. I checked the whole set rather than assuming it: the 400 automatable parameter names hash to 400 distinct ids with no collisions. The assertion that would catch a collision is compiled out of a release build, so this is a check to repeat if the parameter set grows, not a result that stays true on its own.

The third difference is the interesting one, because it is a real capability the instrument declines to use. CLAP can deliver a parameter change as a sample-accurate event inside the audio call. Eidolon reads its parameters once per block, as it does in every format, so that precision is flattened to the value standing at the block boundary. Automation therefore feels the same in all three formats, and for a fixed parameter set, identical MIDI, and the same block size, the three produce bit-identical audio by construction rather than by luck. Taking the finer timing would mean consuming CLAP's events directly, which the JUCE abstraction does not expose.

The same pattern holds across the extension model. CLAP is a plain C interface assembled from optional extensions, and the bridge maps the ones JUCE already expresses: parameters, state, editor, note and audio ports, latency, and tail. Two CLAP capabilities have no JUCE path and go unused. Its host thread pool is one, so the eight voices run serially in every format, and its native voice information is the other. Neither absence is a defect, but naming them is the honest answer to what parity buys here. Eidolon matches VST3 and AU on everything those formats expose and leaves the CLAP-only capabilities on the table. One further asymmetry belongs on the record: a state the instrument rejects is still reported to a CLAP host as a successful load, because JUCE's state method returns nothing the bridge could pass back. VST3 and AU go through that same method, so the observable behaviour matches across formats, and CLAP is only the one whose bridge names the limitation in its own source.

Evidence

These three clips are deterministic offline renders, not screen recordings. A headless render tool constructs the plugin's audio processor, applies the same parameter values the editor writes when a control is moved, and renders the result to a file. Rendering any clip twice produces a byte-identical result. The screenshots above are the evidence of the controls; the audio here is the evidence that the controls reach the engine.

Filter cutoff from the Front surface

Spectrogram of FLT A Cutoff swept open to closed and back from the Front surface, showing the spectral centroid descending then rising across the phrase.
FLT A Cutoff swept from the Front surface: knob from near-open (around 16 kHz) to near-shut (around 224 Hz), then back. Program 15 is monophonic, so the phrase sounds as a single voice-led line. A control on the Front panel reaches the filter, and the filter reaches the engine.

A modulation route from the Back surface

Spectrogram of the same program played twice, gain-matched: once with no route assigned in matrix slot 5, and once with LFO 3 assigned to WF A Drive at depth plus 0.55, showing increased spectral variation in the routed half.
One route assigned on the Back surface: LFO 3 to WF A Drive at depth +0.55. The clip plays the program twice, gain-matched, with that one route absent on the first pass and active on the second. Program 23 is monophonic. Any level difference you hear between the two halves is not a level difference; the two passes are gain-matched before peak normalization.

Six factory programs

Spectrogram of six factory programs in sequence: Deep Sub, Acid Lead, Glass Marimba, Tape Warmth, Juno Chorus, Folded Lead, showing distinct spectral characters across the six sections.
Six factory programs in sequence: Deep Sub, Acid Lead, Glass Marimba, Tape Warmth, Juno Chorus, Folded Lead. This clip is normalised as a whole rather than per program, so the programs keep their own calibrated level relationships. The spread from sub bass to resonant lead to percussive bell gives a sense of the instrument's range.

CPU: where it stands

The instrument ships with a voice ceiling defaulting to 6. Raising that ceiling to 8 pushes several programs past comfortable CPU headroom. The ceiling costs nothing sonically: all 34 renders are byte-identical at either setting.

Running under 40% of one core at eight voices is unreachable without changing the audio. That is why the ceiling exists.

4× per-voice oversampling is 63 to 73% of every voice's cost, and that figure went unmeasured until four optimization passes had already run inside it. The workload is latency-bound, not instruction-bound: three correct instruction-count reductions measured slower.

One result shipped: a SIMD additive-oscillator rewrite that cut P32 by 12.75% and the full 34-program corpus by 3.07%, with every output sample byte-identical.

The largest single win found was declined, and the decision carries a stated, measured price. A replacement wavefolder solver measured 17.39 to 18.43% on P01 and was more numerically accurate than the solver currently in the instrument. I declined it by ear because it changed the sound of three factory programs. The instrument currently runs P01 at 47.02 to 50.45%, 2.69 to 2.89 times what the rejected solver would have cost. That decision is recorded as D-030. An instrument is not a benchmark.

During the measurement campaign, planning forecast that roughly 14 of 19 polyphonic programs would run under 40% at ceiling 6. The measurement at that point returned 7 of 19. Class averages do not predict per-program outcomes when several programs sit within a few points of the threshold. The shipping tree has not been remeasured in full since that pass; those figures describe a specific measurement, not the current state.

Work in progress: whether the retained solver can be made faster without changing a single output sample is measured but unattempted.

Reflection

Presenting 400 parameters to this standard was a slog. The result is a working editor and a useful compromise: each parameter has a home across three surfaces, but the interaction hierarchy is not the final word on how 400 controls should live together. That work continues.

The development method felt right throughout. Measurements localize mechanisms and protect regressions; controlled comparison, host interaction, and listening decide whether the combined instrument is usable and musically right. Every question that mattered got settled by that sequence, and my ears feel vindicated in it.

The development approach, including AI tool use throughout the project, is documented on the Tooling Disclosure page.

References

Eidolon · Week 7 of 8 · The Evergreen State College