Compiled Faust devices
SkillFiles & storageWork on MAGDA's compiled Faust devices (synths/FX whose DSP is a .dsp file compiled to C++ at build time). Use when adding or changing a parameter on a compiled Faust plugin (e.g. Poly Synth, the filter engines, compiled FX), creating a new compiled Faust device, or wiring its custom UI. Covers the .dsp -> generated.cpp pipeline, the host-slot contract, and UI/linking.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Compiled Faust devices skill
What this skill tells your AI
The instructions your AI receives, as published by conceptual-machines/magda-core in .claude/skills/faust-device/SKILL.md and read by ahel’s review.
MAGDA has two kinds of Faust device:
- Compiled (this skill): the
.dspis compiled to C++ at build time and statically linked. Fast, ships in the binary. Lives inmagda/daw/audio/faust_dsp/*.dsp+magda/daw/audio/plugins/compiled/Magda*CompiledPlugin.{hpp,cpp}. - Interpreted/JIT (FaustPlugin / FaustInstrumentPlugin): loads
.dspat runtime. Not this skill.
The pipeline (how a .dsp becomes a device)
faust_dsp/magda_x.dsp
--(CMake: magda_compile_faust_dsp, runs `faust -lang cpp`)-->
cmake-build-debug/compiled_dsps/magda_x.generated.cpp (NOT checked in)
--(#include'd by)-->
plugins/compiled/MagdaXCompiledPlugin.cpp (the host wrapper)
--(custom UI)-->
ui/components/chain/custom_ui/XUI.cpp
- The generated
.cppis produced by the build — there is no checked-in generated file to hand-edit. Just edit the.dspand rebuild (make debug); CMake reruns faust. Confirm the result incmake-build-debug/compiled_dsps/<name>.generated.cpp. - Registration lives in
magda/daw/audio/CMakeLists.txtviamagda_compile_faust_dsp(<dsp> <ClassName> <OUT_VAR>); the generated path is added totarget_sourcesand the wrapper#includes<name>.generated.cpp. - Validate DSP before building with the
faust-mcp-magdaMCP tools (compile_faust,search_faust_libraries,get_faust_library).
Host-slot contract (THE thing to get right)
Each control is a Faust hslider/nentry tagged [idx:N]. N is the host slot index — the stable contract between dsp, wrapper, and UI. The wrapper harvests zones by reading the [idx:N] metadata.
Three places must agree on slot indices:
.dsp—[idx:N]on each param.MagdaXCompiledPlugin.hpp—kFooSlot = Nconstants +kHostSlotCount.MagdaXCompiledPlugin.cpp—hostSlotInfo_[kFooSlot] = {.name, .scale, .minValue, .maxValue, .defaultValue, .choices}.XUI.{hpp,cpp}— its ownkFooSlotconstants +kNumParams, label, layout.
Adding a parameter (common case)
Append at the end (idx = old kHostSlotCount) so existing slot indices stay stable — never insert in the middle and renumber.
.dsp: add thehslider/nentrywith the next free[idx:N]; wire it into the DSP graph. Reuse-by-copy from a sibling.dspwhen matching behaviour (e.g. the drive lerp(1-d)*x + d*(tanh(4x)/tanh(4))frommagda_filter_svf.dsp).- Wrapper
.hpp: addkFooSlot = N, bumpkHostSlotCount. Thestd::array<…, kHostSlotCount>members grow automatically. - Wrapper
.cpp: addhostSlotInfo_[kFooSlot] = {…}. UseParameterScale::Discrete+.choices = {...}for menus,Linear/Logarithmic/FaderDBotherwise. - UI: add
kFooSlot, bumpkNumParams, add a label, place it inresized(). make debug(regenerates the dsp), confirm the param appears in the generated.cpp.
[style:menu{'A':0;'B':1}] in the dsp = a discrete choice param; mirror with .choices in slot info.
Custom UI + linking
- The UI exposes
std::vector<LinkableTextSlider*> getLinkableSliders(); each slider carries its slot viasetParamIndex().DeviceSlotComponent::setupCustomUILinking()wires mod/macro/automation/MIDI-Learn off that list. Any control that must be linkable has to be aLinkableTextSliderin that list. - To present a slot as something else (e.g. segmented buttons for a menu): keep the hidden
LinkableTextSliderincontrols_/getLinkableSliders()(carries value + linking) and drive it from the custom widget. Set selection explicitly for segmented buttons (setToggleState(i==sel)), not via JUCE radio groups (radio exclusivity left two segments lit). - Theme-font buttons:
setLookAndFeel(&FlatTabButtonLookAndFeel::getInstance())(flat tab) orSmallButtonLookAndFeel(rounded), both inthemes/SmallButtonLookAndFeel.hpp; clear withsetLookAndFeel(nullptr)in the destructor. - Labels/fonts:
FontManager::getInstance().getUIFont(...), colours viaDarkTheme::getColour(...).
Reusing a curve view across devices
Curve/response views (e.g. CompiledFilterCurveView) are normally bound to one plugin's slots via a DeviceInfo snapshot. To reuse one in a different device, add a raw-value entry point (setRawState(...)) that sets the target values directly and bypasses the device-snapshot path, plus optional per-instance theming (e.g. setCurveColour). Drive it from the host UI on every relevant param change.
Gotchas
- Generated
.cppis build output — don't look for it in git; rebuild to refresh. - Never reorder/insert
[idx:N]; append. (Unreleased features: no migration needed, but stable indices keep diffs sane.) ba.selectn(n, sel, ...)is strict — every branch runs, so cascading/parallel filter modes is cheap and safe.- Polyphonic synths use Faust's
mydsp_poly(group=false) — per-voice zones are harvested intovoiceZonesBySlot_. - Build with the
buildingskill (make debug), never raw faust/cmake.
Signals
- GitHub stars
- 206
- Forks
- 24
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
faust-device- Source
- github.com/conceptual-machines/magda-core