Custom DSP Modules (Enterprise)
Your DSP algorithms are what make your sound unique. Gorilla Engine lets you bring them into your instruments and effects rather than replacing them with built-in processing. Whether you have a signature filter, oscillator, effect, or modulator, custom C++ modules let your code become part of the instrument’s signal flow.
Start with Examples/Custom Module Example in the Enterprise SDK. It includes the custom-module SDK, build files, and examples of a bus effect, a voice filter, an oscillator, and a modulation source. On macOS, the default location is /Applications/Gorilla Engine SDK/Examples/Custom Module Example.
Where Your DSP Runs
Section titled “Where Your DSP Runs”Custom modules use the same processing infrastructure as the engine’s built-in modules:
- Effects derive from
GorillaEngine::Effectand process audio on an effect bus. Use them for processing a mixed signal, such as a delay or compressor. - Filters derive from
GorillaEngine::Filterand run in the voice signal path of a group. They can process or generate audio and provide modulation signals.
The example’s custom-module-sdk/effects/Effect.h and custom-module-sdk/filters/Filter.h describe the interfaces. src/CustomModuleExample.cpp demonstrates both. Choose the interface that matches where your algorithm belongs in the signal flow.
Example: A Custom Gain Effect
Section titled “Example: A Custom Gain Effect”The following adapts the SDK’s EffectExample, retaining its parameter layout and gain calculation while omitting its meter output. It is a stereo example, not a general multichannel processor.
#include <effects/Effect.h>#include <string>
using namespace GorillaEngine;
static const Param effectExampleParams[] = { { "Module", 0, 0, 0, 0, 0, 1, Param::String, Param::ReadOnly, "EffectExample" }, { "Bypass", 0, 0, 0, 1, 0, 1, Param::Int, Param::Switch, "Off|On" }, { "EffectMods", 0, 0, 0, 0, Param::NeedsReset, 0, Param::Module, Param::None, 0 }, { "Gain", "dB", 0.0, 1.0, 0.0, 0, 1, Param::Knob, Param::Volume, 0 }, { "Left Only", 0, 0, 0, 1, 0, 1, Param::Int, Param::Switch, "No|Yes" }, {}};
class EffectExample : public Effect{public: explicit EffectExample(Module* parent) : Effect(parent) { const void* valuePointers[] = { &moduleType, &bypass, &effectMods, &gain, &leftOnly }; initParams(effectExampleParams, valuePointers); }
void setParameter(int parameterIndex, ParamValue& value) override { Effect::setParameter(parameterIndex, value); if (parameterIndex == FirstParam) { gainCoefficient = static_cast<float>(gain * gain * gain); } }
void initAudio(Context* context) override { Effect::initAudio(context); }
void renderAudio(Bus* bus, int frames) override { if (!(bus->activeChannels & 3)) { return; }
float* left = bus->channels[0]; float* right = bus->channels[1]; for (int frame = 0; frame < frames; ++frame) { left[frame] *= gainCoefficient; if (!leftOnly) { right[frame] *= gainCoefficient; } } }
private: double gain; int leftOnly; float gainCoefficient;};
#if defined(_WIN32)#define GE_CUSTOM_EXPORT extern "C" __declspec(dllexport)#else#define GE_CUSTOM_EXPORT extern "C" __attribute__((visibility("default")))#endif
GE_CUSTOM_EXPORT const char** listModules(){ static const char* names[] = { "EffectExample", nullptr }; return names;}
GE_CUSTOM_EXPORT Module* createModule(const char* name, Module* parent){ if (std::string(name) == "EffectExample") { return new EffectExample(parent); } return nullptr;}Parameters and Initialization
Section titled “Parameters and Initialization”The Param table describes each parameter’s name, units, range, default, flags, element count, storage type, and display format. The first three entries belong to the base effect; your own parameters begin at Effect::FirstParam. Keep the parameter table and valuePointers in the same order.
initParams() binds descriptors to member storage and initializes defaults. Calling Effect::setParameter() first updates that storage; your override then calculates the coefficients needed by your algorithm. Here, Gain is a normalized knob value, not a dB value: the example cubes it to obtain the audio multiplier, while Param::Volume supplies its dB display. A value of 1 gives unity gain.
initAudio() is where you initialize sample-rate-dependent processing. Call the base implementation first and handle subsequent audio reinitialization, not only the first playback.
Audio Processing and Registration
Section titled “Audio Processing and Registration”renderAudio() receives planar floating-point bus buffers and processes them in place. Replace the gain multiplication with your algorithm. The activeChannels bitmask indicates active channels; a tail-producing effect must keep its output channels active while the tail remains. The effect-bus infrastructure handles bypass crossfading, so your processing may still be called while bypassed.
Keep audio processing real-time safe: do not allocate memory, access files, or block in the render callback. Prepare required buffers outside that callback and process exactly the frame count supplied by the engine.
For dynamic libraries, listModules() advertises a null-terminated list of module names, and createModule() constructs the requested module with its engine-provided parent. Names must start with Effect for bus effects or Filter for voice modules. The names in the list, factory, and parameter table must agree. Keep them stable so saved instruments can find their modules.
Build and Test in Gorilla Editor
Section titled “Build and Test in Gorilla Editor”Use the SDK example’s CMake project as your starting point. It requires CMake 3.18 or newer and a C++11-capable compiler, builds a module library, and links GorillaEngine::CustomModule. From the example’s src directory:
cmake -B buildcmake --build build --config ReleaseKeep the sibling custom-module-sdk directory available, or set CUSTOM_MODULE_SDK_DIR when configuring the project. Build a .dylib on macOS or .dll on Windows, matching the architecture of the Editor and eventual plugin. For a product supporting both Intel and Apple silicon hosts, supply a universal macOS library; for an x64 Windows product, supply an x64 DLL.
On macOS, custom-module libraries must be code signed before Gorilla Editor can load them. Ad-hoc signing is sufficient for local testing:
codesign --force --sign - "path/to/MyCustomModule.dylib"Sign the library again after rebuilding it. Ad-hoc signing is for local testing, not a substitute for release signing.
To make the library available in Gorilla Editor, copy it into its machine-wide PlugIns directory:
| Platform | Directory |
|---|---|
| macOS | /Library/Application Support/UJAM/Gorilla Editor/PlugIns |
| Windows | %PROGRAMDATA%\UJAM\Gorilla Editor\PlugIns |
Restart Gorilla Editor after installing or replacing the library. Select an effect bus or group, then use its + module menu to insert your effect or filter. Test the processing, parameter behavior, bypass, and any tails before exporting your instrument.
When exporting from the command line, use the Blob Export Tool option --modules-path to point to the directory containing your library:
blob-export --modules-path "MyCustomModule/build/Release" MyInst.inst MyBlob.blobThe blob contains the instrument configuration, not a replacement for the native DSP library. The product also needs the library at runtime.
Deliver with Gorilla Compiler
Section titled “Deliver with Gorilla Compiler”Installing a module in Gorilla Editor does not automatically include it in your product. In Gorilla Compiler:
- Open your product project and select Packaging → Native Plugins.
- Drag your custom-module library files into the application while that tab is selected. The list shows each library’s Name and Path.
- Include the
.dyliband.dllversions if you ship on both platforms. Save the project with Save Project. - Use Create Product on each target operating system to build its product and installer.
Gorilla Compiler includes the platform’s native libraries in the installer which installs them with your product. It does not compile your DSP source or convert a library to another CPU architecture. Keep the listed paths valid and rebuild the library when your algorithm changes. Sign your native libraries separately - Gorilla Compiler does not sign these.
Validate the installed product on a system without your development copies of the modules. Confirm that the instrument loads, the DSP runs correctly, and every supported host architecture can load the library. If you distribute reusable dynamic libraries, consider how your licensing protects them against use in other instruments.
Direct C++ Integration
Section titled “Direct C++ Integration”When using the Gorilla Engine C++ Library, you can instead compile custom modules into your application. Implement CustomModuleFactory, defined in state/Module.h, and register it through GEPub::setCustomModuleFactory() before loading instruments that use your modules. This avoids separate custom-module dynamic libraries while retaining the same DSP interfaces.

