Old 10-02-2015, 06:50 PM   #1
Kyran
Human being with feelings
 
Join Date: Sep 2010
Posts: 48
Default Control Surface Plugin Guide?

Is there any up to date resource on implementing a reaper plugin, specifically related to control surfaces? I have a general, vague idea of what's going on by looking at the exported Reaper_Plugin_Functions header and the virtual functions in IReaperControlSurface, but there's still a lot of murky undocumented information it seems.

A lot of confusion surrounds things which need to be Register()'d. I know you can specify "csurf" and pass in a reaper_csurf_reg_t, but what exactly do I put in that struct? The Create function pointer looks like it should be a factory function to instantiate your control surface, but what's the ShowConfig function? How do I parse the configString params in the Create function? This applies to the other infostructs in the plugin header as well.

How would I create a midi input/output? Can I aggregate the data from several controllers by constructing several midi in/outs? How do I/can I distinguish between controllers sending data, or do I have to put them on different midi channels? Does each controller have its own port? Do I do all my midi processing in the Run function or is there an event driven way that it can be handled?


It seems like I should have a class that handles loading each control surface, similar to csurf_main.cpp, and have an implementation of IReaperControlSurface for each controller I have. Then pass all their input back to a central class that handles all the logic and LED feedback as well as the rest of the plugin functions.

If that central class wants to access the reaper API, how exactly does it do that? The IMPAPI macro in the csurf example seems fairly simple, but how do I know which functions are available, what their signatures are, and what their arguments contain? Am I right in assuming that process is to register Reaper functions which will be called by the plugin? There's also calls that Reaper makes to the plugin, correct? Are these handled the same way, or are they the virtual functions in IReaperControlSurface/reaper_plugin.h? Or do I just include Reaper_plugin_functions.h and call them directly?


I'm essentially looking for an explanation of the core functionality of the csurf API, and a description of all the relevant function arguments (specifically the structs). Once I get things up and running enough to be able to do some testing inside Reaper, I can work out the idiosyncrasies of individual API calls and how to make them do what I want.

I'm sure there's also things I'm not even aware about yet, like updating the Reaper UI after changing things from the plugin, etc. It's difficult to figure that out on my own, given the length of Reaper_plugin_functions.h and the fact that things are ordered alphabetically instead of grouped by functionality. Most of what I find on the forum, wiki, or external repos is from at least several years ago, so I have no clue what's still relevant today.

Any answers to the above questions or questions I haven't yet asked would be very much appreciated.

I'm using Reaper 5, MSVS, and Windows 10 if that matters.
Kyran is offline   Reply With Quote
Old 10-02-2015, 07:02 PM   #2
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,053
Default

There's no documentation or guide for this stuff. Your best (or worst, depending on how you look at it) chance to make any sense out of it is the example plugins.

