/* * Carla Bridge Plugin * Copyright (C) 2012-2020 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. */ #ifndef BUILD_BRIDGE # error This file should not be compiled if not building bridge #endif #include "CarlaEngine.hpp" #include "CarlaHost.h" #include "CarlaBackendUtils.hpp" #include "CarlaJuceUtils.hpp" #include "CarlaMainLoop.hpp" #include "CarlaMIDI.h" #ifdef CARLA_OS_UNIX # include #endif #ifdef CARLA_OS_LINUX # include # define SCHED_RESET_ON_FORK 0x40000000 #endif #ifdef CARLA_OS_WIN # include # include #endif #ifdef HAVE_X11 # include #endif #ifdef USING_JUCE # if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" # pragma GCC diagnostic ignored "-Weffc++" # pragma GCC diagnostic ignored "-Wsign-conversion" # pragma GCC diagnostic ignored "-Wundef" # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" # endif # include "AppConfig.h" # if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) # include "juce_gui_basics/juce_gui_basics.h" # else # include "juce_events/juce_events.h" # endif # if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # pragma GCC diagnostic pop # endif #endif #include "jackbridge/JackBridge.hpp" #include "water/files/File.h" #include "water/misc/Time.h" using CarlaBackend::CarlaEngine; using CarlaBackend::EngineCallbackOpcode; using CarlaBackend::EngineCallbackOpcode2Str; using CarlaBackend::runMainLoopOnce; using water::CharPointer_UTF8; using water::File; using water::String; // ------------------------------------------------------------------------- static bool gIsInitiated = false; static volatile bool gCloseNow = false; static volatile bool gSaveNow = false; #if defined(CARLA_OS_UNIX) static void closeSignalHandler(int) noexcept { gCloseNow = true; } static void saveSignalHandler(int) noexcept { gSaveNow = true; } #elif defined(CARLA_OS_WIN) static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept { if (dwCtrlType == CTRL_C_EVENT) { gCloseNow = true; return TRUE; } return FALSE; } #endif static void initSignalHandler() { #if defined(CARLA_OS_UNIX) struct sigaction sig; carla_zeroStruct(sig); sig.sa_handler = closeSignalHandler; sig.sa_flags = SA_RESTART; sigemptyset(&sig.sa_mask); sigaction(SIGTERM, &sig, nullptr); sigaction(SIGINT, &sig, nullptr); sig.sa_handler = saveSignalHandler; sig.sa_flags = SA_RESTART; sigemptyset(&sig.sa_mask); sigaction(SIGUSR1, &sig, nullptr); #elif defined(CARLA_OS_WIN) SetConsoleCtrlHandler(winSignalHandler, TRUE); #endif } // ------------------------------------------------------------------------- static String gProjectFilename; static CarlaHostHandle gHostHandle; static void gIdle() { carla_engine_idle(gHostHandle); if (gSaveNow) { gSaveNow = false; if (gProjectFilename.isNotEmpty()) { if (! carla_save_plugin_state(gHostHandle, 0, gProjectFilename.toRawUTF8())) carla_stderr("Plugin preset save failed, error was:\n%s", carla_get_last_error(gHostHandle)); } } } // ------------------------------------------------------------------------- #if defined(USING_JUCE) && (defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)) class CarlaJuceApp : public juce::JUCEApplication, private juce::Timer { public: CarlaJuceApp() {} ~CarlaJuceApp() {} void initialise(const juce::String&) override { startTimer(8); } void shutdown() override { gCloseNow = true; stopTimer(); } const juce::String getApplicationName() override { return "CarlaPlugin"; } const juce::String getApplicationVersion() override { return CARLA_VERSION_STRING; } void timerCallback() override { gIdle(); if (gCloseNow) { quit(); gCloseNow = false; } } }; static juce::JUCEApplicationBase* juce_CreateApplication() { return new CarlaJuceApp(); } #endif // ------------------------------------------------------------------------- class CarlaBridgePlugin { public: CarlaBridgePlugin(const bool useBridge, const char* const clientName, const char* const audioPoolBaseName, const char* const rtClientBaseName, const char* const nonRtClientBaseName, const char* const nonRtServerBaseName) : fEngine(nullptr), #ifdef USING_JUCE fJuceInitialiser(), #endif fUsingBridge(false), fUsingExec(false) { CARLA_ASSERT(clientName != nullptr && clientName[0] != '\0'); carla_debug("CarlaBridgePlugin::CarlaBridgePlugin(%s, \"%s\", %s, %s, %s, %s)", bool2str(useBridge), clientName, audioPoolBaseName, rtClientBaseName, nonRtClientBaseName, nonRtServerBaseName); carla_set_engine_callback(gHostHandle, callback, this); if (useBridge) { carla_engine_init_bridge(gHostHandle, audioPoolBaseName, rtClientBaseName, nonRtClientBaseName, nonRtServerBaseName, clientName); } else if (std::getenv("CARLA_BRIDGE_DUMMY") != nullptr) { carla_engine_init(gHostHandle, "Dummy", clientName); } else { carla_engine_init(gHostHandle, "JACK", clientName); } fEngine = carla_get_engine_from_handle(gHostHandle); } ~CarlaBridgePlugin() { carla_debug("CarlaBridgePlugin::~CarlaBridgePlugin()"); if (fEngine != nullptr && ! fUsingExec) carla_engine_close(gHostHandle); } bool isOk() const noexcept { return (fEngine != nullptr); } // --------------------------------------------------------------------- void exec(const bool useBridge) { fUsingBridge = useBridge; fUsingExec = true; if (! useBridge) { const CarlaPluginInfo* const pInfo = carla_get_plugin_info(gHostHandle, 0); CARLA_SAFE_ASSERT_RETURN(pInfo != nullptr,); gProjectFilename = CharPointer_UTF8(pInfo->name); gProjectFilename += ".carxs"; if (! File::isAbsolutePath(gProjectFilename)) gProjectFilename = File::getCurrentWorkingDirectory().getChildFile(gProjectFilename).getFullPathName(); if (File(gProjectFilename).existsAsFile()) { if (carla_load_plugin_state(gHostHandle, 0, gProjectFilename.toRawUTF8())) carla_stdout("Plugin state loaded successfully"); else carla_stderr("Plugin state load failed, error was:\n%s", carla_get_last_error(gHostHandle)); } else { carla_stdout("Previous plugin state in '%s' is non-existent, will start from default state", gProjectFilename.toRawUTF8()); } } gIsInitiated = true; const bool testing = std::getenv("CARLA_BRIDGE_TESTING") != nullptr; int64_t timeToEnd = 0; if (testing) { timeToEnd = water::Time::currentTimeMillis() + 10 * 1000; fEngine->transportPlay(); } #if defined(USING_JUCE) && (defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)) # ifndef CARLA_OS_WIN static const int argc = 0; static const char* argv[] = {}; # endif juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; juce::JUCEApplicationBase::main(JUCE_MAIN_FUNCTION_ARGS); #else for (; runMainLoopOnce() && ! gCloseNow;) { gIdle(); # if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) // MacOS and Win32 have event-loops to run, so minimize sleep time carla_msleep(1); # else carla_msleep(5); # endif if (testing && timeToEnd - water::Time::currentTimeMillis() < 0) break; } #endif carla_engine_close(gHostHandle); } // --------------------------------------------------------------------- protected: void handleCallback(const EngineCallbackOpcode action, const int value1, const int, const int, const float, const char* const) { CARLA_BACKEND_USE_NAMESPACE; switch (action) { case ENGINE_CALLBACK_ENGINE_STOPPED: case ENGINE_CALLBACK_PLUGIN_REMOVED: case ENGINE_CALLBACK_QUIT: gCloseNow = true; break; case ENGINE_CALLBACK_UI_STATE_CHANGED: if (gIsInitiated && value1 != 1 && ! fUsingBridge) gCloseNow = true; break; default: break; } } private: CarlaEngine* fEngine; #ifdef USING_JUCE const juce::ScopedJuceInitialiser_GUI fJuceInitialiser; #endif bool fUsingBridge; bool fUsingExec; static void callback(void* ptr, EngineCallbackOpcode action, unsigned int pluginId, int value1, int value2, int value3, float valuef, const char* valueStr) { carla_debug("CarlaBridgePlugin::callback(%p, %i:%s, %i, %i, %i, %i, %f, \"%s\")", ptr, action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, static_cast(valuef), valueStr); // ptr must not be null CARLA_SAFE_ASSERT_RETURN(ptr != nullptr,); #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH // pluginId must be 0 (first), except for patchbay things if (action < CarlaBackend::ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED || action > CarlaBackend::ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED) #endif { CARLA_SAFE_ASSERT_UINT_RETURN(pluginId == 0, pluginId,); } return ((CarlaBridgePlugin*)ptr)->handleCallback(action, value1, value2, value3, valuef, valueStr); } CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgePlugin) }; // ------------------------------------------------------------------------- int main(int argc, char* argv[]) { // --------------------------------------------------------------------- // Check argument count if (argc != 4 && argc != 5) { carla_stdout("usage: %s