Let's make a CLAP audio plugin. This tutorial is geared towards people who already have experience writing audio plugins, so I'll be moving pretty fast.

First, grab the SDK. It's header only; feel free to integrate it into your project in whatever way is most convenient for you. The following commands simply copy the headers directly into our project directory.

git clone https://github.com/free-audio/clap.git clap2
mv clap2/include/clap clap
rm -rf clap2

We're going to put our code in plugin.cpp. Here's the compile command for Linux.

g++ -shared -g -Wall -Wextra -Wno-unused-parameter -o ~/.clap/HelloCLAP.clap plugin.cpp

macOS is similar.

g++ -shared -g -Wall -Wextra --std=gnu++17 -Wno-unused-parameter -o ~/Library/Audio/Plug-Ins/CLAP/HelloCLAP.clap plugin.cpp

And for Windows, you can run this from a Visual Studio command prompt.

cl /DLL /Zi /std:c++20 plugin.cpp

Let's start by including some standard headers and the CLAP SDK header, and adding a little dynamic array implementation.

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>

#include "clap/clap.h"

template <class T>
struct Array {
	T *array;
	size_t length, allocated;

	void Insert(T newItem, uintptr_t index) {
		if (length + 1 > allocated) {
			allocated *= 2;
			if (length + 1 > allocated) allocated = length + 1;
			array = (T *) realloc(array, allocated * sizeof(T));
		}

		length++;
		memmove(array + index + 1, array + index, (length - index - 1) * sizeof(T));
		array[index] = newItem;
	}

	void Delete(uintptr_t index) { 
		memmove(array + index, array + index + 1, (length - index - 1) * sizeof(T)); 
		length--;
	}

	void Add(T item) { Insert(item, length); }
	void Free() { free(array); array = nullptr; length = allocated = 0; }
	int Length() { return length; }
	T &operator[](uintptr_t index) { assert(index < length); return array[index]; }
};

With that out of the way, we can turn our attention to the structure of a CLAP plugin. We need to export from our shared library a global variable clap_plugin_entry_t clap_entry. This will contain information about the version of the CLAP SDK we're using, and point to information about the shared library's plugin factory. The plugin factory, which we'll be describing in a variable clap_plugin_factory_t pluginFactory, is responsible for listing the plugins inside our shared library. We're only going to have one. The plugin factory will point to the descriptor of our plugin, clap_plugin_descriptor_t pluginDescriptor, and the main plugin class, clap_plugin_t pluginClass. The plugin descriptor contains the basic attributes of the plugin which the host can quickly grab while scanning. The plugin class contains pointers to the functions that actually implement the plugin. There are several of these, the most important being process, which is called by the host to let the plugin process input events and audio data; and the other important function is get_extension, which is where the plugin can advertise support of CLAP extensions.

A diagram repeating the information from the above paragraph.

We begin with the definition of clap_entry. I'm going to be using C++ lambdas to implement the functions inline in the structure definition to help readability, but you could easily have these as standard functions.

extern "C" const clap_plugin_entry_t clap_entry = {
	.clap_version = CLAP_VERSION_INIT,

	.init = [] (const char *path) -> bool { 
		return true; 
	},

	.deinit = [] () {},

	.get_factory = [] (const char *factoryID) -> const void * {
        // Return a pointer to our pluginFactory definition.
		return strcmp(factoryID, CLAP_PLUGIN_FACTORY_ID) ? nullptr : &pluginFactory;
	},
};

Next, the plugin factory.

static const clap_plugin_factory_t pluginFactory = {
	.get_plugin_count = [] (const clap_plugin_factory *factory) -> uint32_t { 
		return 1; 
	},

	.get_plugin_descriptor = [] (const clap_plugin_factory *factory, uint32_t index) -> const clap_plugin_descriptor_t * { 
		// Return a pointer to our pluginDescriptor definition.
		return index == 0 ? &pluginDescriptor : nullptr; 
	},

	.create_plugin = [] (const clap_plugin_factory *factory, const clap_host_t *host, const char *pluginID) -> const clap_plugin_t * {
		if (!clap_version_is_compatible(host->clap_version) || strcmp(pluginID, pluginDescriptor.id)) {
			return nullptr;
		}

		// Allocate the plugin structure, and fill in the plugin information from the pluginClass variable.
		MyPlugin *plugin = (MyPlugin *) calloc(1, sizeof(MyPlugin));
		plugin->host = host;
		plugin->plugin = pluginClass;
		plugin->plugin.plugin_data = plugin;
		return &plugin->plugin;
	},
};

Oh, and before we forget, let's actually define the MyPlugin structure! We also define the structure we'll use to store active voices.

