Basics

Processing

// Process audio.
void Gain::subProcess( int bufferOffset, int sampleFrames )
{
// assign pointers to your in/output buffers. Each buffer is an array of float samples.
float* in1 = bufferOffset + pinInput1.getBuffer();
float* in2 = bufferOffset + pinInput2.getBuffer();
float* out1 = bufferOffset + pinOutput1.getBuffer(); for( int s = sampleFrames; s > 0; --s ) // sampleFrames = how many samples to process (can vary). repeat (loop) that many times.
{
float input1 = *in1; // get the sample 'POINTED TO' by in1. float input2 = *in2; // Multiplying the two input's samples together. float result = input1 * input2; // store the result in the output buffer. *out1 = result; // increment the pointers (move to next sample in buffers). ++in1; ++in2; ++out1; } }

Like VST, your module processes 'blocks' of audio samples. These samples arrive in an array/buffer. You process around 100 samples during each call to subProcess(). In this example the audio is retrieved via a pointer ( *in1 ), then after each individual sample is processed, we advance the pointer to the next sample (++in1).

Advanced note: If your code needs to change the floating point flags - set the flags at the start of subProcess(), then restore the original flags at the end.

SEM XML

Every module needs some meta-data to describe the module, it's parameters, and it's input and output pins.

SDK3 introduces an easy, compact format based on XML. This example is called "Gain3", it has 2 inputs and one output.

<?xml version="1.0" encoding="utf-8" ?>

<PluginList>
<Plugin id="SynthEdit Gain example V3" name="Gain3" category="SDK Examples" helpUrl="gain.htm">
<Audio>
<Pin id="0" name="Input1" direction="in" datatype="float" rate="audio" default="0.8" />
<Pin id="1" name="Input2" direction="in" datatype="float" rate="audio" default="0.8" />
<Pin id="2" name="Output" direction="out" datatype="float" rate="audio" />
</Audio>
</Plugin>
</PluginList>

Module pins

Pins are the inputs and outputs of your module. Audio pins are the most common type. SynthEdit also supports MIDI, text, and numeric pins. You declare your pins in two places. Firstly in the XML file, secondly in code in your header file. Here you see the Audio Input and Output pins being declared.

#include "../se_sdk3/mp_sdk_audio.h"

class Gain: public MpBase
{
public:
	Gain(IMpUnknown* host);
	void subProcess(int bufferOffset, int sampleFrames);
	virtual void onSetPins(void);
private:
	AudioInPin pinInput1;
	AudioInPin pinInput2;
	AudioOutPin pinOutput1;
};

Initializing you module

When your module first loads, SynthEdit calls it's constructor. This is the place to register your pins. What this does is associate the pins in your class header file with their descriptions in the XML file.

Gain::Gain( IMpUnknown* host ) : MpBase( host )
{
	// Register pins.
	initializePin( 0, pinInput1 );
	initializePin( 1, pinInput2 );
	initializePin( 2, pinOutput1 );
}

Opening you module

After creating you module and calling it's constructor, SynthEdit opens you module. This signals that audio proccesing is about to begin. This is the time to perform any heavy-duty initializing, like loading samples or allocating memory.

You also specify your subProcess() function. This is whichever member function will proccess audio.

int32_t Gain::open()
{
	MpBase::open();
	// choose which function is used to process audio
	SET_PROCESS(&Gain::subProcess);
	return gmpi::MP_OK;
}

Module Calling sequence

When SynthEdit starts, it scans your Module folder for .sem modules. Each is loaded and it's details queried..

  1. Module is loaded.
  2. Constructor is called - Module::Module()
  3. Module is destroyed.
  4. Destructor is called - Module::~Module()

So when SE first loads, it creates your plugin, to query it's abilities. But it doesn’t use it. So best not to do anything intensive in the constructor - Module::Module()

Later, user pushes "PLAY"

  1. Module is loaded.
  2. Constructor is called: Module::Module()
  3. open() called just once, good place to initialize data, load samples etc.
  4. Soundcard online, audio running. Everything now is time-critical.
  5. onGraphStart() is called.
  6. subProcess() called repeatedly.
  7. onSetPins() called as needed in between sub_process() calls.
  8. User pushes "STOP".
  9. Module is destroyed calling destructor - Module::~Module()

The destructor is the place to release any resources allocated in open() or in the constructor - Module::Module(). Remember also that several instances of your plug-in may be used at once, therefore your module might allocate memory several times.
Once your module is running, SynthEdit will never change the Sample Rate or Block Size. To do so, SynthEdit destroys, then re-creates your module afresh.