GUI for VST plugin on pure C

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Hello all!
I'm writing a plugin to pure C.
I have a problem with the translation of some code with C++ for GUI.

Who can set an example to create a code for the plug-in window?
I know how to write code for Windows standalone programm, only plug I Do not know how and where to call a function createwindow ()

Here my example code in C

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <windows.h>

#include "aeffect.h"
#include "aeffectx.h"

__declspec(dllexport) AEffect* VSTPluginMain(audioMasterCallback audioMaster);

typedef struct ERect ERect;
typedef struct VstEvents VstEvents;
typedef struct VstMidiEvent VstMidiEvent;
typedef struct VstParameterProperties VstParameterProperties;

const int channels = 2;
const int NumParams = 2;
float sampleRate;
int blockSize;

float param_volume = 0.5f;
float mode_value;

float sample(float in)
{
return in * (param_volume * 4.0f);
}

void int2string(VstInt32 value, char *string) { sprintf(string, "%d", value); }
void float2string(float value, char *string) { sprintf(string, "%.2f", value); }
VstInt32 float2int(float number) { return (VstInt32)(1000.0f * number); }

void setSampleRate(float sampleRate) { sampleRate = sampleRate; }
void setBlockSize(VstInt32 blockSize) { blockSize = blockSize; }

VstInt32 processEvents(VstEvents* ev) {
for (int i = 0; i < ev->numEvents; i++) {
VstEvent* event = ev->events[i];
if (event->type == kVstMidiType) {
VstMidiEvent* midiEvent = (VstMidiEvent*) event;
VstInt8 status = midiEvent->midiData[0];
VstInt8 data1 = midiEvent->midiData[0];
VstInt8 data2 = midiEvent->midiData[0];
printf("MIDI: %02x %02x %02x\n", status, data1, data2); } }
return 0; }

enum ParamNames {
parameter1,
parameter2 };

bool getVendorString(char* ptr) {
strncpy((char*)ptr, "Alex Longard", kVstMaxVendorStrLen);
return true; }

void getParameterName(VstInt32 index, char* text) {
switch (index) {
case parameter1:
strncpy((char*)text, "Volume", kVstMaxParamStrLen);
break;
case parameter2:
strncpy((char*)text, "Mode", kVstMaxParamStrLen);
break; } }

void getParameterLabel(VstInt32 index, char *label) {
switch (index) {
case parameter1:
float2string(param_volume, label);
break;
case parameter2: switch (float2int(mode_value)) {
case 0:
strncpy((char*)label, "Left", kVstMaxParamStrLen);
break;
case 1:
strncpy((char*)label, "Center", kVstMaxParamStrLen);
break;
case 2:
strncpy((char*)label, "Right", kVstMaxParamStrLen);
break;
default:
strncpy((char*)label, "Empty", kVstMaxParamStrLen);
break; } } }

void getParameterDisplay (VstInt32 index, char* text) {
*text = 0; }

bool getParameterProperties(VstInt32 index, VstParameterProperties* p) {
return false; }

VstIntPtr Dispatcher(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
VstIntPtr result = 0;
switch (opcode)
{
case effSetSampleRate:
setSampleRate(opt);
break;
case effSetBlockSize:
setBlockSize((VstInt32)value);
break;
case effProcessEvents:
result = processEvents((VstEvents*)ptr);
break;
case effGetPlugCategory:
result = kPlugCategEffect | kPlugCategAnalysis;
break;
case effGetVendorString:
result = getVendorString(ptr);
break;
case effGetEffectName:
strncpy((char*)ptr, "VST test plugin", kVstMaxEffectNameLen);
break;
case effGetProductString:
strncpy((char*)ptr, "magic plugin", kVstMaxEffectNameLen);
break;
case effGetParamName:
getParameterName(index, (char*)ptr);
break;
case effGetParamDisplay:
getParameterDisplay(index, (char*)ptr);
break;
case effGetParamLabel:
getParameterLabel(index, (char*)ptr);
break;
case effGetParameterProperties:
result = getParameterProperties(index, (VstParameterProperties*)ptr);
break;
case effClose:
free(effect);
free(ptr);
break;
default:
break;
}
return result;
}