struct Voice {
	bool held;
	int32_t noteID;
	int16_t channel, key;
	float phase;
};

struct MyPlugin {
	clap_plugin_t plugin;
	const clap_host_t *host;
	float sampleRate;
	Array<Voice> voices;
};

Next, we'll define the pluginDescriptor. (Then we've got the pluginClass to do.) In the plugin descriptor, we specify an ID for our plugin; this should be unique to the plugin.

static const clap_plugin_descriptor_t pluginDescriptor = {
	.clap_version = CLAP_VERSION_INIT,
	.id = "nakst.HelloCLAP",
	.name = "HelloCLAP",
	.vendor = "nakst",
	.url = "https://nakst.gitlab.io",
	.manual_url = "https://nakst.gitlab.io",
	.support_url = "https://nakst.gitlab.io",
	.version = "1.0.0",
	.description = "The best audio plugin ever.",

	.features = (const char *[]) {
		CLAP_PLUGIN_FEATURE_INSTRUMENT,
		CLAP_PLUGIN_FEATURE_SYNTHESIZER,
		CLAP_PLUGIN_FEATURE_STEREO,
		NULL,
	},
};

Now for the pluginClass. There are lot of functions in here, and we're just going to do the simplest implementation of each at first.

static const clap_plugin_t pluginClass = {
	.desc = &pluginDescriptor,
	.plugin_data = nullptr,

	.init = [] (const clap_plugin *_plugin) -> bool {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
		(void) plugin;
		return true;
	},

	.destroy = [] (const clap_plugin *_plugin) {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
		plugin->voices.Free();
		free(plugin);
	},

	.activate = [] (const clap_plugin *_plugin, double sampleRate, uint32_t minimumFramesCount, uint32_t maximumFramesCount) -> bool {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
		plugin->sampleRate = sampleRate;
		return true;
	},

	.deactivate = [] (const clap_plugin *_plugin) {
	},

	.start_processing = [] (const clap_plugin *_plugin) -> bool {
		return true;
	},

	.stop_processing = [] (const clap_plugin *_plugin) {
	},

	.reset = [] (const clap_plugin *_plugin) {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
		plugin->voices.Free();
	},

	.process = [] (const clap_plugin *_plugin, const clap_process_t *process) -> clap_process_status {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
		// TODO.
		return CLAP_PROCESS_CONTINUE;
	},

	.get_extension = [] (const clap_plugin *plugin, const char *id) -> const void * {
		// TODO.
		return nullptr;
	},

	.on_main_thread = [] (const clap_plugin *_plugin) {
	},
};

Now we need to implement .process and .get_extension properly. The extensions we'll be implementing are the note ports extension and audio ports extensions. The former will let us receive note on and off events from the host. The latter will let us define an audio port to which we can output sample data. In the .get_extension function, we check the extension id given to us by the host and match it against the extensions we implement.

	.get_extension = [] (const clap_plugin *plugin, const char *id) -> const void * {
		if (0 == strcmp(id, CLAP_EXT_NOTE_PORTS )) return &extensionNotePorts;
		if (0 == strcmp(id, CLAP_EXT_AUDIO_PORTS)) return &extensionAudioPorts;
		return nullptr;
	},

The implementation of these extensions is fairly simple. We report that the plugin has one input note port and one output audio port.

static const clap_plugin_note_ports_t extensionNotePorts = {
	.count = [] (const clap_plugin_t *plugin, bool isInput) -> uint32_t {
		return isInput ? 1 : 0;
	},

	.get = [] (const clap_plugin_t *plugin, uint32_t index, bool isInput, clap_note_port_info_t *info) -> bool {
		if (!isInput || index) return false;
		info->id = 0;
		info->supported_dialects = CLAP_NOTE_DIALECT_CLAP; // TODO Also support the MIDI dialect.
		info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
		snprintf(info->name, sizeof(info->name), "%s", "Note Port");
		return true;
	},
};

static const clap_plugin_audio_ports_t extensionAudioPorts = {
	.count = [] (const clap_plugin_t *plugin, bool isInput) -> uint32_t { 
		return isInput ? 0 : 1; 
	},

	.get = [] (const clap_plugin_t *plugin, uint32_t index, bool isInput, clap_audio_port_info_t *info) -> bool {
		if (isInput || index) return false;
		info->id = 0;
		info->channel_count = 2;
		info->flags = CLAP_AUDIO_PORT_IS_MAIN;
		info->port_type = CLAP_PORT_STEREO;
		info->in_place_pair = CLAP_INVALID_ID;
		snprintf(info->name, sizeof(info->name), "%s", "Audio Output");
		return true;
	},
};

