Cockos Extensions to VST SDK
  • Introduction
  • Implementation Notes
  • OS X: Cocoa Extension, 64-bit Support
  • Extensions
  • Host Functions
  • Host Context
  • Video access


    top  Introduction

    Note to plug-in developers: implement support for these APIs to encourage host developers to add support for it.

    Note to host developers: implement these APIs to encourage plug-ins to support them.

    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.



    top  Implementation Notes

    REAPER currently implements a subset of the VST 2.4 standard as well as additional extensions-- this section discusses the former.

    • Audio/sample processing: REAPER will use processDoubleReplacing if effFlagsCanDoubleReplacing is set in the flags.
      REAPER will use processReplacing if effFlagsCanReplacing is set and processReplacing != process (if it equals it assumes that it is a faulty implementation of processReplacing). Additionally there is an option in the preferences to not use processReplacing for buggy plug-ins. Note that for plug-ins that have Cockos extensions (see below), the option (to not use processReplacing) is ignored, allowing known-good plug-ins to always run at full speed.

    • Threading/concurrency: Since audio processing can run in other threads from the UI, it's important to note what can run at the same time. REAPER is highly optimized to prevent UI and other actions from interrupting audio, so assume everything can (and WILL) run at the same time as your process/processReplacing, except:
      • effOpen/effClose
      • effSetChunk -- while effGetChunk can run at the same time as audio (user saves project, or for automatic undo state tracking), effSetChunk is guaranteed to not run while audio is processing.
      So nearly everything else should be threadsafe. For many things this doesn't mean having to do much, but there are plenty of plug-ins that barf when audio is running and you open the window (using effEditOpen). There is an option to bypass audio while opening the config window, but it sucks and shouldn't be required.

    • Dynamic parameters: REAPER can deal with the number of parameters for a plug-in changing on the fly. Use the extended host audioMasterAutomate callback described below to notify the host of parameter count changes (i.e. parameter X deleted, or new parameter Y), to preserve automation of higher indexed parameters when adding/removing lower parameters.

    • Multiple inputs/outputs: REAPER allows the user to dynamically connect input/output pins of VSTs wherever they want, so enable as much I/O as you need. REAPER also allows input/output counts to change, HOWEVER it is recommended that any changes you make be done from within processReplacing() or process(), and use the old value of numInputs/numOutputs until the next call. Additionally the initial value of numInputs/numOutputs should be set to the most common settings.

    • Longer labels: effGetParamName/effGetParamLabel/effGetParamDisplay all support up to 256 character strings (255+null).




    top  OS X: Cocoa Extension, 64-bit Support

    REAPER on OS X is built using Cocoa and supports an extension which allows VSTs to create their UIs as Cocoa. Using Cocoa for UI of VST plug-ins within REAPER will result in a much cleaner integration, and has numerous advantages, including 64-bit (x86_64) support. We strongly encourage plug-in developers and other host developers to add support for Cocoa UIs via this API.

    Note: on x86_64, REAPER assumes that all configuration windows are Cocoa, as Carbon is not supported.

    When loading a VST plug-in on OS X, REAPER asks the plug-in if it would like to use Cocoa for its UI. This request is in the form of an effCanDo with the string "hasCockosViewAsConfig", looking for the response of 0xbeef0000 - 0xbeefffff (the low 16 bits are ignored). If the VST has returned this value, all future effEditOpen calls will be passed with an NSView * as the parameter, rather than a Carbon window. The plug-in must track whether it has received a "hasCockosViewAsConfig", and if it has returned the correct value. When adding a Cocoa UI, a plug-in should preferably add a single NSView to the passed NSView, via [(NSView *)ptr addSubview:myView] or similar.

    An example plug-in implementation:
    
    // (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)


    top  Extensions

    The following are extensions made available to VST plug-ins running in REAPER. Feel free to use them in your plug-ins, and encourage other VST host developers to add support for them.

    A REAPER aware VST plug-in can respond to effCanDo "hasCockosExtensions", with 0xbeef0000 (returning this value), which will inform the host that it can call certain extended calls. A plug-in that responds to "hasCockosExtensions" may choose to implement any subset of the extended calls.
      case effCanDo:
        if (ptr && !strcmp((char *)ptr,"hasCockosExtensions")) return 0xbeef0000;
    
    Extended vendor specific calls the plug-in can implement include:
    • effVendorSpecific(effGetParamDisplay, parm, buf, val)

      Gets the formatted display of a particular value for a parameter, without setting the parameter. REAPER uses this when displaying the value of a VST's automated parameter on an envelope tooltip, for example.

      Example host-side implementation:
        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;
            }
          }
      
    • effVendorSpecific(effString2Parameter, parm, buf, val)

      Converts the user's string to a normalized parameter value, without setting the parameter. Reaper uses this when the user is manually editing an envelope node, for example. Calling with buf="" is a test for function availability.

      Example host-side implementation:
        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;
            }
          }
      
    • effVendorSpecific(kVstParameterUsesIntStep, parm, NULL, NULL)
      The plugin responds if the parameter is an enum, meaning that values move in discrete steps between states rather than smoothly.

      Example host-side implementation:
        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;
            }
          }
      
    • effVendorSpecific(effGetEffectName, 0x50, &ptr, 0.0f)
      Queries if the plug-in wants to override its instance name. If implemented, returns 0xf00d, and sets ptr to point to the name.

      Example host-side implementation:
      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;
      
    • effVendorSpecific(effCanBeAutomated, parm, (void*)(INT_PTR)sample_offset_in_block, value)
      Performs sample-accurate automation. Note that this should be used along with plug-in canDo of "hasCockosSampleAccurateAutomation" that returns 0xbeef0000. This will be called one or more times in a block prior to processSamples. Returns 0xbeef if succeessful (sample-accurate automation is supported).

    • effVendorSpecific(effGetChunk,(VstIntPtr) named_parameter_name, buffer, buffer_size)
      Query the string value for named_parameter_name, return 0xf00d if supported.

    • effVendorSpecific(effSetChunk,(VstIntPtr) named_parameter_name, buffer, 0.0)
      Sets the string value for named_parameter_name, return 0xf00d if supported.

    • effVendorSpecific(0xdeadbef0, parm, rangeptr, 0.0)
      Queries the range of a parameter (allowing the plug-in to use more than the 0.0...1.0 range that VST defines).

      Example host-side implementation:
        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;
          }
      
    • audioMasterVendorSpecific(0xdeadbeef, audioMasterAutomate, listadjptr, 0.0)
      Informs the host that the parameter list has changed dynamically (new parameters added or removed within the list), so the host can properly preserve existing automation and other mappings to higher numbered parameters.

      Example host-side implementation:
        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);
      


    top  Host Functions

    The following are extensions made available to VST plug-ins running in REAPER. Feel free to use them in your plug-ins, and encourage other VST host developers to add support for them.

    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:
    • GetPlayPosition()
         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.

    • GetPlayPosition2()
         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.

    • GetCursorPosition()
        double (*GetCursorPosition)();
      
      GetCursorPosition() returns the current edit cursor position (if any), in seconds.

    • GetPlayState()
        int (*GetPlayState)();
      
      GetPlayState() returns an integer value representing the project play state. 1=play, 2=paused, 5=recording, 6=record paused.

    • SetEditCurPos()
        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).

    • GetSetRepeat()
        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).

    • GetProjectPath()
        void (*GetProjectPath)(char *buf, int bufsz);
      
      GetProjectPath() can be used to query the path that media files are stored in for the current project.

    • OnPlayButton(), OnPauseButton(), OnStopButton()
        void (*OnPlayButton)();
        void (*OnStopButton)();
        void (*OnPauseButton)();
      
      These functions control the main transport for the host app. Only call these from the UI thread.

    • IsInRealTimeAudio()
        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.

    • Audio_IsRunning()
        int (*Audio_IsRunning)();
      
      Returns nonzero if the audio device is open and running.

    • Additional API functions are listed in the REAPER Extension Plug-in SDK.

    top  Host Context

    Some API functions take context pointers such as ReaProject*, MediaTrack*, MediaItem_Take*, etc. A VST running within REAPER can request its own context via the audioMasterCallback:
        void *ctx = (void *)hostcb(&effect,0xdeadbeef,0xdeadf00e,request,NULL,0.0);
    
    Valid values for request include:
    • 1: retrieve MediaTrack* (zero if not running as track-FX)
    • 2: retrieve MediaItem_Take* (zero if not running as take-FX)
    • 3: retrieve ReaProject*
    • 5: retrieve channel count of containing track (use (int)(INT_PTR)ctx).
    • 6: retrieve position in chain (zero if unsupported), 1=first item, 2=second. 0x1000000 set if in record-FX or monitoring-FX. -- v6.11+


    top  Video access

    VST plug-ins can register themselves as video processors using video_processor.h and video_frame.h.





  •   Home
        Company
        Reviews
        Radio
      About
        Technical
        Old Versions
        Language Packs
        ReaPlugs
        Distribution
      Developer
        Theme Development
        Custom Cursors
        JSFX Programming
        ReaScript
        Extensions SDK
      • Extensions to VST SDK
        OSC
        Language Pack Template
      Resources
        User Guide
        Videos
        Stash
        Forum