Overview
Instrument plugins receive MIDI input and generate audio output. They’re used for synthesizers, samplers, drum machines, and other sound generators.IPlugInstrument Template
TheIPlugInstrument example demonstrates:
- MIDI note processing with polyphony
- ADSR envelope generator
- LFO modulation (free-running and tempo-synced)
- Parameter groups and UI organization
- Visual feedback (keyboard, meters, LFO visualization)
Project Configuration
config.h
Key Differences from Effects:
PLUG_TYPE 1identifies it as an instrumentPLUG_DOES_MIDI_IN 1enables MIDI inputPLUG_CHANNEL_IO "0-2"= no audio input, stereo output
Basic Structure
- Header
- MIDI Processing
- Audio Processing
IPlugInstrument.h
ProcessMidiMsg()- Handle MIDI inputmDSP- DSP engine with polyphonyIPeakAvgSender- Send meter data to UIEControlTags- Tag controls for updates
Creating an Instrument
#define PLUG_TYPE 1
#define PLUG_DOES_MIDI_IN 1
#define PLUG_CHANNEL_IO "0-2" // Mono synths use "0-1"
#define VST3_SUBCATEGORY "Instrument|Synth"
#define AAX_PLUG_CATEGORY_STR "Synth"
enum EParams
{
// Oscillator
kOscWaveform = 0,
kOscPitch,
kOscDetune,
// Filter
kFilterCutoff,
kFilterResonance,
kFilterEnvAmount,
// Envelope
kEnvAttack,
kEnvDecay,
kEnvSustain,
kEnvRelease,
// Output
kMasterVolume,
kNumParams
};
MySynth::MySynth(const InstanceInfo& info)
: Plugin(info, MakeConfig(kNumParams, kNumPresets))
{
// Oscillator
GetParam(kOscWaveform)->InitEnum("Waveform", 0,
{"Sine", "Saw", "Square", "Triangle"});
GetParam(kOscPitch)->InitInt("Pitch", 0, -24, 24, "", IParam::kFlagsNone, "Osc");
GetParam(kOscDetune)->InitDouble("Detune", 0., -100., 100., 0.01, "cents");
// Filter
GetParam(kFilterCutoff)->InitFrequency("Cutoff", 1000., 20., 20000.);
GetParam(kFilterResonance)->InitPercentage("Resonance", 10.);
GetParam(kFilterEnvAmount)->InitPercentage("Env Amount", 50.);
// Envelope (with power curve for natural feel)
GetParam(kEnvAttack)->InitDouble("Attack", 10., 0.1, 5000., 0.1, "ms",
IParam::kFlagsNone, "ADSR",
IParam::ShapePowCurve(3.));
GetParam(kEnvDecay)->InitDouble("Decay", 100., 1., 5000., 0.1, "ms",
IParam::kFlagsNone, "ADSR",
IParam::ShapePowCurve(3.));
GetParam(kEnvSustain)->InitPercentage("Sustain", 70., 0., 100., "ADSR");
GetParam(kEnvRelease)->InitDouble("Release", 500., 2., 10000., 0.1, "ms",
IParam::kFlagsNone, "ADSR",
IParam::ShapePowCurve(3.));
// Output
GetParam(kMasterVolume)->InitGain("Volume", 0., -60., 12.);
}
template<typename T>
class MySynthDSP
{
public:
MySynthDSP(int maxVoices) : mMaxVoices(maxVoices) {
mVoices.resize(maxVoices);
}
void ProcessBlock(T** inputs, T** outputs, int nChans, int nFrames) {
// Clear output buffers
for (int c = 0; c < nChans; c++) {
memset(outputs[c], 0, nFrames * sizeof(T));
}
// Process each active voice
for (auto& voice : mVoices) {
if (voice.IsActive()) {
voice.ProcessBlock(outputs, nChans, nFrames);
}
}
}
void ProcessMidiMsg(const IMidiMsg& msg) {
switch (msg.StatusMsg()) {
case IMidiMsg::kNoteOn:
if (msg.Velocity() > 0) {
NoteOn(msg.NoteNumber(), msg.Velocity());
} else {
NoteOff(msg.NoteNumber());
}
break;
case IMidiMsg::kNoteOff:
NoteOff(msg.NoteNumber());
break;
case IMidiMsg::kPitchWheel:
SetPitchBend(msg.PitchWheel());
break;
}
}
private:
void NoteOn(int note, int velocity) {
// Find free voice or steal oldest
Voice* voice = FindFreeVoice();
if (!voice) {
voice = FindOldestVoice();
}
voice->Start(note, velocity);
}
void NoteOff(int note) {
for (auto& voice : mVoices) {
if (voice.GetNote() == note) {
voice.Release();
}
}
}
std::vector<Voice> mVoices;
int mMaxVoices;
};
mLayoutFunc = [&](IGraphics* pGraphics) {
pGraphics->AttachPanelBackground(COLOR_GRAY);
pGraphics->EnableMouseOver(true);
pGraphics->EnableMultiTouch(true);
const IRECT bounds = pGraphics->GetBounds().GetPadded(-20);
// Keyboard at bottom
IRECT keyboardBounds = bounds.GetFromBottom(200);
pGraphics->AttachControl(new IVKeyboardControl(keyboardBounds),
kCtrlTagKeyboard);
// Pitch bend and mod wheel
IRECT wheelsBounds = keyboardBounds.ReduceFromLeft(100).GetPadded(-10);
pGraphics->AttachControl(
new IWheelControl(wheelsBounds.FracRectHorizontal(0.5)),
kCtrlTagBender);
pGraphics->AttachControl(
new IWheelControl(wheelsBounds.FracRectHorizontal(0.5, true),
IMidiMsg::EControlChangeMsg::kModWheel));
// Parameter controls in grid
IRECT controlArea = bounds.GetReducedFromBottom(220);
// Oscillator section
IRECT oscSection = controlArea.GetGridCell(0, 3, 1);
pGraphics->AttachControl(new IVKnobControl(
oscSection.GetGridCell(0, 3, 2).GetCentredInside(80),
kOscPitch, "Pitch"));
pGraphics->AttachControl(new IVKnobControl(
oscSection.GetGridCell(1, 3, 2).GetCentredInside(80),
kOscDetune, "Detune"));
// Filter section
IRECT filterSection = controlArea.GetGridCell(1, 3, 1);
pGraphics->AttachControl(new IVKnobControl(
filterSection.GetGridCell(0, 2, 2).GetCentredInside(80),
kFilterCutoff, "Cutoff"));
pGraphics->AttachControl(new IVKnobControl(
filterSection.GetGridCell(1, 2, 2).GetCentredInside(80),
kFilterResonance, "Resonance"));
// QWERTY keyboard support
pGraphics->SetQwertyMidiKeyHandlerFunc([pGraphics](const IMidiMsg& msg) {
pGraphics->GetControlWithTag(kCtrlTagKeyboard)
->As<IVKeyboardControl>()
->SetNoteFromMidi(msg.NoteNumber(), msg.StatusMsg() == IMidiMsg::kNoteOn);
});
};
Voice Management
Polyphony
Most synthesizers support multiple simultaneous notes (polyphony):Voice Stealing
When all voices are busy, steal the oldest:MIDI CC and Pitch Bend
Handling Pitch Bend
Mod Wheel and CCs
Tempo Sync
Access host tempo and position:UI Feedback
Visual Keyboard
Output Meters
MPE Support
For MPE (MIDI Polyphonic Expression):config.h
Next Steps
DSP Utilities
Oscillators, filters, envelopes, and effects
MIDI Reference
Complete MIDI message handling
Control Tags
UI control tagging and updates
IGraphics Controls
Keyboards, meters, and visualizers