/* * Carla CLAP Plugin * Copyright (C) 2022 Filipe Coelho * * This program 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 any later version. * * This program 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. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #include "CarlaPluginInternal.hpp" #include "CarlaEngine.hpp" #include "CarlaBackendUtils.hpp" #include "CarlaClapUtils.hpp" #include "CarlaMathUtils.hpp" #include "CarlaPluginUI.hpp" #ifdef CARLA_OS_MAC # include "CarlaMacUtils.hpp" # import #endif #include "water/files/File.h" CARLA_BACKEND_START_NAMESPACE // -------------------------------------------------------------------------------------------------------------------- struct carla_clap_host : clap_host_t { carla_clap_host() { clap_version = CLAP_VERSION; host_data = this; name = "Carla"; vendor = "falkTX"; url = "https://kx.studio/carla"; version = CARLA_VERSION_STRING; get_extension = carla_get_extension; request_restart = carla_request_restart; request_process = carla_request_process; request_callback = carla_request_callback; } static const void* carla_get_extension(const clap_host_t*, const char*) { return nullptr; } static void carla_request_restart(const clap_host_t*) {} static void carla_request_process(const clap_host_t*) {} static void carla_request_callback(const clap_host_t*) {} }; // -------------------------------------------------------------------------------------------------------------------- class CarlaPluginCLAP : public CarlaPlugin, private CarlaPluginUI::Callback { public: CarlaPluginCLAP(CarlaEngine* const engine, const uint id) : CarlaPlugin(engine, id), fPlugin(nullptr), fPluginDescriptor(nullptr), fPluginEntry(nullptr), fHost() { carla_debug("CarlaPluginCLAP::CarlaPluginCLAP(%p, %i)", engine, id); } ~CarlaPluginCLAP() override { carla_debug("CarlaPluginCLAP::~CarlaPluginCLAP()"); // close UI // if (pData->hints & PLUGIN_HAS_CUSTOM_UI) // { // if (! fUI.isEmbed) // showCustomUI(false); // // if (fUI.isOpen) // { // fUI.isOpen = false; // dispatcher(effEditClose); // } // } pData->singleMutex.lock(); pData->masterMutex.lock(); if (pData->client != nullptr && pData->client->isActive()) pData->client->deactivate(true); // CARLA_ASSERT(! fIsProcessing); if (pData->active) { deactivate(); pData->active = false; } if (fPlugin != nullptr) { fPlugin->destroy(fPlugin); fPlugin = nullptr; } clearBuffers(); if (fPluginEntry != nullptr) { fPluginEntry->deinit(); fPluginEntry = nullptr; } } // ------------------------------------------------------------------- // Information (base) PluginType getType() const noexcept override { return PLUGIN_CLAP; } /* PluginCategory getCategory() const noexcept override { } uint32_t getLatencyInFrames() const noexcept override { } */ // ------------------------------------------------------------------- // Information (count) // nothing // ------------------------------------------------------------------- // Information (current data) /* std::size_t getChunkData(void** const dataPtr) noexcept override { } */ // ------------------------------------------------------------------- // Information (per-plugin data) /* uint getOptionsAvailable() const noexcept override { } float getParameterValue(const uint32_t parameterId) const noexcept override { } */ bool getLabel(char* const strBuf) const noexcept override { CARLA_SAFE_ASSERT_RETURN(fPluginDescriptor != nullptr, false); std::strncpy(strBuf, fPluginDescriptor->id, STR_MAX); return true; } bool getMaker(char* const strBuf) const noexcept override { CARLA_SAFE_ASSERT_RETURN(fPluginDescriptor != nullptr, false); std::strncpy(strBuf, fPluginDescriptor->vendor, STR_MAX); return true; } bool getCopyright(char* const strBuf) const noexcept override { return getMaker(strBuf); } bool getRealName(char* const strBuf) const noexcept override { CARLA_SAFE_ASSERT_RETURN(fPluginDescriptor != nullptr, false); std::strncpy(strBuf, fPluginDescriptor->name, STR_MAX); return true; } /* bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override { } bool getParameterText(const uint32_t parameterId, char* const strBuf) noexcept override { } bool getParameterUnit(const uint32_t parameterId, char* const strBuf) const noexcept override { } bool getParameterGroupName(const uint32_t parameterId, char* const strBuf) const noexcept override { } */ // ------------------------------------------------------------------- // Set data (state) // nothing // ------------------------------------------------------------------- // Set data (internal stuff) /* void setName(const char* const newName) override { } */ // ------------------------------------------------------------------- // Set data (plugin-specific stuff) /* void setParameterValue(const uint32_t parameterId, const float value, const bool sendGui, const bool sendOsc, const bool sendCallback) noexcept override { } void setParameterValueRT(const uint32_t parameterId, const float value, const uint32_t frameOffset, const bool sendCallbackLater) noexcept override { } void setChunkData(const void* const data, const std::size_t dataSize) override { } void setProgram(const int32_t index, const bool sendGui, const bool sendOsc, const bool sendCallback, const bool doingInit) noexcept override { } void setProgramRT(const uint32_t uindex, const bool sendCallbackLater) noexcept override { } */ // ------------------------------------------------------------------- // Set ui stuff /* void setCustomUITitle(const char* const title) noexcept override { } void showCustomUI(const bool yesNo) override { } void* embedCustomUI(void* const ptr) override { } */ void idle() override { CarlaPlugin::idle(); } void uiIdle() override { CarlaPlugin::uiIdle(); } // ------------------------------------------------------------------- // Plugin state void reload() override { CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); // CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,); carla_debug("CarlaPluginCLAP::reload() - start"); const EngineProcessMode processMode = pData->engine->getProccessMode(); // Safely disable plugin for reload const ScopedDisabler sd(this); const clap_plugin_audio_ports_t* const audioPorts = static_cast( fPlugin->get_extension(fPlugin, CLAP_EXT_AUDIO_PORTS)); const clap_plugin_note_ports_t* const notePorts = static_cast( fPlugin->get_extension(fPlugin, CLAP_EXT_NOTE_PORTS)); const clap_plugin_params_t* const params = static_cast( fPlugin->get_extension(fPlugin, CLAP_EXT_PARAMS)); carla_debug("CarlaPluginCLAP::reload() - end"); } void reloadPrograms(const bool doInit) override { carla_debug("CarlaPluginCLAP::reloadPrograms(%s)", bool2str(doInit)); } // ------------------------------------------------------------------- // Plugin processing void activate() noexcept override { } void deactivate() noexcept override { } void process(const float* const*, float** const audioOut, const float* const*, float** const, const uint32_t frames) override { // -------------------------------------------------------------------------------------------------------- // Check if active if (! pData->active) { // disable any output sound for (uint32_t i=0; i < pData->audioOut.count; ++i) carla_zeroFloats(audioOut[i], frames); return; } } void bufferSizeChanged(const uint32_t newBufferSize) override { CARLA_ASSERT_INT(newBufferSize > 0, newBufferSize); carla_debug("CarlaPluginCLAP::bufferSizeChanged(%i)", newBufferSize); } void sampleRateChanged(const double newSampleRate) override { CARLA_ASSERT_INT(newSampleRate > 0.0, newSampleRate); carla_debug("CarlaPluginCLAP::sampleRateChanged(%g)", newSampleRate); } // ------------------------------------------------------------------- // Plugin buffers void clearBuffers() noexcept override { carla_debug("CarlaPluginCLAP::clearBuffers() - start"); CarlaPlugin::clearBuffers(); carla_debug("CarlaPluginCLAP::clearBuffers() - end"); } // ------------------------------------------------------------------- // Post-poned UI Stuff // nothing // ------------------------------------------------------------------- protected: void handlePluginUIClosed() override { // CARLA_SAFE_ASSERT_RETURN(fUI.window != nullptr,); carla_debug("CarlaPluginCLAP::handlePluginUIClosed()"); showCustomUI(false); pData->engine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, pData->id, 0, 0, 0, 0.0f, nullptr); } void handlePluginUIResized(const uint width, const uint height) override { // CARLA_SAFE_ASSERT_RETURN(fUI.window != nullptr,); carla_debug("CarlaPluginCLAP::handlePluginUIResized(%u, %u)", width, height); } // ------------------------------------------------------------------- public: bool init(const CarlaPluginPtr plugin, const char* const filename, const char* const name, const char* const id, const uint options) { CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); // --------------------------------------------------------------- // first checks if (pData->client != nullptr) { pData->engine->setLastError("Plugin client is already registered"); return false; } if (filename == nullptr || filename[0] == '\0') { pData->engine->setLastError("null filename"); return false; } if (id == nullptr || id[0] == '\0') { pData->engine->setLastError("null label/id"); return false; } // --------------------------------------------------------------- const clap_plugin_entry_t* entry; #ifdef CARLA_OS_MAC if (!File(filename).existsAsFile()) { if (! fBundleLoader.load(filename)) { pData->engine->setLastError("Failed to load CLAP bundle executable"); return false; } entry = fBundleLoader.getSymbol(CFSTR("clap_entry")); } else #endif { if (! pData->libOpen(filename)) { pData->engine->setLastError(pData->libError(filename)); return false; } entry = pData->libSymbol("clap_entry"); } if (entry == nullptr) { pData->engine->setLastError("Could not find the CLAP entry in the plugin library"); return false; } if (entry->init == nullptr || entry->deinit == nullptr || entry->get_factory == nullptr) { pData->engine->setLastError("CLAP factory entries are null"); return false; } if (!clap_version_is_compatible(entry->clap_version)) { pData->engine->setLastError("Incompatible CLAP plugin"); return false; } // --------------------------------------------------------------- const water::String pluginPath(water::File(filename).getParentDirectory().getFullPathName()); if (!entry->init(pluginPath.toRawUTF8())) { pData->engine->setLastError("Plugin entry failed to initialize"); return false; } fPluginEntry = entry; // --------------------------------------------------------------- const clap_plugin_factory_t* const factory = static_cast( entry->get_factory(CLAP_PLUGIN_FACTORY_ID)); if (factory == nullptr || factory->get_plugin_count == nullptr || factory->get_plugin_descriptor == nullptr || factory->create_plugin == nullptr) { pData->engine->setLastError("Plugin is missing factory methods"); return false; } // --------------------------------------------------------------- if (const uint32_t count = factory->get_plugin_count(factory)) { for (uint32_t i=0; iget_plugin_descriptor(factory, i); CARLA_SAFE_ASSERT_CONTINUE(desc != nullptr); CARLA_SAFE_ASSERT_CONTINUE(desc->id != nullptr); if (std::strcmp(desc->id, id) == 0) { fPluginDescriptor = desc; break; } } } else { pData->engine->setLastError("Plugin library contains no plugins"); return false; } if (fPluginDescriptor == nullptr) { pData->engine->setLastError("Plugin library does not contain the requested plugin"); return false; } // --------------------------------------------------------------- fPlugin = factory->create_plugin(factory, &fHost, fPluginDescriptor->id); if (fPlugin == nullptr) { pData->engine->setLastError("Failed to create CLAP plugin instance"); return false; } if (!fPlugin->init(fPlugin)) { pData->engine->setLastError("Failed to initialize CLAP plugin instance"); return false; } // --------------------------------------------------------------- // get info pData->name = pData->engine->getUniquePluginName(name != nullptr && name[0] != '\0' ? name : fPluginDescriptor->name); pData->filename = carla_strdup(filename); // --------------------------------------------------------------- // register client pData->client = pData->engine->addClient(plugin); if (pData->client == nullptr || ! pData->client->isOk()) { pData->engine->setLastError("Failed to register plugin client"); return false; } // --------------------------------------------------------------- // set default options pData->options = 0x0; // if (fEffect->initialDelay > 0 || hasMidiOutput() || isPluginOptionEnabled(options, PLUGIN_OPTION_FIXED_BUFFERS)) // pData->options |= PLUGIN_OPTION_FIXED_BUFFERS; // // if (fEffect->flags & effFlagsProgramChunks) // if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS)) // pData->options |= PLUGIN_OPTION_USE_CHUNKS; // // if (hasMidiInput()) // { // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES)) // pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE)) // pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH)) // pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND)) // pData->options |= PLUGIN_OPTION_SEND_PITCHBEND; // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF)) // pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; // if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PROGRAM_CHANGES)) // pData->options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; // if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES)) // pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; // } // // if (fEffect->numPrograms > 1 && (pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) == 0) // if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES)) // pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES; return true; } private: const clap_plugin_t* fPlugin; const clap_plugin_descriptor_t* fPluginDescriptor; const clap_plugin_entry_t* fPluginEntry; const carla_clap_host fHost; #ifdef CARLA_OS_MAC BundleLoader fBundleLoader; #endif }; // -------------------------------------------------------------------------------------------------------------------- CarlaPluginPtr CarlaPlugin::newCLAP(const Initializer& init) { carla_debug("CarlaPlugin::newCLAP({%p, \"%s\", \"%s\", \"%s\"})", init.engine, init.filename, init.name, init.label); std::shared_ptr plugin(new CarlaPluginCLAP(init.engine, init.id)); if (! plugin->init(plugin, init.filename, init.name, init.label, init.options)) return nullptr; return plugin; } // ------------------------------------------------------------------------------------------------------------------- CARLA_BACKEND_END_NAMESPACE