void process32(AEffect* effect, float** inputs, float** outputs, VstInt32 sampleframes)
{
float* ileft = inputs[0]; float* iright = inputs[1];
float* oleft = outputs[0]; float* oright = outputs[1];

while (--sampleframes >= 0) {
(*oleft) = sample((*ileft));
(*oright) = sample((*iright));

ileft++; iright++;
oleft++; oright++; } }

void setparam(AEffect* effect, VstInt32 index, float value)
{
switch (index) {
case parameter1:
param_volume = value;
break;
case parameter2:
mode_value = value;
break;
} }

float getparam(AEffect* effect, VstInt32 index)
{
float result = 0;
switch (index) {
case parameter1:
result = param_volume;
break;
case parameter2:
result = mode_value;
break; }
return result;
}

AEffect* VSTPluginMain(audioMasterCallback audioMaster)
{
AEffect* effect = (AEffect*) malloc(sizeof(AEffect));
memset(effect, 0, sizeof(AEffect));
effect->magic = kEffectMagic;
effect->dispatcher = &Dispatcher;
effect->setParameter = &setparam;
effect->getParameter = &getparam;
effect->numParams = NumParams;
effect->numInputs = channels;
effect->numOutputs = channels;
effect->flags = effFlagsCanReplacing;
effect->processReplacing = &process32;
effect->uniqueID = 1987;
effect->version = 1;
effect->object = 0;
return effect;
}


Post

Use the effEditGetRect, effEditOpen and effEditClose Opcodes provided in aeffect.h in your dispatcher.

Post

Hello Kirsty Roland
I know these opcodes for dispatcher. I can not get the right to write a function to create a window.

Post

Alex_Longard wrote: Tue Dec 03, 2019 8:32 pm Hello Kirsty Roland
I know these opcodes for dispatcher. I can not get the right to write a function to create a window.
For effEditGetRect, the pointer-parameter is a pointer to a ERect pointer that you should set to the address of the editor rectangle struct. The return value should be 1.

For effEditOpen, the pointer-parameter is a HWND on Windows (and NSView on macOS) that you should use as the parent for your editor (ie. on Windows, pass it as the parent HWND to CreateWindowEx). The return value should be 1.

For effEditClose, just close your window.

Post

Hello Mystran
thanks, i will try.

Post

Hello Mystran,
help me please!
I rewrite my code with window functions, only this not show window in DAW Reaper.
Where my bug?

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <windows.h>

#include "aeffect.h"
#include "aeffectx.h"

__declspec(dllexport) AEffect* VSTPluginMain(audioMasterCallback audioMaster);
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static HINSTANCE hinst = NULL;
HWND hwnd;

typedef struct ERect ERect;
typedef struct VstEvents VstEvents;
typedef struct VstMidiEvent VstMidiEvent;
typedef struct VstParameterProperties VstParameterProperties;

const int channels = 2;
const int NumParams = 2;
float sampleRate;
int blockSize;

float param_volume = 0.5f;
float mode_value;

struct window
{
ERect winpos;
} rect = {0, 0, 400, 400};

float sample(float in)
{
return in * (param_volume * 4.0f);
}

void int2string(VstInt32 value, char *string) { sprintf(string, "%d", value); }
void float2string(float value, char *string) { sprintf(string, "%.2f", value); }
VstInt32 float2int(float number) { return (VstInt32)(1000.0f * number); }

void setSampleRate(float sampleRate) { sampleRate = sampleRate; }
void setBlockSize(VstInt32 blockSize) { blockSize = blockSize; }

VstInt32 processEvents(VstEvents* ev) {
for (int i = 0; i < ev->numEvents; i++) {
VstEvent* event = ev->events[i];
if (event->type == kVstMidiType) {
VstMidiEvent* midiEvent = (VstMidiEvent*) event;
VstInt8 status = midiEvent->midiData[0];
VstInt8 data1 = midiEvent->midiData[0];
VstInt8 data2 = midiEvent->midiData[0];
printf("MIDI: %02x %02x %02x\n", status, data1, data2); } }
return 0; }

enum ParamNames {
parameter1,
parameter2 };

bool getVendorString(char* ptr) {
strncpy((char*)ptr, "Alexey Longard", kVstMaxVendorStrLen);
return true; }

