From 102e899c331bd2ed9902467a077164e209c918f9 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Tue, 12 Aug 2008 00:20:16 +0000 Subject: VSTUI X11 port and embeddable GTK wrapper. Build mdaSpecMeter and GUI. git-svn-id: http://svn.drobilla.net/lad/mda-lv2@1340 a436a847-0d15-0410-975c-d299462d15a1 --- lvz/AEffEditor.hpp | 30 ++++++++ lvz/AudioEffect.hpp | 1 + lvz/audioeffectx.h | 40 +++++++++- lvz/gendata.cpp | 100 +++++++++++++++++++------ lvz/gui_wrapper.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lvz/wrapper.cpp | 47 +++++++++--- 6 files changed, 389 insertions(+), 40 deletions(-) create mode 100644 lvz/AEffEditor.hpp create mode 120000 lvz/AudioEffect.hpp create mode 100644 lvz/gui_wrapper.cpp (limited to 'lvz') diff --git a/lvz/AEffEditor.hpp b/lvz/AEffEditor.hpp new file mode 100644 index 0000000..2d5152c --- /dev/null +++ b/lvz/AEffEditor.hpp @@ -0,0 +1,30 @@ +#ifndef __LVZ_AUDIOEFFECT_HPP +#define __LVZ_AUDIOEFFECT_HPP + +class AudioEffect; + +class AEffEditor { +public: + AEffEditor (AudioEffect* eff) + : effect(eff) + , URI("NULL") + , pluginURI("NULL") + {} + + virtual bool open(void* ptr) { return true; } + + virtual void idle() {} + + virtual const char* getURI() { return URI; } + virtual void setURI(const char* u) { URI = u; } + + virtual const char* getPluginURI() { return pluginURI; } + virtual void setPluginURI(const char* u) { pluginURI = u; } + +protected: + AudioEffect* effect; + const char* URI; + const char* pluginURI; +}; + +#endif // __LVZ_AUDIOEFFECT_HPP diff --git a/lvz/AudioEffect.hpp b/lvz/AudioEffect.hpp new file mode 120000 index 0000000..df90333 --- /dev/null +++ b/lvz/AudioEffect.hpp @@ -0,0 +1 @@ +audioeffectx.h \ No newline at end of file diff --git a/lvz/audioeffectx.h b/lvz/audioeffectx.h index 288d1cd..a1f975a 100644 --- a/lvz/audioeffectx.h +++ b/lvz/audioeffectx.h @@ -22,10 +22,29 @@ #include #include +// Some plugins seem to use these names... +#ifndef VstInt32 +# define VstInt32 LvzInt32 +# define VstInt16 LvzInt16 +#endif +#define VstEvents LvzEvents +#define VstMidiEvent LvzMidiEvent +#define VstPinProperty LvzPinProperty + typedef int16_t LvzInt16; typedef int32_t LvzInt32; typedef int (*audioMasterCallback)(int, int ver, int, int, int, int); +class AEffEditor; + +struct VstFileSelect { + int reserved; + char* returnPath; + size_t sizeReturnPath; + char** returnMultiplePaths; + long nbReturnPath; +}; + enum LvzPinFlags { kLvzPinIsActive = 1<<0, kLvzPinIsStereo = 1<<1 @@ -59,9 +78,16 @@ struct LvzEvents { class AudioEffect { public: + AudioEffect() : editor(NULL) {} virtual ~AudioEffect() {} + + void setEditor(AEffEditor* e) { editor = e; } + virtual void masterIdle() {} +protected: + AEffEditor* editor; }; + class AudioEffectX : public AudioEffect { public: AudioEffectX(audioMasterCallback audioMaster, LvzInt32 progs, LvzInt32 params) @@ -91,10 +117,11 @@ public: virtual void getParameterName(LvzInt32 index, char *label) = 0; virtual bool getProductString(char* text) = 0; - virtual void canMono() {} - virtual void canProcessReplacing() {} - virtual void isSynth() {} - virtual void wantEvents() {} + virtual bool canHostDo(const char* act) { return false; } + virtual void canMono() {} + virtual void canProcessReplacing() {} + virtual void isSynth() {} + virtual void wantEvents() {} virtual void setBlockSize(LvzInt32 size) {} virtual void setNumInputs(LvzInt32 num) { numInputs = num; } @@ -104,6 +131,11 @@ public: virtual void setURI(const char* uri) { URI = uri; } virtual void setUniqueID(const char* id) { uniqueID = id; } virtual void suspend() {} + virtual void beginEdit(VstInt32 index) {} + virtual void endEdit(VstInt32 index) {} + + virtual bool openFileSelector (VstFileSelect* sel) { return false; } + virtual bool closeFileSelector (VstFileSelect* sel) { return false; } protected: const char* URI; diff --git a/lvz/gendata.cpp b/lvz/gendata.cpp index 7f1d501..bf9c9ab 100644 --- a/lvz/gendata.cpp +++ b/lvz/gendata.cpp @@ -17,12 +17,14 @@ */ #include +#include #include #include #include #include #include #include "audioeffectx.h" +#include "AEffEditor.hpp" using namespace std; @@ -34,13 +36,16 @@ char name_buf[MAX_NAME_LENGTH]; struct Record { - Record(const string& u, const string& n) : uri(u), base_name(n) {} - string uri; + Record(const string& n) : base_name(n) {} string base_name; + typedef list UIs; + UIs uis; }; -typedef std::list Manifest; +typedef std::map Manifest; Manifest manifest; +typedef std::map GUIManifest; +GUIManifest gui_manifest; string @@ -85,8 +90,8 @@ symbolify(const char* name) void write_plugin(AudioEffectX* effect, const string& lib_file_name) { - string base_name = lib_file_name.substr(0, lib_file_name.find_last_of(".")); - string data_file_name = base_name + ".ttl"; + const string base_name = lib_file_name.substr(0, lib_file_name.find_last_of(".")); + const string data_file_name = base_name + ".ttl"; fstream os(data_file_name.c_str(), ios::out); effect->getProductString(name_buf); @@ -141,7 +146,33 @@ write_plugin(AudioEffectX* effect, const string& lib_file_name) os.close(); - manifest.push_back(Record(effect->getURI(), base_name)); + Manifest::iterator i = manifest.find(effect->getURI()); + if (i != manifest.end()) { + i->second.base_name = base_name; + } else { + manifest.insert(std::make_pair(effect->getURI(), Record(base_name))); + } +} + + +void +write_gui(AEffEditor* gui, const string& lib_file_name) +{ + const string base_name = lib_file_name.substr(0, lib_file_name.find_last_of(".")); + assert(gui_manifest.find(gui->getURI()) == gui_manifest.end()); + gui_manifest.insert(std::make_pair(gui->getURI(), Record(base_name))); + Manifest::iterator plugin_record = manifest.find(lib_file_name); + if (plugin_record != manifest.end()) { + plugin_record->second.uis.push_back(gui->getPluginURI()); + } + Manifest::iterator i = manifest.find(gui->getPluginURI()); + if (i != manifest.end()) { + i->second.uis.push_back(gui->getURI()); + } else { + Record r("ERRNOBASE"); + r.uis.push_back(gui->getURI()); + manifest.insert(std::make_pair(gui->getPluginURI(), r)); + } } @@ -149,11 +180,23 @@ void write_manifest(ostream& os) { os << "@prefix : ." << endl; - os << "@prefix rdfs: ." << endl << endl; + os << "@prefix rdfs: ." << endl; + os << "@prefix uiext: ." << endl << endl; for (Manifest::iterator i = manifest.begin(); i != manifest.end(); ++i) { - os << "<" << i->uri << "> a :Plugin ;" << endl; - os << "\trdfs:seeAlso <" << i->base_name << ".ttl> ;" << endl; - os << "\t:binary <" << i->base_name << ".so> ." << endl << endl; + Record& r = i->second; + os << "<" << i->first << "> a :Plugin ;" << endl; + os << "\trdfs:seeAlso <" << r.base_name << ".ttl> ;" << endl; + os << "\t:binary <" << r.base_name << ".so> "; + for (Record::UIs::iterator j = r.uis.begin(); j != r.uis.end(); ++j) + os << ";" << endl << "\tuiext:ui <" << *j << "> "; + os << "." << endl << endl; + } + + for (GUIManifest::iterator i = gui_manifest.begin(); i != gui_manifest.end(); ++i) { + Record& r = i->second; + os << "<" << i->first << "> a uiext:GtkUI ;" << endl; + os << "\trdfs:seeAlso <" << r.base_name << ".ttl> ;" << endl; + os << "\tuiext:binary <" << r.base_name << ".so> ." << endl << endl; } } @@ -171,33 +214,42 @@ main(int argc, char** argv) } typedef AudioEffectX* (*new_effect_func)(); + typedef AEffEditor* (*new_gui_func)(); typedef AudioEffectX* (*plugin_uri_func)(); - new_effect_func constructor = NULL; - AudioEffectX* effect = NULL; + new_effect_func constructor = NULL; + new_gui_func gui_constructor = NULL; + AudioEffectX* effect = NULL; + AEffEditor* gui = NULL; for (int i = 1; i < argc; ++i) { - void* handle = dlopen(argv[i], RTLD_NOW); + void* handle = dlopen(argv[i], RTLD_LAZY); if (handle == NULL) { - cerr << "ERROR: " << argv[i] << " is not a shared library, ignoring" << endl; + cerr << "ERROR: " << argv[i] << ": " << dlerror() << " (ignoring)" << endl; continue; } - constructor = (new_effect_func)dlsym(handle, "lvz_new_audioeffectx"); - if (constructor == NULL) { - dlclose(handle); - cerr << "ERROR: " << argv[i] << " is not an LVZ plugin library, ignoring" << endl; - continue; - } - - effect = constructor(); string lib_path = argv[i]; size_t last_slash = lib_path.find_last_of("/"); if (last_slash != string::npos) lib_path = lib_path.substr(last_slash + 1); - write_plugin(effect, lib_path); - + constructor = (new_effect_func)dlsym(handle, "lvz_new_audioeffectx"); + if (constructor != NULL) { + effect = constructor(); + write_plugin(effect, lib_path); + } + + gui_constructor = (new_gui_func)dlsym(handle, "lvz_new_aeffeditor"); + if (gui_constructor != NULL) { + gui = gui_constructor(); + write_gui(gui, lib_path); + } + + if (constructor == NULL && gui_constructor == NULL) { + cerr << "ERROR: " << argv[i] << ": not an LVZ plugin library, ignoring" << endl; + } + dlclose(handle); } diff --git a/lvz/gui_wrapper.cpp b/lvz/gui_wrapper.cpp new file mode 100644 index 0000000..ca2b894 --- /dev/null +++ b/lvz/gui_wrapper.cpp @@ -0,0 +1,211 @@ +/* LVZ - A C++ interface for writing LV2 plugins. + * Copyright (C) 2008 Dave Robillard + * + * This library is free software; you can redistribute it and/or modify it + * 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. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef UI_CLASS +#error "This file requires UI_CLASS to be defined" +#endif +#ifndef URI_PREFIX +#error "This file requires URI_PREFIX to be defined" +#endif +#ifndef UI_URI_SUFFIX +#error "This file requires UI_URI_SUFFIX to be defined" +#endif + +#include +#include +#include +#include +#include "AEffEditor.hpp" +#include "lv2_ui.h" +#include UI_HEADER +#include PLUGIN_HEADER + +extern "C" { + +/* UI */ + +typedef struct { + bool stolen; + UI_CLASS* ui; + GtkSocket* socket; + Window x_window; + AudioEffectX* effect; +} MDAPluginUI; + + +static gboolean +mda_ui_idle(void* data) +{ + MDAPluginUI* ui = (MDAPluginUI*)data; + if (!ui->stolen) { + //gtk_socket_add_id(GTK_SOCKET(ui->socket), ui->x_window); + ui->x_window = (Window)gtk_socket_get_id(GTK_SOCKET(ui->socket)); + bool success = ui->ui->open((void*)ui->x_window); + if (!success) + fprintf(stderr, "FAILED TO OPEN GUI\n"); + ui->ui->getFrame()->draw(); + ui->stolen = true; + } + + ui->ui->idle(); + return true; +} + + +static LV2UI_Handle +mda_ui_instantiate(const struct _LV2UI_Descriptor* descriptor, + const char* plugin_uri, + const char* bundle_path, + LV2UI_Write_Function write_function, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) +{ + /* Some extensions need to be defined for this to work. Hosts must: + * 1) Pass a pointer to the plugin (VST is crap and has no UI separation) + * 2) Call idle on the UI periodically (VST is crap and uses polling) + * + * Thoughts: + * + * 1) This wrapper could be written explicitly in GTK and just get at + * the idle handler that way. Less burden on the host... + * + * 2) A better idea is to have a 'get plugin extension data' feature + * the UI (or anything else) can use to ask for any URI-identified + * piece of extension data (failing in this case if plugin is remote) + */ + + MDAPluginUI* ui = (MDAPluginUI*)malloc(sizeof(MDAPluginUI)); + ui->effect = NULL; + + typedef struct { const void* (*extension_data)(const char* uri); } LV2_ExtensionData; + + typedef const void* (*extension_data_func)(const char* uri); + typedef const AudioEffectX* (*get_effect_func)(LV2_Handle instance); + + LV2_Handle instance = NULL; + get_effect_func get_effect = NULL; + + + if (features != NULL) { + const LV2_Feature* feature = features[0]; + for (size_t i = 0; (feature = features[i]) != NULL; ++i) { + if (!strcmp(feature->URI, "http://lv2plug.in/ns/ext/dev/plugin-instance")) { + instance = (LV2_Handle)feature->data; + } else if (!strcmp(feature->URI, "http://lv2plug.in/ns/ext/dev/extension-data")) { + LV2_ExtensionData* ext_data = (LV2_ExtensionData*)feature->data; + extension_data_func func = (extension_data_func)feature->data; + get_effect = (get_effect_func)ext_data->extension_data( + "http://lv2plug.in/ns/ext/dev/vstgui"); + } + } + } + + if (instance && get_effect) { + ui->effect = (AudioEffectX*)get_effect(instance); + } else { + fprintf(stderr, "Host does not support required features, aborting.\n"); + return NULL; + } + + ui->ui = new UI_CLASS(ui->effect); + ui->ui->setBundlePath(bundle_path); + ui->stolen = false; + + ui->socket = GTK_SOCKET(gtk_socket_new()); + gtk_widget_show_all(GTK_WIDGET(ui->socket)); + + *widget = ui->socket; + g_timeout_add(30, mda_ui_idle, ui); + + return ui; +} + + +static void +mda_ui_cleanup(LV2UI_Handle instance) +{ + g_idle_remove_by_data(instance); +} + + +static void +mda_ui_port_event(LV2UI_Handle ui, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ + // VST UIs seem to not use this at all, it's all polling + // The shit the proprietary people come up (and get away) with... +} + + +static const void* +mda_ui_extension_data(const char* uri) +{ + return NULL; +} + + +/* Library */ + +static LV2UI_Descriptor *mda_ui_descriptor = NULL; + +static void +init_ui_descriptor() +{ + mda_ui_descriptor = (LV2UI_Descriptor*)malloc(sizeof(LV2UI_Descriptor)); + + mda_ui_descriptor->URI = URI_PREFIX UI_URI_SUFFIX; + mda_ui_descriptor->instantiate = mda_ui_instantiate; + mda_ui_descriptor->cleanup = mda_ui_cleanup; + mda_ui_descriptor->port_event = mda_ui_port_event; + mda_ui_descriptor->extension_data = mda_ui_extension_data; +} + + +LV2_SYMBOL_EXPORT +const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + if (!mda_ui_descriptor) + init_ui_descriptor(); + + switch (index) { + case 0: + return mda_ui_descriptor; + default: + return NULL; + } +} + + +LV2_SYMBOL_EXPORT +AEffEditor* +lvz_new_aeffeditor(AudioEffect* effect) +{ + UI_CLASS* ui = new UI_CLASS(effect); + ui->setURI(URI_PREFIX UI_URI_SUFFIX); + ui->setPluginURI(URI_PREFIX PLUGIN_URI_SUFFIX); + return ui; +} + + +} // extern "C" + diff --git a/lvz/wrapper.cpp b/lvz/wrapper.cpp index 4ab99df..82a6989 100644 --- a/lvz/wrapper.cpp +++ b/lvz/wrapper.cpp @@ -19,8 +19,8 @@ #ifndef PLUGIN_CLASS #error "This file requires PLUGIN_CLASS to be defined" #endif -#ifndef PLUGIN_URI_PREFIX -#error "This file requires PLUGIN_URI_PREFIX to be defined" +#ifndef URI_PREFIX +#error "This file requires URI_PREFIX to be defined" #endif #ifndef PLUGIN_URI_SUFFIX #error "This file requires PLUGIN_URI_SUFFIX to be defined" @@ -74,6 +74,7 @@ mda_connect_port(LV2_Handle instance, uint32_t port, void* data) static int master_callback(int, int ver, int, int, int, int) { + return 0; } @@ -84,7 +85,7 @@ mda_instantiate(const LV2_Descriptor* descriptor, const LV2_Feature*const* features) { PLUGIN_CLASS* effect = new PLUGIN_CLASS(master_callback); - effect->setURI(PLUGIN_URI_PREFIX PLUGIN_URI_SUFFIX); + effect->setURI(URI_PREFIX PLUGIN_URI_SUFFIX); effect->setSampleRate(rate); uint32_t num_params = effect->getNumParameters(); @@ -97,7 +98,7 @@ mda_instantiate(const LV2_Descriptor* descriptor, if (num_params > 0) { plugin->controls = (float*)malloc(sizeof(float) * num_params); plugin->control_buffers = (float**)malloc(sizeof(float*) * num_params); - for (int32_t i = 0; i < num_params; ++i) { + for (uint32_t i = 0; i < num_params; ++i) { plugin->controls[i] = effect->getParameter(i); plugin->control_buffers[i] = NULL; } @@ -108,7 +109,7 @@ mda_instantiate(const LV2_Descriptor* descriptor, if (num_inputs > 0) { plugin->inputs = (float**)malloc(sizeof(float*) * num_inputs); - for (int32_t i = 0; i < num_inputs; ++i) + for (uint32_t i = 0; i < num_inputs; ++i) plugin->inputs[i] = NULL; } else { plugin->inputs = NULL; @@ -116,7 +117,7 @@ mda_instantiate(const LV2_Descriptor* descriptor, if (num_outputs > 0) { plugin->outputs = (float**)malloc(sizeof(float*) * num_outputs); - for (int32_t i = 0; i < num_outputs; ++i) + for (uint32_t i = 0; i < num_outputs; ++i) plugin->outputs[i] = NULL; } else { plugin->outputs = NULL; @@ -141,6 +142,26 @@ mda_run(LV2_Handle instance, uint32_t sample_count) plugin->effect->processReplacing(plugin->inputs, plugin->outputs, sample_count); } + + +static const AudioEffectX* +mda_get_audioeffectx(LV2_Handle instance) +{ + MDAPlugin* plugin = (MDAPlugin*)instance; + return plugin->effect; +} + + +static const void* +mda_extension_data(const char* uri) +{ + if (!strcmp(uri, "http://lv2plug.in/ns/ext/dev/vstgui")) { + // FIXME: shouldn't return function pointers directly + return (const void*)mda_get_audioeffectx; + } else { + return NULL; + } +} static void @@ -160,13 +181,14 @@ init_descriptor() { mda_descriptor = (LV2_Descriptor*)malloc(sizeof(LV2_Descriptor)); - mda_descriptor->URI = PLUGIN_URI_PREFIX PLUGIN_URI_SUFFIX; - mda_descriptor->activate = NULL; - mda_descriptor->cleanup = mda_cleanup; - mda_descriptor->connect_port = mda_connect_port; - mda_descriptor->deactivate = mda_deactivate; + mda_descriptor->URI = URI_PREFIX PLUGIN_URI_SUFFIX; mda_descriptor->instantiate = mda_instantiate; + mda_descriptor->connect_port = mda_connect_port; + mda_descriptor->activate = NULL; mda_descriptor->run = mda_run; + mda_descriptor->deactivate = mda_deactivate; + mda_descriptor->cleanup = mda_cleanup; + mda_descriptor->extension_data = mda_extension_data; } @@ -191,9 +213,10 @@ AudioEffectX* lvz_new_audioeffectx() { PLUGIN_CLASS* effect = new PLUGIN_CLASS(master_callback); - effect->setURI(PLUGIN_URI_PREFIX PLUGIN_URI_SUFFIX); + effect->setURI(URI_PREFIX PLUGIN_URI_SUFFIX); return effect; } } // extern "C" + -- cgit v1.2.1