Plugins


The SynthEdit Music Plugin Standard (SEM for short) is an advanced API for MIDI and Audio plugins.

SEM Version 3 is a clean modern API designed for power and simplicity.  SEM is easy to use yet more powerful than other plugin standards.  SEM V3 is supported in SynthEdit Version 1.1. 

Download SynthEdit 1.1 Currently in beta testing.

Download SEM V3 SDK



SDK Version 3 Documentation

Getting a Compiler

3 Compilers have been tested with the SDK:

Microsoft Visual C++

http://msdn.microsoft.com/vstudio/

The SDK was written using this compiler.
The Visual C++ Express version is free, but requires you to download Windows Platform SDK yourself.
   Also cheap for Students is the Academic version. Shop around to get the best price.

Gnu C++

Code::Blocks integrated development environment.

This is good, free, modern compiler.

Borland free compiler

This compiler is too old now, it does not work with this SDK.

 

Code generator

To help convert your modules to the new SDK, SE includes a simple code generator.  On your existing module... Right-click->Build Code Skeleton.  This creates the template code for your module in the folder "C:\SE\My Projects\modules_source" - you need to create that folder first.

  The template doesn't do any processing, it's just a non-functioning module with the pins and module-name etc setup for you, to save you typing the boring bits.  NOTE - SE SDK3 does not yet support auto-duplicating pins, and does support GUI DT_ENUM pins.  Avoid any modules with those for the time being.

Types of Windows

SynthEdit supports two types of windows.

1 - Standard Microsoft Window (Native)


These are the plain regular windows as used in most applications.  They are always rectangular and always obscure any window 'behind' them.

Advantages

Disadvantages
2 - SynthEdit Composited Windows

Created to support non-rectangular shapes, these are still actually rectangular but support transparent pixels. Here the red circle is partially transparent.

Advantages:

Disadvantages

Specify which type by deriving your from the appropriate base class. In your graphics class header file, and also in your XML file...

For a native window...

MyPluginGui.h

class MyGui : public SeGuiWindowsGfxBase

{

MyPlugin.xml

<Plugin id="SynthEdit Scope3" graphicsApi="HWND">


 
For a composited window...

class MyGui : public SeGuiCompositedGfxBase

{

 

<Plugin id="SynthEdit Scope3" graphicsApi="Composited">


 

Customizing the popup context menu

You can customize SynthEdit's right-click context menu.

In your .h file...

    virtual int32_t MP_STDCALL onCreateContextMenu();

    virtual int32_t MP_STDCALL onContextMenu( int32_t selection );

In your .cpp file...

// Add custom items to right-click menu.

int32_t MyGui::onCreateContextMenu()

{

    getHost()->addContextMenuItem( L"Cat", 0, 0 );

    getHost()->addContextMenuItem( L"Dog", 1, 0 );

    return gmpi::MP_OK;

}

 

// act on user slelecting right-click item.

int32_t MyGui::onContextMenu( int32_t selection )

{

    switch( selection )

    {

    case 0:

        // 'Cat' selected

        break;

    case 1:

        // 'Dog' selected

        break;

    };

 

    return gmpi::MP_OK;

}

Right-click handler

To override SynthEdit's right-click handler.  NOTE: If possible please avoid doing this as it disables SynthEdit's pop up menu system.

In your .h file...

    // This handles the Windows message loop.

    // Add here to override right-click behaviour.

    virtual LRESULT MsgProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );

    int32_t onRButtonDown( UINT flags, POINT point );

    int32_t onRButtonUp( UINT flags, POINT point );

In your .cpp file...

// Test overriding right-click handling.

// WARNING: This will disable SynthEdit's right-click menu.  If possible please avoid taking

// right-click for your own use.

LRESULT MyGui::MsgProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )

{

    switch (message)

    {

        case (WM_RBUTTONDOWN):

            {

                POINT p;

                p.x = MAKEPOINTS(lParam).x;

                p.y = MAKEPOINTS(lParam).y;

 

                onRButtonDown( (UINT) wParam, p );

            }

            return 1;

            break;

        case (WM_RBUTTONUP):

            {

                POINT p;

                p.x = MAKEPOINTS(lParam).x;

                p.y = MAKEPOINTS(lParam).y;

 

                onRButtonUp( (UINT) wParam, p );

            }

            return 1;

            break;

        default:

            return SeGuiWindowsGfxBase::MsgProc( hwnd, message, wParam, lParam );

            break;

    }

}

 

int32_t MyGui::onRButtonDown( UINT flags, POINT point )

{

    return gmpi::MP_OK;

}

 

int32_t MyGui::onRButtonUp( UINT flags, POINT point )

{

    return gmpi::MP_OK;

}

 

SEM XML

The old SEM SDK described modules and pins in code...

// describe the pins (plugs)

bool Module::getPinProperties (long index, SEPinProperties* properties)

