/* * Carla Bridge Plugin * Copyright (C) 2012-2014 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 "CarlaBridgeClient.hpp" #include "CarlaEngine.hpp" #include "CarlaHost.h" #include "CarlaBackendUtils.hpp" #include "CarlaBridgeUtils.hpp" #include "CarlaMIDI.h" #ifdef CARLA_OS_UNIX # include #endif #include "juce_core.h" #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) # include "juce_gui_basics.h" using juce::JUCEApplication; using juce::JUCEApplicationBase; using juce::Timer; #endif using CarlaBackend::CarlaEngine; using CarlaBackend::EngineCallbackOpcode; using CarlaBackend::EngineCallbackOpcode2Str; using juce::File; using juce::String; // ------------------------------------------------------------------------- static bool gIsInitiated = false; static volatile bool gCloseNow = false; static volatile bool gSaveNow = false; #ifdef CARLA_OS_WIN static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept { if (dwCtrlType == CTRL_C_EVENT) { gCloseNow = true; return TRUE; } return FALSE; } #elif defined(CARLA_OS_LINUX) static void closeSignalHandler(int) noexcept { gCloseNow = true; } static void saveSignalHandler(int) noexcept { gSaveNow = true; } #endif static void initSignalHandler() { #ifdef CARLA_OS_WIN SetConsoleCtrlHandler(winSignalHandler, TRUE); #elif defined(CARLA_OS_LINUX) struct sigaction sint; struct sigaction sterm; struct sigaction susr1; sint.sa_handler = closeSignalHandler; sint.sa_flags = SA_RESTART; sint.sa_restorer = nullptr; sigemptyset(&sint.sa_mask); sigaction(SIGINT, &sint, nullptr); sterm.sa_handler = closeSignalHandler; sterm.sa_flags = SA_RESTART; sterm.sa_restorer = nullptr; sigemptyset(&sterm.sa_mask); sigaction(SIGTERM, &sterm, nullptr); susr1.sa_handler = saveSignalHandler; susr1.sa_flags = SA_RESTART; susr1.sa_restorer = nullptr; sigemptyset(&susr1.sa_mask); sigaction(SIGUSR1, &susr1, nullptr); #endif } // ------------------------------------------------------------------------- #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) static CarlaBridge::CarlaBridgeClient* gBridgeClient = nullptr; class CarlaJuceApp : public JUCEApplication, private Timer { public: CarlaJuceApp() {} ~CarlaJuceApp() {} void initialise(const String&) override { startTimer(30); } void shutdown() override { gCloseNow = true; stopTimer(); } const String getApplicationName() override { return "CarlaPlugin"; } const String getApplicationVersion() override { return CARLA_VERSION_STRING; } void timerCallback() override { carla_engine_idle(); if (gBridgeClient != nullptr) gBridgeClient->oscIdle(); if (gCloseNow) { quit(); gCloseNow = false; } } }; static JUCEApplicationBase* juce_CreateApplication() { return new CarlaJuceApp(); } #endif // ------------------------------------------------------------------------- CARLA_BRIDGE_START_NAMESPACE // ------------------------------------------------------------------------- class CarlaPluginClient : public CarlaBridgeClient { public: CarlaPluginClient(const bool useBridge, const char* const clientName, const char* const audioBaseName, const char* const controlBaseName, const char* const timeBaseName) : CarlaBridgeClient(nullptr), fEngine(nullptr) { CARLA_ASSERT(clientName != nullptr && clientName[0] != '\0'); carla_debug("CarlaPluginClient::CarlaPluginClient(%s, \"%s\", %s, %s, %s)", bool2str(useBridge), clientName, audioBaseName, controlBaseName, timeBaseName); carla_set_engine_callback(callback, this); if (useBridge) carla_engine_init_bridge(audioBaseName, controlBaseName, timeBaseName, clientName); else carla_engine_init("JACK", clientName); fEngine = carla_get_engine(); } ~CarlaPluginClient() override { carla_debug("CarlaPluginClient::~CarlaPluginClient()"); carla_engine_close(); } bool isOk() const noexcept { return (fEngine != nullptr); } void oscInit(const char* const url) { CarlaBridgeClient::oscInit(url); fEngine->setOscBridgeData(&fOscData); } void exec(const bool useOsc, int argc, char* argv[]) { if (! useOsc) { const CarlaPluginInfo* const pInfo(carla_get_plugin_info(0)); CARLA_SAFE_ASSERT_RETURN(pInfo != nullptr,); fProjFilename = pInfo->name; fProjFilename += ".carxs"; if (! File::isAbsolutePath(fProjFilename)) fProjFilename = File::getCurrentWorkingDirectory().getChildFile(fProjFilename).getFullPathName(); if (File(fProjFilename).existsAsFile() && ! carla_load_plugin_state(0, fProjFilename.toRawUTF8())) carla_stderr("Plugin preset load failed, error was:\n%s", carla_get_last_error()); } gIsInitiated = true; #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) gBridgeClient = this; JUCEApplicationBase::createInstance = &juce_CreateApplication; JUCEApplicationBase::main(JUCE_MAIN_FUNCTION_ARGS); gBridgeClient = nullptr; #else for (; ! gCloseNow;) { idle(); carla_msleep(25); } #endif carla_set_engine_about_to_close(); carla_remove_plugin(0); // may be unused return; (void)argc; (void)argv; } void idle() { CARLA_SAFE_ASSERT_RETURN(fEngine != nullptr,); carla_engine_idle(); CarlaBridgeClient::oscIdle(); if (gSaveNow) { gSaveNow = false; if (fProjFilename.isNotEmpty()) { if (! carla_save_plugin_state(0, fProjFilename.toRawUTF8())) carla_stderr("Plugin preset save failed, error was:\n%s", carla_get_last_error()); } } } // --------------------------------------------------------------------- // plugin management void saveNow() { CARLA_SAFE_ASSERT_RETURN(fEngine != nullptr,); carla_debug("CarlaPluginClient::saveNow()"); carla_prepare_for_save(0); for (uint32_t i=0, count=carla_get_custom_data_count(0); ioscSend_bridge_set_custom_data(cdata->type, cdata->key, cdata->value); } //if (fPlugin->getOptionsEnabled() & CarlaBackend::PLUGIN_OPTION_USE_CHUNKS) { //if (const char* const chunkData = carla_get_chunk_data(0)) { #if 0 QString filePath; filePath = QDir::tempPath(); #ifdef Q_OS_WIN filePath += "\\.CarlaChunk_"; #else filePath += "/.CarlaChunk_"; #endif filePath += fPlugin->getName(); QFile file(filePath); if (file.open(QIODevice::WriteOnly)) { QByteArray chunk((const char*)data, dataSize); file.write(chunk); file.close(); fEngine->oscSend_bridge_set_chunk_data(filePath.toUtf8().constData()); } #endif } } fEngine->oscSend_bridge_configure(CARLA_BRIDGE_MSG_SAVED, ""); } // --------------------------------------------------------------------- protected: void handleCallback(const EngineCallbackOpcode action, const int value1, const int value2, const float value3, const char* const valueStr) { CARLA_BACKEND_USE_NAMESPACE; // TODO switch (action) { case ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED: if (isOscControlRegistered()) { CARLA_SAFE_ASSERT_RETURN(value1 >= 0,); fEngine->oscSend_bridge_parameter_value(static_cast(value1), value3); } break; case ENGINE_CALLBACK_UI_STATE_CHANGED: if (! isOscControlRegistered()) { if (value1 != 1 && gIsInitiated) gCloseNow = true; } else { // show-gui button fEngine->oscSend_bridge_configure(CARLA_BRIDGE_MSG_HIDE_GUI, ""); } break; default: break; } return; (void)value2; (void)value3; (void)valueStr; } private: const CarlaEngine* fEngine; String fProjFilename; static void callback(void* ptr, EngineCallbackOpcode action, unsigned int pluginId, int value1, int value2, float value3, const char* valueStr) { carla_debug("CarlaPluginClient::callback(%p, %i:%s, %i, %i, %i, %f, \"%s\")", ptr, action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); CARLA_SAFE_ASSERT_RETURN(ptr != nullptr,); CARLA_SAFE_ASSERT_RETURN(pluginId == 0,); return ((CarlaPluginClient*)ptr)->handleCallback(action, value1, value2, value3, valueStr); } }; // ------------------------------------------------------------------------- int CarlaBridgeOsc::handleMsgShow() { carla_debug("CarlaBridgeOsc::handleMsgShow()"); if (carla_get_plugin_info(0)->hints & CarlaBackend::PLUGIN_HAS_CUSTOM_UI) carla_show_custom_ui(0, true); return 0; } int CarlaBridgeOsc::handleMsgHide() { carla_debug("CarlaBridgeOsc::handleMsgHide()"); if (carla_get_plugin_info(0)->hints & CarlaBackend::PLUGIN_HAS_CUSTOM_UI) carla_show_custom_ui(0, false); return 0; } int CarlaBridgeOsc::handleMsgQuit() { carla_debug("CarlaBridgeOsc::handleMsgQuit()"); gCloseNow = true; return 0; } // ------------------------------------------------------------------------- int CarlaBridgeOsc::handleMsgPluginSaveNow() { CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1); carla_debug("CarlaBridgeOsc::handleMsgPluginSaveNow()"); ((CarlaPluginClient*)fClient)->saveNow(); return 0; } int CarlaBridgeOsc::handleMsgPluginSetParameterMidiChannel(CARLA_BRIDGE_OSC_HANDLE_ARGS) { CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(2, "ii"); CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1); carla_debug("CarlaBridgeOsc::handleMsgPluginSetParameterMidiChannel()"); const int32_t index = argv[0]->i; const int32_t channel = argv[1]->i; CARLA_SAFE_ASSERT_RETURN(index >= 0, 0); CARLA_SAFE_ASSERT_RETURN(channel >= 0 && channel < MAX_MIDI_CHANNELS, 0); carla_set_parameter_midi_channel(0, static_cast(index), static_cast(channel)); return 0; } int CarlaBridgeOsc::handleMsgPluginSetParameterMidiCC(CARLA_BRIDGE_OSC_HANDLE_ARGS) { CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(2, "ii"); CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1); carla_debug("CarlaBridgeOsc::handleMsgPluginSetParameterMidiCC()"); const int32_t index = argv[0]->i; const int32_t cc = argv[1]->i; CARLA_SAFE_ASSERT_RETURN(index >= 0, 0); CARLA_SAFE_ASSERT_RETURN(cc >= 1 && cc < 0x5F, 0); carla_set_parameter_midi_cc(0, static_cast(index), static_cast(cc)); return 0; } int CarlaBridgeOsc::handleMsgPluginSetCustomData(CARLA_BRIDGE_OSC_HANDLE_ARGS) { CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(3, "sss"); CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1); carla_debug("CarlaBridgeOsc::handleMsgPluginSetCustomData()"); const char* const type = (const char*)&argv[0]->s; const char* const key = (const char*)&argv[1]->s; const char* const value = (const char*)&argv[2]->s; carla_set_custom_data(0, type, key, value); return 0; } int CarlaBridgeOsc::handleMsgPluginSetChunk(CARLA_BRIDGE_OSC_HANDLE_ARGS) { CARLA_BRIDGE_OSC_CHECK_OSC_TYPES(1, "s"); CARLA_SAFE_ASSERT_RETURN(fClient != nullptr, 1); carla_debug("CarlaBridgeOsc::handleMsgPluginSetChunk()"); const char* const chunkFilePathTry = (const char*)&argv[0]->s; CARLA_SAFE_ASSERT_RETURN(chunkFilePathTry != nullptr && chunkFilePathTry[0] != '\0', 0); String chunkFilePath(chunkFilePathTry); #ifdef CARLA_OS_WIN if (chunkFilePath.startsWith("/")) { // running under Wine, posix host chunkFilePath = chunkFilePath.replaceSection(0, 1, "Z:\\"); chunkFilePath = chunkFilePath.replace("/", "\\"); } #endif File chunkFile(chunkFilePath); CARLA_SAFE_ASSERT_RETURN(chunkFile.existsAsFile(), 0); String chunkData(chunkFile.loadFileAsString()); chunkFile.deleteFile(); CARLA_SAFE_ASSERT_RETURN(chunkData.isNotEmpty(), 0); carla_set_chunk_data(0, chunkData.toRawUTF8()); return 0; } CARLA_BRIDGE_END_NAMESPACE // ------------------------------------------------------------------------- int main(int argc, char* argv[]) { CARLA_BRIDGE_USE_NAMESPACE; // --------------------------------------------------------------------- // Check argument count if (argc != 7) { carla_stdout("usage: %s