Guidance for AI agents (and humans) working in this repo. Read this before editing.
Out-of-source iPlug2 plugin projects plus a shared DSP/MIR library. It is not an iPlug2 fork — it expects a sibling iPlug2 checkout and links against it.
Dev/
├── iPlug2/ <-- sibling SDK, NOT in this repo (fetched by scripts/setup-iplug2.ps1)
└── plugins/ <-- this repo
├── src/audioagent/ shared C++ DSP + MIR + Camelot wheel library (header-only, INTERFACE lib)
├── CamelotSynth/ reference iPlug2 plugin (links audioagent)
├── third_party/ vendored deps; upstream/ is gitignored, fetched by setup
├── assets/ shared audio (gitignored)
└── scripts/ PowerShell build/setup + CMake helpers
The repo targets Windows + Visual Studio 2022 + CMake as the primary path. macOS/iOS Xcode project files exist (generated by iPlug2's duplicate.py) but the documented, supported workflow is Windows/PowerShell.
iPlug2 is current. iPlug3 is announced but not released — do not assume iPlug3 APIs exist. All code here is iPlug2.
.\scripts\setup-iplug2.ps1 # populates ../iPlug2 dependencies
.\scripts\setup-third-party.ps1 # fetches audioFlux source into third_party/audioFlux/upstream
code plugins.code-workspace$p = "CamelotSynth"
.\scripts\build.ps1 -Plugin $p -Format vst3 -Config Release -Install- Build output:
CamelotSynth/build/out/CamelotSynth.vst3/. - Reaper-locked-file gotcha: if Reaper has the plugin loaded,
-Installstages to*.vst3.pending. Close Reaper, then re-run.\scripts\install-plugin.ps1 -Plugin $p -Format vst3. -Deploy(CMakeIPLUG_DEPLOY_PLUGINS=ON) copies straight into the AppData VST3 folder; off by default to avoid clobbering a plugin Reaper has open.- Two build methods exist:
-Method cmake(default, preferred) and-Method vs(msbuild the generated.sln). Prefer cmake.
.\scripts\new-plugin.ps1 -Name MySynth -Template IPlugInstrument -Manufacturer VecnodeThis calls iPlug2's Examples/duplicate.py, then patches the generated CMakeLists.txt so IPLUG2_DIR resolves to the sibling ../../iPlug2 and appends the Windows resource-embed include. After running: edit config.h (PLUG_MFR_ID, unique IDs, metadata) and link audioagent if you need the shared DSP (see its CMake snippet below).
audioagent is framework-agnostic C++ that touches a deliberately tiny slice of iPlug2/WDL so it stays host-portable. The entire dependency is:
| Symbol / header | Used in | Why |
|---|---|---|
IPlugConstants.h → iplug::sample |
iplug_bridge.h |
RT sample type (double/float) |
Smoothers.h → iplug::LogParamSmooth |
iplug_bridge.h |
param de-zipper on the audio thread |
heapbuf.h → WDL_TypedBuf / WDL_String |
dsp/analysis buffers | RT-safe pre-allocated buffers |
IPlugPaths.h → LocateResource / LoadWinResource / gHINSTANCE |
platform/ResourceLoader.h |
embedded WAV load on Windows |
Rules:
- Only
iplug_bridge.handplatform/ResourceLoader.hmay include iPlug headers. Everything else inaudioagent/is plain C++ (+ audioFlux inanalysis/). - No IGraphics anywhere in
audioagent/. UI lives only in the plugin (CamelotSynth/src/ui/,src/editor/).camelot/WheelLayout.his pure geometry — the plugin mapsIRECT↔Bounds. - To port
audioagentto a non-iPlug host: replace those two files (swapLoadEmbeddedforAssignFromFloaton a decoded buffer).
Layer dependency rules are enforced by convention — see src/audioagent/ARCHITECTURE.md ("Module dependency rules").
The audio thread is SamplerEngine::ProcessBlock → SampleTransport::ProcessBlock → SamplePlayer::ProcessBlock. On that path:
- No audioFlux calls, no heap allocation, no locks/mutexes, no IGraphics.
- audioFlux (pitch shift, PitchYIN detect) runs only on
PitchStreamWorker/OfflineSampleWorkerbackground threads. - The worker scheduler is kicked from
SamplerEngine::Tick()(UI timer), not fromProcessBlock. - CI greps for forbidden APIs: run
.\scripts\check-rt-audio.ps1before committing audio-path changes (also wired in.github/workflows/rt-audit.yml).
add_subdirectory("${CMAKE_SOURCE_DIR}/../src/audioagent" "${CMAKE_BINARY_DIR}/audioagent")
# then, per format target:
target_link_libraries(${PROJECT_NAME}-vst3 PRIVATE audioagent)audioagent is an INTERFACE library: headers + include paths + the audioflux static link, and it pulls iPlug2 headers from IPLUG2_DIR.
- State persistence is param-based (no chunks).
PLUG_DOES_STATE_CHUNKSis0; HPF and pitch mode persist as real params (kParamHPF,kParamPitchMode) and are re-applied to the engine viaOnParamChange(live) andOnReset(restore). If you ever add non-param state, setPLUG_DOES_STATE_CHUNKS 1and implementSerializeState/UnserializeStateso they also callSerializeParams/UnserializeParams— otherwise enabling chunks will stop params from saving. - Asset filename is rewritten at configure time.
assets/AtmosSynth1 D#maj.wav(gitignored, has a space +#) is copied toCamelotSynth/resources/audio/AtmosSynth1_Dmaj.wavby the plugin CMakeLists. Code references the sanitized name viaATMOS_SAMPLE_FNinconfig.h. config.hstill ships placeholder metadata from the template (PLUG_MFR_ID 'Acme',PLUG_URL_STR/PLUG_EMAIL_STRpointing at iplug2.github.io). Fix per real plugin before release.
| Doc | Contents |
|---|---|
README.md |
Overview, setup, build quickstart |
DEVELOPMENT_PLAN.md |
RT DSP roadmap, implemented phases |
src/audioagent/README.md |
Library modules, thread model, iPlug integration |
src/audioagent/ARCHITECTURE.md |
Data flow, transport de-clicking, dependency rules |
CamelotSynth/README.md |
Plugin features, UI, processing chain |
When you change module structure, the thread model, or the iPlug surface, update src/audioagent/ARCHITECTURE.md and this file in the same change.