diff --git a/source/backend/CarlaBridge.hpp b/source/backend/CarlaBridge.hpp new file mode 100644 index 000000000..297674164 --- /dev/null +++ b/source/backend/CarlaBridge.hpp @@ -0,0 +1,82 @@ +/* + * Carla Bridge API + * Copyright (C) 2013 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 GPL.txt file + */ + +#ifndef __CARLA_BRIDGE_HPP__ +#define __CARLA_BRIDGE_HPP__ + +#include + +#define BRIDGE_SHM_RING_BUFFER_SIZE 2048 + +#ifndef BUILD_BRIDGE +/*! + * TODO. + */ +enum PluginBridgeInfoType { + kPluginBridgeAudioCount, + kPluginBridgeMidiCount, + kPluginBridgeParameterCount, + kPluginBridgeProgramCount, + kPluginBridgeMidiProgramCount, + kPluginBridgePluginInfo, + kPluginBridgeParameterInfo, + kPluginBridgeParameterData, + kPluginBridgeParameterRanges, + kPluginBridgeProgramInfo, + kPluginBridgeMidiProgramInfo, + kPluginBridgeConfigure, + kPluginBridgeSetParameterValue, + kPluginBridgeSetDefaultValue, + kPluginBridgeSetProgram, + kPluginBridgeSetMidiProgram, + kPluginBridgeSetCustomData, + kPluginBridgeSetChunkData, + kPluginBridgeUpdateNow, + kPluginBridgeError +}; +#endif + +/*! + * TODO. + */ +struct BridgeRingBuffer { + int head; + int tail; + int written; + bool invalidateCommit; + char buf[BRIDGE_SHM_RING_BUFFER_SIZE]; +}; + +/*! + * TODO. + */ +struct BridgeShmControl { + // 32 and 64-bit binaries align semaphores differently. + // Let's make sure there's plenty of room for either one. + union { + sem_t runServer; + char _alignServer[32]; + }; + union { + sem_t runClient; + char _alignClient[32]; + }; + + BridgeRingBuffer ringBuffer; +}; + +#endif // __CARLA_BRIDGE_HPP__ diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index 7d1f1670f..17fd19dd4 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -58,7 +58,12 @@ enum EngineType { /*! * Plugin engine type, used to export the engine as a plugin. */ - kEngineTypePlugin = 3 + kEngineTypePlugin = 3, + + /*! + * TODO. + */ + kEngineTypeBridge = 4 }; /*! @@ -1008,6 +1013,9 @@ protected: #endif private: +#ifdef BUILD_BRIDGE + static CarlaEngine* newBridge(const char* const audioBaseName, const char* const controlBaseName); +#endif #ifdef WANT_JACK static CarlaEngine* newJack(); #endif diff --git a/source/backend/CarlaPlugin.hpp b/source/backend/CarlaPlugin.hpp index ad0d5b075..d908ad2f2 100644 --- a/source/backend/CarlaPlugin.hpp +++ b/source/backend/CarlaPlugin.hpp @@ -34,34 +34,6 @@ CARLA_BACKEND_START_NAMESPACE } // Fix editor indentation #endif -#if 1//ndef BUILD_BRIDGE -/*! - * TODO. - */ -enum PluginBridgeInfoType { - kPluginBridgeAudioCount, - kPluginBridgeMidiCount, - kPluginBridgeParameterCount, - kPluginBridgeProgramCount, - kPluginBridgeMidiProgramCount, - kPluginBridgePluginInfo, - kPluginBridgeParameterInfo, - kPluginBridgeParameterData, - kPluginBridgeParameterRanges, - kPluginBridgeProgramInfo, - kPluginBridgeMidiProgramInfo, - kPluginBridgeConfigure, - kPluginBridgeSetParameterValue, - kPluginBridgeSetDefaultValue, - kPluginBridgeSetProgram, - kPluginBridgeSetMidiProgram, - kPluginBridgeSetCustomData, - kPluginBridgeSetChunkData, - kPluginBridgeUpdateNow, - kPluginBridgeError -}; -#endif - /*! * TODO. */ diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 9a33b0790..f10f43bd0 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -35,6 +35,13 @@ CARLA_BACKEND_START_NAMESPACE return CarlaEngineProtectedData::getHostWindow(engine); } +#ifndef BUILD_BRIDGE +void registerEnginePlugin(CarlaEngine* const engine, const unsigned int id, CarlaPlugin* const plugin) +{ + CarlaEngineProtectedData::registerEnginePlugin(engine, id, plugin); +} +#endif + // ------------------------------------------------------------------------------------------------------------------- // Carla Engine port (Abstract) diff --git a/source/backend/engine/CarlaEngine.pro b/source/backend/engine/CarlaEngine.pro index 994de78ad..3fe750e7a 100644 --- a/source/backend/engine/CarlaEngine.pro +++ b/source/backend/engine/CarlaEngine.pro @@ -61,6 +61,7 @@ SOURCES = \ CarlaEngine.cpp \ CarlaEngineOsc.cpp \ CarlaEngineThread.cpp \ + CarlaEngineBridge.cpp \ CarlaEngineJack.cpp \ CarlaEnginePlugin.cpp \ CarlaEngineRtAudio.cpp @@ -72,6 +73,7 @@ HEADERS = \ HEADERS += \ ../CarlaBackend.hpp \ + ../CarlaBridge.hpp \ ../CarlaEngine.hpp \ ../CarlaPlugin.hpp diff --git a/source/backend/engine/CarlaEngineBridge.cpp b/source/backend/engine/CarlaEngineBridge.cpp new file mode 100644 index 000000000..b9a8d255d --- /dev/null +++ b/source/backend/engine/CarlaEngineBridge.cpp @@ -0,0 +1,183 @@ +/* + * Carla Plugin Engine + * Copyright (C) 2012-2013 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 GPL.txt file + */ + +#ifdef BUILD_BRIDGE + +#include "CarlaEngineInternal.hpp" +#include "CarlaBackendUtils.hpp" +#include "CarlaMIDI.h" + +#include "CarlaBridge.hpp" +#include "CarlaShmUtils.hpp" + +CARLA_BACKEND_START_NAMESPACE + +#if 0 +} // Fix editor indentation +#endif + +// ----------------------------------------- + +class CarlaEngineBridge : public CarlaEngine +{ +public: + CarlaEngineBridge(const char* const audioBaseName, const char* const controlBaseName) + : CarlaEngine() + { + carla_debug("CarlaEngineBridge::CarlaEngineBridge()"); + + fShmAudioPool.filename = "/carla-bridge_shm_" + audioBaseName; + fShmControl.filename = "/carla-bridge_shc_" + controlBaseName; + } + + ~CarlaEngineBridge() + { + carla_debug("CarlaEngineBridge::~CarlaEngineBridge()"); + } + + // ------------------------------------- + // CarlaEngine virtual calls + + bool init(const char* const clientName) + { + carla_debug("CarlaEngineBridge::init(\"%s\")", clientName); + + char tmpFileBase[60]; + + // SHM Audio Pool + { + fShmAudioPool.shm = carla_shm_attach((const char*)fShmAudioPool.filename); + + if (! carla_is_shm_valid(fShmAudioPool.shm)) + { + _cleanup(); + carla_stdout("Failed to open or create shared memory file #1"); + return false; + } + } + + // SHM Control + { + fShmControl.shm = carla_shm_attach((const char*)fShmControl.filename); + + if (! carla_is_shm_valid(fShmControl.shm)) + { + _cleanup(); + carla_stdout("Failed to open or create shared memory file #2"); + return false; + } + + if (! carla_shm_map(fShmControl.shm, fShmControl.data)) + { + _cleanup(); + carla_stdout("Failed to mmap shared memory file"); + return false; + } + } + + CarlaEngine::init(fName); + return true; + } + + bool close() + { + carla_debug("CarlaEnginePlugin::close()"); + CarlaEngine::close(); + + _cleanup(); + + return true; + } + + bool isRunning() const + { + return true; + } + + bool isOffline() const + { + return false; + } + + EngineType type() const + { + return kEngineTypeBridge; + } + +private: + struct BridgeAudioPool { + CarlaString filename; + float* data; + size_t size; + shm_t shm; + + BridgeAudioPool() + : data(nullptr), + size(0) + { + carla_shm_init(shm); + } + } fShmAudioPool; + + struct BridgeControl { + CarlaString filename; + ShmControl* data; + shm_t shm; + + BridgeControl() + : data(nullptr) + { + carla_shm_init(shm); + } + + } fShmControl; + + void _cleanup() + { + if (fShmAudioPool.filename.isNotEmpty()) + fShmAudioPool.filename.clear(); + + if (fShmControl.filename.isNotEmpty()) + fShmControl.filename.clear(); + + // delete data + fShmAudioPool.data = nullptr; + fShmAudioPool.size = 0; + + // and again + fShmControl.data = nullptr; + + if (carla_is_shm_valid(fShmAudioPool.shm)) + carla_shm_close(fShmAudioPool.shm); + + if (carla_is_shm_valid(fShmControl.shm)) + carla_shm_close(fShmControl.shm); + } + + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineBridge) +}; + +// ----------------------------------------- + +CarlaEngine* CarlaEngine::newBridge(const char* const audioBaseName, const char* const controlBaseName) +{ + return new CarlaEngineBridge(audioBaseName, controlBaseName); +} + +CARLA_BACKEND_END_NAMESPACE + +#endif // BUILD_BRIDGE diff --git a/source/backend/engine/CarlaEngineInternal.hpp b/source/backend/engine/CarlaEngineInternal.hpp index d5ea75917..d3068f0a1 100644 --- a/source/backend/engine/CarlaEngineInternal.hpp +++ b/source/backend/engine/CarlaEngineInternal.hpp @@ -47,6 +47,8 @@ const char* EngineType2Str(const EngineType type) return "kEngineTypeRtAudio"; case kEngineTypePlugin: return "kEngineTypePlugin"; + case kEngineTypeBridge: + return "kEngineTypeBridge"; } carla_stderr("CarlaBackend::EngineType2Str(%i) - invalid type", type); @@ -204,6 +206,16 @@ struct CarlaEngineProtectedData { (void)engine; #endif } + +#ifndef BUILD_BRIDGE + static void registerEnginePlugin(CarlaEngine* const engine, const unsigned int id, CarlaPlugin* const plugin) + { + CARLA_ASSERT(id < engine->kData->curPluginCount); + + if (id < engine->kData->curPluginCount) + engine->kData->plugins[id].plugin = plugin; + } +#endif }; CARLA_BACKEND_END_NAMESPACE diff --git a/source/backend/engine/CarlaEngineOsc.cpp b/source/backend/engine/CarlaEngineOsc.cpp index 5e3f01435..6ecc847af 100644 --- a/source/backend/engine/CarlaEngineOsc.cpp +++ b/source/backend/engine/CarlaEngineOsc.cpp @@ -21,6 +21,10 @@ #include "CarlaPlugin.hpp" #include "CarlaMIDI.h" +#ifndef BUILD_BRIDGE +# include "CarlaBridge.hpp" +#endif + CARLA_BACKEND_START_NAMESPACE #ifndef BUILD_BRIDGE diff --git a/source/backend/engine/Makefile b/source/backend/engine/Makefile index a96013468..72c9059b6 100644 --- a/source/backend/engine/Makefile +++ b/source/backend/engine/Makefile @@ -42,6 +42,7 @@ endif OBJS = \ CarlaEngine.cpp.o \ + CarlaEngineBridge.cpp.o \ CarlaEngineJack.cpp.o \ CarlaEnginePlugin.cpp.o \ CarlaEngineRtAudio.cpp.o \ diff --git a/source/backend/plugin/BridgePlugin.cpp b/source/backend/plugin/BridgePlugin.cpp index 671a9ec40..451960068 100644 --- a/source/backend/plugin/BridgePlugin.cpp +++ b/source/backend/plugin/BridgePlugin.cpp @@ -17,8 +17,13 @@ #include "CarlaPluginInternal.hpp" -#if 1//ndef BUILD_BRIDGE +#ifndef BUILD_BRIDGE + +#include "CarlaBridge.hpp" +#include "CarlaShmUtils.hpp" +#include +#include #include #include #include @@ -49,6 +54,55 @@ CARLA_BACKEND_START_NAMESPACE +// ------------------------------------------------------------------------------------------------------------------- +// Engine Helpers + +extern void registerEnginePlugin(CarlaEngine* const engine, const unsigned int id, CarlaPlugin* const plugin); + +// ------------------------------------------------------------------------------------------------------------------- + +shm_t shm_mkstemp(char* const fileBase) +{ + static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + + const int size = (fileBase != nullptr) ? std::strlen(fileBase) : 0; + + shm_t shm; + carla_shm_init(shm); + + if (size < 6) + { + errno = EINVAL; + return shm; + } + + if (std::strcmp(fileBase + size - 6, "XXXXXX") != 0) + { + errno = EINVAL; + return shm; + } + + std::srand(std::time(NULL)); + + while (true) + { + for (int c = size - 6; c < size; c++) + { + // Note the -1 to avoid the trailing '\0' in charSet. + fileBase[c] = charSet[std::rand() % (sizeof(charSet) - 1)]; + } + + shm_t shm = carla_shm_create(fileBase); + + if (carla_is_shm_valid(shm) || errno != EEXIST) + return shm; + } +} + +// ------------------------------------------------------------------------------------------------------------------- + struct BridgeParamInfo { float value; CarlaString name; @@ -75,7 +129,6 @@ public: kData->osc.thread.setMode(CarlaPluginThread::PLUGIN_THREAD_BRIDGE); } -#if 0 ~BridgePlugin() { carla_debug("BridgePlugin::~BridgePlugin()"); @@ -83,6 +136,7 @@ public: kData->singleMutex.lock(); kData->masterMutex.lock(); +#if 0 if (osc.data.target) { osc_send_hide(&osc.data); @@ -103,8 +157,8 @@ public: } info.chunk.clear(); - } #endif + } // ------------------------------------------------------------------- // Information (base) @@ -909,11 +963,16 @@ public: carla_debug("BridgePlugin::delete_buffers() - end"); } +#endif // ------------------------------------------------------------------- bool init(const char* const filename, const char* const name, const char* const label, const char* const bridgeBinary) { + CARLA_ASSERT(kData->engine != nullptr); + CARLA_ASSERT(kData->client == nullptr); + CARLA_ASSERT(filename != nullptr); + // --------------------------------------------------------------- // first checks @@ -934,46 +993,109 @@ public: return false; } - m_filename = strdup(filename); + // --------------------------------------------------------------- + // set info + + if (name != nullptr) + fName = kData->engine->getNewUniquePluginName(name); + + fFilename = filename; + + // --------------------------------------------------------------- + // SHM Audio Pool + { + char tmpFileBase[60]; - if (name) - m_name = x_engine->getUniquePluginName(name); + std::sprintf(tmpFileBase, "/carla-bridge_shm_XXXXXX"); + + fShmAudioPool.shm = shm_mkstemp(tmpFileBase); + + if (! carla_is_shm_valid(fShmAudioPool.shm)) + { + //_cleanup(); + carla_stdout("Failed to open or create shared memory file #1"); + return false; + } + + fShmAudioPool.filename = tmpFileBase; + } + + // --------------------------------------------------------------- + // SHM Control + { + char tmpFileBase[60]; + std::sprintf(tmpFileBase, "/carla-bridge_shc_XXXXXX"); + + fShmControl.shm = shm_mkstemp(tmpFileBase); + + if (! carla_is_shm_valid(fShmControl.shm)) + { + //_cleanup(); + carla_stdout("Failed to open or create shared memory file #2"); + return false; + } + + fShmControl.filename = tmpFileBase; + + if (! carla_shm_map(fShmControl.shm, fShmControl.data)) + { + //_cleanup(); + carla_stdout("Failed to mmap shared memory file"); + return false; + } + + std::memset(fShmControl.data, 0, sizeof(BridgeShmControl)); + + if (sem_init(&fShmControl.data->runServer, 1, 0) != 0) + { + //_cleanup(); + carla_stdout("Failed to initialize shared memory semaphore #1"); + return false; + } + + if (sem_init(&fShmControl.data->runClient, 1, 0) != 0) + { + //_cleanup(); + carla_stdout("Failed to initialize shared memory semaphore #2"); + return false; + } + } // register plugin now so we can receive OSC (and wait for it) - x_engine->__bridgePluginRegister(m_id, this); + registerEnginePlugin(kData->engine, fId, this); - osc.thread->setOscData(bridgeBinary, label, getPluginTypeString(m_type)); - osc.thread->start(); + kData->osc.thread.setOscData(bridgeBinary, label, getPluginTypeAsString(fPluginType)); + kData->osc.thread.start(); for (int i=0; i < 200; i++) { - if (m_initiated || osc.thread->isFinished()) + if (fInitiated || ! kData->osc.thread.isRunning()) break; carla_msleep(50); } - if (! m_initiated) + if (! fInitiated) { // unregister so it gets handled properly - x_engine->__bridgePluginRegister(m_id, nullptr); + registerEnginePlugin(kData->engine, fId, nullptr); - osc.thread->terminate(); - x_engine->setLastError("Timeout while waiting for a response from plugin-bridge\n(or the plugin crashed on initialization?)"); + kData->osc.thread.terminate(); + kData->engine->setLastError("Timeout while waiting for a response from plugin-bridge\n(or the plugin crashed on initialization?)"); return false; } - else if (m_initError) + else if (fInitError) { // unregister so it gets handled properly - x_engine->__bridgePluginRegister(m_id, nullptr); + registerEnginePlugin(kData->engine, fId, nullptr); - osc.thread->quit(); + kData->osc.thread.stop(); // last error was set before return false; } return true; } -#endif + private: const BinaryType fBinaryType; @@ -983,6 +1105,33 @@ private: bool fInitError; bool fSaved; + struct BridgeAudioPool { + CarlaString filename; + float* data; + size_t size; + shm_t shm; + + BridgeAudioPool() + : data(nullptr), + size(0) + { + carla_shm_init(shm); + } + } fShmAudioPool; + + struct BridgeControl { + CarlaString filename; + BridgeShmControl* data; + shm_t shm; + + BridgeControl() + : data(nullptr) + { + carla_shm_init(shm); + } + + } fShmControl; + struct Info { uint32_t aIns, aOuts; uint32_t mIns, mOuts; diff --git a/source/utils/CarlaShmUtils.hpp b/source/utils/CarlaShmUtils.hpp index 41fd352bb..29ed4af75 100644 --- a/source/utils/CarlaShmUtils.hpp +++ b/source/utils/CarlaShmUtils.hpp @@ -35,7 +35,7 @@ typedef int shm_t; // shared memory calls static inline -bool carla_is_shm_valid(shm_t shm) +bool carla_is_shm_valid(const shm_t& shm) { #ifdef CARLA_OS_WIN return (shm.shm != nullptr && shm.shm != INVALID_HANDLE_VALUE); @@ -105,7 +105,7 @@ void carla_shm_close(shm_t& shm) } static inline -void* carla_shm_map(shm_t shm, const size_t size) +void* carla_shm_map(shm_t& shm, const size_t size) { CARLA_ASSERT(carla_is_shm_valid(shm)); CARLA_ASSERT(size > 0); @@ -129,12 +129,13 @@ void* carla_shm_map(shm_t shm, const size_t size) return ptr; #else + ftruncate(shm, size); return mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm, 0); #endif } static inline -void carla_shm_unmap(const shm_t shm, void* const ptr, const size_t size) +void carla_shm_unmap(const shm_t& shm, void* const ptr, const size_t size) { CARLA_ASSERT(carla_is_shm_valid(shm)); CARLA_ASSERT(ptr != nullptr); @@ -177,14 +178,15 @@ void carla_shm_unmap(const shm_t shm, void* const ptr, const size_t size) template static inline -void carla_shm_map(shm_t shm, T* value) +bool carla_shm_map(shm_t& shm, T* value) { - value = carla_shm_map(shm, sizeof(value)); + value = (T*)carla_shm_map(shm, sizeof(value)); + return (value != nullptr); } template static inline -void carla_shm_unmap(const shm_t shm, T* value) +void carla_shm_unmap(const shm_t& shm, T* value) { carla_shm_unmap(shm, value, sizeof(value)); value = nullptr;