REAPER supports VST plug-ins (up to version 2.4 as well as version 3, though this document only applies to version 2.x). VST is a standard defined by Steinberg Media Technologies GMBH. To get the VST SDK (which you will need to implement VST plug-ins), you will need to download it from Steinberg, as we cannot distribute it.
It is worthwhile noting that while VST is a standard, it is neither an open standard (because you cannot easily distribute the SDK or things derived from it), nor is it a well defined standard.
This document will describe some REAPER-specific implementation notes for VST, as well as list some REAPER-specific extensions to the VST SDK/API that plug-in developers are encouraged to use to achieve great integration with REAPER. Additionally, we encourage other VST host developers to add support for these extensions, as we believe they are useful.
// (in class definition)
bool m_editViewWillBeNSView;
...
// (in constructor)
m_editViewWillBeNSView=false;
...
// (in dispatch)
case effCanDo:
if (ptr && !strcmp((char *)ptr,"hasCockosViewAsConfig"))
{
m_editViewWillBeNSView=true;
return 0xbeef0000;
}
return 0;
case effEditOpen:
if (m_editViewWillBeNSView)
{
// ptr is NSView *, add a single subview which contains our configuration.
myView = ...;
[(NSView *)ptr addSubview:myView];
}
else
{
// ptr is Carbon window
}
...
An example host implementation:
// (init)
effect->dispatcher(effect,effOpen,0,0,NULL,0.0f);
...
m_wantsCocoaConfig = (effect->dispatcher(effect,effCanDo,0,0,(void*)"hasCockosViewAsConfig",0.0f) & 0xffff0000) == 0xbeef0000;
...
// (displaying configuration window)
if (m_wantsCocoaConfig)
{
NSView *par = ...;
effect->dispatcher(effect,effEditOpen,0,0,par,0.0f);
}
else
{
WindowRef ref = ...;
effect->dispatcher(effect,effEditOpen,0,0,ref,0.0f);
}
Note: the above implementation is similar to REAPER's; a host could also query hasCockosViewAsConfig prior to each effEditOpen, should it be cleaner to implement (avoiding the extra per-VST storage)
case effCanDo:
if (ptr && !strcmp((char *)ptr,"hasCockosExtensions")) return 0xbeef0000;
Extended vendor specific calls the plug-in can implement include:
char buf[256];
buf[0] = '\0';
if (effect->dispatcher(effect,effVendorSpecific,effGetParamDisplay,parm_idx,buf,val)>=0xbeef && *buf)
{
printf("parm %d, value=%f is %s\n",parm_idx,val,buf);
}
Example plug-in-side implementation:
case effVendorSpecific:
if (index == effGetParamDisplay && ptr)
{
if (value>=0 && value<NUM_PARAMS)
{
sprintf(ptr,"%f",opt);
return 0xbeef;
}
}
char buf[256], retbuf[256];
strcpy(buf, "3.14");
strcpy(retbuf, buf);
if (effect->dispatcher(effect,effVendorSpecific,effString2Parameter,parm_idx,retbuf,0.0)>=0xbeef && *retbuf)
{
double normval = atof(retbuf);
printf("parm %d, string=%s is a normalized value of %f\n", parm_idx, buf, normval);
}
Example plug-in-side implementation:
case effVendorSpecific:
if (index == effString2Parameter && ptr)
{
if (value>=0 && value<NUM_PARAMS)
{
float val = atof(ptr);
double normval = (val-minval)/(maxval-minval);
sprintf(ptr, "%f", normval);
return 0xbeef;
}
}
if (effect->dispatcher(effect,effVendorSpecific,kVstParameterUsesIntStep,parm_idx,NULL,NULL)>=0xbeef)
{
printf("parm %d is an enum");
}
Example plug-in-side implementation:
case effVendorSpecific:
if (index == kVstParameterUsesIntStep)
{
if (value == MODE_SWITCH_PARAM || value == FILTER_TYPE_PARAM)
{
return 0xbeef;
}
}
const char *ptr=NULL;
if (effect->dispatcher(effect,effVendorSpecific,effGetEffectName,0x50,&ptr,0.0f)==0xf00d)
{
strcpy(overridden_name, ptr?ptr : "");
}
Example plug-in-side implementation:
case effVendorSpecific:
if (index == effGetEffectName && value == 0x50 && ptr)
{
*(const char **)ptr = "myNameIsNowBogart";
return 0xf00d;
}
break;
double range[2]={0,1};
if (effect->dispatcher(effect, effVendorSpecific, 0xdeadbef0, parm_index, range, 0.0)>=0xbeef)
{
// range[0]..range[1] is the range instead of 0..1
}
Example plug-in-side implementation (in addition to responding to effCanDo/"hasCockosExtensions"):
case effVendorSpecific:
if (index == 0xdeadbef0 && ptr && value>=0 && value<NUM_PARAMS)
{
((double *)ptr)[0] = min_val;
((double *)ptr)[1] = max_val;
return 0xbeef;
}
case audioMasterVendorSpecific:
if (index == 0xdeadbeef && value == audioMasterAutomate && ptr)
{
int adjstartparmidx = ((int*)ptr)[0];
int adjnum = ((int*)ptr)[1]; // adjnum > 0 means adjnum parameters were added,
// adjnum < 0 means adjnum parameters were removed
return 1;
}
Example plug-in-side implementation:
listadj[2] = { adjstartparmidx, adjnum };
audioMasterCallback(audioMasterVendorSpecific, 0xdeadbeef, audioMasterAutomate, listadj, 0.0);
There are some additional functions to enable better integration with the host, the primary interface for accessing these extensions is via the audioMaster callback. Suppose your callback pointer is named "hostcb", then you would get each API with the following code:
void (*someFunction)(); *(long *)&someFunction = hostcb(NULL,0xdeadbeef,0xdeadf00d,0,"someFunction",0.0);If an API is not implemented, hostcb() will return 0, and the resulting function shouldn't be called. Except when noted, all functions can be called from any context in any thread at any time. A list of functions defined:
double (*GetPlayPosition)();GetPlayPosition() returns the current playback position of the project. This is the time the user is hearing (and the transport shows etc). The value is in seconds.
double (*GetPlayPosition2)();GetPlayPosition() returns the current playback decode position of the project. This is the time of the audio block that is being processed by the host. The value is in seconds. This may be behind where your plug-in is processing, due to anticipative processing and/or PDC.
double (*GetCursorPosition)();GetCursorPosition() returns the current edit cursor position (if any), in seconds.
int (*GetPlayState)();GetPlayState() returns an integer value representing the project play state. 1=play, 2=paused, 5=recording, 6=record paused.
void (*SetEditCurPos)(double time, bool moveview, bool seekplay);SetEditCurPos() repositions the edit cursor to "time" (in seconds), optionally moving the view if necessary, and optionally seeking playback (if playing back). This function should ONLY be called from a UI context (i.e. from the editor window, NOT from audio processing etc).
int (*GetSetRepeat)(int parm);GetSetRepeat() is used to change or query the transport "loop/repeat" state. Pass a parameter of -1 to query the repeat state, 0 to clear, 1 to set, and >1 to toggle. The return value is the new value of the repeat state. ONLY use this function to change the repeat state from the UI thread (however you can query safely at any time).
void (*GetProjectPath)(char *buf, int bufsz);GetProjectPath() can be used to query the path that media files are stored in for the current project.
void (*OnPlayButton)(); void (*OnStopButton)(); void (*OnPauseButton)();These functions control the main transport for the host app. Only call these from the UI thread.
int (*IsInRealTimeAudio)();Returns nonzero if in the main audio thread, or in a thread doing synchronous multiprocessing. In these instances low latency is key. If this is 0, and you are in processReplacing, then you are being called in an anticipative processing thread.
int (*Audio_IsRunning)();Returns nonzero if the audio device is open and running.
void *ctx = (void *)hostcb(&effect,0xdeadbeef,0xdeadf00e,request,NULL,0.0);
Valid values for request include:VST plug-ins can register themselves as video processors using video_processor.h and video_frame.h.