Skip to main content

Overview

Audio effects process audio in real-time, modifying the input signal and sending it to the output. Examples include EQ, compression, reverb, delay, and distortion.

IPlugEffect Template

The IPlugEffect example demonstrates a minimal audio effect with:
  • Single gain parameter
  • Stereo processing
  • IGraphics UI with knob control
  • Real-time safe DSP

Project Structure

Creating an Effect Plugin

1
Duplicate the Template
2
cd iPlug2/Examples
python duplicate.py IPlugEffect MyCompressor MyCompany
cd MyCompressor
3
Define Parameters
4
Edit the header to add parameters:
5
enum EParams
{
  kThreshold = 0,
  kRatio,
  kAttack,
  kRelease,
  kMakeupGain,
  kNumParams
};
6
Initialize Parameters
7
In the constructor, initialize each parameter:
8
MyCompressor::MyCompressor(const InstanceInfo& info)
: Plugin(info, MakeConfig(kNumParams, kNumPresets))
{
  GetParam(kThreshold)->InitDouble("Threshold", -20., -60., 0., 0.1, "dB");
  GetParam(kRatio)->InitDouble("Ratio", 4., 1., 20., 0.1);
  GetParam(kAttack)->InitDouble("Attack", 10., 0.1, 100., 0.1, "ms", 
                                 IParam::kFlagsNone, "", 
                                 IParam::ShapePowCurve(3.));
  GetParam(kRelease)->InitDouble("Release", 100., 10., 1000., 0.1, "ms",
                                  IParam::kFlagsNone, "",
                                  IParam::ShapePowCurve(3.));
  GetParam(kMakeupGain)->InitGain("Makeup Gain");
  
  // ... UI code
}
9
Implement DSP
10
Add processing logic in ProcessBlock:
11
void MyCompressor::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
  const double threshold = GetParam(kThreshold)->Value();
  const double ratio = GetParam(kRatio)->Value();
  const double attack = GetParam(kAttack)->Value();
  const double release = GetParam(kRelease)->Value();
  const double makeupGain = GetParam(kMakeupGain)->DBToAmp();
  
  const int nChans = NOutChansConnected();
  
  for (int s = 0; s < nFrames; s++) {
    for (int c = 0; c < nChans; c++) {
      // Your DSP algorithm here
      outputs[c][s] = inputs[c][s] * makeupGain;
    }
  }
}
12
Build UI
13
Add controls in mLayoutFunc:
14
mLayoutFunc = [&](IGraphics* pGraphics) {
  pGraphics->AttachPanelBackground(COLOR_GRAY);
  pGraphics->LoadFont("Roboto-Regular", ROBOTO_FN);
  
  const IRECT bounds = pGraphics->GetBounds().GetPadded(-20);
  
  pGraphics->AttachControl(new IVKnobControl(
    bounds.GetGridCell(0, 3, 2).GetCentredInside(90), 
    kThreshold, "Threshold"));
  
  pGraphics->AttachControl(new IVKnobControl(
    bounds.GetGridCell(1, 3, 2).GetCentredInside(90), 
    kRatio, "Ratio"));
  
  pGraphics->AttachControl(new IVKnobControl(
    bounds.GetGridCell(2, 3, 2).GetCentredInside(90), 
    kMakeupGain, "Makeup"));
  
  pGraphics->AttachControl(new IVSliderControl(
    bounds.GetGridCell(0, 1, 2).GetMidVPadded(40), 
    kAttack, "Attack"));
  
  pGraphics->AttachControl(new IVSliderControl(
    bounds.GetGridCell(1, 1, 2).GetMidVPadded(40), 
    kRelease, "Release"));
};

Parameter Types

iPlug2 provides initialization methods for common parameter types:
// Generic double (0-100%)
GetParam(kGain)->InitDouble("Gain", 0., 0., 100., 0.01, "%");

// Frequency (20 Hz - 20 kHz, logarithmic)
GetParam(kFreq)->InitFrequency("Frequency", 1000., 20., 20000.);