Finally, we're ready to implement the .process function of the plugin class. This will be the most complicated. We start by writing two helper functions, one to process a single event sent to the plugin, and another to render a section of audio samples.

We will be processing the note on, off and choke events.

static void PluginProcessEvent(MyPlugin *plugin, const clap_event_header_t *event) {
	if (event->space_id == CLAP_CORE_EVENT_SPACE_ID) {
		if (event->type == CLAP_EVENT_NOTE_ON || event->type == CLAP_EVENT_NOTE_OFF || event->type == CLAP_EVENT_NOTE_CHOKE) {
			const clap_event_note_t *noteEvent = (const clap_event_note_t *) event;

			// Look through our voices array, and if the event matches any of them, it must have been released.
			for (int i = 0; i < plugin->voices.Length(); i++) {
				Voice *voice = &plugin->voices[i];

				if ((noteEvent->key == -1 || voice->key == noteEvent->key)
						&& (noteEvent->note_id == -1 || voice->noteID == noteEvent->note_id)
						&& (noteEvent->channel == -1 || voice->channel == noteEvent->channel)) {
					if (event->type == CLAP_EVENT_NOTE_CHOKE) {
						plugin->voices.Delete(i--); // Stop the voice immediately; don't process the release segment of any ADSR envelopes.
					} else {
						voice->held = false;
					}
				}
			}

			// If this is a note on event, create a new voice and add it to our array.
			if (event->type == CLAP_EVENT_NOTE_ON) {
				Voice voice = { 
					.held = true, 
					.noteID = noteEvent->note_id, 
					.channel = noteEvent->channel, 
					.key = noteEvent->key,
					.phase = 0.0f,
				};

				plugin->voices.Add(voice);
			}
		}
	}
}

To render audio data, we will output a single sine wave for each of the voices.

static void PluginRenderAudio(MyPlugin *plugin, uint32_t start, uint32_t end, float *outputL, float *outputR) {
	for (uint32_t index = start; index < end; index++) {
		float sum = 0.0f;

		for (int i = 0; i < plugin->voices.Length(); i++) {
			Voice *voice = &plugin->voices[i];
			if (!voice->held) continue;
			sum += sinf(voice->phase * 2.0f * 3.14159f) * 0.2f;
			voice->phase += 440.0f * exp2f((voice->key - 57.0f) / 12.0f) / plugin->sampleRate;
			voice->phase -= floorf(voice->phase);
		}

		outputL[index] = sum;
		outputR[index] = sum;
	}
}

Now we actually implement .process. First it will loop over the input events, processing them, as well rendering the necessary audion in between events. Then, for any voices that has completed its lifetime, we will send a note end event to the host.

	.process = [] (const clap_plugin *_plugin, const clap_process_t *process) -> clap_process_status {
		MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;

		assert(process->audio_outputs_count == 1);
		assert(process->audio_inputs_count == 0);

		const uint32_t frameCount = process->frames_count;
		const uint32_t inputEventCount = process->in_events->size(process->in_events);
		uint32_t eventIndex = 0;
		uint32_t nextEventFrame = inputEventCount ? 0 : frameCount;

		for (uint32_t i = 0; i < frameCount; ) {
			while (eventIndex < inputEventCount && nextEventFrame == i) {
				const clap_event_header_t *event = process->in_events->get(process->in_events, eventIndex);

				if (event->time != i) {
					nextEventFrame = event->time;
					break;
				}

				PluginProcessEvent(plugin, event);
				eventIndex++;

				if (eventIndex == inputEventCount) {
					nextEventFrame = frameCount;
					break;
				}
			}

			PluginRenderAudio(plugin, i, nextEventFrame, process->audio_outputs[0].data32[0], process->audio_outputs[0].data32[1]);
			i = nextEventFrame;
		}

		for (int i = 0; i < plugin->voices.Length(); i++) {
			Voice *voice = &plugin->voices[i];

			if (!voice->held) {
				clap_event_note_t event = {};
				event.header.size = sizeof(event);
				event.header.time = 0;
				event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
				event.header.type = CLAP_EVENT_NOTE_END;
				event.header.flags = 0;
				event.key = voice->key;
				event.note_id = voice->noteID;
				event.channel = voice->channel;
				event.port_index = 0;
				process->out_events->try_push(process->out_events, &event.header);

				plugin->voices.Delete(i--);
			}
		}

		return CLAP_PROCESS_CONTINUE;
	},

And that's all there is to it! Run the compile command mentioned earlier, and the plugin will load in Bitwig, and on receiving notes it will play sine waves. Awesome!

clap-tutorial-part-1-plugin.cpp

Part 1: You are here!

Part 2: Parameters.

Part 3: GUI.

Part 4: Gestures.