/* * Carla Plugin JACK * Copyright (C) 2016-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" #ifdef CARLA_OS_LINUX #include "CarlaLibJackHints.h" #include "CarlaBackendUtils.hpp" #include "CarlaBridgeUtils.hpp" #include "CarlaEngineUtils.hpp" #include "CarlaMathUtils.hpp" #include "CarlaPipeUtils.hpp" #include "CarlaScopeUtils.hpp" #include "CarlaShmUtils.hpp" #include "CarlaThread.hpp" #ifdef HAVE_LIBLO # include "CarlaOscUtils.hpp" #else # warning No liblo support, NSM (session state) will not be available #endif #include "water/files/File.h" #include "water/misc/Time.h" #include "water/text/StringArray.h" #include "water/threads/ChildProcess.h" #include "jackbridge/JackBridge.hpp" #include #include // ------------------------------------------------------------------------------------------------------------------- using water::ChildProcess; using water::File; using water::String; using water::StringArray; using water::Time; CARLA_BACKEND_START_NAMESPACE static size_t safe_rand(const size_t limit) { const int r = std::rand(); CARLA_SAFE_ASSERT_RETURN(r >= 0, 0); return static_cast(r) % limit; } // ------------------------------------------------------------------------------------------------------------------- // Fallback data static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 }; // ------------------------------------------------------------------------------------------------------------------- struct Announcer { virtual ~Announcer() {} virtual void nsmAnnounced(bool hasGui) = 0; }; class CarlaPluginJackThread : public CarlaThread { public: CarlaPluginJackThread(Announcer* const ann, CarlaEngine* const engine, CarlaPlugin* const plugin) noexcept : CarlaThread("CarlaPluginJackThread"), kAnnouncer(ann), kEngine(engine), kPlugin(plugin), fShmIds(), fSetupLabel(), #ifdef HAVE_LIBLO fOscClientAddress(nullptr), fOscServer(nullptr), fHasOptionalGui(false), fProject(), #endif fProcess() {} void setData(const char* const shmIds, const char* const setupLabel) noexcept { CARLA_SAFE_ASSERT_RETURN(shmIds != nullptr && shmIds[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(setupLabel != nullptr && setupLabel[0] != '\0',); CARLA_SAFE_ASSERT(! isThreadRunning()); fShmIds = shmIds; fSetupLabel = setupLabel; } uintptr_t getProcessID() const noexcept { CARLA_SAFE_ASSERT_RETURN(fProcess != nullptr, 0); return (uintptr_t)fProcess->getPID(); } #ifdef HAVE_LIBLO void nsmSave(const char* const setupLabel) { if (fOscClientAddress == nullptr) return; if (fSetupLabel != setupLabel) fSetupLabel = setupLabel; maybeOpenFirstTime(false); lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/save", ""); } bool nsmShowGui(const bool yesNo) { if (fOscClientAddress == nullptr || ! fHasOptionalGui) return false; lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, yesNo ? "/nsm/client/show_optional_gui" : "/nsm/client/hide_optional_gui", ""); return true; } #endif char* getEnvVarsToExport() { const EngineOptions& options(kEngine->getOptions()); CarlaString binaryDir(options.binaryDir); #ifdef HAVE_LIBLO const int sessionManager = fSetupLabel[4U] - '0'; #endif CarlaString ret; ret += "export LD_LIBRARY_PATH=" + binaryDir + "/jack\n"; ret += "export LD_PRELOAD=" + binaryDir + "/libcarla_interposer-jack-x11.so\n"; #ifdef HAVE_LIBLO if (sessionManager == LIBJACK_SESSION_MANAGER_NSM) { for (int i=50; fOscServer == nullptr && --i>=0;) carla_msleep(100); ret += "export NSM_URL="; ret += lo_server_get_url(fOscServer); ret += "\n"; } #endif if (kPlugin->getHints() & PLUGIN_HAS_CUSTOM_UI) ret += "export CARLA_FRONTEND_WIN_ID=" + CarlaString(options.frontendWinId) + "\n"; ret += "export CARLA_LIBJACK_SETUP=" + fSetupLabel + "\n"; ret += "export CARLA_SHM_IDS=" + fShmIds + "\n"; return ret.releaseBufferPointer(); } protected: #ifdef HAVE_LIBLO static void _osc_error_handler(int num, const char* msg, const char* path) { carla_stderr2("CarlaPluginJackThread::_osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path); } static int _broadcast_handler(const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* data) { CARLA_SAFE_ASSERT_RETURN(data != nullptr, 0); carla_stdout("CarlaPluginJackThread::_broadcast_handler(%s, %s, %p, %i)", path, types, argv, argc); return ((CarlaPluginJackThread*)data)->handleBroadcast(path, types, argv, msg); } void maybeOpenFirstTime(const bool announced) { if (fSetupLabel.length() <= 6) return; if ((announced || fProject.path.isEmpty()) && fProject.init(kPlugin->getName(), kEngine->getCurrentProjectFolder(), &fSetupLabel[6U])) { carla_stdout("Sending open signal %s %s %s", fProject.path.buffer(), fProject.display.buffer(), fProject.clientName.buffer()); lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/open", "sss", fProject.path.buffer(), fProject.display.buffer(), fProject.clientName.buffer()); } } int handleBroadcast(const char* path, const char* types, lo_arg** argv, lo_message msg) { if (std::strcmp(path, "/nsm/server/announce") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "sssiii") == 0, 0); const lo_address msgAddress(lo_message_get_source(msg)); CARLA_SAFE_ASSERT_RETURN(msgAddress != nullptr, 0); char* const msgURL(lo_address_get_url(msgAddress)); CARLA_SAFE_ASSERT_RETURN(msgURL != nullptr, 0); if (fOscClientAddress != nullptr) lo_address_free(fOscClientAddress); fOscClientAddress = lo_address_new_from_url(msgURL); CARLA_SAFE_ASSERT_RETURN(fOscClientAddress != nullptr, 0); fProject.appName = &argv[0]->s; fHasOptionalGui = std::strstr(&argv[1]->s, ":optional-gui:") != nullptr; kAnnouncer->nsmAnnounced(fHasOptionalGui); static const char* const featuresG = ":server-control:optional-gui:"; static const char* const featuresN = ":server-control:"; static const char* const method = "/nsm/server/announce"; static const char* const message = "Howdy, what took you so long?"; static const char* const smName = "Carla"; const char* const features = ((fSetupLabel[5U] - '0') & LIBJACK_FLAG_CONTROL_WINDOW) ? featuresG : featuresN; lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/reply", "ssss", method, message, smName, features); maybeOpenFirstTime(true); return 0; } CARLA_SAFE_ASSERT_RETURN(fOscClientAddress != nullptr, 0); if (std::strcmp(path, "/reply") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "ss") == 0, 0); const char* const method = &argv[0]->s; const char* const message = &argv[1]->s; carla_stdout("Got reply of '%s' as '%s'", method, message); if (std::strcmp(method, "/nsm/client/open") == 0) { carla_stdout("Sending 'Session is loaded' to %s", fProject.appName.buffer()); lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/session_is_loaded", ""); } } else if (std::strcmp(path, "/nsm/client/gui_is_shown") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "") == 0, 0); kEngine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, kPlugin->getId(), 1, 0, 0, 0.0f, nullptr); } else if (std::strcmp(path, "/nsm/client/gui_is_hidden") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "") == 0, 0); kEngine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, kPlugin->getId(), 0, 0, 0, 0.0f, nullptr); } // special messages else if (std::strcmp(path, "/nsm/gui/client/save") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "s") == 0, 0); lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/save", ""); } else if (std::strcmp(path, "/nsm/server/stop") == 0) { CARLA_SAFE_ASSERT_RETURN(std::strcmp(types, "s") == 0, 0); lo_send_from(fOscClientAddress, fOscServer, LO_TT_IMMEDIATE, "/nsm/client/hide_optional_gui", ""); kEngine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, kPlugin->getId(), 0, 0, 0, 0.0f, nullptr); } return 0; } #endif void run() { #ifdef HAVE_LIBLO if (fOscClientAddress != nullptr) { lo_address_free(fOscClientAddress); fOscClientAddress = nullptr; } const int sessionManager = fSetupLabel[4U] - '0'; if (sessionManager == LIBJACK_SESSION_MANAGER_NSM) { // NSM support fOscServer = lo_server_new_with_proto(nullptr, LO_UDP, _osc_error_handler); CARLA_SAFE_ASSERT_RETURN(fOscServer != nullptr,); lo_server_add_method(fOscServer, nullptr, nullptr, _broadcast_handler, this); } #endif const bool externalProcess = ((fSetupLabel[5U] - '0') & LIBJACK_FLAG_EXTERNAL_START) && ! kEngine->isLoadingProject(); if (! externalProcess) { if (fProcess == nullptr) { fProcess = new ChildProcess(); } else if (fProcess->isRunning()) { carla_stderr("CarlaPluginJackThread::run() - already running"); } String name(kPlugin->getName()); String filename(kPlugin->getFilename()); if (name.isEmpty()) name = "(none)"; CARLA_SAFE_ASSERT_RETURN(filename.isNotEmpty(),); StringArray arguments; // binary arguments.addTokens(filename, true); const EngineOptions& options(kEngine->getOptions()); char winIdStr[STR_MAX+1]; std::snprintf(winIdStr, STR_MAX, P_UINTPTR, options.frontendWinId); winIdStr[STR_MAX] = '\0'; const CarlaString libjackdir(CarlaString(options.binaryDir) + "/jack"); const CarlaString ldpreload(CarlaString(options.binaryDir) + "/libcarla_interposer-jack-x11.so"); const ScopedEngineEnvironmentLocker _seel(kEngine); const CarlaScopedEnvVar sev2("LD_LIBRARY_PATH", libjackdir.buffer()); const CarlaScopedEnvVar sev1("LD_PRELOAD", ldpreload.isNotEmpty() ? ldpreload.buffer() : nullptr); #ifdef HAVE_LIBLO const CarlaScopedEnvVar sev3("NSM_URL", lo_server_get_url(fOscServer)); #endif if (kPlugin->getHints() & PLUGIN_HAS_CUSTOM_UI) carla_setenv("CARLA_FRONTEND_WIN_ID", winIdStr); else carla_unsetenv("CARLA_FRONTEND_WIN_ID"); carla_setenv("CARLA_LIBJACK_SETUP", fSetupLabel.buffer()); carla_setenv("CARLA_SHM_IDS", fShmIds.buffer()); if (! fProcess->start(arguments)) { carla_stdout("failed!"); fProcess = nullptr; return; } } for (; (externalProcess || fProcess->isRunning()) && ! shouldThreadExit();) { #ifdef HAVE_LIBLO if (sessionManager == LIBJACK_SESSION_MANAGER_NSM) { lo_server_recv_noblock(fOscServer, 50); } else #endif { carla_msleep(50); } } #ifdef HAVE_LIBLO if (sessionManager == LIBJACK_SESSION_MANAGER_NSM) { lo_server_free(fOscServer); fOscServer = nullptr; if (fOscClientAddress != nullptr) { lo_address_free(fOscClientAddress); fOscClientAddress = nullptr; } } #endif if (! externalProcess) { // we only get here if bridge crashed or thread asked to exit if (fProcess->isRunning() && shouldThreadExit()) { fProcess->waitForProcessToFinish(2000); if (fProcess->isRunning()) { carla_stdout("CarlaPluginJackThread::run() - application refused to close, force kill now"); fProcess->kill(); } } else { // forced quit, may have crashed if (fProcess->getExitCodeAndClearPID() != 0) { carla_stderr("CarlaPluginJackThread::run() - application crashed"); CarlaString errorString("Plugin '" + CarlaString(kPlugin->getName()) + "' has crashed!\n" "Saving now will lose its current settings.\n" "Please remove this plugin, and not rely on it from this point."); kEngine->callback(true, true, ENGINE_CALLBACK_ERROR, kPlugin->getId(), 0, 0, 0, 0.0f, errorString); } } } fProcess = nullptr; } private: Announcer* const kAnnouncer; CarlaEngine* const kEngine; CarlaPlugin* const kPlugin; CarlaString fShmIds; CarlaString fSetupLabel; #ifdef HAVE_LIBLO lo_address fOscClientAddress; lo_server fOscServer; bool fHasOptionalGui; struct ProjectData { CarlaString appName; CarlaString path; CarlaString display; CarlaString clientName; ProjectData() : appName(), path(), display(), clientName() {} bool init(const char* const pluginName, const char* const engineProjectFolder, const char* const uniqueCodeID) { CARLA_SAFE_ASSERT_RETURN(engineProjectFolder != nullptr && engineProjectFolder[0] != '\0', false); CARLA_SAFE_ASSERT_RETURN(uniqueCodeID != nullptr && uniqueCodeID[0] != '\0', false); CARLA_SAFE_ASSERT_RETURN(appName.isNotEmpty(), false); String child(pluginName); child += "."; child += uniqueCodeID; const File file(File(engineProjectFolder).getChildFile(child)); clientName = appName + "." + uniqueCodeID; path = file.getFullPathName().toRawUTF8(); display = file.getFileNameWithoutExtension().toRawUTF8(); return true; } CARLA_DECLARE_NON_COPYABLE(ProjectData) } fProject; #endif CarlaScopedPointer fProcess; CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJackThread) }; // ------------------------------------------------------------------------------------------------------------------- class CarlaPluginJack : public CarlaPlugin, public Announcer { public: CarlaPluginJack(CarlaEngine* const engine, const uint id) : CarlaPlugin(engine, id), fInitiated(false), fInitError(false), fTimedOut(false), fTimedError(false), fProcCanceled(false), fBufferSize(engine->getBufferSize()), fProcWaitTime(0), fSetupHints(0x0), fBridgeThread(this, engine, this), fShmAudioPool(), fShmRtClientControl(), fShmNonRtClientControl(), fShmNonRtServerControl(), fInfo() { carla_debug("CarlaPluginJack::CarlaPluginJack(%p, %i)", engine, id); pData->hints |= PLUGIN_IS_BRIDGE; } ~CarlaPluginJack() override { carla_debug("CarlaPluginJack::~CarlaPluginJack()"); #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH // close UI if (pData->hints & PLUGIN_HAS_CUSTOM_UI) pData->transientTryCounter = 0; #endif pData->singleMutex.lock(); pData->masterMutex.lock(); if (pData->client != nullptr && pData->client->isActive()) pData->client->deactivate(true); if (pData->active) { deactivate(); pData->active = false; } if (fBridgeThread.isThreadRunning()) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientQuit); fShmRtClientControl.commitWrite(); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientQuit); fShmNonRtClientControl.commitWrite(); if (! fTimedOut) waitForClient("stopping", 3000); } fBridgeThread.stopThread(3000); fShmNonRtServerControl.clear(); fShmNonRtClientControl.clear(); fShmRtClientControl.clear(); fShmAudioPool.clear(); clearBuffers(); fInfo.chunk.clear(); } // ------------------------------------------------------------------- void nsmAnnounced(bool hasGui) override { if (hasGui || (pData->hints & PLUGIN_HAS_CUSTOM_UI) == 0x0) return; { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientShowUI); fShmNonRtClientControl.commitWrite(); } pData->engine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, pData->id, 1, 0, 0, 0.0f, nullptr); } // ------------------------------------------------------------------- // Information (base) PluginType getType() const noexcept override { return PLUGIN_JACK; } PluginCategory getCategory() const noexcept override { return PLUGIN_CATEGORY_NONE; } // ------------------------------------------------------------------- // Information (count) uint32_t getMidiInCount() const noexcept override { return fInfo.mIns; } uint32_t getMidiOutCount() const noexcept override { return fInfo.mOuts; } // ------------------------------------------------------------------- // Information (current data) // ------------------------------------------------------------------- // Information (per-plugin data) uint getOptionsAvailable() const noexcept override { uint options = 0x0; if (fInfo.mIns > 0) { options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES; options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE; options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH; options |= PLUGIN_OPTION_SEND_PITCHBEND; options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF; options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES; options |= PLUGIN_OPTION_SKIP_SENDING_NOTES; } return options; } bool getLabel(char* const strBuf) const noexcept override { std::strncpy(strBuf, fInfo.setupLabel, STR_MAX); return true; } bool getMaker(char* const) const noexcept override { return false; } bool getCopyright(char* const) const noexcept override { return false; } bool getRealName(char* const strBuf) const noexcept override { // FIXME std::strncpy(strBuf, "Carla's libjack", STR_MAX); return true; } // ------------------------------------------------------------------- // Set data (state) void prepareForSave(bool) noexcept override { #ifdef HAVE_LIBLO if (fInfo.setupLabel.length() == 6) setupUniqueProjectID(); #endif { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientPrepareForSave); fShmNonRtClientControl.commitWrite(); } #ifdef HAVE_LIBLO fBridgeThread.nsmSave(fInfo.setupLabel); #endif } // ------------------------------------------------------------------- // Set data (internal stuff) void setOption(const uint option, const bool yesNo, const bool sendCallback) override { { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetOption); fShmNonRtClientControl.writeUInt(option); fShmNonRtClientControl.writeBool(yesNo); fShmNonRtClientControl.commitWrite(); } CarlaPlugin::setOption(option, yesNo, sendCallback); } void setCtrlChannel(const int8_t channel, const bool sendOsc, const bool sendCallback) noexcept override { CARLA_SAFE_ASSERT_RETURN(sendOsc || sendCallback,); // never call this from RT { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientSetCtrlChannel); fShmNonRtClientControl.writeShort(channel); fShmNonRtClientControl.commitWrite(); } CarlaPlugin::setCtrlChannel(channel, sendOsc, sendCallback); } // ------------------------------------------------------------------- // Set data (plugin-specific stuff) void setCustomData(const char* const type, const char* const key, const char* const value, const bool sendGui) override { CARLA_SAFE_ASSERT_RETURN(type != nullptr && type[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(value != nullptr,); if (std::strcmp(type, CUSTOM_DATA_TYPE_PROPERTY) == 0) return CarlaPlugin::setCustomData(type, key, value, sendGui); if (std::strcmp(type, CUSTOM_DATA_TYPE_STRING) == 0 && std::strcmp(key, "__CarlaPingOnOff__") == 0) { #if 0 const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientPingOnOff); fShmNonRtClientControl.writeBool(std::strcmp(value, "true") == 0); fShmNonRtClientControl.commitWrite(); #endif return; } CarlaPlugin::setCustomData(type, key, value, sendGui); } // ------------------------------------------------------------------- // Set ui stuff void showCustomUI(const bool yesNo) override { if (yesNo && ! fBridgeThread.isThreadRunning()) { CARLA_SAFE_ASSERT_RETURN(restartBridgeThread(),); } #ifdef HAVE_LIBLO if (! fBridgeThread.nsmShowGui(yesNo)) #endif { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(yesNo ? kPluginBridgeNonRtClientShowUI : kPluginBridgeNonRtClientHideUI); fShmNonRtClientControl.commitWrite(); } } void idle() override { if (fBridgeThread.isThreadRunning()) { if (fInitiated && fTimedOut && pData->active) setActive(false, true, true); { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientPing); fShmNonRtClientControl.commitWrite(); } try { handleNonRtData(); } CARLA_SAFE_EXCEPTION("handleNonRtData"); } else if (fInitiated) { fTimedOut = true; fTimedError = true; fInitiated = false; handleProcessStopped(); } else if (fProcCanceled) { handleProcessStopped(); fProcCanceled = false; } CarlaPlugin::idle(); } // ------------------------------------------------------------------- // Plugin state void reload() override { CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); carla_debug("CarlaPluginJack::reload() - start"); const EngineProcessMode processMode(pData->engine->getProccessMode()); // Safely disable plugin for reload const ScopedDisabler sd(this); // cleanup of previous data pData->audioIn.clear(); pData->audioOut.clear(); pData->event.clear(); bool needsCtrlIn, needsCtrlOut; needsCtrlIn = needsCtrlOut = false; if (fInfo.aIns > 0) { pData->audioIn.createNew(fInfo.aIns); } if (fInfo.aOuts > 0) { pData->audioOut.createNew(fInfo.aOuts); needsCtrlIn = true; } if (fInfo.mIns > 0) needsCtrlIn = true; if (fInfo.mOuts > 0) needsCtrlOut = true; const uint portNameSize(pData->engine->getMaxPortNameSize()); CarlaString portName; // Audio Ins for (uint8_t j=0; j < fInfo.aIns; ++j) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } if (fInfo.aIns > 1) { portName += "audio_in_"; portName += CarlaString(j+1); } else { portName += "audio_in"; } portName.truncate(portNameSize); pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j); pData->audioIn.ports[j].rindex = j; } // Audio Outs for (uint8_t j=0; j < fInfo.aOuts; ++j) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } if (fInfo.aOuts > 1) { portName += "audio_out_"; portName += CarlaString(j+1); } else { portName += "audio_out"; } portName.truncate(portNameSize); pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j); pData->audioOut.ports[j].rindex = j; } if (needsCtrlIn) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } portName += "event-in"; portName.truncate(portNameSize); pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); } if (needsCtrlOut) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } portName += "event-out"; portName.truncate(portNameSize); pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0); } // extra plugin hints pData->extraHints = 0x0; if (fInfo.mIns > 0) pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_IN; if (fInfo.mOuts > 0) pData->extraHints |= PLUGIN_EXTRA_HINT_HAS_MIDI_OUT; bufferSizeChanged(pData->engine->getBufferSize()); reloadPrograms(true); carla_debug("CarlaPluginJack::reload() - end"); } // ------------------------------------------------------------------- // Plugin processing void activate() noexcept override { if (! fBridgeThread.isThreadRunning()) { CARLA_SAFE_ASSERT_RETURN(restartBridgeThread(),); } CARLA_SAFE_ASSERT_RETURN(! fTimedError,); { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientActivate); fShmNonRtClientControl.commitWrite(); } fTimedOut = false; try { waitForClient("activate", 2000); } CARLA_SAFE_EXCEPTION("activate - waitForClient"); } void deactivate() noexcept override { if (! fBridgeThread.isThreadRunning()) return; CARLA_SAFE_ASSERT_RETURN(! fTimedError,); { const CarlaMutexLocker _cml(fShmNonRtClientControl.mutex); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientDeactivate); fShmNonRtClientControl.commitWrite(); } fTimedOut = false; try { waitForClient("deactivate", 2000); } CARLA_SAFE_EXCEPTION("deactivate - waitForClient"); } void process(const float* const* const audioIn, float** const audioOut, const float* const*, float**, const uint32_t frames) override { // -------------------------------------------------------------------------------------------------------- // Check if active if (fProcCanceled || fTimedOut || fTimedError || ! pData->active) { // disable any output sound for (uint32_t i=0; i < pData->audioOut.count; ++i) carla_zeroFloats(audioOut[i], frames); return; } // -------------------------------------------------------------------------------------------------------- // Check if needs reset if (pData->needsReset) { // TODO pData->needsReset = false; } // -------------------------------------------------------------------------------------------------------- // Event Input if (pData->event.portIn != nullptr) { // ---------------------------------------------------------------------------------------------------- // MIDI Input (External) if (pData->extNotes.mutex.tryLock()) { for (RtLinkedList::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next()) { const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback)); CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS); uint8_t data1, data2, data3; data1 = uint8_t((note.velo > 0 ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF) | (note.channel & MIDI_CHANNEL_BIT)); data2 = note.note; data3 = note.velo; fShmRtClientControl.writeOpcode(kPluginBridgeRtClientMidiEvent); fShmRtClientControl.writeUInt(0); // time fShmRtClientControl.writeByte(0); // port fShmRtClientControl.writeByte(3); // size fShmRtClientControl.writeByte(data1); fShmRtClientControl.writeByte(data2); fShmRtClientControl.writeByte(data3); fShmRtClientControl.commitWrite(); } pData->extNotes.data.clear(); pData->extNotes.mutex.unlock(); } // End of MIDI Input (External) // ---------------------------------------------------------------------------------------------------- // Event Input (System) #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH bool allNotesOffSent = false; #endif for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i) { const EngineEvent& event(pData->event.portIn->getEvent(i)); // Control change switch (event.type) { case kEngineEventTypeNull: break; case kEngineEventTypeControl: { const EngineControlEvent& ctrlEvent = event.ctrl; switch (ctrlEvent.type) { case kEngineControlEventTypeNull: break; case kEngineControlEventTypeParameter: #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH // Control backend stuff if (event.channel == pData->ctrlChannel) { float value; if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0) { value = ctrlEvent.normalizedValue; setDryWetRT(value, true); } if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0) { value = ctrlEvent.normalizedValue*127.0f/100.0f; setVolumeRT(value, true); } if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0) { float left, right; value = ctrlEvent.normalizedValue/0.5f - 1.0f; if (value < 0.0f) { left = -1.0f; right = (value*2.0f)+1.0f; } else if (value > 0.0f) { left = (value*2.0f)-1.0f; right = 1.0f; } else { left = -1.0f; right = 1.0f; } setBalanceLeftRT(left, true); setBalanceRightRT(right, true); } } #endif if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientMidiEvent); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(0); // port fShmRtClientControl.writeByte(3); // size fShmRtClientControl.writeByte(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT))); fShmRtClientControl.writeByte(uint8_t(ctrlEvent.param)); fShmRtClientControl.writeByte(uint8_t(ctrlEvent.normalizedValue*127.0f + 0.5f)); } break; case kEngineControlEventTypeMidiBank: if (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventMidiBank); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(event.channel); fShmRtClientControl.writeUShort(event.ctrl.param); fShmRtClientControl.commitWrite(); } break; case kEngineControlEventTypeMidiProgram: if (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventMidiProgram); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(event.channel); fShmRtClientControl.writeUShort(event.ctrl.param); fShmRtClientControl.commitWrite(); } break; case kEngineControlEventTypeAllSoundOff: if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventAllSoundOff); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(event.channel); fShmRtClientControl.commitWrite(); } break; case kEngineControlEventTypeAllNotesOff: if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF) { #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH if (event.channel == pData->ctrlChannel && ! allNotesOffSent) { allNotesOffSent = true; postponeRtAllNotesOff(); } #endif fShmRtClientControl.writeOpcode(kPluginBridgeRtClientControlEventAllNotesOff); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(event.channel); fShmRtClientControl.commitWrite(); } break; } // switch (ctrlEvent.type) break; } // case kEngineEventTypeControl case kEngineEventTypeMidi: { const EngineMidiEvent& midiEvent(event.midi); if (midiEvent.size == 0 || midiEvent.size >= MAX_MIDI_VALUE) continue; const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data); uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData)); if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES)) continue; if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0) continue; if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0) continue; if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0) continue; if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0) continue; // Fix bad note-off if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0) status = MIDI_STATUS_NOTE_OFF; fShmRtClientControl.writeOpcode(kPluginBridgeRtClientMidiEvent); fShmRtClientControl.writeUInt(event.time); fShmRtClientControl.writeByte(midiEvent.port); fShmRtClientControl.writeByte(midiEvent.size); fShmRtClientControl.writeByte(uint8_t(midiData[0] | (event.channel & MIDI_CHANNEL_BIT))); for (uint8_t j=1; j < midiEvent.size; ++j) fShmRtClientControl.writeByte(midiData[j]); fShmRtClientControl.commitWrite(); if (status == MIDI_STATUS_NOTE_ON) { pData->postponeNoteOnRtEvent(true, event.channel, midiData[1], midiData[2]); } else if (status == MIDI_STATUS_NOTE_OFF) { pData->postponeNoteOffRtEvent(true, event.channel, midiData[1]); } } break; } } pData->postRtEvents.trySplice(); } // End of Event Input if (! processSingle(audioIn, audioOut, frames)) return; // -------------------------------------------------------------------------------------------------------- // MIDI Output if (pData->event.portOut != nullptr) { uint32_t time; uint8_t port, size; const uint8_t* midiData(fShmRtClientControl.data->midiOut); for (std::size_t read=0; readevent.portOut->writeMidiEvent(time, size, data); read += kBridgeBaseMidiOutHeaderSize + size; } // TODO (void)port; } // End of Control and MIDI Output } bool processSingle(const float* const* const audioIn, float** const audioOut, const uint32_t frames) { CARLA_SAFE_ASSERT_RETURN(! fTimedError, false); CARLA_SAFE_ASSERT_RETURN(frames > 0, false); CARLA_SAFE_ASSERT_RETURN(frames <= fBufferSize, false); if (pData->audioIn.count > 0) { CARLA_SAFE_ASSERT_RETURN(audioIn != nullptr, false); } if (pData->audioOut.count > 0) { CARLA_SAFE_ASSERT_RETURN(audioOut != nullptr, false); } // -------------------------------------------------------------------------------------------------------- // Try lock, silence otherwise #ifndef STOAT_TEST_BUILD if (pData->engine->isOffline()) { pData->singleMutex.lock(); } else #endif if (! pData->singleMutex.tryLock()) { for (uint32_t i=0; i < pData->audioOut.count; ++i) carla_zeroFloats(audioOut[i], frames); return false; } // -------------------------------------------------------------------------------------------------------- // Reset audio buffers for (uint32_t i=0; i < fInfo.aIns; ++i) carla_copyFloats(fShmAudioPool.data + (i * fBufferSize), audioIn[i], frames); // -------------------------------------------------------------------------------------------------------- // TimeInfo const EngineTimeInfo timeInfo(pData->engine->getTimeInfo()); BridgeTimeInfo& bridgeTimeInfo(fShmRtClientControl.data->timeInfo); bridgeTimeInfo.playing = timeInfo.playing; bridgeTimeInfo.frame = timeInfo.frame; bridgeTimeInfo.usecs = timeInfo.usecs; bridgeTimeInfo.validFlags = timeInfo.bbt.valid ? kPluginBridgeTimeInfoValidBBT : 0x0; if (timeInfo.bbt.valid) { bridgeTimeInfo.bar = timeInfo.bbt.bar; bridgeTimeInfo.beat = timeInfo.bbt.beat; bridgeTimeInfo.tick = timeInfo.bbt.tick; bridgeTimeInfo.beatsPerBar = timeInfo.bbt.beatsPerBar; bridgeTimeInfo.beatType = timeInfo.bbt.beatType; bridgeTimeInfo.ticksPerBeat = timeInfo.bbt.ticksPerBeat; bridgeTimeInfo.beatsPerMinute = timeInfo.bbt.beatsPerMinute; bridgeTimeInfo.barStartTick = timeInfo.bbt.barStartTick; } // -------------------------------------------------------------------------------------------------------- // Run plugin { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientProcess); fShmRtClientControl.writeUInt(frames); fShmRtClientControl.commitWrite(); } waitForClient("process", fProcWaitTime); if (fTimedOut) { pData->singleMutex.unlock(); return false; } if (fShmRtClientControl.data->procFlags) { fInitiated = false; fProcCanceled = true; } for (uint32_t i=0; i < fInfo.aOuts; ++i) carla_copyFloats(audioOut[i], fShmAudioPool.data + ((i + fInfo.aIns) * fBufferSize), frames); #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH // -------------------------------------------------------------------------------------------------------- // Post-processing (dry/wet, volume and balance) { const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && carla_isNotEqual(pData->postProc.volume, 1.0f); const bool doDryWet = (pData->hints & PLUGIN_CAN_DRYWET) != 0 && carla_isNotEqual(pData->postProc.dryWet, 1.0f); const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); const bool isMono = (pData->audioIn.count == 1); bool isPair; float bufValue, oldBufLeft[doBalance ? frames : 1]; for (uint32_t i=0; i < pData->audioOut.count; ++i) { // Dry/Wet if (doDryWet) { const uint32_t c = isMono ? 0 : i; for (uint32_t k=0; k < frames; ++k) { bufValue = audioIn[c][k]; audioOut[i][k] = (audioOut[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); } } // Balance if (doBalance) { isPair = (i % 2 == 0); if (isPair) { CARLA_ASSERT(i+1 < pData->audioOut.count); carla_copyFloats(oldBufLeft, audioOut[i], frames); } float balRangeL = (pData->postProc.balanceLeft + 1.0f)/2.0f; float balRangeR = (pData->postProc.balanceRight + 1.0f)/2.0f; for (uint32_t k=0; k < frames; ++k) { if (isPair) { // left audioOut[i][k] = oldBufLeft[k] * (1.0f - balRangeL); audioOut[i][k] += audioOut[i+1][k] * (1.0f - balRangeR); } else { // right audioOut[i][k] = audioOut[i][k] * balRangeR; audioOut[i][k] += oldBufLeft[k] * balRangeL; } } } // Volume if (doVolume) { for (uint32_t k=0; k < frames; ++k) audioOut[i][k] *= pData->postProc.volume; } } } // End of Post-processing #endif // -------------------------------------------------------------------------------------------------------- pData->singleMutex.unlock(); return true; } void bufferSizeChanged(const uint32_t newBufferSize) override { fBufferSize = newBufferSize; resizeAudioPool(newBufferSize); { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetBufferSize); fShmRtClientControl.writeUInt(newBufferSize); fShmRtClientControl.commitWrite(); } //fProcWaitTime = newBufferSize*1000/pData->engine->getSampleRate(); fProcWaitTime = 1000; waitForClient("buffersize", 1000); } void sampleRateChanged(const double newSampleRate) override { { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetSampleRate); fShmRtClientControl.writeDouble(newSampleRate); fShmRtClientControl.commitWrite(); } //fProcWaitTime = pData->engine->getBufferSize()*1000/newSampleRate; fProcWaitTime = 1000; waitForClient("samplerate", 1000); } void offlineModeChanged(const bool isOffline) override { { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetOnline); fShmRtClientControl.writeBool(isOffline); fShmRtClientControl.commitWrite(); } waitForClient("offline", 1000); } // ------------------------------------------------------------------- // Post-poned UI Stuff // ------------------------------------------------------------------- void handleNonRtData() { for (; fShmNonRtServerControl.isDataAvailableForReading();) { const PluginBridgeNonRtServerOpcode opcode(fShmNonRtServerControl.readOpcode()); //#ifdef DEBUG if (opcode != kPluginBridgeNonRtServerPong) { carla_debug("CarlaPluginJack::handleNonRtData() - got opcode: %s", PluginBridgeNonRtServerOpcode2str(opcode)); } //#endif switch (opcode) { case kPluginBridgeNonRtServerNull: case kPluginBridgeNonRtServerPong: case kPluginBridgeNonRtServerPluginInfo1: case kPluginBridgeNonRtServerPluginInfo2: case kPluginBridgeNonRtServerAudioCount: case kPluginBridgeNonRtServerMidiCount: case kPluginBridgeNonRtServerCvCount: case kPluginBridgeNonRtServerParameterCount: case kPluginBridgeNonRtServerProgramCount: case kPluginBridgeNonRtServerMidiProgramCount: case kPluginBridgeNonRtServerPortName: case kPluginBridgeNonRtServerParameterData1: case kPluginBridgeNonRtServerParameterData2: case kPluginBridgeNonRtServerParameterRanges: case kPluginBridgeNonRtServerParameterValue: case kPluginBridgeNonRtServerParameterValue2: case kPluginBridgeNonRtServerParameterTouch: case kPluginBridgeNonRtServerDefaultValue: case kPluginBridgeNonRtServerCurrentProgram: case kPluginBridgeNonRtServerCurrentMidiProgram: case kPluginBridgeNonRtServerProgramName: case kPluginBridgeNonRtServerMidiProgramData: case kPluginBridgeNonRtServerSetCustomData: case kPluginBridgeNonRtServerVersion: case kPluginBridgeNonRtServerRespEmbedUI: case kPluginBridgeNonRtServerResizeEmbedUI: break; case kPluginBridgeNonRtServerSetChunkDataFile: // uint/size, str[] (filename) if (const uint32_t chunkFilePathSize = fShmNonRtServerControl.readUInt()) { char chunkFilePath[chunkFilePathSize]; fShmNonRtServerControl.readCustomData(chunkFilePath, chunkFilePathSize); } break; case kPluginBridgeNonRtServerSetLatency: case kPluginBridgeNonRtServerSetParameterText: break; case kPluginBridgeNonRtServerReady: fInitiated = true; break; case kPluginBridgeNonRtServerSaved: break; case kPluginBridgeNonRtServerUiClosed: pData->engine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, pData->id, 0, 0, 0, 0.0f, nullptr); break; case kPluginBridgeNonRtServerError: { // error const uint32_t errorSize(fShmNonRtServerControl.readUInt()); char error[errorSize+1]; carla_zeroChars(error, errorSize+1); fShmNonRtServerControl.readCustomData(error, errorSize); if (fInitiated) { pData->engine->callback(true, true, ENGINE_CALLBACK_ERROR, pData->id, 0, 0, 0, 0.0f, error); // just in case pData->engine->setLastError(error); fInitError = true; } else { pData->engine->setLastError(error); fInitError = true; fInitiated = true; } } break; } } } // ------------------------------------------------------------------- uintptr_t getUiBridgeProcessId() const noexcept override { return fBridgeThread.getProcessID(); } // ------------------------------------------------------------------- bool init(const CarlaPluginPtr plugin, const char* const filename, const char* const name, const char* const label, 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 (label == nullptr || label[0] == '\0') { pData->engine->setLastError("null label"); return false; } // --------------------------------------------------------------- // check setup if (std::strlen(label) < 6) { pData->engine->setLastError("invalid application setup received"); return false; } for (int i=4; --i >= 0;) { CARLA_SAFE_ASSERT_INT2_RETURN(label[i] >= '0' && label[i] <= '0'+64, i, label[i], false); } CARLA_SAFE_ASSERT_INT2_RETURN(label[4] >= '0' && label[4] < '0'+0x4f, 4, label[4], false); CARLA_SAFE_ASSERT_UINT2_RETURN(static_cast(label[5]) >= '0' && static_cast(label[5]) <= '0'+0x73, static_cast(label[5]), static_cast('0'+0x73), false); fInfo.aIns = static_cast(label[0] - '0'); fInfo.aOuts = static_cast(label[1] - '0'); fInfo.mIns = static_cast(carla_minPositive(label[2] - '0', 1)); fInfo.mOuts = static_cast(carla_minPositive(label[3] - '0', 1)); fInfo.setupLabel = label; // --------------------------------------------------------------- // set project unique id if (label[6] == '\0') setupUniqueProjectID(); // --------------------------------------------------------------- // set icon pData->iconName = carla_strdup_safe("application"); // --------------------------------------------------------------- // set info pData->filename = carla_strdup(filename); if (name != nullptr && name[0] != '\0') pData->name = pData->engine->getUniquePluginName(name); else pData->name = pData->engine->getUniquePluginName("Jack Application"); std::srand(static_cast(std::time(nullptr))); // --------------------------------------------------------------- // init sem/shm if (! fShmAudioPool.initializeServer()) { carla_stderr("Failed to initialize shared memory audio pool"); return false; } if (! fShmRtClientControl.initializeServer()) { carla_stderr("Failed to initialize RT client control"); fShmAudioPool.clear(); return false; } if (! fShmNonRtClientControl.initializeServer()) { carla_stderr("Failed to initialize Non-RT client control"); fShmRtClientControl.clear(); fShmAudioPool.clear(); return false; } if (! fShmNonRtServerControl.initializeServer()) { carla_stderr("Failed to initialize Non-RT server control"); fShmNonRtClientControl.clear(); fShmRtClientControl.clear(); fShmAudioPool.clear(); return false; } // --------------------------------------------------------------- // setup hints and options fSetupHints = static_cast(static_cast(label[5]) - '0'); // FIXME dryWet broken pData->hints = PLUGIN_IS_BRIDGE; #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH pData->hints |= /*PLUGIN_CAN_DRYWET |*/ PLUGIN_CAN_VOLUME | PLUGIN_CAN_BALANCE; #endif if (fSetupHints & LIBJACK_FLAG_CONTROL_WINDOW) pData->hints |= PLUGIN_HAS_CUSTOM_UI; // --------------------------------------------------------------- // init bridge thread { char shmIdsStr[6*4+1]; carla_zeroChars(shmIdsStr, 6*4+1); std::strncpy(shmIdsStr+6*0, &fShmAudioPool.filename[fShmAudioPool.filename.length()-6], 6); std::strncpy(shmIdsStr+6*1, &fShmRtClientControl.filename[fShmRtClientControl.filename.length()-6], 6); std::strncpy(shmIdsStr+6*2, &fShmNonRtClientControl.filename[fShmNonRtClientControl.filename.length()-6], 6); std::strncpy(shmIdsStr+6*3, &fShmNonRtServerControl.filename[fShmNonRtServerControl.filename.length()-6], 6); fBridgeThread.setData(shmIdsStr, fInfo.setupLabel); } if (! restartBridgeThread()) return false; // --------------------------------------------------------------- // register client if (pData->name == nullptr) pData->name = pData->engine->getUniquePluginName("unknown"); pData->client = pData->engine->addClient(plugin); if (pData->client == nullptr || ! pData->client->isOk()) { pData->engine->setLastError("Failed to register plugin client"); return false; } // remove unprintable characters if needed if (fSetupHints & LIBJACK_FLAG_EXTERNAL_START) fInfo.setupLabel[5U] = static_cast('0' + (fSetupHints ^ LIBJACK_FLAG_EXTERNAL_START)); // --------------------------------------------------------------- // set options pData->options = PLUGIN_OPTION_FIXED_BUFFERS; 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; return true; } private: bool fInitiated; bool fInitError; bool fTimedOut; bool fTimedError; bool fProcCanceled; uint fBufferSize; uint fProcWaitTime; uint fSetupHints; CarlaPluginJackThread fBridgeThread; BridgeAudioPool fShmAudioPool; BridgeRtClientControl fShmRtClientControl; BridgeNonRtClientControl fShmNonRtClientControl; BridgeNonRtServerControl fShmNonRtServerControl; struct Info { uint8_t aIns, aOuts; uint8_t mIns, mOuts; CarlaString setupLabel; std::vector chunk; Info() : aIns(0), aOuts(0), mIns(0), mOuts(0), setupLabel(), chunk() {} CARLA_DECLARE_NON_COPYABLE(Info) } fInfo; void handleProcessStopped() noexcept { const bool wasActive = pData->active; pData->active = false; if (wasActive) { pData->engine->callback(true, true, ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED, pData->id, PARAMETER_ACTIVE, 0, 0, 0.0f, nullptr); } if (pData->hints & PLUGIN_HAS_CUSTOM_UI) pData->engine->callback(true, true, ENGINE_CALLBACK_UI_STATE_CHANGED, pData->id, 0, 0, 0, 0.0f, nullptr); } void resizeAudioPool(const uint32_t bufferSize) { fShmAudioPool.resize(bufferSize, static_cast(fInfo.aIns+fInfo.aOuts), 0); fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetAudioPool); fShmRtClientControl.writeULong(static_cast(fShmAudioPool.dataSize)); fShmRtClientControl.commitWrite(); waitForClient("resize-pool", 5000); } void setupUniqueProjectID() { const char* const engineProjectFolder = pData->engine->getCurrentProjectFolder(); carla_stdout("setupUniqueProjectID %s", engineProjectFolder); if (engineProjectFolder == nullptr || engineProjectFolder[0] == '\0') return; const File file(engineProjectFolder); CARLA_SAFE_ASSERT_RETURN(file.exists(),); char code[6]; code[5] = '\0'; String child; for (;;) { static const char* const kValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789"; static const size_t kValidCharsLen(std::strlen(kValidChars)-1U); code[0] = kValidChars[safe_rand(kValidCharsLen)]; code[1] = kValidChars[safe_rand(kValidCharsLen)]; code[2] = kValidChars[safe_rand(kValidCharsLen)]; code[3] = kValidChars[safe_rand(kValidCharsLen)]; code[4] = kValidChars[safe_rand(kValidCharsLen)]; child = pData->name; child += "."; child += code; const File newFile(file.getChildFile(child)); if (newFile.existsAsFile()) continue; fInfo.setupLabel += code; carla_stdout("new label %s", fInfo.setupLabel.buffer()); break; } } bool restartBridgeThread() { fInitiated = false; fInitError = false; fTimedError = false; // reset memory fProcCanceled = false; fShmRtClientControl.data->procFlags = 0; carla_zeroStruct(fShmRtClientControl.data->timeInfo); carla_zeroBytes(fShmRtClientControl.data->midiOut, kBridgeRtClientDataMidiOutSize); fShmRtClientControl.clearData(); fShmNonRtClientControl.clearData(); fShmNonRtServerControl.clearData(); // initial values fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientVersion); fShmNonRtClientControl.writeUInt(CARLA_PLUGIN_BRIDGE_API_VERSION_CURRENT); fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeRtClientData))); fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeNonRtClientData))); fShmNonRtClientControl.writeUInt(static_cast(sizeof(BridgeNonRtServerData))); fShmNonRtClientControl.writeOpcode(kPluginBridgeNonRtClientInitialSetup); fShmNonRtClientControl.writeUInt(pData->engine->getBufferSize()); fShmNonRtClientControl.writeDouble(pData->engine->getSampleRate()); fShmNonRtClientControl.commitWrite(); if (fShmAudioPool.dataSize != 0) { fShmRtClientControl.writeOpcode(kPluginBridgeRtClientSetAudioPool); fShmRtClientControl.writeULong(static_cast(fShmAudioPool.dataSize)); fShmRtClientControl.commitWrite(); } else { // testing dummy message fShmRtClientControl.writeOpcode(kPluginBridgeRtClientNull); fShmRtClientControl.commitWrite(); } fBridgeThread.startThread(); const bool needsCancelableAction = ! pData->engine->isLoadingProject(); const bool needsEngineIdle = pData->engine->getType() != kEngineTypePlugin; CarlaString actionName; if (needsCancelableAction) { if (fSetupHints & LIBJACK_FLAG_EXTERNAL_START) { const EngineOptions& options(pData->engine->getOptions()); CarlaString binaryDir(options.binaryDir); char* const hwVars = fBridgeThread.getEnvVarsToExport(); actionName = "Waiting for external JACK application start, please use the following environment variables:\n"; actionName += hwVars; delete[] hwVars; } else { actionName = "Loading JACK application"; } pData->engine->setActionCanceled(false); pData->engine->callback(true, true, ENGINE_CALLBACK_CANCELABLE_ACTION, pData->id, 1, 0, 0, 0.0f, actionName.buffer()); } for (;fBridgeThread.isThreadRunning();) { pData->engine->callback(true, false, ENGINE_CALLBACK_IDLE, 0, 0, 0, 0, 0.0f, nullptr); if (needsEngineIdle) pData->engine->idle(); idle(); if (fInitiated) break; if (pData->engine->isAboutToClose() || pData->engine->wasActionCanceled()) break; carla_msleep(5); } if (needsCancelableAction) { pData->engine->callback(true, true, ENGINE_CALLBACK_CANCELABLE_ACTION, pData->id, 0, 0, 0, 0.0f, actionName.buffer()); } if (fInitError || ! fInitiated) { fBridgeThread.stopThread(6000); if (! fInitError) pData->engine->setLastError("Timeout while waiting for a response from plugin-bridge\n" "(or the plugin crashed on initialization?)"); return false; } return true; } void waitForClient(const char* const action, const uint msecs) { CARLA_SAFE_ASSERT_RETURN(! fTimedOut,); CARLA_SAFE_ASSERT_RETURN(! fTimedError,); if (fShmRtClientControl.waitForClient(msecs)) return; fTimedOut = true; carla_stderr2("waitForClient(%s) timed out", action); } CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJack) }; CARLA_BACKEND_END_NAMESPACE #endif // CARLA_OS_LINUX // ------------------------------------------------------------------------------------------------------------------- CARLA_BACKEND_START_NAMESPACE CarlaPluginPtr CarlaPlugin::newJackApp(const Initializer& init) { carla_debug("CarlaPlugin::newJackApp({%p, \"%s\", \"%s\", \"%s\"})", init.engine, init.filename, init.name, init.label); #ifdef CARLA_OS_LINUX std::shared_ptr plugin(new CarlaPluginJack(init.engine, init.id)); if (! plugin->init(plugin, init.filename, init.name, init.label, init.options)) return nullptr; return plugin; #else init.engine->setLastError("JACK Application support not available"); return nullptr; #endif } CARLA_BACKEND_END_NAMESPACE // -------------------------------------------------------------------------------------------------------------------