// Gain in dB (-60 to +12 dB)
GetParam(kGain)->InitGain("Gain", 0., -60., 12.);

// Percentage (0-100%)
GetParam(kMix)->InitPercentage("Mix", 100.);

Real-Time Safety

ProcessBlock must be real-time safe. Avoid:
  • Memory allocation/deallocation (new, delete, malloc, free)
  • Locks and mutexes
  • File I/O
  • System calls
  • Logging/printing (except debug builds)

Safe Practices

void MyEffect::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
  // ✅ Good: Read parameter values
  const double gain = GetParam(kGain)->Value();
  
  // ✅ Good: Use pre-allocated buffers
  for (int s = 0; s < nFrames; s++) {
    mBuffer[s] = inputs[0][s];
  }
  
  // ❌ Bad: Allocate memory
  // std::vector<double> buffer(nFrames);
  
  // ❌ Bad: Use locks
  // std::lock_guard<std::mutex> lock(mMutex);
  
  // ❌ Bad: Log output
  // printf("Processing %d frames\n", nFrames);
}
Use OnReset() to allocate buffers and initialize DSP state when sample rate changes.

Channel Configuration

Define supported channel layouts in config.h:
// Mono or Stereo
#define PLUG_CHANNEL_IO "1-1 2-2"

// Stereo to Stereo only
#define PLUG_CHANNEL_IO "2-2"

// Mono to Stereo (upmix)
#define PLUG_CHANNEL_IO "1-2"

// Surround (5.1)
#define PLUG_CHANNEL_IO "6-6"

// Multiple configurations
#define PLUG_CHANNEL_IO "1-1 2-2 4-4"

Advanced Features

State Management

bool SerializeState(IByteChunk& chunk) override;
int UnserializeState(const IByteChunk& chunk, int pos) override;
Save/load custom plugin state beyond parameters

Sample Rate Changes

void OnReset() override {
  // Reinitialize DSP for new sample rate
  mFilter.SetSampleRate(GetSampleRate());
}
Handle sample rate and buffer size changes

Latency Reporting

SetLatency(mDelayLine.GetLatencySamples());
Report processing latency to host

Parameter Changes

void OnParamChange(int paramIdx) override {
  // React to parameter changes from DSP thread
}
Respond to automation and user input

Example: Simple Delay

class SimpleDelay : public Plugin
{
public:
  SimpleDelay(const InstanceInfo& info)
  : Plugin(info, MakeConfig(kNumParams, kNumPresets))
  {
    GetParam(kDelayTime)->InitSeconds("Delay Time", 0.5, 0.001, 2.0);
    GetParam(kFeedback)->InitPercentage("Feedback", 50.);
    GetParam(kMix)->InitPercentage("Mix", 50.);
  }
  
  void OnReset() override {
    const int maxDelaySamples = static_cast<int>(GetSampleRate() * 2.0);
    mDelayLine.Resize(maxDelaySamples);
  }
  
  void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override {
    const double delayTime = GetParam(kDelayTime)->Value();
    const double feedback = GetParam(kFeedback)->Value() / 100.;
    const double mix = GetParam(kMix)->Value() / 100.;
    
    const int delaySamples = static_cast<int>(delayTime * GetSampleRate());
    
    for (int s = 0; s < nFrames; s++) {
      const double input = inputs[0][s];
      const double delayed = mDelayLine.Read(delaySamples);
      const double feedbackSample = input + delayed * feedback;
      
      mDelayLine.Write(feedbackSample);
      outputs[0][s] = input * (1. - mix) + delayed * mix;
    }
  }
  
private:
  WDL_TypedBuf<sample> mDelayLine;
  int mWritePos = 0;
};

Next Steps

Parameters

Deep dive into parameter types and shapes

DSP Utilities

Built-in DSP helpers and utilities

IGraphics

Build custom user interfaces

Testing

Validate your plugin in different hosts