Implement the VST3 factory so the plugin actually loads - #7
Merged
Conversation
docs/WIKI.md is a single self-contained page: what danzig is, how the COM vtable machinery in src/vst3.zig maps to Zig extern structs, plugin registration, the audio callback path, the parameter system and its one-cache-line layout, the audio helpers, how the universal VST3 bundle is built, testing, and troubleshooting. Every command and every block of output in it was run. examples/danzig-minimal is new: one parameter, one line of DSP, and the single symbol a VST3 host looks for. The same source builds as the shared library a host would load and as an executable, so the DSP can be checked without a DAW. It is the file to copy when starting a plugin. examples/danzig-test was a print statement claiming to be an integration test. It now links the built DanzigGain plugin, calls its exported GetPluginFactory, and drives the returned object through the raw VST3 C ABI using its own copy of the vtable layout, so a change to the object layout fails here rather than in a DAW. It exits non-zero on failure. Each example directory gets a README with what it demonstrates and the exact commands, plus an examples/README.md index that also flags the two unbuilt leftover files. setup.sh checks the Zig version, warns outside 0.14.1 and 0.15.2, builds, tests, packages the bundle, and prints the install path. Idempotent, non-zero on failure. The README's Build Artifacts section listed sizes and a libdanzig.a that do not exist. Corrected against a real ReleaseFast build. Both the README and the wiki now state plainly that the gain plugin's factory is a stub and that a host currently finds zero classes in the bundle. Verified on 0.14.1 and 0.15.2: 29/29 build steps, 35/35 tests, and lipo reporting x86_64 arm64 for the bundle binary.
The example plugin could not be loaded by any host. The factory claimed one class, wrote nothing into the PClassInfo it was handed, and returned kResultOk from createInstance without producing an object. The bundle also exported no bundleEntry, so a macOS host discarded the module before it ever reached the factory. pluginval reported "Num plugins found: 0". src/vst3.zig now carries the real C ABI: interface ids, PClassInfo and PClassInfo2, bus and process structs, and a vtable type per interface, with comptime size assertions on the layouts a host reads. The old result codes were wrong (kResultFalse was 0, kBypass was 1 << 5) and are corrected. examples/danzig-gain is a working single-component effect. One object exposes IComponent, IAudioProcessor and IEditController, recovering itself from each interface with @fieldParentPtr, sharing one atomic refcount. Gain comes from the lock-free ParamStore and is applied per sample; bypass passes the input through. State is a versioned blob of normalized values. Anything not implemented returns kNotImplemented rather than a false success. The integration harness now asserts on content instead of result codes: buffers are zeroed before every call, the class info must name the Audio Module Class category, and createInstance must yield an object that queries, counts references, and renders audio at the gain it was told to use. Nine of those checks fail against the old stub. pluginval passes at strictness 10 on both Zig 0.14.1 and 0.15.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
godofecht
added a commit
that referenced
this pull request
Jul 29, 2026
Implement the VST3 factory so the plugin actually loads
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Includes the docs/examples work it is based on, plus the factory implementation.
The bug
danzig's example plugin could not be loaded by any host.
A host is told one class exists, asks for it, and gets an uninitialized struct back with a success code.
Root cause was two things
The stub factory was the visible bug. The bundle also exported no
bundleEntry. macOS hosts load a.vst3viaCFBundleand callbundleEntryfirst; without it the module is discarded beforeGetPluginFactoryis ever looked up. Fixing only the factory would still have produced "Num plugins found: 0".Before / after
Passes at
--strictness-level 5and10, in Debug and ReleaseFast. No strictness was lowered.Also corrected in src/vst3.zig
Several constants were wrong:
kResultFalsewas 0 (it is 1),kBypasswas1 << 5(kIsBypassis1 << 16),AudioBusBuffers.channelBuffers32had= nullon a non-optional pointer, andIComponent.getBusCountwas missing itsMediaTypeargument. Comptime assertions now pin the struct sizes a host reads.The test that was lying
The harness asserted
getClassInfo(0) == 0, which passes against the stub because the stub returns 0. Every buffer is now zeroed before the call and the content asserted. Reverting the factory to its stub bodies fails 9 checks:getClassInfo(0) returns kResultOkstill passes against the stub, which is exactly the point: the result code alone proves nothing.The harness went from 12 to 49 checks and now drives the object as a host does, through initialize, bus info, parameter info, setupProcessing, and real audio: DC at 0 dB comes out 1.0, at +6 dB 1.995, bypassed exactly 1.0.
Not implemented
IConnectionPoint,IUnitInfo,IMidiMapping,IPluginFactory3, 64-bit processing, a custom editor, programs/presets. All refuse cleanly withkNotImplementedrather than a falsekResultOk. The plugin does not callrestartComponent(kParamValuesChanged)after a state load; pluginval passes without it, but a host caching parameter values aggressively could show stale numbers after loading a session.Verification
35/35 unit tests and 49/49 integration checks on both 0.14.1 and 0.15.2 from a clean cache.
lipo -inforeportsx86_64 arm64.🤖 Generated with Claude Code