diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..95a3a48 --- /dev/null +++ b/COPYING @@ -0,0 +1,313 @@ + + LICENSING TERMS FOR DSSI-VST + +You may distribute and/or modify dssi-vst under the terms of version 2 +of the GNU General Public License as published by the Free Software +Foundation (full text below), except that you are hereby permitted to +omit the source code to the two VST SDK header files aeffect.h and +aeffectx.h (the source code of which would otherwise be required by +section 3 of the GPL) when redistributing dssi-vst. The full terms of +the GPL continue to apply to all of the remaining source code for +dssi-vst. + + LICENSING TERMS FOR LIBLO OSC LIBRARY WHEN USED WITH DSSI-VST + +You may distribute and/or modify liblo under the terms of version 2 of +the GNU General Public License as published by the Free Software +Foundation (full text below), except that you are hereby permitted to +omit the source code to the two VST SDK header files aeffect.h and +aeffectx.h (the source code of which would otherwise be required by +section 3 of the GPL) when redistributing liblo as part of any version +of dssi-vst. This offer does not imply any exemption to the GPL for +the liblo code itself or the remainder of dssi-vst, nor to any other +derived work that may use liblo. + +*** NOTE: If you build dssi-vst using the VeSTige VST compatibility +*** header instead of the official VST SDK, then the original GNU +*** General Public License applies, and the above exception is not +*** available to you. + +*** Please see the dssi-vst README for more information about these terms. +*** The following is the standard GPL, as referred to in the above. + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fd7ebde --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ + +DSSIDIR = /usr/local/lib/dssi +BINDIR = /usr/local/bin + +# To compile with the VeSTige compatibility header: +CXXFLAGS = -Ivestige -Wall + +# To compile with the official VST SDK v2.4r2: +#CXXFLAGS = -I./vstsdk2.4/pluginterfaces/vst2.x -Wall + +LDFLAGS = + +TARGETS = dssi-vst-server.exe.so \ + dssi-vst-scanner.exe.so \ + dssi-vst.so \ + dssi-vst_gui \ + vsthost + +HEADERS = remoteplugin.h \ + remotepluginclient.h \ + remotepluginserver.h \ + remotevstclient.h \ + rdwrops.h \ + paths.h + +OBJECTS = remotevstclient.o \ + remotepluginclient.o \ + remotepluginserver.o \ + rdwrops.o \ + paths.o + +all: $(TARGETS) + +install: all + mkdir -p $(DSSIDIR)/dssi-vst + mkdir -p $(BINDIR) + install dssi-vst.so $(DSSIDIR) + install dssi-vst-server.exe.so dssi-vst-server dssi-vst-scanner.exe.so dssi-vst-scanner dssi-vst_gui $(DSSIDIR)/dssi-vst + install vsthost $(BINDIR) + +clean: + rm -f $(OBJECTS) libremoteplugin.a + +distclean: clean + rm -f $(TARGETS) dssi-vst-scanner dssi-vst-server *~ *.bak + +%.exe.so: %.cpp libremoteplugin.a $(HEADERS) + wineg++ $(CXXFLAGS) $< -o $* $(LDFLAGS) -L. -lremoteplugin -lpthread + +libremoteplugin.a: remotepluginclient.o remotepluginserver.o rdwrops.o paths.o + ar r $@ $^ + +remotepluginclient.o: remotepluginclient.cpp $(HEADERS) + g++ $(CXXFLAGS) remotepluginclient.cpp -c + +remotevstclient.o: remotevstclient.cpp $(HEADERS) + g++ $(CXXFLAGS) remotevstclient.cpp -c + +remotepluginserver.o: remotepluginserver.cpp $(HEADERS) + g++ $(CXXFLAGS) remotepluginserver.cpp -c + +dssi-vst.so: dssi-vst.cpp libremoteplugin.a remotevstclient.o $(HEADERS) + g++ -shared -Wl,-Bsymbolic -g3 $(CXXFLAGS) -o dssi-vst.so dssi-vst.cpp remotevstclient.o $(LDFLAGS) -L. -lremoteplugin -lasound + +vsthost: vsthost.cpp libremoteplugin.a remotevstclient.o $(HEADERS) + g++ $(CXXFLAGS) vsthost.cpp remotevstclient.o -o vsthost $(LDFLAGS) -L. -lremoteplugin -ljack -lasound + +dssi-vst_gui: dssi-vst_gui.cpp rdwrops.h + g++ $(CXXFLAGS) dssi-vst_gui.cpp rdwrops.o -o dssi-vst_gui $(LDFLAGS) -llo + diff --git a/README b/README new file mode 100644 index 0000000..639c27f --- /dev/null +++ b/README @@ -0,0 +1,214 @@ + +dssi-vst: a DSSI plugin wrapper for VST plugins +=============================================== + +This is a DSSI plugin wrapper for VST effects and instruments with GUI +support. + +Copyright (c) 2004-2007 Chris Cannam, cannam@all-day-breakfast.com. + +This is the 0.5 release of dssi-vst. + +This release is the first to officially support the VeSTige +VST-compatibility header from Javier Serrano Polo (see +vestige/aeffectx.h). With this header, you no longer need to obtain +the official VST SDK in order to build dssi-vst. Many thanks to +Javier for publishing this fine piece of work. + + ** IMPORTANT: The author of dssi-vst has no connection with the + ** author of the VeSTige VST-compatibility header, has had no + ** involvement in its creation, and has not modified it in any way. + ** The VeSTige header is included in this package in the good-faith + ** belief that it has been cleanly and legally reverse engineered + ** without reference to the official VST SDK and without its + ** developer(s) having agreed to the VST SDK license agreement. + ** If you have any reason to believe that this is not the case, + ** please contact the author of dssi-vst. + +Please also read the "Licence" section further down this document. + +This release is also compatible with version 2.4r2 of the official VST +SDK. It may also compile with version 2.3. + + +Build etc +--------- + +To build dssi-vst, you will need: + +* The DSSI header. + +* Wine 0.9.5 or newer and its development tools -- http://www.winehq.com/ + +* Steve Harris's liblo "Lite OSC" library -- http://liblo.sf.net/ -- + version 0.9 or newer. + +* OPTIONALLY the VST SDK headers. The URL for downloading these seems + to change every week, but at the time of writing it is: + + http://www.steinberg.de/331+M54a708de802.html + + These are free to download, but you need to agree to Steinberg's + licensing terms and you may not redistribute them. Unzip the SDK + into the current directory (creating a subdirectory called vstsdk2.4). + + If you wish to use the official VST SDK, please edit the Makefile + as directed in its comments. + +Once you have all of the above, you should be able to build and +install by running "make" then "make install". + +To use dssi-vst: make sure DSSI_PATH is set appropriately, set +VST_PATH to a colon-separated list of the directories containing VST +plugins, and start up your DSSI host. + +The plugin soname is dssi-vst.so, and each VST plugin gets a label +corresponding to its DLL name. So for example, with +jack-dssi-host, you should be able to just run + + jack-dssi-host dssi-vst.so:MyVstPlugin.dll + +Source files: + +* dssi-vst.cpp: DSSI plugin implementation +* dssi-vst_gui.cpp: DSSI plugin GUI process implementation +* dssi-vst-scanner.cpp: Program that determines what VSTs you have and + communicates that to the plugin +* dssi-vst-server.cpp: Program that hosts a single VST with a comms link + to the plugin +* rdwrops.cpp, paths.cpp: misc functions +* remotepluginclient.cpp/remotepluginserver.cpp: Code to handle process + separation for audio plugin (not VST specific), used by DSSI plugin & server +* vsthost.cpp: JACK/aseq host for VSTs using dssi-vst-server, but not using + the actual DSSI plugin. + + +Bugs and limitations +-------------------- + +Does not handle multiple VST plugins in a single DLL. + +Does not handle multi-channel support correctly yet. DSSI does not +use MIDI channels, so dssi-vst should permit separate instances +delivering to separate VST MIDI channels using run_multiple_synths to +share the VST plugin. + +Does not communicate tempo and beat information to the VST plugin (the +VST API has a mechanism for this, but DSSI does not... yet). + +Has a tendency to leave FIFOs and shared-memory files lying around in +the temporary directory (/tmp or /var/tmp). You may want to go and +remove anything starting with "rplugin_" occasionally... + + +Why not use vstserver or libfst? +-------------------------------- + +dssi-vst doesn't use libfst because libfst is just too serious a thing +to start messing about with in a plugin whose host doesn't know about +it (because it involves introducing Wine threads into the host). + +The main reason dssi-vst doesn't use vstserver is that I wrote most of +this code quite a while ago as an exercise, and I've just got used to +my own code. Architecturally dssi-vst is like vstserver in that it +runs the VST plugin in a separate process and communicates with it via +some IPC mechanism (here shared memory and POSIX FIFOs, in vstserver +shared memory and Unix domain sockets). + +Good points of dssi-vst: + +* Bugs in dssi-vst aside, it ought to be impossible for a misbehaving + VST plugin to crash its host. It's therefore theoretically possible + for dssi-vst to be more stable for audio use than an fst host. + (The same is true of vstserver.) + +* DSSI provides a closer match to the VST feature set than LADSPA + does -- apart from the synth stuff, this plugin handles VST programs + as well as parameters -- which makes it more useful than vstserver's + existing ladspavst plugin. + +* Obviously, dssi-vst allows any DSSI host to become a VST host + without having to know anything about VST or to have a code (or + licence) dependency on the VST SDK, which is not the case with fst. + +Bad points: + +* Processes everywhere. Besides your host process, you get a + server process to do the actual audio processing, a scanner + process to identify the available VST plugins, and a GUI process. + All of these except the GUI process (which in fact does nothing + except send information around between the other processes) are + winelib applications that are run under Wine. dssi-vst is useful + for running the odd synth or effect on a fast machine, not for + doing all your effects work. + +* It's hard to make sure things like the communications FIFOs are + tidied up when exiting. Apart from anything else it requires + that the host call cleanup() on its plugins when exiting from + ctrl-C or whatever. A model with a server shared among many + plugins would cope better with this by having the server take + ownership of such resources instead of the DSSI plugin. + +* The comms model dssi-vst uses introduces a fixed latency equal to + the JACK period size, as well as any existing latency in the VST + plugin. (The fixed latency is exposed through the _latency output + control port. Does anyone know how to find out the latency of a + VST plugin?) + + +Licence +------- + +dssi-vst is offered under two alternative licensing schemes: the GNU +General Public License (GPL), or the GPL with a special exception for +the VST SDK headers. + +* GPL. If you choose to compile with the VeSTige VST compatibility +header, then you may distribute and/or modify dssi-vst under the +terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your +option) any later version. Note that in order to accept this +license, you must not have included any part of the official VST +SDK in your dssi-vst build. Provided that this is the case, please +ignore the remainder of this Licence section. + +* GPL-with-exception. If you compile dssi-vst with the official VST +SDK headers, you may distribute the results under the following +licence terms: + +"You may distribute and/or modify dssi-vst under the terms of version 2 +of the GNU General Public License as published by the Free Software +Foundation, except that you are hereby permitted to omit the source +code to the two VST SDK header files AEffect.h and aeffectx.h (the +source code of which would otherwise be required by section 3 of the +GPL) when redistributing dssi-vst. The full terms of the GPL continue +to apply to all of the remaining source code for dssi-vst. + +Chris Cannam, cannam@all-day-breakfast.com" + +Note that these two licensing schemes are exclusive. If you accept +the GPL-with-exception option, you may not distribute the VeSTige +header code with or in dssi-vst. If you accept the GPL option, you +may not distribute binaries of dssi-vst built with the official VST +SDK. The default for dssi-vst is to build using the VeSTige headers, +and therefore the result is covered by the GPL without any additional +exception. + + +Licence for liblo +----------------- + +dssi-vst makes use of the liblo library by Steve Harris, which is +provided under the GPL. Steve Harris has provided the following +exemption to permit the liblo library to be used with dssi-vst: + +"You are hereby permitted to omit the source code to the two VST SDK +header files AEffect.h and aeffectx.h (the source code of which would +otherwise be required by section 3 of the GPL) when redistributing any +version of dssi-vst which uses liblo. This offer does not imply any +exemption to the GPL for the liblo code itself or the remainder of +dssi-vst, nor to any other derived work that may use liblo. + +Steve Harris +steve plugin org uk" + diff --git a/dssi-vst-scanner.cpp b/dssi-vst-scanner.cpp new file mode 100644 index 0000000..972e72a --- /dev/null +++ b/dssi-vst-scanner.cpp @@ -0,0 +1,414 @@ +// -*- c-basic-offset: 4 -*- + +/* + dssi-vst: a DSSI plugin wrapper for VST effects and instruments + Copyright 2004-2006 Chris Cannam +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#include "aeffectx.h" + +#include "remotepluginserver.h" +#include "paths.h" + +#define APPLICATION_CLASS_NAME "dssi_vst" +#define OLD_PLUGIN_ENTRY_POINT "main" +#define NEW_PLUGIN_ENTRY_POINT "VSTPluginMain" + +#if VST_FORCE_DEPRECATED +#define DEPRECATED_VST_SYMBOL(x) __##x##Deprecated +#else +#define DEPRECATED_VST_SYMBOL(x) x +#endif + +using namespace std; + + +#if VST_2_4_EXTENSIONS +VstIntPtr VSTCALLBACK +hostCallback(AEffect *plugin, VstInt32 opcode, VstInt32 index, + VstIntPtr value, void *ptr, float opt) +#else +long VSTCALLBACK +hostCallback(AEffect *plugin, long opcode, long index, + long value, void *ptr, float opt) +#endif +{ + static VstTimeInfo timeInfo; + + switch (opcode) { + + case audioMasterVersion: + return 2300; + + case audioMasterGetVendorString: + strcpy((char *)ptr, "Chris Cannam"); + break; + + case audioMasterGetProductString: + strcpy((char *)ptr, "DSSI VST Wrapper Plugin Scanner"); + break; + + case audioMasterGetVendorVersion: + return long(RemotePluginVersion * 100); + + case audioMasterGetLanguage: + return kVstLangEnglish; + + case audioMasterCanDo: + if (!strcmp((char*)ptr, "sendVstEvents") || + !strcmp((char*)ptr, "sendVstMidiEvent") || + !strcmp((char*)ptr, "sendVstTimeInfo") || + !strcmp((char*)ptr, "sizeWindow") || + !strcmp((char*)ptr, "supplyIdle")) { + return 1; + } + break; + + case audioMasterGetTime: + timeInfo.samplePos = 0; + timeInfo.sampleRate = 48000; + timeInfo.flags = 0; // don't mark anything valid except default samplePos/Rate + return (long)&timeInfo; + + case DEPRECATED_VST_SYMBOL(audioMasterTempoAt): + // can't support this, return 120bpm + return 120 * 10000; + + case audioMasterGetSampleRate: + plugin->dispatcher(plugin, effSetSampleRate, + 0, 0, NULL, 48000.0); + break; + + case audioMasterGetBlockSize: + plugin->dispatcher(plugin, effSetBlockSize, + 0, 1024, NULL, 0); + break; + + case DEPRECATED_VST_SYMBOL(audioMasterWillReplaceOrAccumulate): + // 0 -> unsupported, 1 -> replace, 2 -> accumulate + return 1; + + case audioMasterGetCurrentProcessLevel: + // 0 -> unsupported, 1 -> gui, 2 -> process, 3 -> midi/timer, 4 -> offline + return 1; + + case DEPRECATED_VST_SYMBOL(audioMasterGetParameterQuantization): + return 1; + + case DEPRECATED_VST_SYMBOL(audioMasterNeedIdle): + return 1; + + case DEPRECATED_VST_SYMBOL(audioMasterWantMidi): + return 1; + + default: + ; + } + + return 0; +}; + +int WINAPI +WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) +{ + char *destFile = 0; + + cout << "DSSI VST plugin scanner v0.3" << endl; + cout << "Copyright (c) 2004-2007 Chris Cannam" << endl; + + if (cmdline && cmdline[0]) destFile = strdup(cmdline); + + int targetfd = 0; + if (destFile) { + if ((targetfd = open(destFile, O_WRONLY)) < 0) { + cerr << "dssi-vst-scanner: Failed to open output file " << destFile; + perror(" "); + cerr << "dssi-vst-scanner: Defaulting to stdout" << endl; + targetfd = 0; + } + } + + int version = int(RemotePluginVersion * 1000); + write(targetfd, &version, sizeof(int)); + + HINSTANCE libHandle = 0; + + std::vector vstPath = Paths::getPath + ("VST_PATH", "/usr/local/lib/vst:/usr/lib/vst", "/vst"); + + for (size_t i = 0; i < vstPath.size(); ++i) { + + std::string vstDir = vstPath[i]; + + DIR *directory = opendir(vstDir.c_str()); + if (!directory) { +// cerr << "dssi-vst-scanner: couldn't read VST directory \"" +// << vstDir << "\"" << std::endl; + continue; + } + + struct dirent *entry; + char *home = getenv("HOME"); + std::string cacheDir = std::string(home) + "/.dssi-vst"; + bool haveCacheDir = false; + + DIR *test = opendir(cacheDir.c_str()); + if (!test) { + if (mkdir(cacheDir.c_str(), 0755)) { + cerr << "dssi-vst-scanner: failed to create cache directory " << cacheDir; + perror(0); + } else { + haveCacheDir = true; + } + } else { + haveCacheDir = true; + closedir(test); + } + + while ((entry = readdir(directory))) { + + // For each plugin, we write: + // + // dll name (64 chars) + // name (64 chars) + // vendor (64 chars) + // is synth (bool) + // have editor (bool) + // input count (int) + // output count (int) + // + // parameter count (int) + // then for each parameter: + // name (64 chars) + // default value (float) + // + // program count (int) + // then for each program: + // name (64 chars) + + std::string libname = entry->d_name; + + if (libname[0] == '.' || + libname.length() < 5 || + (libname.substr(libname.length() - 4) != ".dll" && + libname.substr(libname.length() - 4) != ".DLL")) { + continue; + } + + int fd = targetfd; + bool haveCache = false; + bool writingCache = false; + std::string cacheFileName = cacheDir + "/" + libname + ".cache"; + + if (haveCacheDir) { + + struct stat st; + if (!stat(cacheFileName.c_str(), &st)) { + int testfd = open(cacheFileName.c_str(), O_RDONLY); + if (testfd >= 0) { + int testVersion = 0; + if (read(testfd, &testVersion, sizeof(int)) == sizeof(int) && + testVersion == version) { + haveCache = true; + } else { + cerr << "dssi-vst-scanner: Cache version mismatch for file " + << cacheFileName << " (" << testVersion << ", wanted " + << version << ") - rewriting" << endl; + } + close(testfd); + } + } + if (!haveCache) { + if ((fd = open(cacheFileName.c_str(), O_WRONLY | O_CREAT, 0644)) < 0) { + cerr << "dssi-vst-scanner: Failed to open cache file " << cacheFileName; + perror(" for writing"); + fd = targetfd; + } else { + writingCache = true; + write(fd, &version, sizeof(int)); + } + } + } + + if (!haveCache) { + + int inputs = 0, outputs = 0, params = 0, programs = 0; + char buffer[65]; + unsigned long uniqueId = 0; + bool synth = false, gui = false; + int i = 0; + AEffect *(__stdcall* getInstance)(audioMasterCallback) = 0; + AEffect *plugin = 0; + std::string libPath; + + if (vstDir[vstDir.length()-1] == '/') { + libPath = vstDir + libname; + } else { + libPath = vstDir + "/" + libname; + } + + libHandle = LoadLibrary(libPath.c_str()); + cerr << "dssi-vst-scanner: " << (libHandle ? "" : "not ") + << "found in " << libPath << endl; + + if (!libHandle) { + if (home && home[0] != '\0') { + if (libPath.substr(0, strlen(home)) == home) { + libPath = libPath.substr(strlen(home) + 1); + } + libHandle = LoadLibrary(libPath.c_str()); + cerr << "dssi-vst-scanner: " << (libHandle ? "" : "not ") + << "found in " << libPath << endl; + } + } + + if (!libHandle) { + cerr << "dssi-vst-scanner: Couldn't load DLL " << libPath << endl; + goto done; + } + + getInstance = (AEffect*(__stdcall*)(audioMasterCallback)) + GetProcAddress(libHandle, NEW_PLUGIN_ENTRY_POINT); + + if (!getInstance) { + getInstance = (AEffect*(__stdcall*)(audioMasterCallback)) + GetProcAddress(libHandle, OLD_PLUGIN_ENTRY_POINT); + + if (!getInstance) { + cerr << "dssi-vst-scanner: VST entrypoints \"" + << NEW_PLUGIN_ENTRY_POINT << "\" or \"" + << OLD_PLUGIN_ENTRY_POINT << "\" not found in DLL \"" + << libname << "\"" << endl; + goto done; + } + } + + plugin = getInstance(hostCallback); + + if (!plugin) { + cerr << "dssi-vst-scanner: Failed to instantiate plugin in VST DLL \"" + << libPath << "\"" << endl; + goto done; + } + + if (plugin->magic != kEffectMagic) { + cerr << "dssi-vst-scanner: Not a VST effect in DLL \"" + << libPath << "\"" << endl; + goto done; + } + + if (!plugin->flags & effFlagsCanReplacing) { + cerr << "dssi-vst-scanner: Effect does not support processReplacing (required)" + << endl; + goto done; + } + + memset(buffer, 0, 65); + snprintf(buffer, 64, "%s", libname.c_str()); + write(fd, buffer, 64); + + memset(buffer, 0, 65); + plugin->dispatcher(plugin, effGetEffectName, 0, 0, buffer, 0); + if (buffer[0] == '\0') { + snprintf(buffer, 64, "%s", libname.c_str()); + } + write(fd, buffer, 64); + + memset(buffer, 0, 65); + plugin->dispatcher(plugin, effGetVendorString, 0, 0, buffer, 0); + write(fd, buffer, 64); + + synth = false; + if (plugin->flags & effFlagsIsSynth) synth = true; + write(fd, &synth, sizeof(bool)); + + gui = false; + if (plugin->flags & effFlagsHasEditor) gui = true; + write(fd, &gui, sizeof(bool)); + + inputs = plugin->numInputs; + write(fd, &inputs, sizeof(int)); + + outputs = plugin->numOutputs; + write(fd, &outputs, sizeof(int)); + + params = plugin->numParams; + write(fd, ¶ms, sizeof(int)); + + for (i = 0; i < params; ++i) { + memset(buffer, 0, 65); + plugin->dispatcher(plugin, effGetParamName, i, 0, buffer, 0); + write(fd, buffer, 64); + float f = plugin->getParameter(plugin, i); + write(fd, &f, sizeof(float)); + } + + programs = plugin->numPrograms; + write(fd, &programs, sizeof(int)); + + for (i = 0; i < programs; ++i) { + memset(buffer, 0, 65); + // effGetProgramName appears to return the name of the + // current program, not program -- though we + // pass in as well, just in case + plugin->dispatcher(plugin, effSetProgram, 0, i, NULL, 0); + plugin->dispatcher(plugin, effGetProgramName, i, 0, buffer, 0); + write(fd, buffer, 64); + } + + done: + if (plugin) plugin->dispatcher(plugin, effClose, 0, 0, NULL, 0); + FreeLibrary(libHandle); + } + + if (writingCache) { + close(fd); + } + + if (haveCache || writingCache) { + // need to read from cache as well + if ((fd = open(cacheFileName.c_str(), O_RDONLY)) < 0) { + cerr << "dssi-vst-scanner: Failed to open cache file " << cacheFileName; + perror("for reading"); + } else { + int testVersion = 0; + if (read(fd, &testVersion, sizeof(int)) != sizeof(int) || + testVersion != version) { + cerr << "dssi-vst-scanner: Internal error: cache file " << cacheFileName << " verified earlier, but now fails version test (" << testVersion << " != " << version << ")" << endl; + } else { + unsigned char c; + while (read(fd, &c, 1) == 1) { + write(targetfd, &c, 1); + } + } + close(fd); + } + } + } + + closedir(directory); + } + + if (targetfd != 0) { + close(targetfd); + } + + return 0; +} + + diff --git a/dssi-vst-server.cpp b/dssi-vst-server.cpp new file mode 100644 index 0000000..2e12d87 --- /dev/null +++ b/dssi-vst-server.cpp @@ -0,0 +1,1459 @@ +// -*- c-basic-offset: 4 -*- + +/* + dssi-vst: a DSSI plugin wrapper for VST effects and instruments + Copyright 2004-2006 Chris Cannam +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#include "aeffectx.h" + +#include "remotepluginserver.h" + +#include "paths.h" +#include "rdwrops.h" + +#define APPLICATION_CLASS_NAME "dssi_vst" +#define OLD_PLUGIN_ENTRY_POINT "main" +#define NEW_PLUGIN_ENTRY_POINT "VSTPluginMain" + +#if VST_FORCE_DEPRECATED +#define DEPRECATED_VST_SYMBOL(x) __##x##Deprecated +#else +#define DEPRECATED_VST_SYMBOL(x) x +#endif + +struct Rect { + short top; + short left; + short bottom; + short right; +}; + +static bool inProcessThread = false; +static HANDLE audioThreadHandle = 0; +static bool exiting = false; +static HWND hWnd = 0; +static double currentSamplePosition = 0.0; + +static bool ready = false; +static bool alive = false; +static int bufferSize = 0; +static int sampleRate = 0; +static bool guiVisible = false; + +static RemotePluginDebugLevel debugLevel = RemotePluginDebugSetup; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +using namespace std; + +class RemoteVSTServer : public RemotePluginServer +{ +public: + RemoteVSTServer(std::string fileIdentifiers, AEffect *plugin, std::string fallbackName); + virtual ~RemoteVSTServer(); + + virtual bool isReady() { return ready; } + + virtual std::string getName() { return m_name; } + virtual std::string getMaker() { return m_maker; } + virtual void setBufferSize(int); + virtual void setSampleRate(int); + virtual void reset(); + virtual void terminate(); + + virtual int getInputCount() { return m_plugin->numInputs; } + virtual int getOutputCount() { return m_plugin->numOutputs; } + + virtual int getParameterCount() { return m_plugin->numParams; } + virtual std::string getParameterName(int); + virtual void setParameter(int, float); + virtual float getParameter(int); + virtual float getParameterDefault(int); + virtual void getParameters(int, int, float *); + + virtual int getProgramCount() { return m_plugin->numPrograms; } + virtual std::string getProgramName(int); + virtual void setCurrentProgram(int); + + virtual bool hasMIDIInput() { return m_hasMIDI; } + virtual void sendMIDIData(unsigned char *data, + int *frameOffsets, + int events); + + virtual void showGUI(std::string); + virtual void hideGUI(); + + virtual void process(float **inputs, float **outputs); + + virtual void setDebugLevel(RemotePluginDebugLevel level) { + debugLevel = level; + } + + virtual bool warn(std::string); + + void startEdit(); + void endEdit(); + void monitorEdits(); + void scheduleGUINotify(int index, float value); + void notifyGUI(int index, float value); + void checkGUIExited(); + void terminateGUIProcess(); + +private: + AEffect *m_plugin; + + std::string m_name; + std::string m_maker; + + // These should be referred to from the GUI thread only + std::string m_guiFifoFile; + int m_guiFifoFd; + int m_guiEventsExpected; + struct timeval m_lastGuiComms; + + // To be written by the audio thread and read by the GUI thread +#define PARAMETER_CHANGE_COUNT 200 + int m_paramChangeIndices[PARAMETER_CHANGE_COUNT]; + float m_paramChangeValues[PARAMETER_CHANGE_COUNT]; + int m_paramChangeReadIndex; + int m_paramChangeWriteIndex; + + enum { + EditNone, + EditStarted, + EditFinished + } m_editLevel; + + float *m_defaults; + float *m_values; + bool m_hasMIDI; +}; + +static RemoteVSTServer *remoteVSTServerInstance = 0; + +RemoteVSTServer::RemoteVSTServer(std::string fileIdentifiers, + AEffect *plugin, std::string fallbackName) : + RemotePluginServer(fileIdentifiers), + m_plugin(plugin), + m_name(fallbackName), + m_maker(""), + m_guiFifoFile(""), + m_guiFifoFd(-1), + m_guiEventsExpected(0), + m_paramChangeReadIndex(0), + m_paramChangeWriteIndex(0), + m_editLevel(EditNone) +{ + pthread_mutex_lock(&mutex); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: opening plugin" << endl; + } + + m_plugin->dispatcher(m_plugin, effOpen, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 0, NULL, 0); + + m_hasMIDI = false; + + if (m_plugin->dispatcher(m_plugin, effGetVstVersion, 0, 0, NULL, 0) < 2) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin is VST 1.x" << endl; + } + } else { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin is VST 2.0 or newer" << endl; + } + if ((m_plugin->flags & effFlagsIsSynth)) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin is a synth" << endl; + } + m_hasMIDI = true; + } else { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin is not a synth" << endl; + } + if (m_plugin->dispatcher(m_plugin, effCanDo, 0, 0, (void *)"receiveVstMidiEvent", 0) > 0) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin can receive MIDI anyway" << endl; + } + m_hasMIDI = true; + } + } + } + + char buffer[65]; + buffer[0] = '\0'; + m_plugin->dispatcher(m_plugin, effGetEffectName, 0, 0, buffer, 0); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin name is \"" << buffer + << "\"" << endl; + } + if (buffer[0]) m_name = buffer; + + buffer[0] = '\0'; + m_plugin->dispatcher(m_plugin, effGetVendorString, 0, 0, buffer, 0); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: vendor string is \"" << buffer + << "\"" << endl; + } + if (buffer[0]) m_maker = buffer; + + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 1, NULL, 0); + + m_defaults = new float[m_plugin->numParams]; + m_values = new float[m_plugin->numParams]; + for (int i = 0; i < m_plugin->numParams; ++i) { + m_defaults[i] = m_plugin->getParameter(m_plugin, i); + m_values[i] = m_defaults[i]; + } + + pthread_mutex_unlock(&mutex); +} + +RemoteVSTServer::~RemoteVSTServer() +{ + pthread_mutex_lock(&mutex); + + if (m_guiFifoFd >= 0) { + try { + writeOpcode(m_guiFifoFd, RemotePluginTerminate); + } catch (...) { } + close(m_guiFifoFd); + } + + if (guiVisible) { + ShowWindow(hWnd, SW_HIDE); + UpdateWindow(hWnd); + m_plugin->dispatcher(m_plugin, effEditClose, 0, 0, 0, 0); + guiVisible = false; + } + + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effClose, 0, 0, NULL, 0); + delete[] m_defaults; + + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::process(float **inputs, float **outputs) +{ + if (pthread_mutex_trylock(&mutex)) { + for (int i = 0; i < m_plugin->numOutputs; ++i) { + memset(outputs[i], 0, bufferSize * sizeof(float)); + } + currentSamplePosition += bufferSize; + return; + } + + inProcessThread = true; + + // superclass guarantees setBufferSize will be called before this + m_plugin->processReplacing(m_plugin, inputs, outputs, bufferSize); + currentSamplePosition += bufferSize; + + inProcessThread = false; + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::setBufferSize(int sz) +{ + pthread_mutex_lock(&mutex); + + if (bufferSize != sz) { + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effSetBlockSize, 0, sz, NULL, 0); + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 1, NULL, 0); + bufferSize = sz; + } + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: set buffer size to " << sz << endl; + } + + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::setSampleRate(int sr) +{ + pthread_mutex_lock(&mutex); + + if (sampleRate != sr) { + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effSetSampleRate, 0, 0, NULL, (float)sr); + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 1, NULL, 0); + sampleRate = sr; + } + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: set sample rate to " << sr << endl; + } + + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::reset() +{ + pthread_mutex_lock(&mutex); + + cerr << "dssi-vst-server[1]: reset" << endl; + + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effMainsChanged, 0, 1, NULL, 0); + + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::terminate() +{ + cerr << "RemoteVSTServer::terminate: setting exiting flag" << endl; + exiting = true; +} + +std::string +RemoteVSTServer::getParameterName(int p) +{ + char name[24]; + m_plugin->dispatcher(m_plugin, effGetParamName, p, 0, name, 0); + return name; +} + +void +RemoteVSTServer::setParameter(int p, float v) +{ + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: setParameter (" << p << "," << v << ")" << endl; + } + + pthread_mutex_lock(&mutex); + + cerr << "RemoteVSTServer::setParameter (" << p << "," << v << "): " << m_guiEventsExpected << " events expected" << endl; + + if (m_guiFifoFd < 0) { + m_guiEventsExpected = 0; + } + + if (m_guiEventsExpected > 0) { + + //!!! should be per-parameter of course! + + struct timeval tv; + gettimeofday(&tv, NULL); + + if (tv.tv_sec > m_lastGuiComms.tv_sec + 10) { + m_guiEventsExpected = 0; + } else { + --m_guiEventsExpected; + cerr << "Reduced to " << m_guiEventsExpected << endl; + pthread_mutex_unlock(&mutex); + return; + } + } + + pthread_mutex_unlock(&mutex); + + m_plugin->setParameter(m_plugin, p, v); +} + +float +RemoteVSTServer::getParameter(int p) +{ + return m_plugin->getParameter(m_plugin, p); +} + +float +RemoteVSTServer::getParameterDefault(int p) +{ + return m_defaults[p]; +} + +void +RemoteVSTServer::getParameters(int p0, int pn, float *v) +{ + for (int i = p0; i <= pn; ++i) { + v[i - p0] = m_plugin->getParameter(m_plugin, i); + } +} + +std::string +RemoteVSTServer::getProgramName(int p) +{ + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: getProgramName(" << p << ")" << endl; + } + + pthread_mutex_lock(&mutex); + + char name[24]; + // effGetProgramName appears to return the name of the current + // program, not program -- though we pass in as + // well, just in case + long prevProgram = + m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, NULL, 0); + m_plugin->dispatcher(m_plugin, effSetProgram, 0, p, NULL, 0); + m_plugin->dispatcher(m_plugin, effGetProgramName, p, 0, name, 0); + m_plugin->dispatcher(m_plugin, effSetProgram, 0, prevProgram, NULL, 0); + + pthread_mutex_unlock(&mutex); + return name; +} + +void +RemoteVSTServer::setCurrentProgram(int p) +{ + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: setCurrentProgram(" << p << ")" << endl; + } + + pthread_mutex_lock(&mutex); + + m_plugin->dispatcher(m_plugin, effSetProgram, 0, p, 0, 0); + + pthread_mutex_unlock(&mutex); +} + +void +RemoteVSTServer::sendMIDIData(unsigned char *data, int *frameOffsets, int events) +{ +#define MIDI_EVENT_BUFFER_COUNT 1024 + static VstMidiEvent vme[MIDI_EVENT_BUFFER_COUNT]; + static char evbuf[sizeof(VstMidiEvent *) * MIDI_EVENT_BUFFER_COUNT + + sizeof(VstEvents)]; + + VstEvents *vstev = (VstEvents *)evbuf; + vstev->reserved = 0; + + int ix = 0; + + if (events > MIDI_EVENT_BUFFER_COUNT) { + std::cerr << "vstserv: WARNING: " << events << " MIDI events received " + << "for " << MIDI_EVENT_BUFFER_COUNT << "-event buffer" + << std::endl; + events = MIDI_EVENT_BUFFER_COUNT; + } + + while (ix < events) { + + vme[ix].type = kVstMidiType; + vme[ix].byteSize = 24; + vme[ix].deltaFrames = (frameOffsets ? frameOffsets[ix] : 0); + vme[ix].flags = 0; + vme[ix].noteLength = 0; + vme[ix].noteOffset = 0; + vme[ix].detune = 0; + vme[ix].noteOffVelocity = 0; + vme[ix].reserved1 = 0; + vme[ix].reserved2 = 0; + vme[ix].midiData[0] = data[ix*3]; + vme[ix].midiData[1] = data[ix*3+1]; + vme[ix].midiData[2] = data[ix*3+2]; + vme[ix].midiData[3] = 0; + + vstev->events[ix] = (VstEvent *)&vme[ix]; + + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: MIDI event in: " + << (int)data[ix*3] << " " + << (int)data[ix*3+1] << " " + << (int)data[ix*3+2] << endl; + } + + ++ix; + } + + pthread_mutex_lock(&mutex); + + vstev->numEvents = events; + if (!m_plugin->dispatcher(m_plugin, effProcessEvents, 0, 0, vstev, 0)) { + cerr << "WARNING: " << ix << " MIDI event(s) rejected by plugin" << endl; + } + + pthread_mutex_unlock(&mutex); +} + +bool +RemoteVSTServer::warn(std::string warning) +{ + if (hWnd) MessageBox(hWnd, warning.c_str(), "Error", 0); + return true; +} + +void +RemoteVSTServer::showGUI(std::string guiData) +{ + if (debugLevel > 0) { + cerr << "RemoteVSTServer::showGUI(" << guiData << "): guiVisible is " << guiVisible << endl; + } + + if (guiVisible) return; + + if (guiData != m_guiFifoFile || m_guiFifoFd < 0) { + + if (m_guiFifoFd >= 0) { + close(m_guiFifoFd); + m_guiFifoFd = -1; + } + + m_guiFifoFile = guiData; + + if ((m_guiFifoFd = open(m_guiFifoFile.c_str(), O_WRONLY | O_NONBLOCK)) < 0) { + perror(m_guiFifoFile.c_str()); + cerr << "WARNING: Failed to open FIFO to GUI manager process" << endl; + pthread_mutex_unlock(&mutex); + return; + } + + writeOpcode(m_guiFifoFd, RemotePluginIsReady); + } + + m_plugin->dispatcher(m_plugin, effEditOpen, 0, 0, hWnd, 0); + Rect *rect = 0; + m_plugin->dispatcher(m_plugin, effEditGetRect, 0, 0, &rect, 0); + if (!rect) { + cerr << "dssi-vst-server: ERROR: Plugin failed to report window size\n" << endl; + } else { + // Seems we need to provide space in here for the titlebar + // and frame, even though we don't know how big they'll + // be! How crap. + SetWindowPos(hWnd, 0, 0, 0, + rect->right - rect->left + 6, + rect->bottom - rect->top + 25, + SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOZORDER); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: sized window" << endl; + } + + ShowWindow(hWnd, SW_SHOWNORMAL); + UpdateWindow(hWnd); + guiVisible = true; + } + + m_paramChangeReadIndex = m_paramChangeWriteIndex; +} + +void +RemoteVSTServer::hideGUI() +{ + if (!guiVisible) return; + + if (m_guiFifoFd >= 0) { + int fd = m_guiFifoFd; + m_guiFifoFd = -1; + close(m_guiFifoFd); + } + + ShowWindow(hWnd, SW_HIDE); + UpdateWindow(hWnd); + m_plugin->dispatcher(m_plugin, effEditClose, 0, 0, 0, 0); + guiVisible = false; +} + +void +RemoteVSTServer::startEdit() +{ + m_editLevel = EditStarted; +} + +void +RemoteVSTServer::endEdit() +{ + m_editLevel = EditFinished; +} + +void +RemoteVSTServer::monitorEdits() +{ + if (m_editLevel != EditNone) { + + if (m_editLevel == EditFinished) m_editLevel = EditNone; + + for (int i = 0; i < m_plugin->numParams; ++i) { + float actual = m_plugin->getParameter(m_plugin, i); + if (actual != m_values[i]) { + m_values[i] = actual; + notifyGUI(i, actual); + } + } + } + + while (m_paramChangeReadIndex != m_paramChangeWriteIndex) { + int index = m_paramChangeIndices[m_paramChangeReadIndex]; + float value = m_paramChangeValues[m_paramChangeReadIndex]; + if (value != m_values[index]) { + m_values[index] = value; + notifyGUI(index, value); + } + m_paramChangeReadIndex = + (m_paramChangeReadIndex + 1) % PARAMETER_CHANGE_COUNT; + } +} + +void +RemoteVSTServer::scheduleGUINotify(int index, float value) +{ + int ni = (m_paramChangeWriteIndex + 1) % PARAMETER_CHANGE_COUNT; + if (ni == m_paramChangeReadIndex) return; + + m_paramChangeIndices[m_paramChangeWriteIndex] = index; + m_paramChangeValues[m_paramChangeWriteIndex] = value; + + m_paramChangeWriteIndex = ni; +} + +void +RemoteVSTServer::notifyGUI(int index, float value) +{ + if (m_guiFifoFd >= 0) { + + if (debugLevel > 1) { + cerr << "RemoteVSTServer::notifyGUI(" << index << "," << value << "): about to lock" << endl; + } + + try { + writeOpcode(m_guiFifoFd, RemotePluginSetParameter); + int i = (int)index; + writeInt(m_guiFifoFd, i); + writeFloat(m_guiFifoFd, value); + + gettimeofday(&m_lastGuiComms, NULL); + ++m_guiEventsExpected; + + } catch (RemotePluginClosedException e) { + hideGUI(); + } + + if (debugLevel > 1) { + cerr << "wrote (" << index << "," << value << ") to gui (" << m_guiEventsExpected << " events expected now)" << endl; + } + } +} + +void +RemoteVSTServer::checkGUIExited() +{ + if (m_guiFifoFd >= 0) { + + struct pollfd pfd; + pfd.fd = m_guiFifoFd; + pfd.events = POLLHUP; + + if (poll(&pfd, 1, 0) != 0) { + m_guiFifoFd = -1; + } + + } +} + +void +RemoteVSTServer::terminateGUIProcess() +{ + if (m_guiFifoFd >= 0) { + writeOpcode(m_guiFifoFd, RemotePluginTerminate); + m_guiFifoFd = -1; + } +} + +#if VST_2_4_EXTENSIONS +VstIntPtr VSTCALLBACK +hostCallback(AEffect *plugin, VstInt32 opcode, VstInt32 index, + VstIntPtr value, void *ptr, float opt) +#else +long VSTCALLBACK +hostCallback(AEffect *plugin, long opcode, long index, + long value, void *ptr, float opt) +#endif +{ + static VstTimeInfo timeInfo; + int rv = 0; + + switch (opcode) { + + case audioMasterAutomate: + { + /*!!! Automation: + + When something changes here, we send it straight to the GUI + via our back channel. The GUI sends it back to the host via + configure; that comes to us; and we somehow need to know to + ignore it. Checking whether it's the same as the existing + param value won't cut it, as we might be changing that + continuously. (Shall we record that we're expecting the + configure call because we just sent to the GUI?) + + */ + + float v = plugin->getParameter(plugin, index); + + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterAutomate(" << index << "," << v << ")" << endl; + + remoteVSTServerInstance->scheduleGUINotify(index, v); + + break; + } + + case audioMasterVersion: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterVersion requested" << endl; + rv = 2300; + break; + + case audioMasterCurrentId: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterCurrentId requested" << endl; + rv = 0; + break; + + case audioMasterIdle: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterIdle requested" << endl; + plugin->dispatcher(plugin, effEditIdle, 0, 0, 0, 0); + break; + + case DEPRECATED_VST_SYMBOL(audioMasterPinConnected): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterPinConnected requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterWantMidi): + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: audioMasterWantMidi requested" << endl; + } + // happy to oblige + rv = 1; + break; + + case audioMasterGetTime: +// if (debugLevel > 1) +// cerr << "dssi-vst-server[2]: audioMasterGetTime requested" << endl; + timeInfo.samplePos = currentSamplePosition; + timeInfo.sampleRate = sampleRate; + timeInfo.flags = 0; // don't mark anything valid except default samplePos/Rate + rv = (long)&timeInfo; + break; + + case audioMasterProcessEvents: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterProcessEvents requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterSetTime): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterSetTime requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterTempoAt): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterTempoAt requested" << endl; + // can't support this, return 120bpm + rv = 120 * 10000; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetNumAutomatableParameters): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetNumAutomatableParameters requested" << endl; + rv = 5000; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetParameterQuantization): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetParameterQuantization requested" << endl; + rv = 1; + break; + + case audioMasterIOChanged: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterIOChanged requested" << endl; + cerr << "WARNING: Plugin inputs and/or outputs changed: NOT SUPPORTED" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterNeedIdle): + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: audioMasterNeedIdle requested" << endl; + } + // might be nice to handle this better + rv = 1; + break; + + case audioMasterSizeWindow: + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: audioMasterSizeWindow requested" << endl; + } + if (hWnd) { + SetWindowPos(hWnd, 0, 0, 0, + index + 6, + value + 25, + SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOZORDER); + } + rv = 1; + break; + + case audioMasterGetSampleRate: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetSampleRate requested" << endl; + if (!sampleRate) { + cerr << "WARNING: Sample rate requested but not yet set" << endl; + } + plugin->dispatcher(plugin, effSetSampleRate, + 0, 0, NULL, (float)sampleRate); + break; + + case audioMasterGetBlockSize: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetBlockSize requested" << endl; + if (!bufferSize) { + cerr << "WARNING: Buffer size requested but not yet set" << endl; + } + plugin->dispatcher(plugin, effSetBlockSize, + 0, bufferSize, NULL, 0); + break; + + case audioMasterGetInputLatency: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetInputLatency requested" << endl; + break; + + case audioMasterGetOutputLatency: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetOutputLatency requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetPreviousPlug): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetPreviousPlug requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetNextPlug): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetNextPlug requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterWillReplaceOrAccumulate): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterWillReplaceOrAccumulate requested" << endl; + // 0 -> unsupported, 1 -> replace, 2 -> accumulate + rv = 1; + break; + + case audioMasterGetCurrentProcessLevel: + if (debugLevel > 1) { + cerr << "dssi-vst-server[2]: audioMasterGetCurrentProcessLevel requested (level is " << (inProcessThread ? 2 : 1) << ")" << endl; + } + // 0 -> unsupported, 1 -> gui, 2 -> process, 3 -> midi/timer, 4 -> offline + if (inProcessThread) rv = 2; + else rv = 1; + break; + + case audioMasterGetAutomationState: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetAutomationState requested" << endl; + rv = 4; // read/write + break; + + case audioMasterOfflineStart: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOfflineStart requested" << endl; + break; + + case audioMasterOfflineRead: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOfflineRead requested" << endl; + break; + + case audioMasterOfflineWrite: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOfflineWrite requested" << endl; + break; + + case audioMasterOfflineGetCurrentPass: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOfflineGetCurrentPass requested" << endl; + break; + + case audioMasterOfflineGetCurrentMetaPass: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOfflineGetCurrentMetaPass requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterSetOutputSampleRate): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterSetOutputSampleRate requested" << endl; + break; + +/* Deprecated in VST 2.4 and also (accidentally?) renamed in the SDK header, + so we won't retain it here + case audioMasterGetSpeakerArrangement: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetSpeakerArrangement requested" << endl; + break; +*/ + case audioMasterGetVendorString: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetVendorString requested" << endl; + strcpy((char *)ptr, "Chris Cannam"); + break; + + case audioMasterGetProductString: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetProductString requested" << endl; + strcpy((char *)ptr, "DSSI VST Wrapper Plugin"); + break; + + case audioMasterGetVendorVersion: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetVendorVersion requested" << endl; + rv = long(RemotePluginVersion * 100); + break; + + case audioMasterVendorSpecific: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterVendorSpecific requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterSetIcon): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterSetIcon requested" << endl; + break; + + case audioMasterCanDo: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterCanDo(" << (char *)ptr + << ") requested" << endl; + if (!strcmp((char*)ptr, "sendVstEvents") || + !strcmp((char*)ptr, "sendVstMidiEvent") || + !strcmp((char*)ptr, "sendVstTimeInfo") || + !strcmp((char*)ptr, "sizeWindow") /* || + !strcmp((char*)ptr, "supplyIdle") */) { + rv = 1; + } + break; + + case audioMasterGetLanguage: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetLanguage requested" << endl; + rv = kVstLangEnglish; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterOpenWindow): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOpenWindow requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterCloseWindow): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterCloseWindow requested" << endl; + break; + + case audioMasterGetDirectory: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetDirectory requested" << endl; + break; + + case audioMasterUpdateDisplay: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterUpdateDisplay requested" << endl; + break; + + case audioMasterBeginEdit: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterBeginEdit requested" << endl; + remoteVSTServerInstance->startEdit(); + break; + + case audioMasterEndEdit: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterEndEdit requested" << endl; + remoteVSTServerInstance->endEdit(); + break; + + case audioMasterOpenFileSelector: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterOpenFileSelector requested" << endl; + break; + + case audioMasterCloseFileSelector: + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterCloseFileSelector requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterEditFile): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterEditFile requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetChunkFile): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetChunkFile requested" << endl; + break; + + case DEPRECATED_VST_SYMBOL(audioMasterGetInputSpeakerArrangement): + if (debugLevel > 1) + cerr << "dssi-vst-server[2]: audioMasterGetInputSpeakerArrangement requested" << endl; + break; + + default: + if (debugLevel > 0) { + cerr << "dssi-vst-server[0]: unsupported audioMaster callback opcode " + << opcode << endl; + } + } + + return rv; +}; + +DWORD WINAPI +WatchdogThreadMain(LPVOID parameter) +{ + struct sched_param param; + param.sched_priority = 2; + int result = sched_setscheduler(0, SCHED_FIFO, ¶m); + if (result < 0) { + perror("Failed to set realtime priority for watchdog thread"); + } + + int count = 0; + + while (!exiting) { + if (!alive) { + ++count; + } + if (count == 20) { + cerr << "Remote VST plugin watchdog: terminating audio thread" << endl; + // bam + TerminateThread(audioThreadHandle, 0); + exiting = 1; + break; + } else { +// cerr << "Remote VST plugin watchdog: OK, count is " << count << endl; + } + sleep(1); + } + + cerr << "Remote VST plugin watchdog thread: returning" << endl; + + param.sched_priority = 0; + (void)sched_setscheduler(0, SCHED_OTHER, ¶m); + return 0; +} + +DWORD WINAPI +AudioThreadMain(LPVOID parameter) +{ + struct sched_param param; + param.sched_priority = 1; + HANDLE watchdogThreadHandle; + + int result = sched_setscheduler(0, SCHED_FIFO, ¶m); + + if (result < 0) { + perror("Failed to set realtime priority for audio thread"); + } else { + // Start a watchdog thread as well + DWORD watchdogThreadId = 0; + watchdogThreadHandle = + CreateThread(0, 0, WatchdogThreadMain, 0, 0, &watchdogThreadId); + if (!watchdogThreadHandle) { + cerr << "Failed to create watchdog thread -- not using RT priority for audio thread" << endl; + param.sched_priority = 0; + (void)sched_setscheduler(0, SCHED_OTHER, ¶m); + } + } + + while (!exiting) { + alive = true; + try { + // This can call sendMIDIData, setCurrentProgram, process + remoteVSTServerInstance->dispatchProcess(50); + } catch (std::string message) { + cerr << "ERROR: Remote VST server instance failed: " << message << endl; + exiting = true; + } catch (RemotePluginClosedException) { + cerr << "ERROR: Remote VST plugin communication failure" << endl; + exiting = true; + } + } + + cerr << "Remote VST plugin audio thread: returning" << endl; + + param.sched_priority = 0; + (void)sched_setscheduler(0, SCHED_OTHER, ¶m); + + if (watchdogThreadHandle) { + TerminateThread(watchdogThreadHandle, 0); + CloseHandle(watchdogThreadHandle); + } + return 0; +} + +LRESULT WINAPI +MainProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_DESTROY: + remoteVSTServerInstance->terminateGUIProcess(); + guiVisible = false; + return 0; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +int WINAPI +WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) +{ + char *libname = 0; + char *fileInfo = 0; + bool tryGui = false, haveGui = true; + + cout << "DSSI VST plugin server v" << RemotePluginVersion << endl; + cout << "Copyright (c) 2004-2006 Chris Cannam" << endl; + + char *home = getenv("HOME"); + + if (cmdline) { + int offset = 0; + if (cmdline[0] == '"' || cmdline[0] == '\'') offset = 1; + if (!strncmp(&cmdline[offset], "-g ", 3)) { + tryGui = true; + offset += 3; + } + for (int ci = offset; cmdline[ci]; ++ci) { + if (cmdline[ci] == ',') { + libname = strndup(cmdline + offset, ci - offset); + ++ci; + if (cmdline[ci]) { + fileInfo = strdup(cmdline + ci); + int l = strlen(fileInfo); + if (fileInfo[l-1] == '"' || + fileInfo[l-1] == '\'') { + fileInfo[l-1] = '\0'; + } + } + } + } + } + + if (!libname || !libname[0] || !fileInfo || !fileInfo[0]) { + cerr << "Usage: dssi-vst-server ," << endl; + cerr << "(Command line was: " << cmdline << ")" << endl; + exit(2); + } + + // LADSPA labels can't contain spaces so dssi-vst replaces spaces + // with asterisks. + for (int ci = 0; libname[ci]; ++ci) { + if (libname[ci] == '*') libname[ci] = ' '; + } + + cout << "Loading \"" << libname << "\"... "; + if (debugLevel > 0) cout << endl; + + HINSTANCE libHandle = 0; + + std::vector vstPath = Paths::getPath + ("VST_PATH", "/usr/local/lib/vst:/usr/lib/vst", "/vst"); + + for (size_t i = 0; i < vstPath.size(); ++i) { + + std::string vstDir = vstPath[i]; + std::string libPath; + + if (vstDir[vstDir.length()-1] == '/') { + libPath = vstDir + libname; + } else { + libPath = vstDir + "/" + libname; + } + + libHandle = LoadLibrary(libPath.c_str()); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: " << (libHandle ? "" : "not ") + << "found in " << libPath << endl; + } + + if (!libHandle) { + if (home && home[0] != '\0') { + if (libPath.substr(0, strlen(home)) == home) { + libPath = libPath.substr(strlen(home) + 1); + } + libHandle = LoadLibrary(libPath.c_str()); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: " << (libHandle ? "" : "not ") + << "found in " << libPath << endl; + } + } + } + + if (libHandle) break; + } + + if (!libHandle) { + libHandle = LoadLibrary(libname); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: " << (libHandle ? "" : "not ") + << "found in DLL path" << endl; + } + } + + if (!libHandle) { + cerr << "dssi-vst-server: ERROR: Couldn't load VST DLL \"" << libname << "\"" << endl; + return 1; + } + + cout << "done" << endl; + + cout << "Testing VST compatibility... "; + if (debugLevel > 0) cout << endl; + +//!!! better debug level support + + AEffect *(__stdcall* getInstance)(audioMasterCallback); + + getInstance = (AEffect*(__stdcall*)(audioMasterCallback)) + GetProcAddress(libHandle, NEW_PLUGIN_ENTRY_POINT); + + if (!getInstance) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: VST 2.4 entrypoint \"" + << NEW_PLUGIN_ENTRY_POINT << "\" not found in DLL \"" + << libname << "\", looking for \"" + << OLD_PLUGIN_ENTRY_POINT << "\"" << endl; + } + + getInstance = (AEffect*(__stdcall*)(audioMasterCallback)) + GetProcAddress(libHandle, OLD_PLUGIN_ENTRY_POINT); + + if (!getInstance) { + cerr << "dssi-vst-server: ERROR: VST entrypoints \"" + << NEW_PLUGIN_ENTRY_POINT << "\" or \"" + << OLD_PLUGIN_ENTRY_POINT << "\" not found in DLL \"" + << libname << "\"" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: VST entrypoint \"" + << OLD_PLUGIN_ENTRY_POINT << "\" found" << endl; + } + + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: VST entrypoint \"" + << NEW_PLUGIN_ENTRY_POINT << "\" found" << endl; + } + + AEffect *plugin = getInstance(hostCallback); + + if (!plugin) { + cerr << "dssi-vst-server: ERROR: Failed to instantiate plugin in VST DLL \"" + << libname << "\"" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin instantiated" << endl; + } + + if (plugin->magic != kEffectMagic) { + cerr << "dssi-vst-server: ERROR: Not a VST plugin in DLL \"" << libname << "\"" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin is a VST" << endl; + } + + if (!(plugin->flags & effFlagsHasEditor)) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: Plugin has no GUI" << endl; + } + haveGui = false; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin has a GUI" << endl; + } + + if (!plugin->flags & effFlagsCanReplacing) { + cerr << "dssi-vst-server: ERROR: Plugin does not support processReplacing (required)" + << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin supports processReplacing" << endl; + } + + try { + remoteVSTServerInstance = + new RemoteVSTServer(fileInfo, plugin, libname); + } catch (std::string message) { + cerr << "ERROR: Remote VST startup failed: " << message << endl; + return 1; + } catch (RemotePluginClosedException) { + cerr << "ERROR: Remote VST plugin communication failure" << endl; + return 1; + } + + cout << "Initialising Windows subsystem... "; + if (debugLevel > 0) cout << endl; + + WNDCLASSEX wclass; + wclass.cbSize = sizeof(WNDCLASSEX); + wclass.style = 0; + wclass.lpfnWndProc = MainProc; + wclass.cbClsExtra = 0; + wclass.cbWndExtra = 0; + wclass.hInstance = hInst; + wclass.hIcon = LoadIcon(hInst, APPLICATION_CLASS_NAME); + wclass.hCursor = LoadCursor(0, IDI_APPLICATION); +// wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wclass.lpszMenuName = "MENU_DSSI_VST"; + wclass.lpszClassName = APPLICATION_CLASS_NAME; + wclass.hIconSm = 0; + + if (!RegisterClassEx(&wclass)) { + cerr << "dssi-vst-server: ERROR: Failed to register Windows application class!\n" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: registered Windows application class \"" << APPLICATION_CLASS_NAME << "\"" << endl; + } + + hWnd = CreateWindow + (APPLICATION_CLASS_NAME, remoteVSTServerInstance->getName().c_str(), + WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + 0, 0, hInst, 0); + if (!hWnd) { + cerr << "dssi-vst-server: ERROR: Failed to create window!\n" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: created main window" << endl; + } + + if (!haveGui) { + cerr << "Should be showing message here" << endl; + } else { + + if (tryGui) { + + plugin->dispatcher(plugin, effEditOpen, 0, 0, hWnd, 0); + Rect *rect = 0; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &rect, 0); + if (!rect) { + cerr << "dssi-vst-server: ERROR: Plugin failed to report window size\n" << endl; + return 1; + } + + // Seems we need to provide space in here for the titlebar + // and frame, even though we don't know how big they'll + // be! How crap. + SetWindowPos(hWnd, 0, 0, 0, + rect->right - rect->left + 6, + rect->bottom - rect->top + 25, + SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOZORDER); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: sized window" << endl; + } + + ShowWindow(hWnd, SW_SHOWNORMAL); + UpdateWindow(hWnd); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: showed window" << endl; + } + + guiVisible = true; + } + } + + cout << "done" << endl; + + DWORD threadId = 0; + audioThreadHandle = CreateThread(0, 0, AudioThreadMain, 0, 0, &threadId); + if (!audioThreadHandle) { + cerr << "Failed to create audio thread!" << endl; + delete remoteVSTServerInstance; + FreeLibrary(libHandle); + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: created audio thread" << endl; + } + + ready = true; + + MSG msg; + exiting = false; + while (!exiting) { + + while (!exiting && PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + DispatchMessage(&msg); + } + + if (tryGui && haveGui && !guiVisible) { + // Running in GUI-always-on mode and GUI has exited: follow it + exiting = true; + } + + if (exiting) break; + + try { + if (guiVisible) { + remoteVSTServerInstance->dispatchControl(10); + } else { + remoteVSTServerInstance->dispatchControl(500); + } + } catch (RemotePluginClosedException) { + cerr << "ERROR: Remote VST plugin communication failure" << endl; + exiting = true; + break; + } + + remoteVSTServerInstance->checkGUIExited(); + remoteVSTServerInstance->monitorEdits(); + } + + // wait for audio thread to catch up + sleep(1); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: cleaning up" << endl; + } + + CloseHandle(audioThreadHandle); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: closed audio thread" << endl; + } + + delete remoteVSTServerInstance; + remoteVSTServerInstance = 0; + + FreeLibrary(libHandle); + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: freed dll" << endl; + } + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: exiting" << endl; + } + + return 0; +} + diff --git a/dssi-vst.cpp b/dssi-vst.cpp new file mode 100644 index 0000000..e3f2f22 --- /dev/null +++ b/dssi-vst.cpp @@ -0,0 +1,703 @@ + +/* -*- c-basic-offset: 4 -*- */ + +/* + dssi-vst: a DSSI plugin wrapper for VST effects and instruments + Copyright 2004-2006 Chris Cannam +*/ + +#include "remotevstclient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Should be divisible by three +#define MIDI_BUFFER_SIZE 1023 + +class DSSIVSTPluginInstance +{ +public: + static void freeFields(DSSI_Descriptor &descriptor); + + DSSIVSTPluginInstance(std::string dllName, + unsigned long sampleRate); + virtual ~DSSIVSTPluginInstance(); + + bool isOK() { return m_ok; } + + // LADSPA methods: + + void activate(); + void deactivate(); + void connectPort(unsigned long port, LADSPA_Data *location); + void run(unsigned long sampleCount); + + // DSSI methods: + + const DSSI_Program_Descriptor *getProgram(unsigned long index); + void selectProgram(unsigned long bank, unsigned long program); + void runSynth(unsigned long sampleCount, + snd_seq_event_t *events, unsigned long eventCount); + std::string configure(std::string key, std::string value); + +protected: + static RemotePluginClient *load(std::string name); + + unsigned long m_sampleRate; + unsigned long m_lastSampleCount; + + LADSPA_Data **m_controlPorts; + LADSPA_Data *m_controlPortsSaved; + unsigned long m_controlPortCount; + + LADSPA_Data **m_audioIns; + unsigned long m_audioInCount; + + LADSPA_Data **m_audioOuts; + unsigned long m_audioOutCount; + + LADSPA_Data *m_latencyOut; + + DSSI_Program_Descriptor **m_programs; + unsigned long m_programCount; + + unsigned char m_decodeBuffer[MIDI_BUFFER_SIZE]; + int m_frameOffsetsBuffer[MIDI_BUFFER_SIZE / 3]; + snd_midi_event_t *m_alsaDecoder; + + bool m_pendingProgram; + + RemotePluginClient *m_plugin; + bool m_ok; +}; + +class DSSIVSTPlugin +{ +public: + DSSIVSTPlugin(); + virtual ~DSSIVSTPlugin(); + + DSSI_Descriptor *queryDescriptor(unsigned long index); + + // LADSPA methods: + + static LADSPA_Handle instantiate(const LADSPA_Descriptor *descriptor, + unsigned long sampleRate); + + static void connect_port(LADSPA_Handle instance, + unsigned long port, + LADSPA_Data *location); + + static void activate(LADSPA_Handle instance); + + static void run(LADSPA_Handle instance, + unsigned long sampleCount); + + static void deactivate(LADSPA_Handle instance); + + static void cleanup(LADSPA_Handle instance); + + // DSSI methods: + + static const DSSI_Program_Descriptor *get_program(LADSPA_Handle instance, + unsigned long index); + + static void select_program(LADSPA_Handle instance, + unsigned long bank, unsigned long program); + + static void run_synth(LADSPA_Handle instance, unsigned long sampleCount, + snd_seq_event_t *events, unsigned long eventCount); + + static char *configure(LADSPA_Handle instance, const char *key, + const char *value); + +private: + typedef std::pair PluginPair; + typedef std::vector PluginList; + PluginList m_descriptors; +}; + + +#define NO_CONTROL_DATA -10000000000000.0 + +DSSIVSTPluginInstance::DSSIVSTPluginInstance(std::string dllName, + unsigned long sampleRate) : + m_sampleRate(sampleRate), + m_lastSampleCount(0), + m_controlPorts(0), + m_controlPortsSaved(0), + m_controlPortCount(0), + m_audioIns(0), + m_audioInCount(0), + m_audioOuts(0), + m_audioOutCount(0), + m_programs(0), + m_programCount(0), + m_pendingProgram(false), + m_plugin(0), + m_ok(false) +{ + std::cerr << "DSSIVSTPluginInstance::DSSIVSTPluginInstance(" << dllName << ")" << std::endl; + + try { + m_plugin = new RemoteVSTClient(dllName); + + m_controlPortCount = m_plugin->getParameterCount(); + m_controlPorts = new LADSPA_Data*[m_controlPortCount]; + m_controlPortsSaved = new LADSPA_Data[m_controlPortCount]; + + for (unsigned long i = 0; i < m_controlPortCount; ++i) { + m_controlPortsSaved[i] = NO_CONTROL_DATA; + } + + m_audioInCount = m_plugin->getInputCount(); + m_audioIns = new LADSPA_Data*[m_audioInCount]; + + m_audioOutCount = m_plugin->getOutputCount(); + m_audioOuts = new LADSPA_Data*[m_audioOutCount]; + + m_programCount = m_plugin->getProgramCount(); + m_programs = new DSSI_Program_Descriptor*[m_programCount]; + for (unsigned long i = 0; i < m_programCount; ++i) { + m_programs[i] = new DSSI_Program_Descriptor; + m_programs[i]->Bank = 0; + m_programs[i]->Program = i; + m_programs[i]->Name = strdup(m_plugin->getProgramName(i).c_str()); + } + + snd_midi_event_new(MIDI_BUFFER_SIZE, &m_alsaDecoder); + if (!m_alsaDecoder) { + std::cerr << "DSSIVSTPluginInstance::DSSIVSTPluginInstance(" + << dllName << "): failed to initialize ALSA MIDI decoder" + << std::endl; + } else { + snd_midi_event_no_status(m_alsaDecoder, 1); + } + + std::cerr << "DSSIVSTPluginInstance(" << this << "): setting OK true" << std::endl; + + m_ok = true; + + } catch (RemotePluginClosedException) { + std::cerr << "DSSIVSTPluginInstance::DSSIVSTPluginInstance(" + << dllName << "): startup failed" << std::endl; + + m_ok = false; + delete m_plugin; m_plugin = 0; + delete m_controlPorts; m_controlPorts = 0; + delete m_controlPortsSaved; m_controlPortsSaved = 0; + delete m_audioIns; m_audioIns = 0; + delete m_audioOuts; m_audioOuts = 0; + } + + std::cerr << "DSSIVSTPluginInstance::DSSIVSTPluginInstance(" << dllName << ") construction complete" << std::endl; +} + +DSSIVSTPluginInstance::~DSSIVSTPluginInstance() +{ + std::cerr << "DSSIVSTPluginInstance::~DSSIVSTPluginInstance" << std::endl; + + if (m_ok) { + try { + std::cerr << "DSSIVSTPluginInstance::~DSSIVSTPluginInstance: asking plugin to terminate" << std::endl; + m_plugin->terminate(); + } catch (RemotePluginClosedException) { } + } + + delete m_plugin; + + if (m_alsaDecoder) { + snd_midi_event_free(m_alsaDecoder); + } + + delete m_controlPorts; + delete m_controlPortsSaved; + delete m_audioIns; + delete m_audioOuts; + + for (unsigned long i = 0; i < m_programCount; ++i) { + free((void *)m_programs[i]->Name); + delete m_programs[i]; + } + delete m_programs; +} + +void +DSSIVSTPluginInstance::activate() +{ + if (m_ok) { + try { + m_plugin->setSampleRate(m_sampleRate); + } catch (RemotePluginClosedException) { + m_ok = false; + } + } +} + +void +DSSIVSTPluginInstance::deactivate() +{ + if (m_ok) { + try { + m_plugin->reset(); + } catch (RemotePluginClosedException) { + m_ok = false; + } + } +} + +void +DSSIVSTPluginInstance::connectPort(unsigned long port, LADSPA_Data *location) +{ +// std::cerr << "connectPort(" << port << "," << location << ")" << std::endl; + + if (!m_ok) return; + + if (port < m_controlPortCount) { +// std::cerr << "(control port)" << std::endl; + m_controlPorts[port] = location; + return; + } + port -= m_controlPortCount; + + if (port < m_audioInCount) { +// std::cerr << "(audio in port)" << std::endl; + m_audioIns[port] = location; + return; + } + port -= m_audioInCount; + + if (port < m_audioOutCount) { +// std::cerr << "(audio out port)" << std::endl; + m_audioOuts[port] = location; + return; + } + port -= m_audioOutCount; + + if (port < 1) { // latency +// std::cerr << "(latency output port)" << std::endl; + m_latencyOut = location; + if (m_latencyOut) *m_latencyOut = 0; + return; + } +} + +const DSSI_Program_Descriptor * +DSSIVSTPluginInstance::getProgram(unsigned long index) +{ + if (index >= m_programCount) return 0; + m_programs[index]->Name = strdup(m_programs[index]->Name); // host frees + return m_programs[index]; +} + +void +DSSIVSTPluginInstance::selectProgram(unsigned long bank, unsigned long program) +{ + if (bank != 0 || program >= m_programCount) return; + + try { + m_plugin->setCurrentProgram(program); + m_plugin->getParameters(0, m_controlPortCount - 1, m_controlPortsSaved); + + for (unsigned long i = 0; i < m_controlPortCount; ++i) { + if (!m_controlPorts[i]) continue; + *m_controlPorts[i] = m_controlPortsSaved[i]; + } + + } catch (RemotePluginClosedException) { + m_ok = false; + return; + } +} + +void +DSSIVSTPluginInstance::run(unsigned long sampleCount) +{ + if (!m_ok) return; + + try { + if (sampleCount != m_lastSampleCount) { + m_plugin->setBufferSize(sampleCount); + m_lastSampleCount = sampleCount; + if (m_latencyOut) *m_latencyOut = sampleCount; + } + + int modifiedCount = 0; + + for (unsigned long i = 0; i < m_controlPortCount; ++i) { + + if (!m_controlPorts[i]) continue; + + if (m_controlPortsSaved[i] != *m_controlPorts[i]) { +// std::cout << "Sending new value " << *m_controlPorts[i] +// << " for control port " << i << std::endl; + m_plugin->setParameter(i, *m_controlPorts[i]); + m_controlPortsSaved[i] = *m_controlPorts[i]; + if (++modifiedCount > 10) break; + } + } + + m_plugin->process(m_audioIns, m_audioOuts); + + } catch (RemotePluginClosedException) { + m_ok = false; + } +} + +void +DSSIVSTPluginInstance::runSynth(unsigned long sampleCount, + snd_seq_event_t *events, unsigned long eventCount) +{ + if (!m_ok) return; + + try { + if (m_alsaDecoder) { + + unsigned long index = 0; + unsigned long i; + + for (i = 0; i < eventCount; ++i) { + + snd_seq_event_t *ev = &events[i]; + + if (index >= MIDI_BUFFER_SIZE - 4) break; + +// std::cerr << "MIDI event at frame " << ev->time.tick +// << ", channel " << int(ev->data.note.channel) << std::endl; + + m_frameOffsetsBuffer[i] = ev->time.tick; + ev->time.tick = 0; + + long count = snd_midi_event_decode(m_alsaDecoder, + m_decodeBuffer + index, + MIDI_BUFFER_SIZE - index, + ev); + if (count < 0) { + std::cerr << "WARNING: MIDI decoder error " << count + << " for event type " << ev->type << std::endl; + } else if (count == 0 || count > 3) { + std::cerr << "WARNING: MIDI event of type " << ev->type + << " decoded to " << count << " bytes, discarding" << std::endl; + } else { + index += count; + while (count++ < 3) { + m_decodeBuffer[index++] = '\0'; + } + } + } + + if (index > 0) { + m_plugin->sendMIDIData(m_decodeBuffer, m_frameOffsetsBuffer, i); + } + } + } catch (RemotePluginClosedException) { + m_ok = false; + } + + run(sampleCount); +} + +std::string +DSSIVSTPluginInstance::configure(std::string key, std::string value) +{ + std::cerr << "DSSIVSTPluginInstance::configure(" << key << "," << value <<")" << std::endl; + + try { + if (key == "guiVisible") { + if (value.length() > 0) { + std::cerr << "DSSIVSTPluginInstance::configure: show gui: value " << value << std::endl; + m_plugin->showGUI(value); + } else { + std::cerr << "DSSIVSTPluginInstance::configure: hide gui" << std::endl; + m_plugin->hideGUI(); + } + } + } catch (RemotePluginClosedException) { + m_ok = false; + } + + return ""; +} + +void +DSSIVSTPluginInstance::freeFields(DSSI_Descriptor &descriptor) +{ + LADSPA_Descriptor &ldesc = (LADSPA_Descriptor &)*descriptor.LADSPA_Plugin; + + if (ldesc.Name) free((char *)ldesc.Name); + if (ldesc.Maker) free((char *)ldesc.Maker); + if (ldesc.Copyright) free((char *)ldesc.Copyright); + + if (ldesc.PortDescriptors) { + delete[] ldesc.PortDescriptors; + } + + if (ldesc.PortNames) { + for (unsigned long i = 0; i < ldesc.PortCount; ++i) { + free((char *)ldesc.PortNames[i]); + } + delete[] ldesc.PortNames; + } + + if (ldesc.PortRangeHints) { + delete[] ldesc.PortRangeHints; + } +} + + +DSSIVSTPlugin::DSSIVSTPlugin() +{ + std::vector plugins; + + try { + RemoteVSTClient::queryPlugins(plugins); + } catch (std::string error) { + std::cerr << "DSSIVSTPlugin: Error on plugin query: " << error << std::endl; + return; + } + + for (unsigned int p = 0; p < plugins.size(); ++p) { + + DSSI_Descriptor *descriptor = new DSSI_Descriptor; + LADSPA_Descriptor *ldesc = new LADSPA_Descriptor; + descriptor->LADSPA_Plugin = ldesc; + + RemoteVSTClient::PluginRecord &rec = plugins[p]; + + // LADSPA labels mustn't contain spaces. We replace them with + // asterisks here and restore them when used to indicate DLL name + // again. + char *label = strdup(rec.dllName.c_str()); + for (int i = 0; label[i]; ++i) { + if (label[i] == ' ') label[i] = '*'; + } + + ldesc->UniqueID = 6666 + p; + ldesc->Label = label; + ldesc->Name = strdup(std::string(rec.pluginName + " VST").c_str()); + ldesc->Maker = strdup(rec.vendorName.c_str()); + ldesc->Copyright = strdup(ldesc->Maker); + +// std::cerr << "Plugin name: " << ldesc->Name << std::endl; + + int parameters = rec.parameters; + int inputs = rec.inputs; + int outputs = rec.outputs; + int portCount = parameters + inputs + outputs + 1; // 1 for latency output + + LADSPA_PortDescriptor *ports = new LADSPA_PortDescriptor[portCount]; + char **names = new char *[portCount]; + LADSPA_PortRangeHint *hints = new LADSPA_PortRangeHint[portCount]; + + for (int i = 0; i < parameters; ++i) { + ports[i] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; + names[i] = strdup(rec.parameterNames[i].c_str()); + hints[i].LowerBound = 0.0f; + hints[i].UpperBound = 1.0f; + hints[i].HintDescriptor = + LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE; + float deflt = rec.parameterDefaults[i]; + if (deflt < 0.0001) { + hints[i].HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM; + } else if (deflt > 0.999) { + hints[i].HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM; + } else if (deflt < 0.35) { + hints[i].HintDescriptor |= LADSPA_HINT_DEFAULT_LOW; + } else if (deflt > 0.65) { + hints[i].HintDescriptor |= LADSPA_HINT_DEFAULT_HIGH; + } else { + hints[i].HintDescriptor |= LADSPA_HINT_DEFAULT_MIDDLE; + } + } + + for (int i = 0; i < inputs; ++i) { + int j = i + parameters; + ports[j] = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO; + char buf[20]; + snprintf(buf, 19, "in%d", i + 1); + names[j] = strdup(buf); + hints[j].HintDescriptor = 0; + } + + for (int i = 0; i < outputs; ++i) { + int j = i + inputs + parameters; + ports[j] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO; + char buf[20]; + snprintf(buf, 19, "out%d", i + 1); + names[j] = strdup(buf); + hints[j].HintDescriptor = 0; + } + + ports[portCount-1] = LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL; + names[portCount-1] = strdup("_latency"); + hints[portCount-1].HintDescriptor = 0; + + ldesc->PortCount = portCount; + ldesc->PortDescriptors = ports; + ldesc->PortNames = names; + ldesc->PortRangeHints = hints; + ldesc->ImplementationData = 0; + + ldesc->instantiate = DSSIVSTPlugin::instantiate; + ldesc->connect_port = DSSIVSTPlugin::connect_port; + ldesc->activate = DSSIVSTPlugin::activate; + ldesc->run = DSSIVSTPlugin::run; + ldesc->run_adding = 0; + ldesc->set_run_adding_gain = 0; + ldesc->deactivate = DSSIVSTPlugin::deactivate; + ldesc->cleanup = DSSIVSTPlugin::cleanup; + + descriptor->DSSI_API_Version = 1; + descriptor->configure = DSSIVSTPlugin::configure; + descriptor->get_program = DSSIVSTPlugin::get_program; + descriptor->select_program = DSSIVSTPlugin::select_program; + descriptor->get_midi_controller_for_port = 0; + + if (rec.isSynth) { + descriptor->run_synth = DSSIVSTPlugin::run_synth; + } else { + descriptor->run_synth = 0; + } + + descriptor->run_synth_adding = 0; + descriptor->run_multiple_synths = 0; + descriptor->run_multiple_synths_adding = 0; + + m_descriptors.push_back(PluginPair(rec.dllName, descriptor)); + } +} + +DSSIVSTPlugin::~DSSIVSTPlugin() +{ + for (PluginList::iterator i = m_descriptors.begin(); i != m_descriptors.end(); ++i) { + DSSIVSTPluginInstance::freeFields(*i->second); + delete i->second->LADSPA_Plugin; + delete i->second; + } +} + + +DSSI_Descriptor * +DSSIVSTPlugin::queryDescriptor(unsigned long index) +{ + if (index < m_descriptors.size()) { +// std::cerr << "DSSIVSTPlugin::queryDescriptor: index is " << index +// << ", returning " << m_descriptors[index].second->LADSPA_Plugin->Name +// << std::endl; + return m_descriptors[index].second; + } else { + return 0; + } +} + +LADSPA_Handle +DSSIVSTPlugin::instantiate(const LADSPA_Descriptor *descriptor, + unsigned long sampleRate) +{ + std::cerr << "DSSIVSTPlugin::instantiate(" << descriptor->Label << ")" << std::endl; + + try { + return (LADSPA_Handle) + (new DSSIVSTPluginInstance(descriptor->Label, sampleRate)); + } catch (std::string e) { + perror(e.c_str()); + } catch (RemotePluginClosedException) { + std::cerr << "Remote plugin closed." << std::endl; + } + return 0; +} + +void +DSSIVSTPlugin::connect_port(LADSPA_Handle instance, + unsigned long port, + LADSPA_Data *location) +{ + ((DSSIVSTPluginInstance *)instance)->connectPort(port, location); +} + +void +DSSIVSTPlugin::activate(LADSPA_Handle instance) +{ + ((DSSIVSTPluginInstance *)instance)->activate(); +} + +void +DSSIVSTPlugin::run(LADSPA_Handle instance, unsigned long sampleCount) +{ + ((DSSIVSTPluginInstance *)instance)->run(sampleCount); +} + +void +DSSIVSTPlugin::deactivate(LADSPA_Handle instance) +{ + ((DSSIVSTPluginInstance *)instance)->deactivate(); +} + +void +DSSIVSTPlugin::cleanup(LADSPA_Handle instance) +{ + std::cerr << "DSSIVSTPlugin::cleanup" << std::endl; + delete ((DSSIVSTPluginInstance *)instance); +} + +const DSSI_Program_Descriptor * +DSSIVSTPlugin::get_program(LADSPA_Handle instance, unsigned long index) +{ + return ((DSSIVSTPluginInstance *)instance)->getProgram(index); +} + +void +DSSIVSTPlugin::select_program(LADSPA_Handle instance, unsigned long bank, + unsigned long program) +{ + ((DSSIVSTPluginInstance *)instance)->selectProgram(bank, program); +} + +void +DSSIVSTPlugin::run_synth(LADSPA_Handle instance, unsigned long sampleCount, + snd_seq_event_t *events, unsigned long eventCount) +{ + ((DSSIVSTPluginInstance *)instance)->runSynth(sampleCount, events, + eventCount); +} + +char * +DSSIVSTPlugin::configure(LADSPA_Handle instance, const char *key, + const char *value) +{ + std::cerr << "DSSIVSTPlugin::configure(" << key << "," << value << ")" << std::endl; + + std::string rv = ((DSSIVSTPluginInstance *)instance)->configure(key, value); + if (rv == "") { + return NULL; + } else { + return strdup(rv.c_str()); + } +} + + +static DSSIVSTPlugin *_plugin = 0; + +const LADSPA_Descriptor * +ladspa_descriptor(unsigned long index) +{ + return 0; +} + +const DSSI_Descriptor * +dssi_descriptor(unsigned long index) +{ + if (!_plugin) { + _plugin = new DSSIVSTPlugin; + } + return _plugin->queryDescriptor(index); +} + diff --git a/dssi-vst_gui.cpp b/dssi-vst_gui.cpp new file mode 100644 index 0000000..52b2087 --- /dev/null +++ b/dssi-vst_gui.cpp @@ -0,0 +1,301 @@ +// -*- c-basic-offset: 4 -*- + +/* + dssi-vst: a DSSI plugin wrapper for VST effects and instruments + Copyright 2004-2006 Chris Cannam +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rdwrops.h" + +static int debugLevel = 3; +static bool ready = false; +static bool exiting = false; + +static lo_server oscserver = 0; + +static char *hosturl = 0; +static lo_address hostaddr = 0; +static char *hosthostname = 0; +static char *hostport = 0; +static char *hostpath = 0; + +static char *fifoFile = 0; +static int fifoFd = -1; + +using std::cout; +using std::cerr; +using std::endl; + +#include "remoteplugin.h" // for RemotePluginVersion + +void +osc_error(int num, const char *msg, const char *path) +{ + cerr << "Error: liblo server error " << num + << " in path \"" << (path ? path : "(null)") + << "\": " << msg << endl; +} + +int +debug_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + int i; + + cerr << "Warning: unhandled OSC message in GUI:" << endl; + + for (i = 0; i < argc; ++i) { + cerr << "arg " << i << ": type '" << types[i] << "': "; + lo_arg_pp((lo_type)types[i], argv[i]); + cerr << endl; + } + + cerr << "(path is <" << path << ">)" << endl; + return 1; +} + +int +program_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + cerr << "dssi-vst_gui: program_handler" << endl; + return 0; +} + +int +configure_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + cerr << "dssi-vst_gui: configure_handler, returning 0" << endl; + return 0; +} + +int +show_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + cerr << "dssi-vst_gui: show_handler" << endl; + + lo_send(hostaddr, + (std::string(hostpath) + "/configure").c_str(), + "ss", + "guiVisible", + fifoFile); + + return 0; +} + +int +hide_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + cerr << "dssi-vst_gui: hide_handler" << endl; + + lo_send(hostaddr, + (std::string(hostpath) + "/configure").c_str(), + "ss", + "guiVisible", + ""); + + return 0; +} + +int +quit_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + cerr << "quit_handler" << endl; + exiting = true; + return 0; +} + +int +control_handler(const char *path, const char *types, lo_arg **argv, + int argc, void *data, void *user_data) +{ + static int count = 0; + cerr << "dssi-vst_gui: control_handler " << count++ << endl; + return 0; +} + +void +readFromPlugin() +{ + cerr << "dssi-vst_gui: something to read from plugin" << endl; + + try { + RemotePluginOpcode opcode = RemotePluginNoOpcode; + tryRead(fifoFd, &opcode, sizeof(RemotePluginOpcode)); + + switch (opcode) { + + case RemotePluginIsReady: + ready = true; + break; + + case RemotePluginSetParameter: + { + int port = readInt(fifoFd); + float value = readFloat(fifoFd); + + cerr << "dssi-vst_gui: sending (" << port << "," << value << ") to host" << endl; + + lo_send(hostaddr, + (std::string(hostpath) + "/control").c_str(), + "if", port, value); + break; + } + + case RemotePluginTerminate: + cerr << "dssi-vst_gui: asked to terminate" << endl; + lo_send(hostaddr, + (std::string(hostpath) + "/exiting").c_str(), + ""); + exiting = true; + break; + + default: + std::cerr << "WARNING: dssi-vst_gui: unexpected opcode " + << opcode << std::endl; + break; + } + } catch (...) { } +} + +int +main(int argc, char **argv) +{ + cout << "DSSI VST plugin GUI controller v" << RemotePluginVersion << endl; + cout << "Copyright (c) 2004-2006 Chris Cannam" << endl; + + char *pluginlibname = 0; + char *label = 0; + char *friendlyname = 0; + + if (argc != 5) { + cerr << "Usage: dssi-vst_gui