Skip to main content

Overview

The IPlugMidiEffect example demonstrates how to build a plugin that processes MIDI messages. This example shows a simple MIDI effect with a gain control that passes MIDI through and allows triggering MIDI notes from the UI.
This plugin has a channel configuration of 0-0, meaning no audio inputs or outputs - it’s a pure MIDI effect.

Key Features

MIDI Processing

Filter and pass through MIDI messages in the audio thread

MIDI Generation

Trigger MIDI notes directly from the user interface

Audio Passthrough

Apply gain to audio passing through the plugin

MPE Support

Configured to support MIDI Polyphonic Expression

Plugin Configuration

In config.h, the plugin is configured as a MIDI effect:
#define PLUG_CHANNEL_IO "0-0"     // No audio I/O
#define PLUG_TYPE 2                 // MIDI Effect type
#define PLUG_DOES_MIDI_IN 1
#define PLUG_DOES_MIDI_OUT 1
#define PLUG_DOES_MPE 1             // MPE support enabled

Implementation

Header Structure

The plugin class extends the base Plugin class and overrides MIDI processing methods:
class IPlugMidiEffect final : public Plugin
{
public:
  IPlugMidiEffect(const InstanceInfo& info);

#if IPLUG_DSP
  void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override;
  void ProcessMidiMsg(const IMidiMsg& msg) override;
#endif
};

Processing MIDI Messages

The ProcessMidiMsg method filters which MIDI messages to pass through:
void IPlugMidiEffect::ProcessMidiMsg(const IMidiMsg& msg)
{
  int status = msg.StatusMsg();
  
  switch (status)
  {
    case IMidiMsg::kNoteOn:
    case IMidiMsg::kNoteOff:
    case IMidiMsg::kPolyAftertouch:
    case IMidiMsg::kControlChange:
    case IMidiMsg::kProgramChange:
    case IMidiMsg::kChannelAftertouch:
    case IMidiMsg::kPitchWheel:
    {
      goto handle;
    }
    default:
      return;
  }
  
handle:
  SendMidiMsg(msg);
}
Only channel voice messages are passed through. System messages and other MIDI data are filtered out.

Audio Processing

Even though this is a MIDI effect, it can process audio that passes through:
void IPlugMidiEffect::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
  const double gain = GetParam(kParamGain)->Value() / 100.;
  const int nChans = NOutChansConnected();

  for (auto s = 0; s < nFrames; s++) {
    for (auto c = 0; c < nChans; c++) {
      outputs[c][s] = outputs[c][s] * gain;
    }
  }
}

Triggering MIDI from the UI

The example includes a button that generates MIDI notes when clicked:
auto actionFunc = [&](IControl* pCaller) {
  static bool onoff = false;
  onoff = !onoff;
  IMidiMsg msg;
  constexpr int pitches[3] = {60, 65, 67};  // C, F, G
  
  for (int i = 0; i<3; i++)
  {
    if(onoff)
      msg.MakeNoteOnMsg(pitches[i], 60, 0);  // Velocity 60, channel 0
    else
      msg.MakeNoteOffMsg(pitches[i], 0);
  
    SendMidiMsgFromUI(msg);
  }
  
  SplashClickActionFunc(pCaller);
};

pGraphics->AttachControl(new IVButtonControl(
  pGraphics->GetBounds().GetPadded(-10), 
  actionFunc, 
  "Trigger Chord"
));
1

Create MIDI messages

Use IMidiMsg methods like MakeNoteOnMsg() and MakeNoteOffMsg() to construct messages
2

Send from UI thread

Call SendMidiMsgFromUI() to safely send MIDI from the UI thread to the audio thread
3

Handle in ProcessMidiMsg

Process the messages in ProcessMidiMsg() on the audio thread

Setting Tail Size

MIDI effects may need to declare a tail time for proper plugin behavior:
#if IPLUG_DSP
  SetTailSize(4410000);  // Large tail to keep plugin alive
#endif
Setting a very large tail size (like 4410000 samples = ~100 seconds at 44.1kHz) keeps the plugin processing even when no audio is present. This is important for MIDI effects that need to respond to incoming MIDI.

Building a MIDI Effect

1

Configure as MIDI effect

Set PLUG_TYPE 2 and PLUG_CHANNEL_IO "0-0" in config.h
2

Enable MIDI I/O

Set PLUG_DOES_MIDI_IN 1 and PLUG_DOES_MIDI_OUT 1
3

Override ProcessMidiMsg

Implement MIDI message filtering and processing logic
4

Send MIDI output

Use SendMidiMsg() to output MIDI messages from the audio thread
5

Optional: UI to MIDI

Use SendMidiMsgFromUI() to generate MIDI from UI controls

Common MIDI Operations

Creating MIDI Messages

IMidiMsg msg;

// Note messages
msg.MakeNoteOnMsg(noteNumber, velocity, channel);
msg.MakeNoteOffMsg(noteNumber, channel);

// Control change
msg.MakeControlChangeMsg(controllerNumber, value, channel);

// Pitch bend (0-16383, 8192 is center)
msg.MakePitchWheelMsg(value, channel);

Reading MIDI Messages

void ProcessMidiMsg(const IMidiMsg& msg)
{
  int status = msg.StatusMsg();        // Note On, Note Off, etc.
  int noteNumber = msg.NoteNumber();   // 0-127
  int velocity = msg.Velocity();       // 0-127
  int channel = msg.Channel();         // 0-15
  int offset = msg.mOffset;            // Sample offset in block
}

Drum Synth

A synthesizer that responds to MIDI input

Visualizer

Audio visualization with UI communication

Source Files

  • Examples/IPlugMidiEffect/IPlugMidiEffect.h
  • Examples/IPlugMidiEffect/IPlugMidiEffect.cpp
  • Examples/IPlugMidiEffect/config.h