diff --git a/CMakeLists.txt b/CMakeLists.txt index 38fa0f8f0..a3fcf4050 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,7 +133,6 @@ CHECK_INCLUDE_FILES(semaphore.h LMMS_HAVE_SEMAPHORE_H) CHECK_INCLUDE_FILES(unistd.h LMMS_HAVE_UNISTD_H) CHECK_INCLUDE_FILES(sys/types.h LMMS_HAVE_SYS_TYPES_H) CHECK_INCLUDE_FILES(sys/ipc.h LMMS_HAVE_SYS_IPC_H) -CHECK_INCLUDE_FILES(sys/shm.h LMMS_HAVE_SYS_SHM_H) CHECK_INCLUDE_FILES(sys/time.h LMMS_HAVE_SYS_TIME_H) CHECK_INCLUDE_FILES(sys/times.h LMMS_HAVE_SYS_TIMES_H) CHECK_INCLUDE_FILES(sched.h LMMS_HAVE_SCHED_H) @@ -146,6 +145,9 @@ CHECK_INCLUDE_FILES(string.h LMMS_HAVE_STRING_H) CHECK_INCLUDE_FILES(process.h LMMS_HAVE_PROCESS_H) CHECK_INCLUDE_FILES(locale.h LMMS_HAVE_LOCALE_H) +include(CheckLibraryExists) +check_library_exists(rt shm_open "" LMMS_HAVE_LIBRT) + LIST(APPEND CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}") FIND_PACKAGE(Qt5 5.6.0 COMPONENTS Core Gui Widgets Xml REQUIRED) diff --git a/include/RaiiHelpers.h b/include/RaiiHelpers.h new file mode 100644 index 000000000..bebd91ff9 --- /dev/null +++ b/include/RaiiHelpers.h @@ -0,0 +1,60 @@ +/* + * RaiiHelpers.h + * + * Copyright (c) 2022 Dominic Clark + * + * This file is part of LMMS - https://lmms.io + * + * 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 (at your option) 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. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#ifndef RAII_HELPERS_H +#define RAII_HELPERS_H + +#include +#include + +template +class NullableResource +{ +public: + NullableResource() = default; + NullableResource(std::nullptr_t) noexcept { } + NullableResource(T value) noexcept : m_value{value} { } + operator T() const noexcept { return m_value; } + explicit operator bool() const noexcept { return m_value != Null; } + friend bool operator==(NullableResource a, NullableResource b) noexcept { return a.m_value == b.m_value; } + friend bool operator==(NullableResource a, T b) noexcept { return a.m_value == b; } + friend bool operator==(T a, NullableResource b) noexcept { return a == b.m_value; } + friend bool operator!=(NullableResource a, NullableResource b) noexcept { return a.m_value != b.m_value; } + friend bool operator!=(NullableResource a, T b) noexcept { return a.m_value != b; } + friend bool operator!=(T a, NullableResource b) noexcept { return a != b.m_value; } + +private: + T m_value = Null; +}; + +template +struct NullableResourceDeleter +{ + using pointer = NullableResource; + void operator()(T value) const noexcept { Deleter(value); } +}; + +template +using UniqueNullableResource = std::unique_ptr>; + +#endif // RAII_HELPERS_H diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index a132beded..615cce2c8 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -26,6 +26,7 @@ #define REMOTE_PLUGIN_H #include "RemotePluginBase.h" +#include "SharedMemory.h" class RemotePlugin; @@ -155,13 +156,9 @@ private: QMutex m_commMutex; bool m_splitChannels; -#ifdef USE_QT_SHMEM - QSharedMemory m_shmObj; -#else - int m_shmID; -#endif - size_t m_shmSize; - float * m_shm; + + SharedMemory m_audioBuffer; + std::size_t m_audioBufferSize; int m_inputCount; int m_outputCount; diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index 8dc2e04e9..a5646ad11 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -46,26 +46,12 @@ #include #include -#endif - - -#ifdef LMMS_HAVE_SYS_SHM_H -#include - +#include +#else #ifdef LMMS_HAVE_UNISTD_H #include #endif -#else -#define USE_QT_SHMEM - -#include -#include - -#if !defined(LMMS_HAVE_SYS_TYPES_H) || defined(LMMS_BUILD_WIN32) -typedef int32_t key_t; #endif -#endif - #ifdef LMMS_HAVE_LOCALE_H #include @@ -100,6 +86,9 @@ typedef int32_t key_t; #endif #ifdef SYNC_WITH_SHM_FIFO + +#include "SharedMemory.h" + // sometimes we need to exchange bigger messages (e.g. for VST parameter dumps) // so set a usable value here const int SHM_FIFO_SIZE = 512*1024; @@ -120,8 +109,8 @@ class shmFifo sem32_t dataSem; // semaphore for locking this // FIFO management data sem32_t messageSem; // semaphore for incoming messages - volatile int32_t startPtr; // current start of FIFO in memory - volatile int32_t endPtr; // current end of FIFO in memory + int32_t startPtr; // current start of FIFO in memory + int32_t endPtr; // current end of FIFO in memory char data[SHM_FIFO_SIZE]; // actual data } ; @@ -130,33 +119,11 @@ public: shmFifo() : m_invalid( false ), m_master( true ), - m_shmKey( 0 ), -#ifdef USE_QT_SHMEM - m_shmObj(), -#else - m_shmID( -1 ), -#endif - m_data( nullptr ), m_dataSem( QString() ), m_messageSem( QString() ), m_lockDepth( 0 ) { -#ifdef USE_QT_SHMEM - do - { - m_shmObj.setKey( QString( "%1" ).arg( ++m_shmKey ) ); - m_shmObj.create( sizeof( shmData ) ); - } while( m_shmObj.error() != QSharedMemory::NoError ); - - m_data = (shmData *) m_shmObj.data(); -#else - while( ( m_shmID = shmget( ++m_shmKey, sizeof( shmData ), - IPC_CREAT | IPC_EXCL | 0600 ) ) == -1 ) - { - } - m_data = (shmData *) shmat( m_shmID, 0, 0 ); -#endif - assert( m_data != nullptr ); + m_data.create(QUuid::createUuid().toString().toStdString()); m_data->startPtr = m_data->endPtr = 0; static int k = 0; m_data->dataSem.semKey = ( getpid()<<10 ) + ++k; @@ -170,51 +137,19 @@ public: // constructor for remote-/client-side - use _shm_key for making up // the connection to master - shmFifo( key_t _shm_key ) : + shmFifo(const std::string& shmKey) : m_invalid( false ), m_master( false ), - m_shmKey( 0 ), -#ifdef USE_QT_SHMEM - m_shmObj( QString::number( _shm_key ) ), -#else - m_shmID( shmget( _shm_key, 0, 0 ) ), -#endif - m_data( nullptr ), m_dataSem( QString() ), m_messageSem( QString() ), m_lockDepth( 0 ) { -#ifdef USE_QT_SHMEM - if( m_shmObj.attach() ) - { - m_data = (shmData *) m_shmObj.data(); - } -#else - if( m_shmID != -1 ) - { - m_data = (shmData *) shmat( m_shmID, 0, 0 ); - } -#endif - assert( m_data != nullptr ); + m_data.attach(shmKey); m_dataSem.setKey( QString::number( m_data->dataSem.semKey ) ); m_messageSem.setKey( QString::number( m_data->messageSem.semKey ) ); } - ~shmFifo() - { - // master? - if( m_master ) - { -#ifndef USE_QT_SHMEM - shmctl( m_shmID, IPC_RMID, nullptr ); -#endif - } -#ifndef USE_QT_SHMEM - shmdt( m_data ); -#endif - } - inline bool isInvalid() const { return m_invalid; @@ -314,9 +249,9 @@ public: } - inline int shmKey() const + const std::string& shmKey() const { - return m_shmKey; + return m_data.key(); } @@ -395,13 +330,7 @@ private: volatile bool m_invalid; bool m_master; - key_t m_shmKey; -#ifdef USE_QT_SHMEM - QSharedMemory m_shmObj; -#else - int m_shmID; -#endif - shmData * m_data; + SharedMemory m_data; QSystemSemaphore m_dataSem; QSystemSemaphore m_messageSem; std::atomic_int m_lockDepth; diff --git a/include/RemotePluginClient.h b/include/RemotePluginClient.h index 8056b3e0e..94f05af0f 100644 --- a/include/RemotePluginClient.h +++ b/include/RemotePluginClient.h @@ -27,6 +27,8 @@ #include "RemotePluginBase.h" +#include + #ifndef LMMS_BUILD_WIN32 # include # include @@ -36,18 +38,20 @@ # include #endif +#include "SharedMemory.h" + class RemotePluginClient : public RemotePluginBase { public: #ifdef SYNC_WITH_SHM_FIFO - RemotePluginClient( key_t _shm_in, key_t _shm_out ); + RemotePluginClient( const std::string& _shm_in, const std::string& _shm_out ); #else RemotePluginClient( const char * socketPath ); #endif virtual ~RemotePluginClient(); -#ifdef USE_QT_SHMEM - VstSyncData * getQtVSTshm(); -#endif + + const VstSyncData* getVstSyncData(); + virtual bool processMessage( const message & _m ); virtual void process( const sampleFrame * _in_buf, @@ -57,11 +61,6 @@ public: { } - inline float * sharedMemory() - { - return m_shm; - } - virtual void updateSampleRate() { } @@ -118,15 +117,12 @@ public: private: - void setShmKey( key_t _key, int _size ); + void setShmKey(const std::string& key); void doProcessing(); -#ifdef USE_QT_SHMEM - QSharedMemory m_shmObj; - QSharedMemory m_shmQtID; -#endif - VstSyncData * m_vstSyncData; - float * m_shm; + SharedMemory m_audioBuffer; + SharedMemory m_vstSyncShm; + const VstSyncData* m_vstSyncData; int m_inputCount; int m_outputCount; @@ -177,18 +173,13 @@ private: #endif #ifdef SYNC_WITH_SHM_FIFO -RemotePluginClient::RemotePluginClient( key_t _shm_in, key_t _shm_out ) : +RemotePluginClient::RemotePluginClient( const std::string& _shm_in, const std::string& _shm_out ) : RemotePluginBase( new shmFifo( _shm_in ), new shmFifo( _shm_out ) ), #else RemotePluginClient::RemotePluginClient( const char * socketPath ) : RemotePluginBase(), -#endif -#ifdef USE_QT_SHMEM - m_shmObj(), - m_shmQtID( "/usr/bin/lmms" ), #endif m_vstSyncData( nullptr ), - m_shm( nullptr ), m_inputCount( 0 ), m_outputCount( 0 ), m_sampleRate( 44100 ), @@ -218,60 +209,23 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) : } #endif -#ifdef USE_QT_SHMEM - if( m_shmQtID.attach( QSharedMemory::ReadOnly ) ) + try { - m_vstSyncData = (VstSyncData *) m_shmQtID.data(); + m_vstSyncShm.attach("usr_bin_lmms"); + m_vstSyncData = m_vstSyncShm.get(); m_bufferSize = m_vstSyncData->m_bufferSize; m_sampleRate = m_vstSyncData->m_sampleRate; - sendMessage( IdHostInfoGotten ); - return; } -#else - key_t key; - int m_shmID; - - if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) + catch (const std::runtime_error&) { - perror( "RemotePluginClient::ftok" ); - } - else - { // connect to shared memory segment - if( ( m_shmID = shmget( key, 0, 0 ) ) == -1 ) + // if attaching shared memory fails + sendMessage( IdSampleRateInformation ); + sendMessage( IdBufferSizeInformation ); + if( waitForMessage( IdBufferSizeInformation ).id + != IdBufferSizeInformation ) { - perror( "RemotePluginClient::shmget" ); + fprintf( stderr, "Could not get buffer size information\n" ); } - else - { // attach segment - m_vstSyncData = (VstSyncData *)shmat(m_shmID, 0, 0); - if( m_vstSyncData == (VstSyncData *)( -1 ) ) - { - perror( "RemotePluginClient::shmat" ); - } - else - { - m_bufferSize = m_vstSyncData->m_bufferSize; - m_sampleRate = m_vstSyncData->m_sampleRate; - sendMessage( IdHostInfoGotten ); - - // detach segment - if( shmdt(m_vstSyncData) == -1 ) - { - perror("RemotePluginClient::shmdt"); - } - return; - } - } - } -#endif - - // if attaching shared memory fails - sendMessage( IdSampleRateInformation ); - sendMessage( IdBufferSizeInformation ); - if( waitForMessage( IdBufferSizeInformation ).id - != IdBufferSizeInformation ) - { - fprintf( stderr, "Could not get buffer size information\n" ); } sendMessage( IdHostInfoGotten ); } @@ -281,15 +235,8 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) : RemotePluginClient::~RemotePluginClient() { -#ifdef USE_QT_SHMEM - m_shmQtID.detach(); -#endif sendMessage( IdQuit ); -#ifndef USE_QT_SHMEM - shmdt( m_shm ); -#endif - #ifndef SYNC_WITH_SHM_FIFO if ( close( m_socket ) == -1) { @@ -300,12 +247,12 @@ RemotePluginClient::~RemotePluginClient() -#ifdef USE_QT_SHMEM -VstSyncData * RemotePluginClient::getQtVSTshm() + +const VstSyncData* RemotePluginClient::getVstSyncData() { return m_vstSyncData; } -#endif + @@ -353,7 +300,7 @@ bool RemotePluginClient::processMessage( const message & _m ) break; case IdChangeSharedMemoryKey: - setShmKey( _m.getInt( 0 ), _m.getInt( 1 ) ); + setShmKey(_m.getString(0)); break; case IdInitDone: @@ -378,43 +325,16 @@ bool RemotePluginClient::processMessage( const message & _m ) -void RemotePluginClient::setShmKey( key_t _key, int _size ) +void RemotePluginClient::setShmKey(const std::string& key) { -#ifdef USE_QT_SHMEM - m_shmObj.setKey( QString::number( _key ) ); - if( m_shmObj.attach() || m_shmObj.error() == QSharedMemory::NoError ) + try { - m_shm = (float *) m_shmObj.data(); + m_audioBuffer.attach(key); } - else + catch (const std::runtime_error& error) { - char buf[64]; - sprintf( buf, "failed getting shared memory: %d\n", m_shmObj.error() ); - debugMessage( buf ); + debugMessage(std::string{"failed getting shared memory: "} + error.what() + '\n'); } -#else - if( m_shm != nullptr ) - { - shmdt( m_shm ); - m_shm = nullptr; - } - - // only called for detaching SHM? - if( _key == 0 ) - { - return; - } - - int shm_id = shmget( _key, _size, 0 ); - if( shm_id == -1 ) - { - debugMessage( "failed getting shared memory\n" ); - } - else - { - m_shm = (float *) shmat( shm_id, 0, 0 ); - } -#endif } @@ -422,10 +342,10 @@ void RemotePluginClient::setShmKey( key_t _key, int _size ) void RemotePluginClient::doProcessing() { - if( m_shm != nullptr ) + if (m_audioBuffer) { - process( (sampleFrame *)( m_inputCount > 0 ? m_shm : nullptr ), - (sampleFrame *)( m_shm + + process( (sampleFrame *)( m_inputCount > 0 ? m_audioBuffer.get() : nullptr ), + (sampleFrame *)( m_audioBuffer.get() + ( m_inputCount*m_bufferSize ) ) ); } else diff --git a/include/SharedMemory.h b/include/SharedMemory.h new file mode 100644 index 000000000..bc649110b --- /dev/null +++ b/include/SharedMemory.h @@ -0,0 +1,143 @@ +/* + * SharedMemory.h + * + * Copyright (c) 2022 Dominic Clark + * + * This file is part of LMMS - https://lmms.io + * + * 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 (at your option) 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. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#ifndef SHARED_MEMORY_H +#define SHARED_MEMORY_H + +#include +#include +#include + +namespace detail { + +class SharedMemoryImpl; + +class SharedMemoryData +{ +public: + SharedMemoryData() noexcept; + SharedMemoryData(std::string&& key, bool readOnly); + SharedMemoryData(std::string&& key, std::size_t size, bool readOnly); + ~SharedMemoryData(); + + SharedMemoryData(SharedMemoryData&& other) noexcept; + SharedMemoryData& operator=(SharedMemoryData&& other) noexcept + { + auto temp = std::move(other); + swap(*this, temp); + return *this; + } + + friend void swap(SharedMemoryData& a, SharedMemoryData& b) noexcept + { + using std::swap; + swap(a.m_key, b.m_key); + swap(a.m_impl, b.m_impl); + swap(a.m_ptr, b.m_ptr); + } + + const std::string& key() const noexcept { return m_key; } + void* get() const noexcept { return m_ptr; } + +private: + std::string m_key; + std::unique_ptr m_impl; + void* m_ptr = nullptr; +}; + +} // namespace detail + +template +class SharedMemory +{ + // This is stricter than necessary, but keeps things easy for now + static_assert(std::is_trivial_v, "objects held in shared memory must be trivial"); + +public: + SharedMemory() = default; + SharedMemory(SharedMemory&&) = default; + SharedMemory& operator=(SharedMemory&&) = default; + + void attach(std::string key) + { + m_data = detail::SharedMemoryData{std::move(key), std::is_const_v}; + } + + void create(std::string key) + { + m_data = detail::SharedMemoryData{std::move(key), sizeof(T), std::is_const_v}; + } + + void detach() noexcept + { + m_data = detail::SharedMemoryData{}; + } + + const std::string& key() const noexcept { return m_data.key(); } + T* get() const noexcept { return static_cast(m_data.get()); } + + T* operator->() const noexcept { return get(); } + T& operator*() const noexcept { return *get(); } + explicit operator bool() const noexcept { return get() != nullptr; } + +private: + detail::SharedMemoryData m_data; +}; + +template +class SharedMemory +{ + // This is stricter than necessary, but keeps things easy for now + static_assert(std::is_trivial_v, "objects held in shared memory must be trivial"); + +public: + SharedMemory() = default; + SharedMemory(SharedMemory&&) = default; + SharedMemory& operator=(SharedMemory&&) = default; + + void attach(std::string key) + { + m_data = detail::SharedMemoryData{std::move(key), std::is_const_v}; + } + + void create(std::string key, std::size_t size) + { + m_data = detail::SharedMemoryData{std::move(key), size * sizeof(T), std::is_const_v}; + } + + void detach() noexcept + { + m_data = detail::SharedMemoryData{}; + } + + const std::string& key() const noexcept { return m_data.key(); } + T* get() const noexcept { return static_cast(m_data.get()); } + + T& operator[](std::size_t index) const noexcept { return get()[index]; } + explicit operator bool() const noexcept { return get() != nullptr; } + +private: + detail::SharedMemoryData m_data; +}; + +#endif // SHARED_MEMORY_H diff --git a/include/VstSyncController.h b/include/VstSyncController.h index 7ed0e4999..ec7d9820c 100644 --- a/include/VstSyncController.h +++ b/include/VstSyncController.h @@ -27,8 +27,8 @@ #define VST_SYNC_CONTROLLER_H #include -#include +#include "SharedMemory.h" #include "VstSyncData.h" @@ -75,11 +75,7 @@ private slots: private: VstSyncData* m_syncData; - - int m_shmID; - - QSharedMemory m_shm; - + SharedMemory m_shm; }; #endif diff --git a/include/VstSyncData.h b/include/VstSyncData.h index 07694b81b..ac1bed218 100644 --- a/include/VstSyncData.h +++ b/include/VstSyncData.h @@ -33,10 +33,6 @@ // When defined, latency should be subtracted from song PPQ position //#define VST_SNC_LATENCY -// define file for ftok as shared memory shmget key -constexpr const char* VST_SNC_SHM_KEY_FILE = "/dev/null"; -//constexpr int64_t VST_SNC_SHM_RND_KEY = 3561653564469; - struct VstSyncData diff --git a/plugins/GigPlayer/CMakeLists.txt b/plugins/GigPlayer/CMakeLists.txt index 05e9f529d..24db813bd 100644 --- a/plugins/GigPlayer/CMakeLists.txt +++ b/plugins/GigPlayer/CMakeLists.txt @@ -2,11 +2,6 @@ if(LMMS_HAVE_GIG) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${GIG_INCLUDE_DIRS}) - # Disable C++11 on Clang until gig.h is patched - IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - SET(CMAKE_CXX_STANDARD 98) - ENDIF() - # Required for not crashing loading files with libgig SET(GCC_COVERAGE_COMPILE_FLAGS "-fexceptions") add_definitions(${GCC_COVERAGE_COMPILE_FLAGS}) diff --git a/plugins/VstBase/CMakeLists.txt b/plugins/VstBase/CMakeLists.txt index 68bdb9939..8d3262b1a 100644 --- a/plugins/VstBase/CMakeLists.txt +++ b/plugins/VstBase/CMakeLists.txt @@ -31,7 +31,9 @@ set(export_variables "CMAKE_BUILD_TYPE" "LMMS_BUILD_LINUX" "LMMS_BUILD_WIN32" - "PLUGIN_DIR") + "PLUGIN_DIR" + "LMMS_HAVE_LIBRT" +) SET(EXTERNALPROJECT_CMAKE_ARGS "-DBUILD_WITH_EXTERNALPROJECT=ON" diff --git a/plugins/VstBase/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index 21da830cc..f2199893a 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -125,18 +125,6 @@ struct ERect #include "VstSyncData.h" -#ifdef LMMS_BUILD_WIN32 -#define USE_QT_SHMEM -#endif - -#ifndef USE_QT_SHMEM -#include -#include -#include -#include -#include -#endif - using namespace std; static VstHostLanguages hlang = LanguageEnglish; @@ -182,7 +170,7 @@ class RemoteVstPlugin : public RemotePluginClient { public: #ifdef SYNC_WITH_SHM_FIFO - RemoteVstPlugin( key_t _shm_in, key_t _shm_out ); + RemoteVstPlugin( const std::string& _shm_in, const std::string& _shm_out ); #else RemoteVstPlugin( const char * socketPath ); #endif @@ -497,8 +485,7 @@ private: in * m_in; - int m_shmID; - VstSyncData* m_vstSyncData; + const VstSyncData* m_vstSyncData; } ; @@ -506,7 +493,7 @@ private: #ifdef SYNC_WITH_SHM_FIFO -RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : +RemoteVstPlugin::RemoteVstPlugin( const std::string& _shm_in, const std::string& _shm_out ) : RemotePluginClient( _shm_in, _shm_out ), #else RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : @@ -530,49 +517,26 @@ RemoteVstPlugin::RemoteVstPlugin( const char * socketPath ) : m_currentSamplePos( 0 ), m_currentProgram( -1 ), m_in( nullptr ), - m_shmID( -1 ), m_vstSyncData( nullptr ) { __plugin = this; -#ifndef USE_QT_SHMEM - key_t key; - if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) - { - perror( "RemoteVstPlugin.cpp::ftok" ); - } - else - { // connect to shared memory segment - if( ( m_shmID = shmget( key, 0, 0 ) ) == -1 ) - { - perror( "RemoteVstPlugin.cpp::shmget" ); - } - else - { // attach segment - m_vstSyncData = (VstSyncData *)shmat(m_shmID, 0, 0); - if( m_vstSyncData == (VstSyncData *)( -1 ) ) - { - perror( "RemoteVstPlugin.cpp::shmat" ); - } - } - } -#else - m_vstSyncData = RemotePluginClient::getQtVSTshm(); -#endif + m_vstSyncData = RemotePluginClient::getVstSyncData(); if( m_vstSyncData == nullptr ) { fprintf(stderr, "RemoteVstPlugin.cpp: " "Failed to initialize shared memory for VST synchronization.\n" " (VST-host synchronization will be disabled)\n"); - m_vstSyncData = (VstSyncData*) malloc( sizeof( VstSyncData ) ); - m_vstSyncData->isPlaying = true; - m_vstSyncData->timeSigNumer = 4; - m_vstSyncData->timeSigDenom = 4; - m_vstSyncData->ppqPos = 0; - m_vstSyncData->isCycle = false; - m_vstSyncData->hasSHM = false; - m_vstSyncData->m_playbackJumped = false; - m_vstSyncData->m_sampleRate = sampleRate(); + const auto vstSyncData = (VstSyncData*) malloc( sizeof( VstSyncData ) ); + vstSyncData->isPlaying = true; + vstSyncData->timeSigNumer = 4; + vstSyncData->timeSigDenom = 4; + vstSyncData->ppqPos = 0; + vstSyncData->isCycle = false; + vstSyncData->hasSHM = false; + vstSyncData->m_playbackJumped = false; + vstSyncData->m_sampleRate = sampleRate(); + m_vstSyncData = vstSyncData; } m_in = ( in* ) new char[ sizeof( in ) ]; @@ -600,21 +564,12 @@ RemoteVstPlugin::~RemoteVstPlugin() destroyEditor(); setResumed( false ); pluginDispatch( effClose ); -#ifndef USE_QT_SHMEM - // detach shared memory segment - if( shmdt( m_vstSyncData ) == -1) + + if (!m_vstSyncData->hasSHM) { - if( __plugin->m_vstSyncData->hasSHM ) - { - perror( "~RemoteVstPlugin::shmdt" ); - } - if( m_vstSyncData != nullptr ) - { - delete m_vstSyncData; - m_vstSyncData = nullptr; - } + delete m_vstSyncData; + m_vstSyncData = nullptr; } -#endif if( m_libInst != nullptr ) { @@ -2575,7 +2530,7 @@ int main( int _argc, char * * _argv ) // constructor automatically will process messages until it receives // a IdVstLoadPlugin message and processes it #ifdef SYNC_WITH_SHM_FIFO - __plugin = new RemoteVstPlugin( atoi( _argv[1] ), atoi( _argv[2] ) ); + __plugin = new RemoteVstPlugin( _argv[1], _argv[2] ); #else __plugin = new RemoteVstPlugin( _argv[1] ); #endif diff --git a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt index b4773c4b4..43e67f7de 100644 --- a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt +++ b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt @@ -43,6 +43,10 @@ if(NOT WIN32) target_link_libraries(${EXE_NAME} pthread) endif() +if(LMMS_HAVE_LIBRT) + target_link_libraries(${EXE_NAME} rt) +endif() + target_include_directories(${EXE_NAME} PRIVATE "${LMMS_SOURCE_DIR}/plugins/vst_base/common" diff --git a/plugins/ZynAddSubFx/CMakeLists.txt b/plugins/ZynAddSubFx/CMakeLists.txt index d36c65812..0c8fa8681 100644 --- a/plugins/ZynAddSubFx/CMakeLists.txt +++ b/plugins/ZynAddSubFx/CMakeLists.txt @@ -185,6 +185,10 @@ ENDIF() TARGET_LINK_LIBRARIES(RemoteZynAddSubFx zynaddsubfx_gui -lZynAddSubFxCore ${FLTK_FILTERED_LDFLAGS} -lpthread ) ADD_DEPENDENCIES(RemoteZynAddSubFx ZynAddSubFxCore) +if(LMMS_HAVE_LIBRT) + target_link_libraries(RemoteZynAddSubFx rt) +endif() + # Support qt_version_tag in Qt 5.6 TARGET_LINK_LIBRARIES(RemoteZynAddSubFx Qt5::Core) diff --git a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp index 25b23e851..0c0c0af39 100644 --- a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp @@ -43,7 +43,7 @@ class RemoteZynAddSubFx : public RemotePluginClient, public LocalZynAddSubFx { public: #ifdef SYNC_WITH_SHM_FIFO - RemoteZynAddSubFx( int _shm_in, int _shm_out ) : + RemoteZynAddSubFx( const std::string& _shm_in, const std::string& _shm_out ) : RemotePluginClient( _shm_in, _shm_out ), #else RemoteZynAddSubFx( const char * socketPath ) : @@ -282,7 +282,7 @@ int main( int _argc, char * * _argv ) #ifdef SYNC_WITH_SHM_FIFO RemoteZynAddSubFx * remoteZASF = - new RemoteZynAddSubFx( atoi( _argv[1] ), atoi( _argv[2] ) ); + new RemoteZynAddSubFx( _argv[1], _argv[2] ); #else RemoteZynAddSubFx * remoteZASF = new RemoteZynAddSubFx( _argv[1] ); #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 954697eb2..7f8a0dfcb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,10 @@ IF(LMMS_BUILD_HAIKU) SET(EXTRA_LIBRARIES "-lnetwork") ENDIF() +if(LMMS_HAVE_LIBRT) + list(APPEND EXTRA_LIBRARIES "rt") +endif() + SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c0783a06e..877bea3fc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,5 +1,6 @@ set(COMMON_SRCS RemotePluginBase.cpp + SharedMemory.cpp ) foreach(SRC ${COMMON_SRCS}) diff --git a/src/common/SharedMemory.cpp b/src/common/SharedMemory.cpp new file mode 100644 index 000000000..3c546ac32 --- /dev/null +++ b/src/common/SharedMemory.cpp @@ -0,0 +1,201 @@ +/* + * SharedMemory.cpp + * + * Copyright (c) 2022 Dominic Clark + * + * This file is part of LMMS - https://lmms.io + * + * 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 (at your option) 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. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + */ + +#include "SharedMemory.h" + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_UNISTD_H +# include +#endif + +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 +# include +# +# include +# include +# include +# +# include "RaiiHelpers.h" +#else +# include +# +# include +# include +#endif + +namespace detail { + +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 + +namespace { + +template +int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v) +{ + int result; + do + { + result = function(); + } + while (result == -1 && errno == EINTR); + return result; +} + +void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); } +void deleteShmObject(const char* name) noexcept { shm_unlink(name); } + +using FileDescriptor = UniqueNullableResource; +using ShmObject = UniqueNullableResource; + +} // namespace + +class SharedMemoryImpl +{ +public: + SharedMemoryImpl(const std::string& key, bool readOnly) : + m_key{"/" + key} + { + const auto openFlags = readOnly ? O_RDONLY : O_RDWR; + const auto fd = FileDescriptor{ + retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); }) + }; + if (!fd) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; + } + auto stat = (struct stat){}; + if (fstat(fd.get(), &stat) == -1) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"}; + } + m_size = stat.st_size; + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; + m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); + if (m_mapping == MAP_FAILED) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; + } + } + + SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : + m_key{"/" + key}, + m_size{size} + { + const auto fd = FileDescriptor{ + retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); }) + }; + if (fd.get() == -1) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; + } + m_object.reset(m_key.c_str()); + if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"}; + } + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; + m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); + if (m_mapping == MAP_FAILED) + { + throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; + } + } + + SharedMemoryImpl(const SharedMemoryImpl&) = delete; + SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete; + + ~SharedMemoryImpl() + { + munmap(m_mapping, m_size); + } + + void* get() { return m_mapping; } + +private: + std::string m_key; + std::size_t m_size; + void* m_mapping; + ShmObject m_object; +}; + +#else + +class SharedMemoryImpl +{ +public: + SharedMemoryImpl(const std::string& key, bool readOnly) : + m_shm{QString::fromStdString(key)} + { + const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; + if (!m_shm.attach(mode)) + { + throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"}; + } + } + + SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : + m_shm{QString::fromStdString(key)} + { + const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; + if (!m_shm.create(size, mode)) + { + throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"}; + } + } + + SharedMemoryImpl(const SharedMemoryImpl&) = delete; + SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete; + + void* get() { return m_shm.data(); } + +private: + QSharedMemory m_shm; +}; + +#endif + +SharedMemoryData::SharedMemoryData() noexcept +{ } + +SharedMemoryData::SharedMemoryData(std::string&& key, bool readOnly) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key, readOnly)}, + m_ptr{m_impl->get()} +{ } + +SharedMemoryData::SharedMemoryData(std::string&& key, std::size_t size, bool readOnly) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key, std::max(size, std::size_t{1}), readOnly)}, + m_ptr{m_impl->get()} +{ } + +SharedMemoryData::~SharedMemoryData() { } + +SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept : + m_key{std::move(other.m_key)}, + m_impl{std::move(other.m_impl)}, + m_ptr{other.m_ptr} +{ } + +} // namespace detail diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 4b5056381..a70ce0a31 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -40,9 +40,9 @@ #include #include #include +#include #ifndef SYNC_WITH_SHM_FIFO -#include #include #include #endif @@ -137,13 +137,7 @@ RemotePlugin::RemotePlugin() : m_watcher( this ), m_commMutex( QMutex::Recursive ), m_splitChannels( false ), -#ifdef USE_QT_SHMEM - m_shmObj(), -#else - m_shmID( 0 ), -#endif - m_shmSize( 0 ), - m_shm( nullptr ), + m_audioBufferSize( 0 ), m_inputCount( DEFAULT_CHANNELS ), m_outputCount( DEFAULT_CHANNELS ) { @@ -209,11 +203,6 @@ RemotePlugin::~RemotePlugin() } unlock(); } - -#ifndef USE_QT_SHMEM - shmdt( m_shm ); - shmctl( m_shmID, IPC_RMID, nullptr ); -#endif } #ifndef SYNC_WITH_SHM_FIFO @@ -273,8 +262,8 @@ bool RemotePlugin::init(const QString &pluginExecutable, QStringList args; #ifdef SYNC_WITH_SHM_FIFO // swap in and out for bidirectional communication - args << QString::number( out()->shmKey() ); - args << QString::number( in()->shmKey() ); + args << QString::fromStdString(out()->shmKey()); + args << QString::fromStdString(in()->shmKey()); #else args << m_socketFile; #endif @@ -342,13 +331,13 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf return false; } - if( m_shm == nullptr ) + if (!m_audioBuffer) { - // m_shm being zero means we didn't initialize everything so + // m_audioBuffer being zero means we didn't initialize everything so // far so process one message each time (and hope we get // information like SHM-key etc.) until we process messages // in a later stage of this procedure - if( m_shmSize == 0 ) + if( m_audioBufferSize == 0 ) { lock(); fetchAndProcessAllMessages(); @@ -361,7 +350,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf return false; } - memset( m_shm, 0, m_shmSize ); + memset( m_audioBuffer.get(), 0, m_audioBufferSize ); ch_cnt_t inputs = qMin( m_inputCount, DEFAULT_CHANNELS ); @@ -373,18 +362,18 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf { for( fpp_t frame = 0; frame < frames; ++frame ) { - m_shm[ch * frames + frame] = + m_audioBuffer[ch * frames + frame] = _in_buf[frame][ch]; } } } else if( inputs == DEFAULT_CHANNELS ) { - memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME ); + memcpy( m_audioBuffer.get(), _in_buf, frames * BYTES_PER_FRAME ); } else { - sampleFrame * o = (sampleFrame *) m_shm; + sampleFrame * o = (sampleFrame *) m_audioBuffer.get(); for( ch_cnt_t ch = 0; ch < inputs; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) @@ -415,19 +404,19 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf { for( fpp_t frame = 0; frame < frames; ++frame ) { - _out_buf[frame][ch] = m_shm[( m_inputCount+ch )* + _out_buf[frame][ch] = m_audioBuffer[( m_inputCount+ch )* frames + frame]; } } } else if( outputs == DEFAULT_CHANNELS ) { - memcpy( _out_buf, m_shm + m_inputCount * frames, + memcpy( _out_buf, m_audioBuffer.get() + m_inputCount * frames, frames * BYTES_PER_FRAME ); } else { - sampleFrame * o = (sampleFrame *) ( m_shm + + sampleFrame * o = (sampleFrame *) ( m_audioBuffer.get() + m_inputCount*frames ); // clear buffer, if plugin didn't fill up both channels BufferManager::clear( _out_buf, frames ); @@ -481,37 +470,19 @@ void RemotePlugin::hideUI() void RemotePlugin::resizeSharedProcessingMemory() { - const size_t s = ( m_inputCount+m_outputCount ) * Engine::audioEngine()->framesPerPeriod() * sizeof( float ); - if( m_shm != nullptr ) + const size_t s = (m_inputCount + m_outputCount) * Engine::audioEngine()->framesPerPeriod(); + try { -#ifdef USE_QT_SHMEM - m_shmObj.detach(); -#else - shmdt( m_shm ); - shmctl( m_shmID, IPC_RMID, nullptr ); -#endif + m_audioBuffer.create(QUuid::createUuid().toString().toStdString(), s); } - - static int shm_key = 0; -#ifdef USE_QT_SHMEM - do - { - m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) ); - m_shmObj.create( s ); - } while( m_shmObj.error() != QSharedMemory::NoError ); - - m_shm = (float *) m_shmObj.data(); -#else - while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL | - 0600 ) ) == -1 ) + catch (const std::runtime_error& error) { + qCritical() << "Failed to allocate shared audio buffer:" << error.what(); + m_audioBuffer.detach(); + return; } - - m_shm = (float *) shmat( m_shmID, 0, 0 ); -#endif - m_shmSize = s; - sendMessage( message( IdChangeSharedMemoryKey ). - addInt( shm_key ).addInt( m_shmSize ) ); + m_audioBufferSize = s * sizeof(float); + sendMessage(message(IdChangeSharedMemoryKey).addString(m_audioBuffer.key())); } diff --git a/src/core/VstSyncController.cpp b/src/core/VstSyncController.cpp index c905b5561..efe295d7a 100644 --- a/src/core/VstSyncController.cpp +++ b/src/core/VstSyncController.cpp @@ -25,6 +25,8 @@ #include "VstSyncController.h" +#include + #include #include "AudioEngine.h" @@ -32,53 +34,23 @@ #include "Engine.h" #include "RemotePlugin.h" -#ifndef USE_QT_SHMEM -#include -#include -#include -#endif - VstSyncController::VstSyncController() : - m_syncData( nullptr ), - m_shmID( -1 ), - m_shm( "/usr/bin/lmms" ) + m_syncData( nullptr ) { if( ConfigManager::inst()->value( "ui", "syncvstplugins" ).toInt() ) { connect( Engine::audioEngine(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); -#ifdef USE_QT_SHMEM - if ( m_shm.create( sizeof( VstSyncData ) ) ) + try { - m_syncData = (VstSyncData*) m_shm.data(); + m_shm.create("usr_bin_lmms"); + m_syncData = m_shm.get(); } - else + catch (const std::runtime_error& error) { - qWarning() << QString( "Failed to allocate shared memory for VST sync: %1" ).arg( m_shm.errorString() ); + qWarning() << "Failed to allocate shared memory for VST sync:" << error.what(); } -#else - key_t key; // make the key: - if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) - { - qWarning( "VstSyncController: ftok() failed" ); - } - else - { // connect to shared memory segment - if( ( m_shmID = shmget( key, sizeof( VstSyncData ), 0644 | IPC_CREAT ) ) == -1 ) - { - qWarning( "VstSyncController: shmget() failed" ); - } - else - { // attach segment - m_syncData = (VstSyncData *)shmat( m_shmID, 0, 0 ); - if( m_syncData == (VstSyncData *)( -1 ) ) - { - qWarning( "VstSyncController: shmat() failed" ); - } - } - } -#endif } else { @@ -111,25 +83,6 @@ VstSyncController::~VstSyncController() { delete m_syncData; } - else - { -#ifdef USE_QT_SHMEM - if( m_shm.data() ) - { - // detach shared memory, delete it: - m_shm.detach(); - } -#else - if( shmdt( m_syncData ) != -1 ) - { - shmctl( m_shmID, IPC_RMID, nullptr ); - } - else - { - qWarning( "VstSyncController: shmdt() failed" ); - } -#endif - } } diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index f76bae469..db9ba90bf 100644 --- a/src/lmmsconfig.h.in +++ b/src/lmmsconfig.h.in @@ -40,7 +40,6 @@ #cmakedefine LMMS_HAVE_SYS_TYPES_H #cmakedefine LMMS_HAVE_SYS_IPC_H #cmakedefine LMMS_HAVE_SEMAPHORE_H -#cmakedefine LMMS_HAVE_SYS_SHM_H #cmakedefine LMMS_HAVE_SYS_TIME_H #cmakedefine LMMS_HAVE_SYS_TIMES_H #cmakedefine LMMS_HAVE_SCHED_H