-
Notifications
You must be signed in to change notification settings - Fork 1
module architecture
#sdk #architecture #lifecycle #callbacks #events #audio #threading
A Usine user module is a C++ dynamic library (DLL/dylib) that Usine loads at runtime. Each module inherits from UserModuleBase and communicates with Usine through a well-defined callback system.
| Type | Constant | Description | Examples |
|---|---|---|---|
| Simple | mtSimple |
Data, audio, or MIDI processing (no canvas) | AudioVolume, Reverb |
| Control | mtControl |
Module with a visual canvas for custom drawing | DrawBox, Granulator |
| Video | mtVideo |
Video frame processing module | VideoDimmer |
| Device Audio | mtDeviceAudio |
Audio device driver | — |
| Device MIDI | mtDeviceMidi |
MIDI device driver | — |
| Device DMX | mtDeviceDMX |
DMX lighting device | — |
| Device Laser | mtDeviceLaser |
ILDA laser device | — |
| Device Other | mtDeviceOther |
Other device types | — |
| Plugin Wrapper | mtPluginWrapper |
VST/AU plugin wrapper | — |
| Other | mtOther |
Other module types | — |
See Data Types for the full TModuleType enum.
flowchart TD
A[GetBrowserInfo] -->|Usine lists module in browser| B[CreateModule]
B --> C[onGetModuleInfo]
C --> D{Query needed?}
D -->|Yes| E[onGetNumberOfParams]
E --> F[onAfterQuery]
D -->|No| G[onGetParamInfo × N]
F --> G
G --> H[onInitModule]
H --> I[onBlocSizeChange / onSampleRateChange]
I --> J{Module Running}
J -->|Every audio block or frame| K[onProcess / onProcessVideo]
J -->|Parameter change| L[onCallBack]
J -->|User closes| M[DestroyModule]
K --> J
L --> J
style A fill:#4a9,color:#fff
style M fill:#d55,color:#fff
style K fill:#59d,color:#fff
style L fill:#da5,color:#fff
Every module DLL must export exactly three functions:
void CreateModule(void*& pModule, AnsiCharPtr optionalString,
LongBool Flag, TMasterInfo* pMasterInfo,
AnsiCharPtr optionalContent);Called by Usine to instantiate your module. Allocate your module class with new and assign to pModule.
void DestroyModule(void* pModule);Called by Usine to destroy the module. Cast back to your class and delete it.
void GetBrowserInfo(TModuleInfo* pModuleInfo);Called by Usine to populate the module browser. Set at minimum Name and Description.
See Getting Started for a complete minimal implementation.
All data in Usine flows through events (UsineEventClass). An event is essentially a typed array of float values that can represent:
graph LR
subgraph Event Types
A[Audio] --- B[Data]
B --- C[MIDI]
C --- D[Color]
D --- E[Text]
E --- F[Pointer]
end
subgraph UsineEventClass
G["setData / getData"]
H["setArrayData / getArrayData"]
I["setArrayMidi / getArrayMidi"]
J["setColor / getColor"]
K["setPChar / getPChar"]
L["getPointerData"]
end
A --> G
A --> H
B --> G
B --> H
C --> I
D --> J
E --> K
F --> L
- Audio data — samples in a block (size = block size)
- Data values — single floats or arrays
- MIDI codes — packed TUsineMidiCode messages
- Colors — ARGB color values (TUsineColor)
- Text — string data
- Pointers — raw typed pointer data (see UsinePointers)
Parameters are bound to events in onGetParamInfo() using setEventClass():
UsineEventClass myInput;
UsineEventClass myOutput;
void onGetParamInfo(int ParamIndex, TParamInfo* pParamInfo) override
{
if (ParamIndex == 0)
{
pParamInfo->Caption = "input";
pParamInfo->IsInput = TRUE;
pParamInfo->setEventClass(myInput); // bind event
}
// ...
}Once bound, Usine automatically populates input events and reads output events.
Callbacks receive a TUsineMessage with:
-
message— alwaysMSG_CHANGEfor parameter changes -
wParam— theCallBackIdyou assigned inonGetParamInfo() -
lParam— message type (MSG_CHANGE,MSG_CLICK,MSG_DBLCLICK, etc.)
graph TB
subgraph "Callback Threading Model"
CT[ctNormal] -->|Main Thread| MT[Deferred to next tick]
CI[ctImmediate] -->|Audio Thread| AT["Immediate — no alloc!"]
CA[ctAsynchronous] -->|Window Msg Handler| BT[Slow — for long tasks]
CV[cttVideo] -->|Video Thread| VT[Video rendering]
CR[cbtRealTime] -->|Real-time Thread| RT[Fast non-immediate]
CG[cbtGraphic] -->|Main Thread| GT[Lower priority graphics]
CNW[cbtNetwork] -->|Network Thread| NT[Network operations]
CN[ctNone] -->|—| NONE[No callback fired]
end
style CI fill:#d55,color:#fff
style CT fill:#4a9,color:#fff
style CA fill:#59d,color:#fff
style CR fill:#d95,color:#fff
style CV fill:#95d,color:#fff
| Type | Constant | Thread | When |
|---|---|---|---|
| Normal | ctNormal |
Main thread | Deferred to next tick |
| Immediate | ctImmediate |
Audio thread | Immediately on change |
| Asynchronous | ctAsynchronous |
Window message handler | For long processes or modal windows |
| Video | cttVideo |
Video thread | Video rendering thread |
| Real-time | cbtRealTime |
Real-time thread | Fast but not immediate |
| Graphic | cbtGraphic |
Main thread (slower) | Lower priority graphic updates |
| Network | cbtNetwork |
Network thread | Dedicated network operations |
| None | ctNone |
— | No callback fired |
Warning:
ctImmediatecallbacks run on the audio thread. Never allocate memory or perform blocking operations in immediate callbacks.
The onProcess() callback is called on the audio thread at each block:
void onProcess() override
{
// Process audio - called every block (e.g., 256 samples)
outputAudio.copyfrom(inputAudio);
outputAudio.mult(gainCoefficient);
}Rules for onProcess():
- No memory allocation (
new,malloc,std::vector::push_back) - No blocking operations (locks, file I/O, network)
- No Usine SDK calls that aren't marked as real-time safe
- Keep processing as fast as possible
Set DontProcess = TRUE in onGetModuleInfo() if your module doesn't need audio processing.
Examples: AudioVolume, Reverb, RingMod, Granulator
Modules can support variable channel counts using the audio query system (see sdkGetAudioQueryTitle):
void onGetModuleInfo(TMasterInfo* pMasterInfo, TModuleInfo* pModuleInfo) override
{
pModuleInfo->QueryListString = sdkGetAudioQueryTitle();
pModuleInfo->QueryListValues = sdkGetAudioQueryChannelList();
pModuleInfo->QueryListDefaultIdx = 1;
}
int onGetNumberOfParams(int QIdx1, int QIdx2) override
{
numChannels = sdkGetAudioQueryToNbChannels(QIdx1);
return numChannels * 2 + extraParams; // inputs + outputs + controls
}Examples: AudioVolume, RingMod, Reverb, Granulator
For long-running tasks that can't run in onProcess():
sequenceDiagram
participant User
participant Main as Main Thread
participant Job as Job Thread
User->>Main: Click "start job"
Main->>Job: startJob()
Job->>Job: onJobBegin()
Job->>Job: onJobProcess() — heavy work
Job-->>Main: notify completion
Main->>Main: onJobEnd() — collect results
void onCallBack(TUsineMessage* Message) override
{
if (/* user clicked start */)
startJob(); // launches background thread
}
void onJobBegin() override { /* setup */ }
void onJobProcess() override { /* heavy computation here */ }
void onJobEnd() override { /* collect results, update UI */ }onJobBegin and onJobProcess run on a background thread. onJobEnd runs on the main thread.
Example: BackgroundJob
For saving/loading custom module state beyond parameter values:
int onGetChunkLen(LongBool Preset) override
{
return sizeof(MyData);
}
void onGetChunk(void* chunk, LongBool Preset) override
{
memcpy(chunk, &myData, sizeof(MyData));
}
void onSetChunk(const void* chunk, int sizeInBytes, LongBool Preset) override
{
memcpy(&myData, chunk, sizeInBytes);
}Examples: DrawTrajectoryBox, SimplePad, UsineChunks
Add persistent settings via the Settings Panel (see Settings Tab Constants):
void onCreateSettings() override
{
sdkAddSettingLineColor(DESIGN_TAB_NAME, &myColor, "Color", TRUE);
sdkAddSettingLineBoolean(DESIGN_TAB_NAME, &showGrid, "Show Grid", TRUE);
sdkAddSettingLineInteger(DESIGN_TAB_NAME, &gridSize, "Grid Size",
1, 100, scLinear, "", 10, TRUE);
}
void onSettingsHasChanged() override
{
sdkRepaintPanel();
}Examples: DataOscilloscope, DrawBox, SimplePad
Modules of type mtControl can draw on a canvas using the drawing utility methods:
void onPaint() override
{
TRectF rect = {0.1f, 0.1f, 0.9f, 0.9f};
sdkFillRect(rect, myColor, 2.0f, clBlack, 0.0f);
sdkFillText(rect, "Hello", clWhite, 14.0f, FALSE, FALSE, taCenter, taCenter);
}
void onMouseDown(TMouseButton Button, TShiftState Shift, float X, float Y) override
{
// Handle mouse click at normalized coordinates (0-1)
}Examples: DrawBox, DrawTrajectoryBox, DataOscilloscope, SimplePad
graph LR
subgraph "Thread-Safe Patterns"
A["std::atomic<float>"] -->|Simple values| B[onProcess ↔ onCallBack]
C[CriticalSection] -->|Complex state| D[Multiple threads]
E[SyncObject] -->|Signal/Wait| F[Thread coordination]
end
- Use
std::atomic<>for values shared betweenonProcess()andonCallBack()— see AudioVolume - Use
sdkCriticalSectionCreate/Lock/UnLockfor complex shared state — see MultiThreading - Use
sdkSyncObjectCreate/Set/Waitfor thread synchronization — see RingModMultithread - Never lock in
onProcess()if it can be avoided
- UserModuleBase Class — Complete callback reference
- UsineEventClass — Event manipulation API
- Data Types & Constants — All SDK types
- SDK Functions — Utility function reference
- Utility Functions — Color, geometry, math helpers
- Getting Started — Your first module
- Windows Setup — Visual Studio configuration
- macOS Setup — Xcode configuration