| @@ -0,0 +1,82 @@ | |||
| /* | |||
| * Carla Bridge API | |||
| * Copyright (C) 2013 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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 <semaphore.h> | |||
| #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__ | |||
| @@ -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 | |||
| @@ -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. | |||
| */ | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -0,0 +1,183 @@ | |||
| /* | |||
| * Carla Plugin Engine | |||
| * Copyright (C) 2012-2013 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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<ShmControl>(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 | |||
| @@ -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 | |||
| @@ -21,6 +21,10 @@ | |||
| #include "CarlaPlugin.hpp" | |||
| #include "CarlaMIDI.h" | |||
| #ifndef BUILD_BRIDGE | |||
| # include "CarlaBridge.hpp" | |||
| #endif | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| #ifndef BUILD_BRIDGE | |||
| @@ -42,6 +42,7 @@ endif | |||
| OBJS = \ | |||
| CarlaEngine.cpp.o \ | |||
| CarlaEngineBridge.cpp.o \ | |||
| CarlaEngineJack.cpp.o \ | |||
| CarlaEnginePlugin.cpp.o \ | |||
| CarlaEngineRtAudio.cpp.o \ | |||
| @@ -17,8 +17,13 @@ | |||
| #include "CarlaPluginInternal.hpp" | |||
| #if 1//ndef BUILD_BRIDGE | |||
| #ifndef BUILD_BRIDGE | |||
| #include "CarlaBridge.hpp" | |||
| #include "CarlaShmUtils.hpp" | |||
| #include <cerrno> | |||
| #include <ctime> | |||
| #include <QtCore/QDir> | |||
| #include <QtCore/QFile> | |||
| #include <QtCore/QStringList> | |||
| @@ -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<BridgeShmControl>(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; | |||
| @@ -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<typename T> | |||
| 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<typename T> | |||
| 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; | |||