The header files (reaper_plugin.h and reaper_plugin_functions.h) contained in the official SDK are severely out of date. More up to date versions of reaper_plugin.h have appeared. Reaper itself can now generate the up to date reaper_plugin_functions.h file. (It's an action in the main actions list in the actions window.)

This might be a somewhat up to date version of reaper_plugin.h :

http://ge.tt/4NimWzE2/v/0?c

Regarding all the other stuff, that's a lot of questions that would require lots of writing. I will try to answer some of those questions after sleeping.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/

Last edited by Xenakios; 10-02-2015 at 07:09 PM.
Xenakios is online now   Reply With Quote
Old 10-02-2015, 08:08 PM   #3
Kyran
Human being with feelings
 
Join Date: Sep 2010
Posts: 48
Default

Yep, I found the action to write a recent reaper_plugin_functions.h. Too bad that doesn't exist for reaper_plugin.h as well!

I see the one you linked has command IDs defined for the extended functions, so that's quite helpful. Curious why the Reaper devs wouldn't release that version or a more up to date one (or just write it like the plugin functions header) Obviously they've had their hands full recently, but I hope that an overhaul of the developer resources is planned in the near future. Or alternately it would be cool to expose select Reaper innards to a couple people who were willing to figure things out and write documentation so it wouldn't cut into development time.

Anyway, thanks for the link! I look forward to hearing from you when you're well rested.
Kyran is offline   Reply With Quote
Old 10-03-2015, 05:19 AM   #4
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,053
Default

Quote:
A lot of confusion surrounds things which need to be Register()'d. I know you can specify "csurf" and pass in a reaper_csurf_reg_t, but what exactly do I put in that struct? The Create function pointer looks like it should be a factory function to instantiate your control surface, but what's the ShowConfig function? How do I parse the configString params in the Create function? This applies to the other infostructs in the plugin header as well.
For a control surface you only need to register the control surface registration struct instance via the plugin entry point registration system. There are other things that can be registered, since the Reaper extension plugin system isn't just for control surfaces support, it can in fact do a LOT more. But those other things might not be of immediate interest to you.

"create" should be a function pointer to a function to create the control surface like you suspected. Parsing the configString is your business (since it's data you yourself should have returned previously from your control surface class's GetConfigString method). There isn't anything in the Reaper API itself to help with that. Cockos WDL's LineParse class might be useful. Reaper itself uses that to parse its text based project files.

ShowConfig should create a configuration GUI window and return the HWND to it. The function must exist even if you don't yet want to have a configuration window. From such a dummy function you can return NULL.
Quote:
How would I create a midi input/output? Can I aggregate the data from several controllers by constructing several midi in/outs? How do I/can I distinguish between controllers sending data, or do I have to put them on different midi channels? Does each controller have its own port? Do I do all my midi processing in the Run function or is there an event driven way that it can be handled?
For creating the midi inputs and outputs, see the example plugin code. Aggregating data will probably require some nasty global variables or similar to pass data between the control surface object instances. There's no direct support for something like that. Each controller will have its own MIDI port that is "stolen" from the MIDI ports available to Reaper.

Run() is the only place where you can safely access the incoming MIDI events. Again, see the code in the examples.
Quote:
It seems like I should have a class that handles loading each control surface, similar to csurf_main.cpp, and have an implementation of IReaperControlSurface for each controller I have. Then pass all their input back to a central class that handles all the logic and LED feedback as well as the rest of the plugin functions.
You should do that in whatever manner seems reasonable. Reaper only cares about instances of subclasses of the IReaperControlSurface class. But since you are using C++, yes, some kind of a central object is probably a good idea but that is not anything Reaper will be aware about.

Quote:
If that central class wants to access the reaper API, how exactly does it do that? The IMPAPI macro in the csurf example seems fairly simple, but how do I know which functions are available, what their signatures are, and what their arguments contain? Am I right in assuming that process is to register Reaper functions which will be called by the plugin?
The available API functions and their signatures are the functions in reaper_plugin_functions.h. Instead of using the IMPAPI macro, there is now a more straightforward way to import all the API functions in one go. And you should probably just use that since any API function you want to use has to be loaded from Reaper before use, otherwise a crash will result.

Code:
#define REAPERAPI_IMPLEMENT
#define REAPERAPI_DECL

#include "reaper_plugin_functions.h"
extern "C"
{
	REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
	{
		if (rec)
		{
			if (REAPERAPI_LoadAPI(rec->GetFunc) > 0)
			{
				return 0;
			}
...
Quote:
There's also calls that Reaper makes to the plugin, correct? Are these handled the same way, or are they the virtual functions in IReaperControlSurface/reaper_plugin.h? Or do I just include Reaper_plugin_functions.h and call them directly?
Callbacks from Reaper for the control surface implementations will be into the IReaperControlSurface subclass instance's virtual functions. reaper_plugin_functions.h has no callbacks. There's in fact a frustrating lack of callbacks/event notifications in the Reaper extension plugins system. You will realize IReaperControlSurface will not be able to handle everything you might want to get notified about. You will need to poll for some changes in Reaper's state yourself.

Quote:
I'm essentially looking for an explanation of the core functionality of the csurf API, and a description of all the relevant function arguments (specifically the structs). Once I get things up and running enough to be able to do some testing inside Reaper, I can work out the idiosyncrasies of individual API calls and how to make them do what I want.

I'm sure there's also things I'm not even aware about yet, like updating the Reaper UI after changing things from the plugin, etc. It's difficult to figure that out on my own, given the length of Reaper_plugin_functions.h and the fact that things are ordered alphabetically instead of grouped by functionality. Most of what I find on the forum, wiki, or external repos is from at least several years ago, so I have no clue what's still relevant today.
Like I explained in my shorter answer previously, this stuff isn't well documented or explained centrally anywhere. You will need to be prepared to do a lot of frustrating experiments to discover how things work. There is limited documentation for some of the C API functions in the html file that is generated by the "Open ReaScript documentation (html)" action. The ReaScript API is almost the same as the available C functions. (Don't get excited by ReaScript now though, it's completely useless for doing control surfaces support.)

Some of the API functions will automatically update Reaper's UI when called, some won't. You will need to test. There are separate functions to update different parts of the UI. Again, experimentation might be needed to see what you need to use. UpdateArrange(), UpdateTimeline(), some others that are not very logically named.

Almost nothing in the API has been removed since the beginning, so most old stuff you see around should still work. There may however be newer better ways of doing things.

In my opinion it's pretty miserable C++ even has to be used for implementing control surfaces support. A scripting language based implementation would be much better. But I am not aware of any plans Cockos would have for that.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/

Last edited by Xenakios; 10-03-2015 at 05:38 AM.
Xenakios is online now   Reply With Quote
Old 10-03-2015, 09:21 AM   #5
snooks
Human being with feelings
 
Join Date: Sep 2015
Posts: 1,515
Default

Great post Xen. Don't forget there is the open source Alphatrack Pro Reaper code hanging out at SourceForge (somebody should change that).

http://sourceforge.net/p/alphatrackp...lphatrack_src/
snooks is offline   Reply With Quote
Old 10-03-2015, 06:38 PM   #6
Kyran
Human being with feelings
 
Join Date: Sep 2010
Posts: 48
Default

Quote:
Originally Posted by Xenakios View Post
[...]
Ahh okay, so the configString is defined by me, makes more sense. Is this just arbitrary data I want to persist? Is it per-project or global? Persisted on Reaper close? When is GetConfigString called?

And for the ShowConfig pointer I can just give it a function that immediately returns nullptr until I need a proper config window?


As for midi, I should be able to have each independent CSurf call a midi handler function in my main class which manages all the midi input. I can just add an additional param so it knows which controller sent the data and not end up with overlaps if two controllers send on the same channel. As long as I call everything from a Run function it should be fine. Would I be right in assuming I could call SwapBuffsPrecise on CSurf A from the Run function of CSurf B since all that matters is that it's running from the GUI thread?

Something that still confuses me is how does the plugin know which controller it's latching onto? Just by the name in the struct? So I should mimic the device name I see in Reaper's midi devices preferences?

For midi outputs, it seems like I can create those fairly simply for sending feedback back to the controllers. But how do I send midi data to Reaper? I want to be able to record midi note data after it's been processed by my plugin, as well as midi learn virtual controls that my plugin exposes. I think I remember seeing some script or plugin for sending midi data to Reaper's control path so I'm hoping there's some mechanism in the API to do what I want. An alternate approach would be writing directly to midi clip data or changing parameters through function calls. It would just be a lot less intuitive than record arming or using midi learn.


Thanks for the tip on the API import. Should clean up the entrypoint function considerably. So for that to work I just need to have the function pointers with the right signatures as member variables in my main plugin class? Or do I even need to do that? Can I just call them directly like a global somehow once they've been imported? My assumption is the former, possibly returning non-zero if it can't find the appropriate function pointer?

I checked out the Reascript help file; nice that it gets generated by Reaper. Could definitely use a lot more documentation but what's there should hopefully be useful, I'll take what I can get.

Thanks for all the help Xenakios!


Quote:
Originally Posted by snooks View Post
Don't forget there is the open source Alphatrack Pro Reaper code hanging out at SourceForge
Thanks for the link, this might actually be very helpful. It's been rough finding example source for CSurfs.
I also just found Klinke's MCU plugin which looks to be quite extensive. Hopefully I'll find some clues in there between all the irrelevant Mackie stuff.
Kyran is offline   Reply With Quote
Old 10-03-2015, 07:16 PM   #7
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,053
Default

Quote:
Originally Posted by Kyran View Post

Thanks for the tip on the API import. Should clean up the entrypoint function considerably. So for that to work I just need to have the function pointers with the right signatures as member variables in my main plugin class? Or do I even need to do that? Can I just call them directly like a global somehow once they've been imported?
Before using the "import all API functions" trick, you should in fact remove all your own global function pointer definitions from your code. (IIRC the very old Reaper extension examples have the function pointers defined in the plugin code itself, which is now an obsolete and messy way of doing it.) Via some "magic", the reaper_plugin_functions.h file can now be used to both declare the available functions and have callable function pointers to them. And yes, the API functions are so called global functions because they are C, not C++. There's no reason to pass around the function pointers to your classes.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is online now   Reply With Quote
Old 10-04-2015, 12:23 PM   #8
Kyran
Human being with feelings
 
Join Date: Sep 2010
Posts: 48
Default

That's actually really great, nice surprise. Less pointers to pass around.

Any idea on specifying which device you want when creating midi input? How does the plugin target a certain device?

Also regarding the API callback issue, it seems like there's some functions starting with "CSurf_" that appear to be callbacks (ie CSurf_SetSurfaceSolo) Any idea if these are still applicable or have been superseded by the virtual IReaperControlSurface functions?
Kyran is offline   Reply With Quote
Old 10-04-2015, 12:40 PM   #9
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,053
Default

Quote:
Originally Posted by Kyran View Post
That's actually really great, nice surprise. Less pointers to pass around.

Any idea on specifying which device you want when creating midi input? How does the plugin target a certain device?

Also regarding the API callback issue, it seems like there's some functions starting with "CSurf_" that appear to be callbacks (ie CSurf_SetSurfaceSolo) Any idea if these are still applicable or have been superseded by the virtual IReaperControlSurface functions?
Look at the plugin example codes how the MIDI device creation stuff works. (It's pretty awful, I can tell you that right away.)

There are no callbacks in the C API. Some of the functions just are illogically named. For example CSurf_SetSurfaceSolo sets the solo status of the Reaper track, it's not a function you could yourself set that would be called back when the solo status changes in Reaper.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is online now   Reply With Quote
Old 01-21-2018, 04:52 AM   #10
fundorin
Banned
 
Join Date: Feb 2014
Location: Moscow, Russia
Posts: 554
Default

Quote:
Originally Posted by Xenakios View Post
Code:
#define REAPERAPI_IMPLEMENT
#define REAPERAPI_DECL

#include "reaper_plugin_functions.h"
extern "C"
{
    REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec)
    {
        if (rec)
        {
            if (REAPERAPI_LoadAPI(rec->GetFunc) > 0)
            {
                return 0;
            }
...
Could you, please, specify, how exactly this "hack should be exploited?
I've added both reaper_plugin (from you) and reaper_plugin_functions (generated by Reaper) headers into my solution. Then, i've added defines and include (above) into csurf_main.cpp. Then, I've replaced all extern "C" contents with the code above.
I've got lots of errors upon building the solution:



Here's my csurf_main.cpp code after editing: http://pastebin.ru/Oof3S6PQ
fundorin is offline   Reply With Quote
Old 01-21-2018, 10:23 AM   #11
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,053
Default

Quote:
Originally Posted by fundorin View Post
Could you, please, specify, how exactly this "hack should be exploited?
Like I wrote above, you need to remove the API function pointers from your own code first.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is online now   Reply With Quote
Old 01-27-2018, 12:25 PM   #12
ajaym
Human being with feelings
 
Join Date: Aug 2009
Posts: 141
Default

A while back I wrote a control surface for the Behringer BCR2000 which while originally designed for Sonar was then ported to Reaper. The full source code is at
https://sourceforge.net/projects/bcr2000control/

there are plenty of comments in the code and it was deliberately written using a fairly standard subset of ANSI C (not C++) and so it should be relatively easy to adapt for any purpose.

You probably don't want to use callbacks, by the way. Not everything will result in a callback so its better to poll Reaper for state changes (you will be called every 1/15th second typically) and if something has changed, update accordingly,

Unfortunately Reaper's various SDKs have fairly minimal documentation. This is unfortunate and makes the learning curve a bit harder than it should be, but unfortunately (and I speak as a professional software engineer myself), good engineers often don't understand the need for documention - why, *they* understand it perfectly, after all.

However its not too difficult. I do also have source for a KMI K-Mix control surface which I haven't posted up as open source but can share - it's derived, however, by adapting the BCR2000 codebase, so isn't really any simpler. Best of luck!
ajaym is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 06:29 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2018, vBulletin Solutions Inc.