diff --git a/.gitignore b/.gitignore index cf6d26b96..bd0f61b21 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ carla-native-export data/windows/Carla data/windows/CarlaControl +source/plugin/carla-native.lv2/*.ttl source/tests/ANSI source/tests/CarlaString source/tests/DGL1 diff --git a/source/plugin/Makefile b/source/plugin/Makefile index 625ae2846..0d1386f46 100644 --- a/source/plugin/Makefile +++ b/source/plugin/Makefile @@ -118,7 +118,7 @@ all: $(TARGETS) clean: rm -f *.o - rm -f carla-native.lv2/* + rm -f carla-native.lv2/*.* debug: $(MAKE) DEBUG=true diff --git a/source/plugin/carla-native-export.cpp b/source/plugin/carla-native-export.cpp index 324bc014e..d4be343b5 100644 --- a/source/plugin/carla-native-export.cpp +++ b/source/plugin/carla-native-export.cpp @@ -134,6 +134,11 @@ void writeManifestFile() text += " a <" LV2_EXTERNAL_UI__Widget "> ;\n"; text += " ui:binary ;\n"; text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n"; + text += "\n"; + text += "\n"; + text += " a <" LV2_EXTERNAL_UI_DEPRECATED_URI "> ;\n"; + text += " ui:binary ;\n"; + text += " lv2:requiredFeature <" LV2_INSTANCE_ACCESS_URI "> .\n"; // ------------------------------------------------------------------- // Write file now @@ -219,7 +224,8 @@ void writePluginFile(const PluginDescriptor* const pluginDesc) if (pluginDesc->hints & PLUGIN_HAS_GUI) { - text += " ui:ui ;\n"; + text += " ui:ui ,\n"; + text += " ;\n"; text += "\n"; } diff --git a/source/plugin/carla-native-plugin.cpp b/source/plugin/carla-native-plugin.cpp index 2df60b5ab..065821dbe 100644 --- a/source/plugin/carla-native-plugin.cpp +++ b/source/plugin/carla-native-plugin.cpp @@ -21,9 +21,11 @@ #include "lv2/atom.h" #include "lv2/buf-size.h" +#include "lv2/instance-access.h" #include "lv2/options.h" #include "lv2/state.h" #include "lv2/ui.h" +#include "lv2/lv2_external_ui.h" #include @@ -36,90 +38,113 @@ // ----------------------------------------------------------------------- // LV2 descriptor functions -class NativePlugin +class NativePlugin : public LV2_External_UI_Widget { public: - static const uint32_t kMaxMidiEvents = 512; - - NativePlugin(const PluginDescriptor* const desc, const double sampleRate, const char* const bundlePath, const LV2_Feature* const* features) - : fHandle(nullptr), - fDescriptor(desc), - fIsProcessing(false), - fIsUiVisible(false), - fMidiEventCount(0), - fBufferSize(0), - fSampleRate(sampleRate), - fFeatures(features) - { - fHost.handle = this; - fHost.resourceDir = carla_strdup(bundlePath); - fHost.uiName = nullptr; - - fHost.get_buffer_size = carla_host_get_buffer_size; - fHost.get_sample_rate = carla_host_get_sample_rate; - fHost.is_offline = carla_host_is_offline; - fHost.get_time_info = carla_host_get_time_info; - fHost.write_midi_event = carla_host_write_midi_event; - fHost.ui_parameter_changed = carla_host_ui_parameter_changed; - fHost.ui_custom_data_changed = carla_host_ui_custom_data_changed; - fHost.ui_closed = carla_host_ui_closed; - fHost.ui_open_file = carla_host_ui_open_file; - fHost.ui_save_file = carla_host_ui_save_file; - fHost.dispatcher = carla_host_dispatcher; - - const LV2_Options_Option* options = nullptr; - const LV2_URID_Map* uridMap = nullptr; - - for (int i=0; features[i] != nullptr; ++i) - { - if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) - options = (const LV2_Options_Option*)features[i]->data; - else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) - uridMap = (const LV2_URID_Map*)features[i]->data; - } - - if (uridMap == nullptr || options == nullptr) - return; - - for (int i=0; options[i].key != 0; ++i) - { - if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__maxBlockLength)) - { - if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) - fBufferSize = *(const int*)options[i].value; - else - carla_stderr("Host provides maxBlockLength but has wrong value type"); - break; - } - } - } - - ~NativePlugin() - { - CARLA_ASSERT(fHandle != nullptr); - - if (fHost.resourceDir != nullptr) - { - delete[] fHost.resourceDir; - fHost.resourceDir = nullptr; - } - } - - bool init() - { - if (fDescriptor->instantiate == nullptr || fDescriptor->process == nullptr || fBufferSize == 0) - return false; - - fHandle = fDescriptor->instantiate(&fHost); - - if (fHandle == nullptr) - return false; - - carla_zeroStruct(fMidiEvents, kMaxMidiEvents*2); - carla_zeroStruct(fTimeInfo); - fPorts.init(fDescriptor, fHandle); - return true; - } + static const uint32_t kMaxMidiEvents = 512; + + NativePlugin(const PluginDescriptor* const desc, const double sampleRate, const char* const bundlePath, const LV2_Feature* const* features) + : fHandle(nullptr), + fDescriptor(desc), + fMidiEventCount(0), + fIsProcessing(false), + fBufferSize(0), + fSampleRate(sampleRate) + { + run = extui_run; + show = extui_show; + hide = extui_hide; + + fHost.handle = this; + fHost.resourceDir = carla_strdup(bundlePath); + fHost.uiName = nullptr; + + fHost.get_buffer_size = host_get_buffer_size; + fHost.get_sample_rate = host_get_sample_rate; + fHost.is_offline = host_is_offline; + fHost.get_time_info = host_get_time_info; + fHost.write_midi_event = host_write_midi_event; + fHost.ui_parameter_changed = host_ui_parameter_changed; + fHost.ui_custom_data_changed = host_ui_custom_data_changed; + fHost.ui_closed = host_ui_closed; + fHost.ui_open_file = host_ui_open_file; + fHost.ui_save_file = host_ui_save_file; + fHost.dispatcher = host_dispatcher; + + const LV2_Options_Option* options = nullptr; + const LV2_URID_Map* uridMap = nullptr; + + for (int i=0; features[i] != nullptr; ++i) + { + if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0) + options = (const LV2_Options_Option*)features[i]->data; + else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0) + uridMap = (const LV2_URID_Map*)features[i]->data; + } + + if (options == nullptr || uridMap == nullptr) + { + carla_stderr("Host don't provides option or urid-map features"); + return; + } + + fBufferSize = 1024; + + for (int i=0; options[i].key != 0; ++i) + { + if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__maxBlockLength)) + { + if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) + fBufferSize = *(const int*)options[i].value; + else + carla_stderr("Host provides maxBlockLength but has wrong value type"); + break; + } + } + + fUridMap = uridMap; + + fUI.offset += (desc->midiIns > 0) ? desc->midiIns : 1; + fUI.offset += desc->midiOuts; + fUI.offset += 1; // freewheel + fUI.offset += desc->audioIns; + fUI.offset += desc->audioOuts; + } + + ~NativePlugin() + { + CARLA_ASSERT(fHandle == nullptr); + + if (fHost.resourceDir != nullptr) + { + delete[] fHost.resourceDir; + fHost.resourceDir = nullptr; + } + } + + bool init() + { + if (fDescriptor->instantiate == nullptr || fDescriptor->process == nullptr) + { + carla_stderr("Plugin is missing something..."); + return false; + } + if (fBufferSize == 0) + { + carla_stderr("Plugin is missing bufferSize value"); + return false; + } + + fHandle = fDescriptor->instantiate(&fHost); + + if (fHandle == nullptr) + return false; + + carla_zeroStruct(fMidiEvents, kMaxMidiEvents*2); + carla_zeroStruct(fTimeInfo); + fPorts.init(fDescriptor, fHandle); + return true; + } // ------------------------------------------------------------------- // LV2 functions @@ -145,6 +170,7 @@ public: { if (fDescriptor->cleanup != nullptr) fDescriptor->cleanup(fHandle); + fHandle = nullptr; } void lv2_run(const uint32_t frames) @@ -178,7 +204,84 @@ public: // ------------------------------------------------------------------- + void lv2ui_instantiate(LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, + const LV2_Feature* const* features) + { + for (int i=0; features[i] != nullptr; ++i) + { + if (std::strcmp(features[i]->URI, LV2_EXTERNAL_UI__Host) == 0 || + std::strcmp(features[i]->URI, LV2_EXTERNAL_UI_DEPRECATED_URI) == 0) + { + fUI.host = (const LV2_External_UI_Host*)features[i]->data; + break; + } + } + + if (fUI.host != nullptr) + fHost.uiName = fUI.host->plugin_human_id; + + fUI.writeFunction = writeFunction; + fUI.controller = controller; + *widget = this; + } + + void lv2ui_port_event(uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) + { + if (format != 0 || bufferSize != sizeof(float) || buffer == nullptr) + return; + if (portIndex >= fUI.offset || ! fUI.isVisible) + return; + if (fDescriptor->ui_set_parameter_value == nullptr) + return; + + const float value(*(const float*)buffer); + fDescriptor->ui_set_parameter_value(fHandle, portIndex-fUI.offset, value); + } + + void lv2ui_cleanup() + { + fUI.host = nullptr; + fUI.writeFunction = nullptr; + fUI.controller = nullptr; + + if (! fUI.isVisible) + return; + + if (fDescriptor->ui_show != nullptr) + fDescriptor->ui_show(fHandle, false); + + fUI.isVisible = false; + } + + // ------------------------------------------------------------------- + protected: + void handleUiRun() + { + // TODO - idle Qt if needed + + if (fDescriptor->ui_idle != nullptr) + fDescriptor->ui_idle(fHandle); + } + + void handleUiShow() + { + if (fDescriptor->ui_show != nullptr) + fDescriptor->ui_show(fHandle, true); + + fUI.isVisible = true; + } + + void handleUiHide() + { + if (fDescriptor->ui_show != nullptr) + fDescriptor->ui_show(fHandle, false); + + fUI.isVisible = false; + } + + // ------------------------------------------------------------------- + uint32_t handleGetBufferSize() { return fBufferSize; @@ -191,13 +294,15 @@ protected: bool handleIsOffline() { - CARLA_ASSERT(fIsProcessing); - return (fPorts.freewheel != nullptr && *fPorts.freewheel < 0.5f); + CARLA_SAFE_ASSERT_RETURN(fIsProcessing, false); + + return (fPorts.freewheel != nullptr && *fPorts.freewheel > 0.5f); } const TimeInfo* handleGetTimeInfo() { - CARLA_ASSERT(fIsProcessing); + CARLA_SAFE_ASSERT_RETURN(fIsProcessing, nullptr); + return &fTimeInfo; } @@ -224,20 +329,26 @@ protected: return true; } - void handleUiParameterChanged(const uint32_t /*index*/, const float /*value*/) + void handleUiParameterChanged(const uint32_t index, const float value) { - //setParameterValue(index, value, false, true, true); + if (fUI.writeFunction != nullptr && fUI.controller != nullptr) + fUI.writeFunction(fUI.controller, index+fUI.offset, sizeof(float), 0, &value); } void handleUiCustomDataChanged(const char* const /*key*/, const char* const /*value*/) { - //setCustomData(CUSTOM_DATA_STRING, key, value, false); + //storeCustomData(key, value); } void handleUiClosed() { - // call external ui close - fIsUiVisible = false; + if (fUI.host != nullptr && fUI.host->ui_closed != nullptr && fUI.controller != nullptr) + fUI.host->ui_closed(fUI.controller); + + fUI.host = nullptr; + fUI.writeFunction = nullptr; + fUI.controller = nullptr; + fUI.isVisible = false; } const char* handleUiOpenFile(const bool isDir, const char* const title, const char* const filter) @@ -287,21 +398,15 @@ protected: break; case HOST_OPCODE_GET_PARAMETER_MIDI_CC: case HOST_OPCODE_SET_PARAMETER_MIDI_CC: - break; case HOST_OPCODE_SET_PROCESS_PRECISION: - break; case HOST_OPCODE_UPDATE_PARAMETER: - break; case HOST_OPCODE_UPDATE_MIDI_PROGRAM: - break; case HOST_OPCODE_RELOAD_PARAMETERS: - break; case HOST_OPCODE_RELOAD_MIDI_PROGRAMS: - break; case HOST_OPCODE_RELOAD_ALL: break; case HOST_OPCODE_UI_UNAVAILABLE: - //kData->engine->callback(CALLBACK_SHOW_GUI, fId, -1, 0, 0.0f, nullptr); + handleUiClosed(); break; } @@ -335,19 +440,32 @@ private: HostDescriptor fHost; const PluginDescriptor* const fDescriptor; - bool fIsProcessing; - bool fIsUiVisible; - uint32_t fMidiEventCount; MidiEvent fMidiEvents[kMaxMidiEvents*2]; + TimeInfo fTimeInfo; - TimeInfo fTimeInfo; + bool fIsProcessing; // Lv2 host data uint32_t fBufferSize; double fSampleRate; - const LV2_Feature* const* fFeatures; + const LV2_URID_Map* fUridMap; + + struct UI { + const LV2_External_UI_Host* host; + LV2UI_Write_Function writeFunction; + LV2UI_Controller controller; + bool isVisible; + uint32_t offset; + + UI() + : host(nullptr), + writeFunction(nullptr), + controller(nullptr), + isVisible(false), + offset(0) {} + } fUI; struct Ports { LV2_Atom_Sequence** eventsIn; @@ -531,59 +649,80 @@ private: // ------------------------------------------------------------------- + #define handlePtr ((NativePlugin*)_this_) + + static void extui_run(LV2_External_UI_Widget* _this_) + { + handlePtr->handleUiRun(); + } + + static void extui_show(LV2_External_UI_Widget* _this_) + { + handlePtr->handleUiShow(); + } + + static void extui_hide(LV2_External_UI_Widget* _this_) + { + handlePtr->handleUiHide(); + } + + #undef handlePtr + + // ------------------------------------------------------------------- + #define handlePtr ((NativePlugin*)handle) - static uint32_t carla_host_get_buffer_size(::HostHandle handle) + static uint32_t host_get_buffer_size(HostHandle handle) { return handlePtr->handleGetBufferSize(); } - static double carla_host_get_sample_rate(::HostHandle handle) + static double host_get_sample_rate(HostHandle handle) { return handlePtr->handleGetSampleRate(); } - static bool carla_host_is_offline(::HostHandle handle) + static bool host_is_offline(HostHandle handle) { return handlePtr->handleIsOffline(); } - static const ::TimeInfo* carla_host_get_time_info(::HostHandle handle) + static const TimeInfo* host_get_time_info(HostHandle handle) { return handlePtr->handleGetTimeInfo(); } - static bool carla_host_write_midi_event(::HostHandle handle, const ::MidiEvent* event) + static bool host_write_midi_event(HostHandle handle, const ::MidiEvent* event) { return handlePtr->handleWriteMidiEvent(event); } - static void carla_host_ui_parameter_changed(::HostHandle handle, uint32_t index, float value) + static void host_ui_parameter_changed(HostHandle handle, uint32_t index, float value) { handlePtr->handleUiParameterChanged(index, value); } - static void carla_host_ui_custom_data_changed(::HostHandle handle, const char* key, const char* value) + static void host_ui_custom_data_changed(HostHandle handle, const char* key, const char* value) { handlePtr->handleUiCustomDataChanged(key, value); } - static void carla_host_ui_closed(::HostHandle handle) + static void host_ui_closed(HostHandle handle) { handlePtr->handleUiClosed(); } - static const char* carla_host_ui_open_file(::HostHandle handle, bool isDir, const char* title, const char* filter) + static const char* host_ui_open_file(HostHandle handle, bool isDir, const char* title, const char* filter) { return handlePtr->handleUiOpenFile(isDir, title, filter); } - static const char* carla_host_ui_save_file(::HostHandle handle, bool isDir, const char* title, const char* filter) + static const char* host_ui_save_file(HostHandle handle, bool isDir, const char* title, const char* filter) { return handlePtr->handleUiSaveFile(isDir, title, filter); } - static intptr_t carla_host_dispatcher(::HostHandle handle, ::HostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt) + static intptr_t host_dispatcher(HostHandle handle, HostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt) { return handlePtr->handleDispatcher(opcode, index, value, ptr, opt); } @@ -724,6 +863,50 @@ static const void* lv2_extension_data(const char* uri) // ----------------------------------------------------------------------- // Startup code +static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char*, const char*, LV2UI_Write_Function writeFunction, + LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) +{ + NativePlugin* plugin = nullptr; + + for (int i=0; features[i] != nullptr; ++i) + { + if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) + { + plugin = (NativePlugin*)features[i]->data; + break; + } + } + + if (plugin == nullptr) + { + carla_stderr("Host doesn't support instance-access, cannot show UI"); + return nullptr; + } + + plugin->lv2ui_instantiate(writeFunction, controller, widget, features); + + return (LV2UI_Handle)plugin; +} + +#define uiPtr ((NativePlugin*)ui) + +static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) +{ + carla_debug("lv2ui_port_event(%p, %i, %i, %i, %p)", ui, portIndex, bufferSize, format, buffer); + uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer); +} + +static void lv2ui_cleanup(LV2UI_Handle ui) +{ + carla_debug("lv2ui_cleanup(%p)", ui); + uiPtr->lv2ui_cleanup(); +} + +#undef uiPtr + +// ----------------------------------------------------------------------- +// Startup code + CARLA_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { carla_debug("lv2_descriptor(%i)", index); @@ -775,23 +958,31 @@ CARLA_EXPORT const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { carla_debug("lv2ui_descriptor(%i)", index); - //if (index != 0) - return nullptr; - -#if 0 static const LV2UI_Descriptor lv2UiDesc = { /* URI */ "http://kxstudio.sf.net/carla#UI", /* instantiate */ lv2ui_instantiate, - /* connect_port */ nullptr, - /* activate */ nullptr, - /* run */ nullptr, - /* deactivate */ nullptr, - /* cleanup */ nullptr, + /* cleanup */ lv2ui_cleanup, + /* port_event */ lv2ui_port_event, /* extension_data */ nullptr }; - return &lv2UiDesc; -#endif + static const LV2UI_Descriptor lv2UiDescOld = { + /* URI */ "http://kxstudio.sf.net/carla#UIold", + /* instantiate */ lv2ui_instantiate, + /* cleanup */ lv2ui_cleanup, + /* port_event */ lv2ui_port_event, + /* extension_data */ nullptr + }; + + switch (index) + { + case 0: + return &lv2UiDesc; + case 1: + return &lv2UiDescOld; + default: + return nullptr; + } } // ----------------------------------------------------------------------- diff --git a/source/plugin/carla-native.lv2/resources b/source/plugin/carla-native.lv2/resources new file mode 120000 index 000000000..0a4fbdbe9 --- /dev/null +++ b/source/plugin/carla-native.lv2/resources @@ -0,0 +1 @@ +../../backend/resources/ \ No newline at end of file