void getParameterName(VstInt32 index, char* text) {
switch (index) {
case parameter1:
strncpy((char*)text, "Volume", kVstMaxParamStrLen);
break;
case parameter2:
strncpy((char*)text, "Mode", kVstMaxParamStrLen);
break; } }

void getParameterLabel(VstInt32 index, char *label) {
switch (index) {
case parameter1:
float2string(param_volume, label);
break;
case parameter2: switch (float2int(mode_value)) {
case 0:
strncpy((char*)label, "Left", kVstMaxParamStrLen);
break;
case 1:
strncpy((char*)label, "Center", kVstMaxParamStrLen);
break;
case 2:
strncpy((char*)label, "Right", kVstMaxParamStrLen);
break;
default:
strncpy((char*)label, "Empty", kVstMaxParamStrLen);
break; } } }

void getParameterDisplay (VstInt32 index, char* text) {
*text = 0; }

bool getParameterProperties(VstInt32 index, VstParameterProperties* p) {
return false; }

VstIntPtr Dispatcher(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
{
VstIntPtr result = 0;
switch (opcode)
{
case effSetSampleRate:
setSampleRate(opt);
break;
case effSetBlockSize:
setBlockSize((VstInt32)value);
break;
case effProcessEvents:
result = processEvents((VstEvents*)ptr);
break;
case effGetPlugCategory:
result = kPlugCategEffect | kPlugCategAnalysis;
break;
case effGetVendorString:
result = getVendorString(ptr);
break;
case effGetEffectName:
strncpy((char*)ptr, "VST test plugin", kVstMaxEffectNameLen);
break;
case effGetProductString:
strncpy((char*)ptr, "magic plugin", kVstMaxEffectNameLen);
break;
case effGetParamName:
getParameterName(index, (char*)ptr);
break;
case effGetParamDisplay:
getParameterDisplay(index, (char*)ptr);
break;
case effGetParamLabel:
getParameterLabel(index, (char*)ptr);
break;
case effGetParameterProperties:
result = getParameterProperties(index, (VstParameterProperties*)ptr);
break;
case effEditGetRect:
*(ERect**)ptr = &rect;
break;
case effEditOpen:
{
MSG msg;
WNDCLASS wc;
wc.style = CS_GLOBALCLASS;
wc.lpfnWndProc = WndProc;
wc.hInstance = hinst;
wc.lpszClassName = "Test_plugin";
if (!RegisterClass(&wc)) {
MessageBox(NULL, "Cannot register class", "Error", MB_OK);
return 0; }
if (!(hwnd = CreateWindow(wc.lpszClassName, "Magic plugin", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, rect.winpos.right, rect.winpos.bottom, (HWND)ptr, 0, wc.hInstance, NULL))) {
return 0; }
}
break;
case effEditClose:
SendMessage(hwnd, WM_CLOSE, 0, 0);
break;
case effClose:
free(effect);
free(ptr);
break;
default:
break;
}
return result;
}

void process32(AEffect* effect, float** inputs, float** outputs, VstInt32 sampleframes)
{
float* ileft = inputs[0]; float* iright = inputs[1];
float* oleft = outputs[0]; float* oright = outputs[1];

while (--sampleframes >= 0) {
(*oleft) = sample((*ileft));
(*oright) = sample((*iright));

ileft++; iright++;
oleft++; oright++; } }

void setparam(AEffect* effect, VstInt32 index, float value)
{
switch (index) {
case parameter1:
param_volume = value;
break;
case parameter2:
mode_value = value;
break;
} }

float getparam(AEffect* effect, VstInt32 index)
{
float result = 0;
switch (index) {
case parameter1:
result = param_volume;
break;
case parameter2:
result = mode_value;
break; }
return result;
}

AEffect* VSTPluginMain(audioMasterCallback audioMaster)
{
AEffect* effect = (AEffect*) malloc(sizeof(AEffect));
memset(effect, 0, sizeof(AEffect));
effect->magic = kEffectMagic;
effect->dispatcher = &Dispatcher;
effect->setParameter = &setparam;
effect->getParameter = &getparam;
effect->numParams = NumParams;
effect->numInputs = channels;
effect->numOutputs = channels;
effect->flags = effFlagsCanReplacing;
effect->processReplacing = &process32;
effect->uniqueID = 1987;
effect->version = 1;
effect->object = 0;
return effect;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// AudioEffect* effect = (AudioEffect*)GetWindowLong(hwnd, GWLP_USERDATA);

HDC hDC;
PAINTSTRUCT ps;
ERect rect;

switch (msg)
{
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);

GetClientRect(hWnd, &rect);
DrawText(hDC, "Hello, World!", -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER );

EndPaint(hWnd, &ps);
return 0;

case WM_CLOSE:
DestroyWindow(hWnd);
return 0;

    case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}

Post

I'm sorry, but I refuse to read source code without at some sort of basic formatting (eg. indenting and line-breaks roughly following some common convention).

Post

O'k, thanks.

Post

I believe it does not work because flags does not contain effFlagsHasEditor flag. I didn’t examine the whole code though.

Post

Hello Vokbuz,
my very big thanks!
Sorry for bad formating code, my codeeditor compress free space in text.

Post

Yes it's very hard to read without proper formatting.
Does this actually compile safely?
After scrolling through your code the following points might help.
I'm assuming it's going to be a synth plugin as you reference processEvents() for midi so...

1. Don't forget the effFlagsIsSynth.

2. Why effect->object = 0; Surely this should be a new AudioEffect object :!:

3. If it's going to be a plugin in a DAW you will need to either register the window once
and keep a count of each instance so that it can be safely deleted when all instances are
used up or create a new WNDCLASS name for each lpszClassName instance.
A niffty trick would be to cast the window HWND ptr to a LONG_PTR and append it to a
WinClassName with your chosen plugin name.
Otherwise the DAW will be asking windows to access the same HWND for each instance
and will throw lots of nasty stuff and proberbly crash your computer.

4.

Code: Select all

#include "aeffect.h"
#include "aeffectx.h"
Why do this? aeffect.h is included in aeffectx.h
Why not just include audioeffectx.h instead

5. Why use 'pure c'. The whole VST specification is based on c++ and will expect the same in a plugin.

I would recomend you read more of mystran's and other devs posts on this subject, they know what
they are talking about and have helped me in the past.

Kirsty :)