{

    switch( index )                                // !!TODO!! list your in / out plugs

    {

        // typical input plug (inputs are listed first)

    case 0:

        properties->name                = "Input";

        properties->variable_address    = &input1_buffer;

        properties->direction           = DR_IN;

        properties->datatype            = DT_FSAMPLE;

        properties->default_value       = "8";

        break;

SDK3 introduces an easier, compact format based on XML...

    <Audio>

      <Pin id="0" name="Input" direction="in" datatype="float" rate="audio" default="8"/>

This new method results in smaller executable files and is easier to add new features without breaking old code.

XML Plugin Properties

id
name
category
helpUrl
graphicsApi{HWND,composited,none}
polyphonicSource{ true, false }
polyphonicAggregator{ true, false }
cloned{ true, false }
voiceMonitorIgnore{ true, false }
GUI
Parameters
Pins

XML Pin Properties

id
name
datatype {float, int, text, blob, midi, bool, enum (DSP only. not fully supported)}
default
direction {in,out}
rate {audio} - float pins only.
private{ true, false }
autoRename{ true, false }
isFilename{ true, false }
linearInput{ true, false }
ignorePatchChange{ true, false }
autoDuplicate{ true, false }
isMinimised{ true, false }
isPolyphonic{ true, false }
autoConfigureParameter{ true, false }
parameterId
parameterField{ Value, writeable only in SynthEdit environment -ShortName , MenuItems,MenuSelection,RangeMinimum,RangeMaximum,EnumList,FileExtension
IgnoreProgramChange,Private,Automation,Automation Sysex,Default,Grab,Normalized }
metadata
notes
hostConnect

Controlling Bank Load and Save

SDK Version 3 is designed to control the host in a very natural way. You put a special pin on your GUI Module class, and specify what you want to connect it to (on the host).
 Think of the host itself being like a SEM with pins exposed for various features...

 For example the keyboard2 module connects to the host's "Voice/Pitch" pin like so...

      <Pin id="0" name="Pitch" direction="in" datatype="float" hostConnect="Voice/Pitch" />

 ..the difference between this and a normal pin is the 'hostConnect' part. When the keyboard2 wants to set a voice's pitch, it transmits the new pitch out that pin. The host receives that signal and acts on it. Another example - loading a fxb bank:

 - Put an DT_INT pin on your GUI Module.
 - specify - hostConnect="PatchCommands".
 - Valid commands are:
       0 null
       1 CopyPatch
       2 LoadPatch
       3 SavePatch
       4 LoadBank
       5 SaveBank

 To initiate a bank load. Set your pin to 4, then back to zero (ready for the next command). SynthEdit will display the Bank-Load dialog box, then load whatever bank the user selects.

Windows 2000 users
To enable runtime support for Visual Studio V8, you may need to download these dlls: MFC80U.DLL MSVCR80.DLL MSVCP80.DLL.  Put them in C:\Program Files\SynthEdit\

DEV C++ Compiler Notes
Install DEV C++, Install gcc V4.1 (or better) from Tools menu. 3.4 don't support templates very well.

File, New Project,  DLL,  Add module files (Gain.cpp, Gain.h)
Project-Project Options - Directories  - Include Directories - Add se_sdk3 folder

Converting SDK Version 2 modules to Version 3

Although Version3 has been re-written from scratch, it's not much different from the older SDK.  Some function names have changed, most only slightly to reflect modern naming conventions.  Some features like sleep-mode are now more integrated into the SDK to save you time.

The easiest way to get your module ported to the new SDK is with the Skeleton Code Generator. The code generator produces a basic template module.  It contains only the basic code to generate a module with the correct pins and some standard methods.  You can then copy over your signal processing code from your old module.

Porting SE-Gain

You should now be able to re-open SynthEdit and insert your new module. (It is not functional yet because it has an empty sub-process method).

Back in Visual Studio you can inspect the new project files.  There are a handful of SDK files, plus the actual module files: Gain.h, Gain.cpp, and Gain.xml.

Open Gain2.cpp, inside is the minimal code for your plugin, including a basic subProcess method.

void Gain2::subProcess( int bufferOffset, int sampleFrames )

{

    // get pointers to in/output buffers.

    float* input    = bufferOffset + pinInput.getBuffer();

    float* input2    = bufferOffset + pinInput2.getBuffer();

    float* output    = bufferOffset + pinOutput.getBuffer();

 

    for( int s = sampleFrames; s > 0; --s )

    {

        // TODO: Signal processing goes here.

 

        // Increment buffer pointers.

        ++input;

        ++input2;

        ++output;

    }

}

Note the name has changed from 'sub_process' to 'subProcess', this is an example of the updated coding conventions.  Also the buffer pointers are retrieved differently.  Otherwise the method is the same as before.

The TODO is a reminder to copy your signal processing code from your old module...

    for( int s = sampleFrames; s > 0; --s )

    {

        float in1 = *input;    // get the sample 'POINTED TO' by in1

        float in2 = *input2;

 

        // do the actual processing (multiplying the two input samples together)

        float result = in1 * in2;

 

        *output = result;    // store the result in the output buffer

 

        // Increment buffer pointers.

        ++input;

        ++input2;

        ++output;

    }

What happened to getModuleProperties() and getPinProperties()?

In the old SDK you had 2 methods to provide your module name and pin names and properties...

// describe your module

bool Module::getModuleProperties (SEModuleProperties* properties)

{

    // describe the plugin, this is the name the end-user will see.

    properties->name = "Gain Example";                                // !!TODO!!

 

    // return a unique string 32 characters max

    properties->id = "Synthedit Gain Example";                        // !!TODO!!

 

    properties->about = "by Jeff M (MS)" ;

 

    return true;

}

 

// describe the pins (plugs)

bool Module::getPinProperties (long index, SEPinProperties* properties)

{

    switch( index )                                // !!TODO!! list your in / out plugs

    {

        // typical input plug (inputs are listed first)

    case 0:

        properties->name                = "Input";

        properties->variable_address    = &input1_buffer;

        properties->direction            = DR_IN;

        properties->datatype            = DT_FSAMPLE;

        properties->default_value        = "0";

        break;

    case 1:

        properties->name                = "Input";

        properties->variable_address    = &input2_buffer;

        properties->direction            = DR_IN;

        properties->datatype            = DT_FSAMPLE;

        properties->default_value        = "5";

        break;

        // typical output plug

    case 2:

        properties->name                = "Output";

        properties->variable_address    = &output1_buffer;

        properties->direction            = DR_OUT;

        properties->datatype            = DT_FSAMPLE;

        break;

    default:

        return false; // host will ask for plugs 0,1,2,3 etc. return false to signal when done

    };

 

    return true;

}

 

These methods are gone.  In their place is a simple text file - Gain2.xml.

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

 

<PluginList>

  <Plugin id="My Gain2" name="Gain2" category="MyModules" graphicsApi="HWND" helpUrl="Gain2.htm">

    <Audio>

      <Pin id="0" name="Input" direction="in" datatype="float" rate="audio" default="0"/>

      <Pin id="1" name="Input 2" direction="in" datatype="float" rate="audio" default="0.5"/>

      <Pin id="2" name="Output" direction="out" datatype="float" rate="audio"/>

    </Audio>

  </Plugin>

</PluginList>

 

 

 As you can see this is less typing and easy to edit.  It also results in faster compilation, less code generated and smaller SEM files.

OnPlugStateChange

This method was called anytime a pin was updated.

void Module::OnPlugStateChange(SEPin *pin)

{

    state_type in_stat1 = getPin(PN_INPUT1)->getStatus();

 SDK3 replaces OnPlugStateChange() with onSetPins().

void Gain2::onSetPins(void)

{

    // Check which pins are updated.

    if( pinInput.isStreaming() )

    {

    }

 Rather than check inputs for a status of ST_RUN, you now use the isStreaming() method.

Rather than check non-audio pins for ST_ONE_OFF, you now use the isUpdated() method.

void Gain2::onSetPins(void)

{

    // Check which pins are updated.

    if( pinInput.isUpdated() && pinInput2.isUpdated() )

    {

        // They both changed at the same time.

    }

The functionality is the same as before, just a more readable syntax.

The only substantial difference is - When two pins are updated at the same time, onSetPins() is called only once (with both pins flagged).  It was very difficult before to detect simultaneous updates on two pins, now it's easy.

Likewise, when the audio engine starts SynthEdit flags all your input pins as 'updated' and calls onSetPins() just the once.  This results in less function call overhead and faster SEMs.

SET_PROCESS_FUNC

This has been updated. Search-and-Replace your old code with the new look.  Don't forget the '&' (address-of) symbol.

Old.

    SET_PROCESS_FUNC(Module::sub_process);

New.

    SET_PROCESS(&Gain2::subProcess);

TransmitStatusChange

TransmitStatusChange() has been replaced with setStreaming(). Less typing, more readable...

Old

    getPin(PN_OUTPUT1)->TransmitStatusChange( SampleClock(), ST_RUN );

New

    pinOutput.setStreaming(true);

Old

    getPin(PN_OUTPUT1)->TransmitStatusChange( SampleClock(), ST_STATIC );

New

    pinOutput.setStreaming(false);

Sleep Mode

Previously, after checking your input pins were silent, you implemented sleep mode with a special process method to filled the output buffers with silent samples. Once that was done you asked the host to put the module to sleep...

// every module has an output buffer of approx 100 samples.

// before deactivating module, need to fill that buffer with silence.

// "static_count" is counting out those 100 samples.

void Module::sub_process_static(long buffer_offset, long sampleFrames )

{

    sub_process(buffer_offset, sampleFrames);

    static_count = static_count - sampleFrames;

    if( static_count <= 0 )

    {

        CallHost(seaudioMasterSleepMode);

    }

}

Although this is still necessary.  The new SDK does it for you. Your module no longer needs any special code. You don't need to port that code to SDK V3.

If you don't want automatic sleep mode, or need to handle it manually you can disable automatic sleep mode...

    setSleep(false);

Summay

That was the quick guide to porting to SDK3.  Any further questions please post to the SynthEdit SDK group at yahoo.

SEM Supported Features

 

SEM is an implementation of GMPI (Generalized Music Plugin Interface) with a few extensions for SynthEdit.  Some less crucial GMPI features are not supported yet.

GMPI requirements not supported