edit: ... and what's with those 4 typedef's :!:

Post

kirsty roland wrote: Fri Dec 06, 2019 1:51 pm 5. Why use 'pure c'. The whole VST specification is based on c++ and will expect the same in a plugin.
No, it's not. VST3 is C++ based API. VST2 (that is used by the OP) is C API. C++ wrapper from VST2 SDK is not a part of API or specification.

Post

kirsty roland wrote: Fri Dec 06, 2019 1:51 pm 5. Why use 'pure c'. The whole VST specification is based on c++ and will expect the same in a plugin.
The API itself has no reliance on C++ whatsoever and even if you're writing in C++ the value of AudioEffect/AudioEffectX can be quite questionable (for the most part it's just bloat, especially if you're translating to some internal format anyway), even though it's certainly nicer to work with compared to the raw dispatcher.

You can actually also put your state at the end of the AEffect struct (eg. allocate a larger struct where AEffect is the first member; this is how you often do "inheritance" in C code) if you want to skip a level of indirection. The host doesn't care as long as a valid AEffect is found at the pointer address.

Finally, you can receive MIDI in an effect plugin just fine. Various hosts have various weird limitations with respect to synths vs. effects, but technically there isn't really anything that one could do while the other couldn't.

Post

Ok, fine, I'll try to help.
mystran wrote: Wed Dec 04, 2019 9:25 am For effEditGetRect, the pointer-parameter is a pointer to a ERect pointer that you should set to the address of the editor rectangle struct. The return value should be 1.

For effEditOpen, the pointer-parameter is a HWND on Windows (and NSView on macOS) that you should use as the parent for your editor (ie. on Windows, pass it as the parent HWND to CreateWindowEx). The return value should be 1.

For effEditClose, just close your window.

Post

kirsty roland
My code on pure C, not C++ :)
For C code in plugin not use audioeffect->object.

I read some codes in forum and final my plugin GUI skeleton.

My big thanks to all for your answers!

If who interesting, there my code and compilet plugins for Windows x86 | x64:
https://www.dropbox.com/sh/6o2h7ddgvba2 ... MPIOa?dl=0

Post Reply

Return to “DSP and Plugin Development”