diff --git a/CMakeLists.txt b/CMakeLists.txt index ca4729d..6050eb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # ====================== # ARA Library CMake Integration # -# Copyright (c) 2020-2025, Celemony Software GmbH, All Rights Reserved. +# Copyright (c) 2020-2026, Celemony Software GmbH, All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -41,6 +41,14 @@ project(ARA_Library LANGUAGES C CXX ) +# language standards + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + # ====================== add_subdirectory("${ARA_API_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ARA_API.build" EXCLUDE_FROM_ALL) @@ -55,13 +63,6 @@ function(configure_ARA_Library_target target) # file grouping ara_group_target_files(${target}) - # language standards - target_compile_features(${target} - PUBLIC - cxx_std_11 - c_std_11 - ) - # ARA_API dependency target_link_libraries(${target} PUBLIC ARA_API) @@ -85,6 +86,14 @@ function(configure_ARA_Library_target target) PRIVATE -DNOMINMAX=1 ) + # /Zc:__cplusplus, added in Visual Studio 2017 version 15.7, is required to make __cplusplus + # accurate, see https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + if(MSVC AND (MSVC_VERSION GREATER_EQUAL 1914)) + target_compile_options(${target} + PRIVATE + "/Zc:__cplusplus" + ) + endif() elseif(APPLE) if(XCODE) set_target_properties(${target} PROPERTIES @@ -167,8 +176,6 @@ configure_ARA_Library_target(ARA_PlugIn_Library) # ====================== -if(FALSE) - add_library(ARA_IPC_Library ${ARA_LIBRARY_TARGET_TYPE} "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPC.h" "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCEncoding.h" @@ -199,4 +206,3 @@ target_link_libraries(ARA_IPC_Library PRIVATE ) configure_ARA_Library_target(ARA_IPC_Library) -endif() diff --git a/ChangeLog.txt b/ChangeLog.txt index d9fd87f..b5f1c63 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,18 @@ -Proposed Features which have been postponed to a later subrelease: -- generic ARA IPC library, providing a proxy host and a proxy plug-in, - based on the IPC Example from earlier SDK releases +This is a development build of the ARA Library 3.0. +=== PRELIMINARY - DO NOT USE FOR SHIPPING PRODUCTS! === + + +Changes since previous releases: +- initial draft of ARA generator plug-ins that operate merely based on content descriptions (no sample input) +- initial draft of lyrics-related content types, tailored to support singing voice synthesis +- initial draft of region sequence persistency +- added isPlaybackRegionPreservingAudioSourceSignal(), superseding isAudioModificationPreservingAudioSourceSignal() +- initial draft of generic ARA IPC library providing a proxy host and a proxy plug-in, + based on heavily refactored IPC Example from earlier SDK releases +- dropped support for ARA 1 and 2.0 Draft APIs + If previously optionally supporting obsolete ARA 1 persistency, now exclusively use ARA 2 persistency. +- C++17 is now consistently required on all platforms +- fixed inconsistent spelling of "timestretch" in various identifiers === ARA SDK 2.3 release (aka 2.3.001) (2025/11/07) === diff --git a/Debug/ARAContentLogger.h b/Debug/ARAContentLogger.h index 85e3b5f..1c930e4 100644 --- a/Debug/ARAContentLogger.h +++ b/Debug/ARAContentLogger.h @@ -2,7 +2,7 @@ //! \file ARAContentLogger.h //! class to conveniently log (and validate) ARA content //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -40,13 +40,8 @@ namespace ARA { struct ContentLogger { -#if __cplusplus >= 201703L template using ContentReaderRef = decltype ((static_cast (nullptr)->*ContentReaderFunctionMapper::createContentReader) (nullptr, kARAContentTypeNotes, nullptr)); -#else - template - using ContentReaderRef = typename std::result_of::createContentReader) (ControllerType, ModelObjectRefType, ARAContentType, const ARAContentTimeRange*)>::type; -#endif template #if ARA_VALIDATE_API_CALLS @@ -60,12 +55,13 @@ struct ContentLogger // array of all content types defined in the api - static inline constexpr std::array getAllContentTypes () noexcept + static inline constexpr std::array getAllContentTypes () noexcept { return { { kARAContentTypeNotes, kARAContentTypeTempoEntries, kARAContentTypeBarSignatures, kARAContentTypeStaticTuning, - kARAContentTypeKeySignatures, kARAContentTypeSheetChords + kARAContentTypeKeySignatures, kARAContentTypeSheetChords, + kARAContentTypeLyricEntries } }; } @@ -93,6 +89,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return ContentTypeMapper::enumName; case kARAContentTypeKeySignatures: return ContentTypeMapper::enumName; case kARAContentTypeSheetChords: return ContentTypeMapper::enumName; + case kARAContentTypeLyricEntries: return ContentTypeMapper::enumName; default: ARA_INTERNAL_ASSERT (false); return "kARAContentType???"; } } @@ -107,6 +104,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return ContentTypeMapper::typeName; case kARAContentTypeKeySignatures: return ContentTypeMapper::typeName; case kARAContentTypeSheetChords: return ContentTypeMapper::typeName; + case kARAContentTypeLyricEntries: return ContentTypeMapper::typeName; default: ARA_INTERNAL_ASSERT (false); return "ARAContent???"; } } @@ -193,14 +191,22 @@ struct ContentLogger (logGivenName && logParsedName) ? " aka " : "", (logGivenName && logParsedName) ? parsedChordName.c_str () : "", chordData.position); } + static inline void logEvent (ARAInt32 idx, const ARAContentLyricsEntry& lyricsEntry) + { + ARA_LOG ("%s[%i] %s%s, %i %s phonemes%s, position = %.3f", getTypeNameForContentType (kARAContentTypeLyricEntries), idx, + (lyricsEntry.continuesPreviousWord) ? "-" : "", lyricsEntry.lyrics, + lyricsEntry.phonemeCount, getNameForContentGrade (lyricsEntry.phonemesGrade), + (lyricsEntry.phonemeOffsets) ? " w/ offsets": "", lyricsEntry.position); + } + // internal helper for log () - template ::type = true> + template = true> static inline void logEventIteration (ContentReader& reader, ARAInt32 i) { logEvent (i, reader[i]); } - template ::type = true> + template = true> static inline void logEventIteration (ContentReader& reader, ARAInt32 i) { const auto event { reader[i] }; @@ -252,6 +258,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return log (controller, modelObjectRef, range, logIfNotAvailable); case kARAContentTypeKeySignatures: return log (controller, modelObjectRef, range, logIfNotAvailable); case kARAContentTypeSheetChords: return log (controller, modelObjectRef, range, logIfNotAvailable); + case kARAContentTypeLyricEntries: return log (controller, modelObjectRef, range, logIfNotAvailable); default: ARA_INTERNAL_ASSERT (false); return false; } } @@ -321,6 +328,11 @@ struct ContentLogger log (controller, modelObjectRef, range, false); log (controller, modelObjectRef, range, false); } + if (scopeFlags.affectLyrics ()) + { + ARA_LOG ("lyrics scope updated, related content is:"); + log (controller, modelObjectRef, range, false); + } } }; diff --git a/Debug/ARAContentValidator.h b/Debug/ARAContentValidator.h index dff8b8a..666ba27 100644 --- a/Debug/ARAContentValidator.h +++ b/Debug/ARAContentValidator.h @@ -2,7 +2,7 @@ //! \file ARAContentValidator.h //! utility classes vor validating content readers //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -137,6 +137,25 @@ struct ContentReaderValidatorImplementation } }; +template <> +struct ContentReaderValidatorImplementation +{ + static inline void validateEventCount (ARAInt32 eventCount) { ARA_VALIDATE_API_CONDITION (eventCount >= 0); } + + static inline void validateEvent (const ARAContentLyricsEntry* event) + { + ARA_VALIDATE_API_CONDITION ((event->continuesPreviousWord == kARAFalse) || (event->lyrics != nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount == 0) || (event->phonemes != nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount != 0) || (event->phonemes == nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount != 0) || (event->phonemeOffsets == nullptr)); + } + + static inline void validateEventSequence (const ARAContentLyricsEntry* event, const ARAContentLyricsEntry* prevEvent) + { + ARA_VALIDATE_API_CONDITION (prevEvent->position < event->position); + } +}; + /*******************************************************************************/ // ContentReaderValidator diff --git a/Debug/ARADebug.c b/Debug/ARADebug.c index f6e828c..d3fcea5 100644 --- a/Debug/ARADebug.c +++ b/Debug/ARADebug.c @@ -2,7 +2,7 @@ //! \file ARADebug.c //! debug helpers for the ARA SDK Library //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -256,6 +256,7 @@ void ARASetExternalAssertReference(ARAAssertFunction * address) _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wmissing-noreturn\"") #endif +void ARAAssertionFailure(ARAAssertCategory category, const void * problematicArgument, const char * file, int line, const char * diagnosis); void ARAAssertionFailure(ARAAssertCategory category, const void * problematicArgument, const char * file, int line, const char * diagnosis) { #if ARA_ENABLE_INTERNAL_ASSERTS diff --git a/Debug/ARADebug.h b/Debug/ARADebug.h index b92e7c0..9ea6d47 100644 --- a/Debug/ARADebug.h +++ b/Debug/ARADebug.h @@ -2,7 +2,7 @@ //! \file ARADebug.h //! debug helpers for the ARA SDK Library //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -97,22 +97,20 @@ extern "C" // prevent unused variable warnings /*******************************************************************************/ -#if defined(__cplusplus) && (__cplusplus >= 201703L) - #define ARA_MAYBE_UNUSED_VAR(var) var [[maybe_unused]] -#elif defined(__GNUC__) - #define ARA_MAYBE_UNUSED_VAR(var) var __attribute__((unused)) -#elif defined(_MSC_VER) - #define ARA_MAYBE_UNUSED_VAR(var) var; (false ? (void)var : (void)false) -#else - #define ARA_MAYBE_UNUSED_VAR(var) var; (void)var -#endif +#if !defined(__cplusplus) + #if defined(__GNUC__) + #define ARA_MAYBE_UNUSED_VAR(var) var __attribute__((unused)) + #elif defined(_MSC_VER) + #define ARA_MAYBE_UNUSED_VAR(var) var; (false ? (void)var : (void)false) + #else + #define ARA_MAYBE_UNUSED_VAR(var) var; (void)var + #endif -#if defined(__cplusplus) && (__cplusplus >= 201703L) - #define ARA_MAYBE_UNUSED_ARG(var) var [[maybe_unused]] -#elif defined(__GNUC__) - #define ARA_MAYBE_UNUSED_ARG(var) var __attribute__((unused)) -#else - #define ARA_MAYBE_UNUSED_ARG(var) var + #if defined(__GNUC__) + #define ARA_MAYBE_UNUSED_ARG(var) var __attribute__((unused)) + #else + #define ARA_MAYBE_UNUSED_ARG(var) var + #endif #endif diff --git a/Dispatch/ARAContentReader.h b/Dispatch/ARAContentReader.h index 701ca6e..a67edb5 100644 --- a/Dispatch/ARAContentReader.h +++ b/Dispatch/ARAContentReader.h @@ -2,7 +2,7 @@ //! \file ARAContentReader.h //! content reading utility classes //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -51,6 +51,7 @@ struct ContentTypeMapper; ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeStaticTuning, ARAContentTuning) ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeKeySignatures, ARAContentKeySignature) ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeSheetChords, ARAContentChord) + ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeLyricEntries, ARAContentLyricsEntry) #undef ARA_SPECIALIZE_CONTENT_TYPE_MAPPER diff --git a/Dispatch/ARADispatchBase.h b/Dispatch/ARADispatchBase.h index c570c4e..cb02043 100644 --- a/Dispatch/ARADispatchBase.h +++ b/Dispatch/ARADispatchBase.h @@ -5,7 +5,7 @@ //! Typically, this file is not included directly - either ARAHostDispatch.h or //! ARAPlugInDispatch.h will be used instead. //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with PreSonus Software Ltd. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. @@ -31,6 +31,19 @@ #include "ARA_API/ARAInterface.h" +#if !defined (__cplusplus) || ((__cplusplus < 201703L) && (!defined (_MSVC_LANG) || (_MSVC_LANG < 201703L))) + #error "C++17 or higher is required to compile this code." +#endif + +#if !defined(__cpp_nontype_template_parameter_auto) && !defined (__cpp_template_auto) + #error "Template auto support is required to compile this code." +#endif + +#if defined (__clang__) && (__clang_major__ < 10) + #error "Clang 10 or newer is required to compile this code." +#endif + + /*******************************************************************************/ /** Optional ARA 1 backwards compatibility. Hosts and plug-ins can choose support ARA 1 hosts in addition to ARA 2 hosts. @@ -52,14 +65,6 @@ */ /*******************************************************************************/ -#if !defined (ARA_SUPPORT_VERSION_1) - #define ARA_SUPPORT_VERSION_1 0 -#endif - -#if ARA_SUPPORT_VERSION_1 && ARA_CPU_ARM - #error "ARA v1 is not supported on ARM architecture" -#endif - namespace ARA { @@ -144,20 +149,6 @@ class FromRefConversionHelper : public FromRef ClassName& operator= (ClassName&& other) = delete; #endif -/*******************************************************************************/ -// ARA_STRUCT_MEMBER pre-C++17 utility macro -/** When dealing with ARA variable-sized structures, this eases defining the related C++ template arguments */ -/*******************************************************************************/ - -#if defined (__cpp_template_auto) - // defined here so that code can be written to support both pre-C++17 and C++17 by always using the macro - // note that code which can rely on C++17 and up does not need to use the macro, - // writing e.g. SizedStruct<&ARAFactory::supportedPlaybackTransformationFlags> instead. - #define ARA_STRUCT_MEMBER(StructType, member) &ARA::StructType::member -#else - #define ARA_STRUCT_MEMBER(StructType, member) ARA::StructType, decltype (ARA::StructType::member), &ARA::StructType::member -#endif - /*******************************************************************************/ // SizedStruct @@ -169,7 +160,7 @@ class FromRefConversionHelper : public FromRef //! struct that implements every function up until ARADocumentControllerInterface::storeObjectsToArchive(), //! you can declare a SizedStruct like so: //! \code{.cpp} -//! SizedStruct dci; +//! SizedStruct<&ARA::ARADocumentControllerInterface::storeObjectsToArchive> dci; //! \endcode //! In this example, `dci` won't support any function declared after ARADocumentControllerInterface::storeObjectsToArchive(), //! meaning it won't support ARA Analysis Algorithm selection. @@ -180,16 +171,11 @@ class FromRefConversionHelper : public FromRef __pragma (warning(disable : 4324)) // disable padding warnings, instances of this struct template may need to be padded. #endif -#if defined (__cpp_template_auto) template struct alignas (alignof (void*)) SizedStruct; template struct alignas (alignof (void*)) SizedStruct : public StructType -#else -template -struct alignas (alignof (void*)) SizedStruct : public StructType -#endif { protected: //! \p SizedStruct alias, to allow any derived class to easily reference its templated base class. @@ -201,7 +187,7 @@ struct alignas (alignof (void*)) SizedStruct : public StructType //! the size of the struct as though \p member was the last implemented field. static constexpr inline size_t getImplementedSize () { - static_assert (std::is_class::value && std::is_standard_layout::value, "C compatible standard layout struct required"); + static_assert (std::is_class_v && std::is_standard_layout_v, "C compatible standard layout struct required"); return reinterpret_cast (&(static_cast (nullptr)->*member)) + sizeof (static_cast (nullptr)->*member); } @@ -215,10 +201,7 @@ struct alignas (alignof (void*)) SizedStruct : public StructType //! Default constructor. //! Special-cases the initializer list constructor only to suppress warnings about missing initializers for default construction. -#if __cplusplus >= 201402L - constexpr -#endif - inline SizedStruct () noexcept + inline constexpr SizedStruct () noexcept : StructType {} { this->structSize = SizedStruct::getImplementedSize (); @@ -242,7 +225,7 @@ struct alignas (alignof (void*)) SizedStruct : public StructType //! bool supportsAnalysisAlgorithmSelection (ARADocumentControllerInterface* dci) //! { //! SizedStructPtr sizedStructPtr (dci); -//! return sizedStructPtr.implements (); +//! return sizedStructPtr.implements<&ARA::ARADocumentControllerInterface::getProcessingAlgorithmsCount> (); //! } //! \endcode /*******************************************************************************/ @@ -268,27 +251,9 @@ class SizedStructPtr //! ARA uses the \p StructType::structSize member as a means of versioning - //! if it is large enough to contain \p member then this function returns true. //! This is a C++ version of ARA_IMPLEMENTS_FIELD. -#if defined (__cpp_template_auto) template inline bool implements () const noexcept - { - #if defined (__clang__) && (__clang_major__ < 10) - // see clang bug: https://bugs.llvm.org/show_bug.cgi?id=35655 - // as workaround, we're copying the code from SizedStruct::getImplementedSize () here - static_assert (std::is_class::value && std::is_standard_layout::value, "C compatible standard layout struct required"); - return this->_ptr->structSize >= reinterpret_cast (&(static_cast (nullptr)->*member)) + sizeof (static_cast (nullptr)->*member); - #else - return this->_ptr->structSize >= SizedStruct::getImplementedSize (); - #endif - } -#else - template - inline bool implements () const noexcept - { - static_assert (std::is_same::value, "must test member of same struct"); - return this->_ptr->structSize >= SizedStruct::getImplementedSize (); - } -#endif + { return this->_ptr->structSize >= SizedStruct::getImplementedSize (); } private: const StructType* _ptr; @@ -369,6 +334,8 @@ class ContentUpdateScopes static constexpr ContentUpdateScopes tuningIsAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateTuningScopeRemainsUnchanged; } //! Content readers for key signatures, chords etc. are affected by the change. static constexpr ContentUpdateScopes harmoniesAreAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateHarmonicScopeRemainsUnchanged; } + //! Content readers for lyrics, phonemes etc. are affected by the change. + static constexpr ContentUpdateScopes lyricsAreAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateLyricsScopeRemainsUnchanged; } //! Everything is affected by the change. static constexpr ContentUpdateScopes everythingIsAffected () noexcept { return kARAContentUpdateEverythingChanged; } @@ -411,14 +378,16 @@ class ContentUpdateScopes constexpr bool affectTuning () const noexcept { return ((_flags & kARAContentUpdateTuningScopeRemainsUnchanged) == 0); } //! \copybrief harmoniesAreAffected constexpr bool affectHarmonies () const noexcept { return ((_flags & kARAContentUpdateHarmonicScopeRemainsUnchanged) == 0); } + //! \copybrief lyricsAreAffected + constexpr bool affectLyrics () const noexcept { return ((_flags & kARAContentUpdateLyricsScopeRemainsUnchanged) == 0); } //! \copybrief everythingIsAffected constexpr bool affectEverything () const noexcept { return ((_flags & _knownFlags) == 0); } //@} private: - static constexpr ARAContentUpdateFlags _knownFlags { (kARAContentUpdateSignalScopeRemainsUnchanged | - kARAContentUpdateNoteScopeRemainsUnchanged | kARAContentUpdateTimingScopeRemainsUnchanged | - kARAContentUpdateTuningScopeRemainsUnchanged | kARAContentUpdateHarmonicScopeRemainsUnchanged) }; + static constexpr ARAContentUpdateFlags _knownFlags { (kARAContentUpdateSignalScopeRemainsUnchanged | kARAContentUpdateNoteScopeRemainsUnchanged | + kARAContentUpdateTimingScopeRemainsUnchanged | kARAContentUpdateTuningScopeRemainsUnchanged | + kARAContentUpdateHarmonicScopeRemainsUnchanged | kARAContentUpdateLyricsScopeRemainsUnchanged) }; ARAContentUpdateFlags _flags; }; diff --git a/Dispatch/ARAHostDispatch.cpp b/Dispatch/ARAHostDispatch.cpp index 5444481..ea62e79 100644 --- a/Dispatch/ARAHostDispatch.cpp +++ b/Dispatch/ARAHostDispatch.cpp @@ -3,7 +3,7 @@ //! C-to-C++ adapter for implementing ARA hosts //! Originally written and contributed to the ARA SDK by PreSonus Software Ltd. //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with PreSonus Software Ltd. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ namespace AudioAccessControllerDispatcher static const ARAAudioAccessControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAAudioAccessControllerInterface::destroyAudioReader> ifc = { AudioAccessControllerDispatcher::createAudioReaderForSource, AudioAccessControllerDispatcher::readAudioSamples, @@ -98,7 +98,7 @@ namespace ArchivingControllerDispatcher static const ARAArchivingControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAArchivingControllerInterface::getDocumentArchiveID> ifc = { ArchivingControllerDispatcher::getArchiveSize, ArchivingControllerDispatcher::readBytesFromArchive, @@ -167,7 +167,7 @@ namespace ContentAccessControllerDispatcher static const ARAContentAccessControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAContentAccessControllerInterface::destroyContentReader> ifc = { ContentAccessControllerDispatcher::isMusicalContextContentAvailable, ContentAccessControllerDispatcher::getMusicalContextContentGrade, @@ -218,15 +218,21 @@ namespace ModelUpdateControllerDispatcher fromHostRef (controllerHostRef)->notifyDocumentDataChanged (); } + static void ARA_CALL notifyRegionSequenceDataChanged (ARAModelUpdateControllerHostRef controllerHostRef, ARARegionSequenceHostRef regionSequenceHostRef) noexcept + { + fromHostRef (controllerHostRef)->notifyRegionSequenceDataChanged (regionSequenceHostRef); + } + static const ARAModelUpdateControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged> ifc = { ModelUpdateControllerDispatcher::notifyAudioSourceAnalysisProgress, ModelUpdateControllerDispatcher::notifyAudioSourceContentChanged, ModelUpdateControllerDispatcher::notifyAudioModificationContentChanged, ModelUpdateControllerDispatcher::notifyPlaybackRegionContentChanged, - ModelUpdateControllerDispatcher::notifyDocumentDataChanged + ModelUpdateControllerDispatcher::notifyDocumentDataChanged, + ModelUpdateControllerDispatcher::notifyRegionSequenceDataChanged }; return &ifc; } @@ -265,7 +271,7 @@ namespace PlaybackControllerDispatcher static const ARAPlaybackControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAPlaybackControllerInterface::requestEnableCycle> ifc = { PlaybackControllerDispatcher::requestStartPlayback, PlaybackControllerDispatcher::requestStopPlayback, @@ -360,72 +366,23 @@ void DocumentController::updateDocumentProperties (const ARADocumentProperties* getInterface ()->updateDocumentProperties (getRef (), properties); } -bool DocumentController::beginRestoringDocumentFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept -{ - // begin-/endRestoringDocumentFromArchive () is deprecated, prefer to use the new partial persistency calls if supported by the plug-in - if (supportsPartialPersistency ()) - { - getInterface ()->beginEditing (getRef ()); - return true; - } - else - { - return (getInterface ()->beginRestoringDocumentFromArchive (getRef (), archiveReaderHostRef) != kARAFalse); - } -} - -bool DocumentController::endRestoringDocumentFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept -{ - // begin-/endRestoringDocumentFromArchive () is deprecated, prefer to use the new partial persistency calls if supported by the plug-in - if (supportsPartialPersistency ()) - { - const auto result { getInterface ()->restoreObjectsFromArchive (getRef (), archiveReaderHostRef, nullptr) }; - getInterface ()->endEditing (getRef ()); - return (result != kARAFalse); - } - else - { - return (getInterface ()->endRestoringDocumentFromArchive (getRef (), archiveReaderHostRef) != kARAFalse); - } -} - -bool DocumentController::storeDocumentToArchive (ARAArchiveWriterHostRef archiveWriterHostRef) noexcept -{ - // storeDocumentToArchive () is deprecated, prefer to use the new partial persistency calls if supported by the plug-in - if (supportsPartialPersistency ()) - return (getInterface ()->storeObjectsToArchive (getRef (), archiveWriterHostRef, nullptr) != kARAFalse); - else - return (getInterface ()->storeDocumentToArchive (getRef (), archiveWriterHostRef) != kARAFalse); -} - -bool DocumentController::supportsPartialPersistency () noexcept -{ - return getInterface ().implements (); -} - bool DocumentController::restoreObjectsFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, const ARARestoreObjectsFilter* filter) noexcept { - if (!supportsPartialPersistency ()) - return false; - return (getInterface ()->restoreObjectsFromArchive (getRef (), archiveReaderHostRef, filter) != kARAFalse); } bool DocumentController::storeObjectsToArchive (ARAArchiveWriterHostRef archiveWriterHostRef, const ARAStoreObjectsFilter* filter) noexcept { - if (!supportsPartialPersistency ()) - return false; - return (getInterface ()->storeObjectsToArchive (getRef (), archiveWriterHostRef, filter) != kARAFalse); } bool DocumentController::supportsStoringAudioFileChunks () noexcept { - if (!getInterface ().implements ()) + if (!getInterface ().implements<&ARADocumentControllerInterface::storeAudioSourceToAudioFileChunk> ()) return false; const SizedStructPtr factory { getInterface ()->getFactory (getRef ()) }; - if (!factory.implements ()) + if (!factory.implements<&ARAFactory::supportsStoringAudioFileChunks> ()) return false; return (factory->supportsStoringAudioFileChunks != kARAFalse); } @@ -463,31 +420,16 @@ void DocumentController::destroyMusicalContext (ARAMusicalContextRef musicalCont ARARegionSequenceRef DocumentController::createRegionSequence (ARARegionSequenceHostRef hostRef, const ARARegionSequenceProperties* properties) noexcept { -#if ARA_SUPPORT_VERSION_1 - if (!getInterface ().implements ()) - return nullptr; -#endif - return getInterface ()->createRegionSequence (getRef (), hostRef, properties); } void DocumentController::updateRegionSequenceProperties (ARARegionSequenceRef regionSequenceRef, const ARARegionSequenceProperties* properties) noexcept { -#if ARA_SUPPORT_VERSION_1 - if (!getInterface ().implements ()) - return; -#endif - return getInterface ()->updateRegionSequenceProperties (getRef (), regionSequenceRef, properties); } void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept { -#if ARA_SUPPORT_VERSION_1 - if (!getInterface ().implements ()) - return; -#endif - return getInterface ()->destroyRegionSequence (getRef (), regionSequenceRef); } @@ -538,7 +480,7 @@ void DocumentController::updateAudioModificationProperties (ARAAudioModification bool DocumentController::supportsIsAudioModificationPreservingAudioSourceSignal () noexcept { - return getInterface ().implements (); + return getInterface ().implements<&ARADocumentControllerInterface::isAudioModificationPreservingAudioSourceSignal> (); } bool DocumentController::isAudioModificationPreservingAudioSourceSignal (ARAAudioModificationRef audioModificationRef) noexcept @@ -568,17 +510,20 @@ void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef pl getInterface ()->updatePlaybackRegionProperties (getRef (), playbackRegionRef, properties); } -void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept +bool DocumentController::supportsIsPlaybackRegionPreservingAudioSourceSignal () noexcept { -#if ARA_SUPPORT_VERSION_1 - if (!getInterface ().implements ()) - { - *headTime = 0.0; - *tailTime = 0.0; - return; - } -#endif + return getInterface ().implements<&ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal> (); +} + +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + if (!supportsIsPlaybackRegionPreservingAudioSourceSignal ()) + return false; + return (getInterface ()->isPlaybackRegionPreservingAudioSourceSignal (getRef (), playbackRegionRef) != kARAFalse); +} +void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept +{ return getInterface ()->getPlaybackRegionHeadAndTailTime (getRef (), playbackRegionRef, headTime, tailTime); } @@ -664,7 +609,7 @@ bool DocumentController::supportsProcessingAlgorithms () noexcept ARAInt32 DocumentController::getProcessingAlgorithmsCount () noexcept { - if (getInterface ().implements ()) + if (getInterface ().implements<&ARADocumentControllerInterface::requestProcessingAlgorithmForAudioSource> ()) return getInterface ()->getProcessingAlgorithmsCount (getRef ()); else return 0; @@ -687,7 +632,7 @@ void DocumentController::requestProcessingAlgorithmForAudioSource (ARAAudioSourc bool DocumentController::isLicensedForCapabilities (bool runModalActivationDialogIfNeeded, ARASize contentTypesCount, const ARAContentType contentTypes[], ARAPlaybackTransformationFlags transformationFlags) noexcept { - if (!getInterface ().implements ()) + if (!getInterface ().implements<&ARADocumentControllerInterface::isLicensedForCapabilities> ()) return true; return (getInterface ()->isLicensedForCapabilities (getRef (), (runModalActivationDialogIfNeeded) ? kARATrue : kARAFalse, contentTypesCount, contentTypes, transformationFlags) != kARAFalse); @@ -697,65 +642,6 @@ bool DocumentController::isLicensedForCapabilities (bool runModalActivationDialo // PlaybackRenderer /*******************************************************************************/ -#if ARA_SUPPORT_VERSION_1 - -bool supportsARA2 (const ARAPlugInExtensionInstance* instance) -{ - return SizedStructPtr (instance).implements (); -} - -class ARA1PlugInExtension : public InterfaceInstance -{ -public: - explicit ARA1PlugInExtension (const ARAPlugInExtensionInstance* instance) noexcept - : BaseType { instance->plugInExtensionRef, instance->plugInExtensionInterface } - {} -}; -// this is not really a host ref but rather a dummy plug-in ref created by the host, -// but since this macro only deals with proper casting this difference does not matter. -ARA_MAP_HOST_REF (ARA1PlugInExtension, ARAPlaybackRendererRef) - -// playback renderer for ARA 1 is forwarding to plug-in extension -namespace ARA1PlaybackRendererAdapterDispatcher -{ - static void ARA_CALL addPlaybackRegion (ARAPlaybackRendererRef playbackRendererRef, ARAPlaybackRegionRef playbackRegionRef) - { - auto plugInExtension { fromHostRef (playbackRendererRef) }; - fromHostRef (playbackRendererRef)->getInterface ()->setPlaybackRegion (plugInExtension->getRef (), playbackRegionRef); - } - - static void ARA_CALL removePlaybackRegion (ARAPlaybackRendererRef playbackRendererRef, ARAPlaybackRegionRef playbackRegionRef) - { - auto plugInExtension { fromHostRef (playbackRendererRef) }; - plugInExtension->getInterface ()->removePlaybackRegion (plugInExtension->getRef (), playbackRegionRef); - } - - static const ARAPlaybackRendererInterface* getInterface () noexcept - { - static const SizedStruct ifc = - { - ARA1PlaybackRendererAdapterDispatcher::addPlaybackRegion, - ARA1PlaybackRendererAdapterDispatcher::removePlaybackRegion - }; - return &ifc; - } -} - -PlaybackRenderer::PlaybackRenderer (const ARAPlugInExtensionInstance* instance) -: BaseType { supportsARA2 (instance) ? instance->playbackRendererRef : toHostRef (new ARA1PlugInExtension (instance)), - supportsARA2 (instance) ? instance->playbackRendererInterface : ARA1PlaybackRendererAdapterDispatcher::getInterface () } -{} - -PlaybackRenderer::~PlaybackRenderer () -{ - // ARA 2 plug-ins will provide their own distinct interface pointers, so this test - // basically equals a dynamic_cast (PlaybackRenderer.getRef ()) - if (getInterface () == ARA1PlaybackRendererAdapterDispatcher::getInterface ()) - delete reinterpret_cast (getRef ()); -} - -#endif // ARA_SUPPORT_VERSION_1 - void PlaybackRenderer::addPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept { getInterface ()->addPlaybackRegion (getRef (), playbackRegionRef); @@ -770,43 +656,6 @@ void PlaybackRenderer::removePlaybackRegion (ARAPlaybackRegionRef playbackRegion // EditorRenderer //************************************************************************************************ -#if ARA_SUPPORT_VERSION_1 - -// editor renderer for ARA 1 is empty stub -namespace ARA1EditorRendererAdapterDispatcher -{ - static void ARA_CALL addPlaybackRegion (ARAEditorRendererRef /*editorRendererRef*/, ARAPlaybackRegionRef /*playbackRegionRef*/) - {} - - static void ARA_CALL removePlaybackRegion (ARAEditorRendererRef /*editorRendererRef*/, ARAPlaybackRegionRef /*playbackRegionRef*/) - {} - - static void ARA_CALL addRegionSequence (ARAEditorRendererRef /*editorRendererRef*/, ARARegionSequenceRef /*regionSequenceRef*/) - {} - - static void ARA_CALL removeRegionSequence (ARAEditorRendererRef /*editorRendererRef*/, ARARegionSequenceRef /*regionSequenceRef*/) - {} - - static const ARAEditorRendererInterface* getInterface () noexcept - { - static const SizedStruct ifc = - { - ARA1EditorRendererAdapterDispatcher::addPlaybackRegion, - ARA1EditorRendererAdapterDispatcher::removePlaybackRegion, - ARA1EditorRendererAdapterDispatcher::addRegionSequence, - ARA1EditorRendererAdapterDispatcher::removeRegionSequence - }; - return &ifc; - } -} - -EditorRenderer::EditorRenderer (const ARAPlugInExtensionInstance* instance) noexcept -: BaseType { supportsARA2 (instance) ? instance->editorRendererRef : nullptr, - supportsARA2 (instance) ? instance->editorRendererInterface : ARA1EditorRendererAdapterDispatcher::getInterface () } -{} - -#endif // ARA_SUPPORT_VERSION_1 - void EditorRenderer::addPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept { getInterface ()->addPlaybackRegion (getRef (), playbackRegionRef); @@ -831,35 +680,6 @@ void EditorRenderer::removeRegionSequence (ARARegionSequenceRef regionSequenceRe // EditorView //************************************************************************************************ -#if ARA_SUPPORT_VERSION_1 - -// editor view for ARA 1 is empty stub -namespace ARA1EditorViewAdapterDispatcher -{ - static void ARA_CALL notifySelection (ARAEditorViewRef /*editorViewRef*/, const ARAViewSelection* /*selection*/) - {} - - static void ARA_CALL notifyHideRegionSequences (ARAEditorViewRef /*editorViewRef*/, ARASize /*regionSequenceRefsCount*/, const ARARegionSequenceRef /*regionSequenceRefs*/[]) - {} - - static const ARAEditorViewInterface* getInterface () noexcept - { - static const SizedStruct ifc = - { - ARA1EditorViewAdapterDispatcher::notifySelection, - ARA1EditorViewAdapterDispatcher::notifyHideRegionSequences - }; - return &ifc; - } -} - -EditorView::EditorView (const ARAPlugInExtensionInstance* instance) noexcept -: BaseType { supportsARA2 (instance) ? instance->editorViewRef : nullptr, - supportsARA2 (instance) ? instance->editorViewInterface : ARA1EditorViewAdapterDispatcher::getInterface () } -{} - -#endif // ARA_SUPPORT_VERSION_1 - void EditorView::notifySelection (const ARAViewSelection* selection) noexcept { getInterface ()->notifySelection (getRef (), selection); diff --git a/Dispatch/ARAHostDispatch.h b/Dispatch/ARAHostDispatch.h index 4463fb6..0cf647f 100644 --- a/Dispatch/ARAHostDispatch.h +++ b/Dispatch/ARAHostDispatch.h @@ -3,7 +3,7 @@ //! C-to-C++ adapter for implementing ARA hosts //! Originally written and contributed to the ARA SDK by PreSonus Software Ltd. //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with PreSonus Software Ltd. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ ARA_DISABLE_DOCUMENTATION_DEPRECATED_WARNINGS_BEGIN #if !defined (ARA_MAP_HOST_REF) #define ARA_MAP_HOST_REF(HostClassType, FirstHostRefType, ...) \ static inline ARA::ToRefConversionHelper toHostRef (const HostClassType* ptr) noexcept { return ARA::ToRefConversionHelper { ptr }; } \ - template , HostRefType>::value, bool>::type = true> \ + template , HostRefType>, bool> = true> \ static inline DesiredHostClassType* fromHostRef (HostRefType ref) noexcept { HostClassType* object { ARA::FromRefConversionHelper (ref) }; return static_cast (object); } #endif @@ -79,13 +79,8 @@ ARA_DISABLE_DOCUMENTATION_DEPRECATED_WARNINGS_BEGIN /** C++ wrapper for ARA variable-sized properties structs, ensures their proper initialization. */ /*******************************************************************************/ -#if defined (__cpp_template_auto) template using Properties = SizedStruct; -#else - template - using Properties = SizedStruct; -#endif //! @addtogroup ARA_Library_Host_Dispatch_Host_Interfaces Host Interfaces //! @{ @@ -185,6 +180,8 @@ class ModelUpdateControllerInterface virtual void notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept = 0; //! \copydoc ARAModelUpdateControllerInterface::notifyDocumentDataChanged virtual void notifyDocumentDataChanged () noexcept = 0; + //! \copydoc ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged + ARA_DRAFT virtual void notifyRegionSequenceDataChanged (ARA::ARARegionSequenceHostRef regionSequenceHostRef) noexcept = 0; }; ARA_MAP_HOST_REF (ModelUpdateControllerInterface, ARAModelUpdateControllerHostRef) @@ -216,13 +213,10 @@ ARA_MAP_HOST_REF (PlaybackControllerInterface, ARAPlaybackControllerHostRef) /** Wrapper class for the ARADocumentControllerHostInstance. */ /*******************************************************************************/ -class DocumentControllerHostInstance : public SizedStruct +class DocumentControllerHostInstance : public SizedStruct<&ARADocumentControllerHostInstance::playbackControllerInterface> { public: -#if __cplusplus >= 201402L - constexpr -#endif - DocumentControllerHostInstance () noexcept : BaseType {} {} + constexpr DocumentControllerHostInstance () noexcept : BaseType {} {} DocumentControllerHostInstance (AudioAccessControllerInterface* audioAccessController, ArchivingControllerInterface* archivingController, ContentAccessControllerInterface* contentAccessController = nullptr, @@ -292,17 +286,6 @@ class DocumentController : public InterfaceInstance { public: -#if ARA_SUPPORT_VERSION_1 - explicit PlaybackRenderer (const ARAPlugInExtensionInstance* instance); - ~PlaybackRenderer (); -#else explicit PlaybackRenderer (const ARAPlugInExtensionInstance* instance) noexcept : BaseType { instance->playbackRendererRef, instance->playbackRendererInterface } {} -#endif //! @name Assigning the playback region(s) for playback rendering //! For details, see \ref Assigning_ARAPlaybackRendererInterface_Regions "ARAPlaybackRendererInterface". @@ -510,13 +492,9 @@ class PlaybackRenderer : public InterfaceInstance { public: -#if ARA_SUPPORT_VERSION_1 - explicit EditorRenderer (const ARAPlugInExtensionInstance* instance) noexcept; -#else explicit EditorRenderer (const ARAPlugInExtensionInstance* instance) noexcept : BaseType { instance->editorRendererRef, instance->editorRendererInterface } {} -#endif //! @name Assigning the playback region(s) for preview while editing //! For details, see \ref Assigning_ARAEditorRendererInterface_Regions "ARAEditorRendererInterface". @@ -536,13 +514,9 @@ class EditorRenderer : public InterfaceInstance { public: -#if ARA_SUPPORT_VERSION_1 - explicit EditorView (const ARAPlugInExtensionInstance* instance) noexcept; -#else explicit EditorView (const ARAPlugInExtensionInstance* instance) noexcept : BaseType { instance->editorViewRef, instance->editorViewInterface } {} -#endif //! @name Host UI notifications //@{ @@ -565,11 +539,7 @@ class EditorView : public InterfaceInstanceupdatePlaybackRegionProperties (playbackRegionRef, properties); } - static void ARA_CALL destroyPlaybackRegion (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept + static ARABool ARA_CALL isPlaybackRegionPreservingAudioSourceSignal (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept { - fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + return fromRef (controllerRef)->isPlaybackRegionPreservingAudioSourceSignal (playbackRegionRef) ? kARATrue : kARAFalse; } static void ARA_CALL getPlaybackRegionHeadAndTailTime (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef, @@ -235,6 +235,11 @@ namespace DocumentControllerDispatcher fromRef (controllerRef)->getPlaybackRegionHeadAndTailTime (playbackRegionRef, headTime, tailTime); } + static void ARA_CALL destroyPlaybackRegion (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept + { + fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + } + // Content Reader Management static ARABool ARA_CALL isAudioSourceContentAvailable (ARADocumentControllerRef controllerRef, ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept @@ -342,7 +347,7 @@ namespace DocumentControllerDispatcher static const ARADocumentControllerInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal> ifc = { DocumentControllerDispatcher::destroyDocumentController, DocumentControllerDispatcher::getFactory, @@ -397,7 +402,8 @@ namespace DocumentControllerDispatcher DocumentControllerDispatcher::requestProcessingAlgorithmForAudioSource, DocumentControllerDispatcher::isLicensedForCapabilities, DocumentControllerDispatcher::storeAudioSourceToAudioFileChunk, - DocumentControllerDispatcher::isAudioModificationPreservingAudioSourceSignal + DocumentControllerDispatcher::isAudioModificationPreservingAudioSourceSignal, + DocumentControllerDispatcher::isPlaybackRegionPreservingAudioSourceSignal }; return &ifc; } @@ -429,7 +435,7 @@ namespace PlaybackRendererDispatcher static const ARAPlaybackRendererInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAPlaybackRendererInterface::removePlaybackRegion> ifc = { PlaybackRendererDispatcher::addPlaybackRegion, PlaybackRendererDispatcher::removePlaybackRegion @@ -466,7 +472,7 @@ namespace EditorRendererDispatcher static const ARAEditorRendererInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAEditorRendererInterface::removeRegionSequence> ifc = { EditorRendererDispatcher::addPlaybackRegion, EditorRendererDispatcher::removePlaybackRegion, @@ -495,7 +501,7 @@ namespace EditorViewDispatcher static const ARAEditorViewInterface* getInterface () noexcept { - static const SizedStruct ifc = + static const SizedStruct<&ARAEditorViewInterface::notifyHideRegionSequences> ifc = { EditorViewDispatcher::notifySelection, EditorViewDispatcher::notifyHideRegionSequences @@ -504,81 +510,17 @@ namespace EditorViewDispatcher } } -/*******************************************************************************/ -// ARA1PlugInExtensionDispatcher -/*******************************************************************************/ - -#if ARA_SUPPORT_VERSION_1 - -ARA_MAP_REF (PlugInExtensionInstance, ARAPlugInExtensionRef) - -namespace ARA1PlugInExtensionDispatcher -{ - static void ARA_CALL setPlaybackRegion (ARAPlugInExtensionRef plugInExtensionRef, ARAPlaybackRegionRef playbackRegionRef) noexcept - { - fromRef (plugInExtensionRef)->setPlaybackRegion (playbackRegionRef); - } - - static void ARA_CALL removePlaybackRegion (ARAPlugInExtensionRef plugInExtensionRef, ARAPlaybackRegionRef playbackRegionRef) noexcept - { - fromRef (plugInExtensionRef)->removePlaybackRegion (playbackRegionRef); - } - - static const ARAPlugInExtensionInterface* getInterface () noexcept - { - static const SizedStruct ifc = - { - ARA1PlugInExtensionDispatcher::setPlaybackRegion, - ARA1PlugInExtensionDispatcher::removePlaybackRegion - }; - return &ifc; - } -} - -#endif - /*******************************************************************************/ // PlugInExtensionInstance /*******************************************************************************/ PlugInExtensionInstance::PlugInExtensionInstance (PlaybackRendererInterface* playbackRenderer, EditorRendererInterface* editorRenderer, EditorViewInterface* editorView) noexcept -#if ARA_SUPPORT_VERSION_1 -: BaseType { toRef (this), ARA1PlugInExtensionDispatcher::getInterface (), -#else : BaseType { nullptr, nullptr, -#endif toRef (playbackRenderer), (playbackRenderer) ? PlaybackRendererDispatcher::getInterface () : nullptr, toRef (editorRenderer), (editorRenderer) ? EditorRendererDispatcher::getInterface () : nullptr, toRef (editorView), (editorView) ? EditorViewDispatcher::getInterface () : nullptr } {} -#if ARA_SUPPORT_VERSION_1 -void PlugInExtensionInstance::setPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept -{ - if (_hasPlaybackRegion) - removePlaybackRegion (_playbackRegionRef); - - if (getPlaybackRenderer ()) - getPlaybackRenderer ()->addPlaybackRegion (playbackRegionRef); - if (getEditorRenderer ()) - getEditorRenderer ()->addPlaybackRegion (playbackRegionRef); - - _playbackRegionRef = playbackRegionRef; - _hasPlaybackRegion = true; -} - -void PlugInExtensionInstance::removePlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept -{ - if (getPlaybackRenderer ()) - getPlaybackRenderer ()->removePlaybackRegion (playbackRegionRef); - if (getEditorRenderer ()) - getEditorRenderer ()->removePlaybackRegion (playbackRegionRef); - - _playbackRegionRef = nullptr; - _hasPlaybackRegion = false; -} -#endif - /*******************************************************************************/ // AudioAccessController /*******************************************************************************/ @@ -633,7 +575,7 @@ void HostArchivingController::notifyDocumentUnarchivingProgress (float value) no ARAPersistentID HostArchivingController::getDocumentArchiveID (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept { // getDocumentArchiveID() was added in the ARA 2.0 final release, so check its presence here to support older hosts - if (getInterface ().implements ()) + if (getInterface ().implements<&ARAArchivingControllerInterface::getDocumentArchiveID> ()) return getInterface ()->getDocumentArchiveID (getRef (), archiveReaderHostRef); return nullptr; } @@ -709,21 +651,40 @@ void HostModelUpdateController::notifyAudioModificationContentChanged (ARAAudioM getInterface ()->notifyAudioModificationContentChanged (getRef (), audioModificationHostRef, range, scopeFlags); } +bool HostModelUpdateController::supportsNotifyPlaybackRegionContentChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyPlaybackRegionContentChanged> (); +} + void HostModelUpdateController::notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept { - // notifyPlaybackRegionContentChanged was optional in the ARA 2.0 draft, so check its presence here to be safe - if (getInterface ().implements ()) + if (supportsNotifyPlaybackRegionContentChanged ()) getInterface ()->notifyPlaybackRegionContentChanged (getRef (), playbackRegionHostRef, range, scopeFlags); } +bool HostModelUpdateController::supportsNotifyDocumentDataChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyDocumentDataChanged> (); +} + void HostModelUpdateController::notifyDocumentDataChanged () noexcept { - // notifyDocumentDataChanged was added in ARA 2.3 draft, so check its presence here - if (getInterface ().implements ()) + if (supportsNotifyDocumentDataChanged ()) getInterface ()->notifyDocumentDataChanged (getRef ()); } +bool HostModelUpdateController::supportsNotifyRegionSequenceDataChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged> (); +} + +void HostModelUpdateController::notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept +{ + if (supportsNotifyRegionSequenceDataChanged ()) + getInterface ()->notifyRegionSequenceDataChanged (getRef (), regionSequenceHostRef); +} + /*******************************************************************************/ // PlaybackController /*******************************************************************************/ diff --git a/Dispatch/ARAPlugInDispatch.h b/Dispatch/ARAPlugInDispatch.h index 8d7e089..4810d36 100644 --- a/Dispatch/ARAPlugInDispatch.h +++ b/Dispatch/ARAPlugInDispatch.h @@ -2,7 +2,7 @@ //! \file ARAPlugInDispatch.h //! C-to-C++ adapter for implementing ARA plug-ins //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -56,7 +56,7 @@ namespace PlugIn { #if !defined (ARA_MAP_REF) #define ARA_MAP_REF(ClassType, FirstRefType, ...) \ static inline ARA::ToRefConversionHelper toRef (const ClassType* ptr) noexcept { return ARA::ToRefConversionHelper { ptr }; } \ - template , RefType>::value, bool>::type = true> \ + template , RefType>, bool> = true> \ static inline DesiredClassType* fromRef (RefType ref) noexcept { ClassType* object { ARA::FromRefConversionHelper (ref) }; return static_cast (object); } #endif @@ -186,10 +186,12 @@ class DocumentControllerInterface virtual ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept = 0; //! \copydoc ARADocumentControllerInterface::updatePlaybackRegionProperties virtual void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept = 0; - //! \copydoc ARADocumentControllerInterface::destroyPlaybackRegion - virtual void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; + //! \copydoc ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal + ARA_DRAFT virtual bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; //! \copydoc ARADocumentControllerInterface::getPlaybackRegionHeadAndTailTime virtual void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept = 0; + //! \copydoc ARADocumentControllerInterface::destroyPlaybackRegion + virtual void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; //@} //! @name Content Reader Management @@ -250,7 +252,7 @@ ARA_MAP_REF (DocumentControllerInterface, ARADocumentControllerRef) /** Wrapper class for the ARADocumentControllerInstance. */ /*******************************************************************************/ -class DocumentControllerInstance : public SizedStruct +class DocumentControllerInstance : public SizedStruct<&ARADocumentControllerInstance::documentControllerInterface> { public: explicit DocumentControllerInstance (DocumentControllerInterface* documentController) noexcept; @@ -324,7 +326,7 @@ ARA_MAP_REF (EditorViewInterface, ARAEditorViewRef) /** Wrapper class for ARAPlugInExtensionInstance. */ /*******************************************************************************/ -class PlugInExtensionInstance : public SizedStruct +class PlugInExtensionInstance : public SizedStruct<&ARAPlugInExtensionInstance::editorViewInterface> { public: explicit PlugInExtensionInstance (PlaybackRendererInterface* playbackRenderer, EditorRendererInterface* editorRenderer, EditorViewInterface* editorView) noexcept; @@ -337,15 +339,6 @@ class PlugInExtensionInstance : public SizedStruct toIPCRef (const IPCClassType* ptr) noexcept { return ARA::ToRefConversionHelper { ptr }; } \ + template , IPCRefType>, bool> = true> \ + static inline DesiredIPCClassType* fromIPCRef (IPCRefType ref) noexcept { IPCClassType* object { ARA::FromRefConversionHelper (ref) }; return static_cast (object); } +#endif + + //! helper define to properly insert ARA::IPC namespace into C compatible headers + #define ARA_IPC_NAMESPACE ARA::IPC:: +#else + #define ARA_IPC_NAMESPACE +#endif + + +//! IPC reference markup type identifier. \br\br +//! Examples: \br +//! ::ARAARAIPCProxyHostRef \br +//! ::ARAARAIPCProxyPlugInRef \br +//! ::ARAIPCMessageChannelRef \br +//! ::ARAIPCPlugInInstanceRef \br +#define ARA_IPC_REF(IPCRefType) struct IPCRefType##MarkupType * IPCRefType + +//! C-compatible wrapper of ARA IPC ProxyHost +typedef ARA_IPC_REF(ARAIPCProxyHostRef); + +//! C-compatible wrapper of ARA IPC ProxyPlugIn +typedef ARA_IPC_REF(ARAIPCProxyPlugInRef); + +//! to keep the IPC decoupled from the companion API in use, the IPC code uses +//! an opaque encapsulation to represent a companion API plug-in instance +typedef ARA_IPC_REF(ARAIPCPlugInInstanceRef); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + +#endif // ARA_ENABLE_IPC + +//! @} ARA_Library_IPC + +#endif // ARAIPC_h diff --git a/IPC/ARAIPCAudioUnit_v3.h b/IPC/ARAIPCAudioUnit_v3.h new file mode 100644 index 0000000..328128c --- /dev/null +++ b/IPC/ARAIPCAudioUnit_v3.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCAudioUnit_v3.h +//! Implementation of ARA IPC message sending through AUMessageChannel +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#import "ARA_API/ARAAudioUnit_v3.h" + + +//! @addtogroup ARA_Library_IPC +//! @{ + + +//! using ARA IPC for Audio Units requires to compile for macOS 13 or higher +#if defined(__MAC_13_0) + #define ARA_AUDIOUNITV3_IPC_IS_AVAILABLE 1 +#else + #define ARA_AUDIOUNITV3_IPC_IS_AVAILABLE 0 +#endif + +#if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE + + +#import "ARA_Library/IPC/ARAIPC.h" + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +API_AVAILABLE_BEGIN(macos(13.0), ios(16.0)) + +//! optional delegate that can be provided in ARAIPCAUProxyPlugInInitialize() +typedef void (*ARAMainThreadWaitForMessageDelegate) (void * _Nullable delegateUserData); + +//! host side: initialize proxy plug-in component and its internal the message channels +//! will return nullptr if the Audio Unit does not implement [AUAudioUnit messageChannelFor:] +//! for the required ARA message channels +//! the provided audioUnit is only used to establish the channels, it can be closed again +//! after the call if not needed otherwise +//! must be balanced with ARAIPCAUProxyPlugInUninitialize() +//! the optional delegate will be called periodically when the IPC needs to block the main thread, +//! with the opaque user data pointer being passed back into the call +ARAIPCProxyPlugInRef _Nullable ARA_CALL ARAIPCAUProxyPlugInInitialize(AUAudioUnit * _Nonnull audioUnit, + ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate, + void * _Nullable delegateUserData); + +//! allows the host to let the plug-in perform ARA IPC on the main thread when otherwise +//! blocking it for an extended period of time +void ARA_CALL ARAIPCAUProxyPlugInPerformPendingMainThreadTasks(ARAIPCProxyPlugInRef _Nonnull proxyPlugInRef); + +//! host side: Audio Unit specialization of ARAIPCProxyPlugInBindToDocumentController() +//! must be balanced with ARAIPCProxyPlugInCleanupBinding() when the given audioUnit is destroyed +const ARAPlugInExtensionInstance * _Nonnull ARA_CALL ARAIPCAUProxyPlugInBindToDocumentController(AUAudioUnit * _Nonnull audioUnit, + ARADocumentControllerRef _Nonnull documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, + ARAPlugInInstanceRoleFlags assignedRoles); + +//! host side: uninitialize the proxy component set up in ARAIPCAUProxyPlugInInitialize() +void ARA_CALL ARAIPCAUProxyPlugInUninitialize(ARAIPCProxyPlugInRef _Nonnull proxyPlugInRef); + + + +//! plug-in side: implementation for AUAudioUnit messageChannelFor: +id _Nullable ARA_CALL ARAIPCAUProxyHostMessageChannelFor(NSString * _Nonnull channelName); + + +API_AVAILABLE_END + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +//! @} ARA_Library_IPC + +#endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE diff --git a/IPC/ARAIPCAudioUnit_v3.mm b/IPC/ARAIPCAudioUnit_v3.mm new file mode 100644 index 0000000..df23dc0 --- /dev/null +++ b/IPC/ARAIPCAudioUnit_v3.mm @@ -0,0 +1,591 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCAudioUnit_v3.m +//! Implementation of ARA IPC message sending through AUMessageChannel +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#import "ARAIPCAudioUnit_v3.h" + + +#if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE + + +#import "ARA_Library/Debug/ARADebug.h" +#import "ARA_Library/IPC/ARAIPCEncoding.h" +#import "ARA_Library/IPC/ARAIPCCFEncoding.h" +#import "ARA_Library/IPC/ARAIPCConnection.h" +#import "ARA_Library/IPC/ARAIPCProxyHost.h" +#import "ARA_Library/IPC/ARAIPCProxyPlugIn.h" +#if !ARA_ENABLE_IPC + #error "configuration mismatch: enabling ARA_AUDIOUNITV3_IPC_IS_AVAILABLE requires enabling ARA_ENABLE_IPC too" +#endif + +#import "CoreFoundation/CoreFoundation.h" +#import + +#include + +#if __cplusplus >= 202002L + #include +#else + // crude hack to use std::bit_width in C++17 + namespace std + { + constexpr size_t bit_width (size_t n) + { + return (n <= 1) ? n : 1 + bit_width (n / 2); + } + } +#endif + + +// JUCE hotfix: because JUCE directly includes the .cpp/.mm files from this SDK instead of properly +// compiling the ARA_IPC_Library, these switches allow for skipping the host- or the +// plug-in side of the code, depending on which side is being compiled. +#if !defined(ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY) + #define ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY 0 +#endif +#if !defined(ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY) + #define ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY 0 +#endif + + +namespace ARA { +namespace IPC { + + +_Pragma ("GCC diagnostic push") +_Pragma ("GCC diagnostic ignored \"-Wold-style-cast\"") // __bridge casts can only be done old-style +_Pragma ("GCC diagnostic ignored \"-Wnullability-completeness\"") // \todo add proper nullability annotation + + +API_AVAILABLE_BEGIN(macos(13.0), ios(16.0)) + + +// key to store message ID in XPC message +constexpr NSString * _messageIDKey { @"msgID" }; + + +// message channel base class for both proxy implementations +class AudioUnitMessageChannel : public MessageChannel +{ +public: + AudioUnitMessageChannel (NSObject * _Nonnull audioUnitChannel) + : _audioUnitChannel { audioUnitChannel } + { +#if !__has_feature(objc_arc) + [_audioUnitChannel retain]; +#endif + } + +#if !__has_feature(objc_arc) + ~AudioUnitMessageChannel () override + { + [_audioUnitChannel release]; + } +#endif + +public: + void routeReceivedDictionary (NSDictionary * _Nonnull message) + { + _currentRoutingThread = [NSThread currentThread]; + ARA_INTERNAL_ASSERT (![NSThread isMainThread]); + const MessageID messageID { [(NSNumber *) [message objectForKey:_messageIDKey] intValue] }; + auto decoder { std::make_unique ((__bridge CFDictionaryRef) message) }; + routeReceivedMessage (messageID, std::move (decoder)); + _currentRoutingThread = nil; + } + + void sendMessage (MessageID messageID, std::unique_ptr && encoder) override + { + const auto dictionary { static_cast (encoder.get ())->copyDictionary () }; +#if !__has_feature(objc_arc) + auto message { (__bridge NSMutableDictionary *) dictionary }; +#else + auto message { (__bridge_transfer NSMutableDictionary *) dictionary }; +#endif + [message setObject:[NSNumber numberWithInt: messageID] forKey:_messageIDKey]; + const auto reply { _sendMessage (message) }; + ARA_INTERNAL_ASSERT ([reply count] == 0); +#if !__has_feature(objc_arc) + CFRelease (dictionary); +#endif + } + + bool receivesMessagesOnCurrentThread () override + { + return _currentRoutingThread == [NSThread currentThread]; + } + + bool waitForMessageOnCurrentThread () override + { + return CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.01, true) != kCFRunLoopRunTimedOut; + } + +protected: + virtual NSDictionary * _sendMessage (NSDictionary * message) = 0; + +protected: + NSObject * __strong _Nonnull _audioUnitChannel; + NSThread * __strong _Nullable _currentRoutingThread { nil }; +}; + + +// plug-in side: proxy host message channel specialization +#if !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY +class ProxyHostMessageChannel : public AudioUnitMessageChannel +{ +public: + using AudioUnitMessageChannel::AudioUnitMessageChannel; + +protected: + NSDictionary * _sendMessage (NSDictionary * message) override + { + if (const auto callHostBlock { _audioUnitChannel.callHostBlock }) + { + NSDictionary * reply { callHostBlock (message) }; + return reply; + } + else + { + ARA_INTERNAL_ASSERT (false && "trying to send IPC message while host has not set callHostBlock"); + return nil; + } + } +}; +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY + + +// host side: proxy plug-in message channel specialization +#if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY +class ProxyPlugInMessageChannel : public AudioUnitMessageChannel +{ +public: + ProxyPlugInMessageChannel (NSObject * _Nonnull audioUnitChannel) + : AudioUnitMessageChannel { audioUnitChannel } + { + _audioUnitChannel.callHostBlock = + ^NSDictionary * _Nullable (NSDictionary * _Nonnull message) + { + routeReceivedDictionary (message); + return [NSDictionary dictionary]; // \todo it would yield better performance if the callHostBlock would allow nil as return value + }; + + // \todo macOS as of 14.3.1 does not forward the above assignment of callHostBlock to the + // remote side until the first message is sent. However, if another channel is assigned + // its (same) callHostBlock, then the pending callHostBlock for the first channel is + // lost for some reason. We need to send an empty dummy message to work around this bug, + // which them must be filtered before calling routeReceivedDictionary(). + [audioUnitChannel callAudioUnit:[NSDictionary dictionary]]; + } + + ~ProxyPlugInMessageChannel () override + { + _audioUnitChannel.callHostBlock = nil; + } + +protected: + NSDictionary * _sendMessage (NSDictionary * message) override + { + const auto reply { [_audioUnitChannel callAudioUnit:message] }; + return reply; + } +}; +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY + +// host side: proxy plug-in implementation +#if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY +class AUProxyPlugIn : public ProxyPlugIn +{ +public: + static AUProxyPlugIn* createWithAudioUnit (AUAudioUnit * _Nonnull audioUnit, + ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate, + void * _Nullable delegateUserData) + { + // AUAudioUnits created before macOS 13 will not know about this API yet + if (![audioUnit respondsToSelector:@selector(messageChannelFor:)]) + return nullptr; + + auto mainChannel { [audioUnit messageChannelFor:ARA_AUDIOUNIT_MAIN_THREAD_MESSAGES_UTI] }; + if (!mainChannel) + return nullptr; + + auto otherChannel { [audioUnit messageChannelFor:ARA_AUDIOUNIT_OTHER_THREADS_MESSAGES_UTI] }; + if (!otherChannel) + return nullptr; + + return new AUProxyPlugIn { static_cast*> (mainChannel), + static_cast*> (otherChannel), + audioUnit, waitForMessageDelegate, delegateUserData }; + } + + ~AUProxyPlugIn () + { +#if !__has_feature(objc_arc) + [_initAU release]; +#endif + } + +private: + AUProxyPlugIn (NSObject * _Nonnull mainChannel, + NSObject * _Nonnull otherChannel, + AUAudioUnit * _Nonnull initAU, + ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate, + void * _Nullable delegateUserData) + : ProxyPlugIn { std::make_unique (&CFMessageEncoder::create, + ProxyPlugIn::handleReceivedMessage, + true, // \todo shouldn't the AUMessageChannel provide this information? + (waitForMessageDelegate) ? + [waitForMessageDelegate, delegateUserData] { waitForMessageDelegate(delegateUserData); } : + Connection::WaitForMessageDelegate {}) }, + _initAU { initAU } + { + getConnection ()->setMainThreadChannel (std::make_unique (mainChannel)); + getConnection ()->setOtherThreadsChannel (std::make_unique (otherChannel)); +#if !__has_feature(objc_arc) + [_initAU retain]; +#endif + } + +private: + const AUAudioUnit * __strong _initAU; // workaround for macOS 14: keep the AU that vends the message channels alive, otherwise the channels will eventually stop working + // \todo once this is fixed in macOS, we only need to store this on older macOS versions +}; +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY + + +// host side: proxy plug-in C adapter +#if !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY + +ARA_MAP_IPC_REF (AUProxyPlugIn, ARAIPCProxyPlugInRef) + +extern "C" { + +ARAIPCProxyPlugInRef ARA_CALL ARAIPCAUProxyPlugInInitialize (AUAudioUnit * _Nonnull audioUnit, + ARAMainThreadWaitForMessageDelegate _Nullable waitForMessageDelegate, + void * _Nullable delegateUserData) +{ + return toIPCRef (AUProxyPlugIn::createWithAudioUnit (audioUnit, waitForMessageDelegate, delegateUserData)); +} + +void ARA_CALL ARAIPCAUProxyPlugInPerformPendingMainThreadTasks (ARAIPCProxyPlugInRef _Nonnull proxyPlugInRef) +{ + fromIPCRef (proxyPlugInRef)->getConnection ()->processPendingMessageOnCreationThreadIfNeeded (); +} + +const ARAPlugInExtensionInstance * _Nonnull ARA_CALL ARAIPCAUProxyPlugInBindToDocumentController (AUAudioUnit * _Nonnull audioUnit, + ARADocumentControllerRef _Nonnull documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, + ARAPlugInInstanceRoleFlags assignedRoles) +{ + static_assert (sizeof (AUAudioUnit *) == sizeof (NSUInteger), "opaque ref type size mismatch"); + auto remoteInstance { static_cast ([audioUnit valueForKey:@"araRemoteInstanceRef"]) }; + auto remoteInstanceRef { reinterpret_cast ([remoteInstance unsignedIntegerValue]) }; + const auto plugInExtensionInstance { ARAIPCProxyPlugInBindToDocumentController (remoteInstanceRef, documentControllerRef, knownRoles, assignedRoles) }; + return plugInExtensionInstance; +} + +void ARA_CALL ARAIPCAUProxyPlugInUninitialize (ARAIPCProxyPlugInRef _Nonnull proxyPlugInRef) +{ + delete fromIPCRef (proxyPlugInRef); +} + +} // extern "C" + +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_HOST_ONLY + + +// plug-in side: proxy host C adapter +#if !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY + +class AUProxyHost : public ProxyHost +{ +public: + AUProxyHost () + : ProxyHost { std::make_unique (&CFMessageEncoder::create, + [this] (auto&& ...args) { handleReceivedMessage (args...); }, + true) } // \todo shouldn't the AUMessageChannel provide this information? + { + ARAIPCProxyHostSetBindingHandler (handleBinding); + } + +private: + static const ARAPlugInExtensionInstance * ARA_CALL handleBinding (ARAIPCPlugInInstanceRef plugInInstanceRef, + ARADocumentControllerRef controllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles) + { + auto audioUnit { (__bridge AUAudioUnit *) plugInInstanceRef }; + return [audioUnit bindToDocumentController:controllerRef withRoles:assignedRoles knownRoles:knownRoles]; + } +}; + +AUProxyHost* _proxyHost; + +typedef ARA_IPC_REF(ARAIPCMessageChannelRef); +ARA_MAP_IPC_REF (AudioUnitMessageChannel, ARAIPCMessageChannelRef) + +static ARAIPCMessageChannelRef _Nullable initializeMessageChannel (NSObject * _Nonnull audioUnitChannel, + bool isMainThreadChannel) +{ + // \todo the connection currently stores the creation thread as main thread for Windows compatibility, + // so we need to make sure the proxy is created on the main thread + auto createProxyIfNeeded { + ^void () + { + if (!_proxyHost) + _proxyHost = new AUProxyHost {}; + }}; + + if ([NSThread isMainThread]) + createProxyIfNeeded (); + else + dispatch_sync (dispatch_get_main_queue (), createProxyIfNeeded); + + auto channel { std::make_unique (audioUnitChannel) }; + const auto result { toIPCRef (channel.get ()) }; + + if (isMainThreadChannel) + _proxyHost->getConnection ()->setMainThreadChannel (std::move (channel)); + else + _proxyHost->getConnection ()->setOtherThreadsChannel (std::move (channel)); + + return result; +} + +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY + + +// plug-in side: NSObject implementation +#if !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY + + +/* +The following code implements the below class at runtime to work around potential ObjC class name conflicts + +@interface ARAIPCAUMessageChannelImpl : NSObject +@end + +@implementation ARAIPCAUMessageChannel { + ARA::IPC::ARAIPCMessageChannelRef _messageChannelRef; +} + +@synthesize callHostBlock = _callHostBlock; + +- (instancetype)initAsMainThreadChannel:(BOOL) isMainThreadChannel { + self = [super init]; + + if (self == nil) { return nil; } + + _callHostBlock = nil; + _messageChannelRef = initializeMessageChannel (self, isMainThreadChannel); + + return self; +} + +- (NSDictionary * _Nonnull)callAudioUnit:(NSDictionary *)message { + if ([message count]) + static_cast (fromIPCRef (_messageChannelRef))->routeReceivedDictionary (message); + return [NSDictionary dictionary]; +} + +@end +*/ + +// helper to add ivars to an ObjC class +template +void addObjCIVar (Class cls, const char * name) +{ + constexpr auto size { sizeof (T) }; + constexpr auto alignment { static_cast (std::bit_width (size)) }; + [[maybe_unused]] const auto success = class_addIvar (cls, name, size, alignment, @encode (T)); + ARA_INTERNAL_ASSERT (success); +} + +// helpers to get/set a scalar ivar of an ObjC object +template +inline T getScalarIVar (id self, Ivar ivar) +{ + const auto offset { ivar_getOffset (ivar) }; + const auto bytes = static_cast ((__bridge void *) self); + return *reinterpret_cast (bytes + offset); +} + +template +inline void setScalarIVar (id self, Ivar ivar, T value) +{ + const auto offset { ivar_getOffset (ivar) }; + auto bytes = static_cast ((__bridge void *) self); + *reinterpret_cast (bytes + offset) = value; +} + + +// optimization of object_getInstanceVariable: cache the ivars of our class +Ivar messageChannelRefIvar { nullptr }; +Ivar callHostBlockIvar { nullptr }; + + +// methods of our class +extern "C" { + +static CallHostBlock callHostBlock_getter (id self, SEL /*_cmd*/) +{ + return object_getIvar (self, callHostBlockIvar); +} + +static void callHostBlock_setter (id self, SEL /*_cmd*/, CallHostBlock newBlock) +{ + id oldBlock { object_getIvar (self, callHostBlockIvar) }; + if (oldBlock != newBlock) + { + if (oldBlock) + Block_release ((__bridge void *)oldBlock); + if (newBlock) + newBlock = (__bridge CallHostBlock) Block_copy ((__bridge void *)newBlock); + object_setIvar (self, callHostBlockIvar, newBlock); + } +} + +static NSDictionary * _Nonnull callAudioUnit_impl (id self, SEL /*_cmd*/, NSDictionary * message) +{ + const auto messageChannelRef { getScalarIVar (self, messageChannelRefIvar) }; + if ([message count]) // \todo filter dummy message sent in ProxyPlugInMessageChannel, see there + static_cast (fromIPCRef (messageChannelRef))->routeReceivedDictionary (message); + return [NSDictionary dictionary]; // \todo it would yield better performance if -callAudioUnit: would allow nil as return value +} + +} // extern "C" + + +// creation of the ARAIPCAUMessageChannelImpl Class +static Class createMessageChannelObjCClass () +{ + // create a unique class name by appending a unique address to the base name + std::string className { "ARAIPCAUMessageChannelImpl" + std::to_string (reinterpret_cast (&messageChannelRefIvar)) }; + ARA_INTERNAL_ASSERT (!objc_getClass (className.c_str ())); + + // allocate class + Class cls { objc_allocateClassPair ([NSObject class], className.c_str (), 0) }; + ARA_INTERNAL_ASSERT (cls); + + // add iVars + addObjCIVar (cls, "_messageChannelRef"); + addObjCIVar (cls, "_callHostBlock"); + + // add properties + objc_property_attribute_t attr[] { { "T", "@?" }, // type block + { "C", "" }, // C = copy + { "V", "_callHostBlock" } }; // backing i-var + [[maybe_unused]] auto success { class_addProperty (cls, "callHostBlock", attr, sizeof (attr) / sizeof (attr[0])) }; + + // add getters/setters + success = class_addMethod (cls, @selector(callHostBlock), (IMP)callHostBlock_getter, "@@:"); + ARA_INTERNAL_ASSERT (success); + class_addMethod (cls, @selector(setCallHostBlock:), (IMP)callHostBlock_setter, "v@:@"); + ARA_INTERNAL_ASSERT (success); + + // add methods + class_addMethod (cls, @selector(callAudioUnit:), (IMP)callAudioUnit_impl, "@@:@"); + ARA_INTERNAL_ASSERT (success); + + // declare conformance to + const auto messageChannelProtocol { objc_getProtocol ("AUMessageChannel") }; + ARA_INTERNAL_ASSERT (messageChannelProtocol); + success = class_addProtocol (cls, (Protocol * _Nonnull)messageChannelProtocol); + ARA_INTERNAL_ASSERT (success); + + // activate class + objc_registerClassPair (cls); + + // update cached ivar ptrs after activation + messageChannelRefIvar = class_getInstanceVariable (cls, "_messageChannelRef"); + ARA_INTERNAL_ASSERT (messageChannelRefIvar); + callHostBlockIvar = class_getInstanceVariable (cls, "_callHostBlock"); + ARA_INTERNAL_ASSERT (callHostBlockIvar); + + return cls; +} + +// creation and designated initialisation of instances of ARAIPCAUMessageChannelImpl Class +static NSObject * createAndInitMessageChannel (Class cls, bool isMainThreadChannel) +{ + NSObject * obj { class_createInstance (cls, 0) }; + ARA_INTERNAL_ASSERT (obj); + ARA_INTERNAL_ASSERT ([obj conformsToProtocol:@protocol(AUMessageChannel)]); + obj = [obj init]; + ARA_INTERNAL_ASSERT (obj); + + object_setIvar (obj, callHostBlockIvar, nil); + + const auto messageChannelRef { initializeMessageChannel (obj, isMainThreadChannel) }; + setScalarIVar (obj, messageChannelRefIvar, messageChannelRef); + + return obj; +} + + +NSObject * __strong _mainMessageChannel { nil }; +NSObject * __strong _otherMessageChannel { nil }; + +static void createSharedMessageChannels () +{ + ARA_INTERNAL_ASSERT(!_mainMessageChannel && !_otherMessageChannel); + + const auto cls { createMessageChannelObjCClass () }; + _mainMessageChannel = createAndInitMessageChannel (cls, true); + _otherMessageChannel = createAndInitMessageChannel (cls, false); +} + +extern "C" id _Nullable ARA_CALL ARAIPCAUProxyHostMessageChannelFor (NSString * _Nonnull channelName) +{ + if ([channelName isEqualToString:ARA_AUDIOUNIT_MAIN_THREAD_MESSAGES_UTI]) + { + if (!_mainMessageChannel) + createSharedMessageChannels (); + return _mainMessageChannel; + } + else if ([channelName isEqualToString:ARA_AUDIOUNIT_OTHER_THREADS_MESSAGES_UTI]) + { + if (!_otherMessageChannel) + createSharedMessageChannels (); + return _otherMessageChannel; + } + return nil; +} + +API_AVAILABLE_END + +__attribute__((destructor)) +static void destroySharedMessageChannelsIfNeeded () +{ + if (@available(macOS 13.0, iOS 16.0, *)) + { + if (_proxyHost) + delete _proxyHost; + + _mainMessageChannel = nil; + _otherMessageChannel = nil; + } +} + +#endif // !ARA_AUDIOUNITV3_IPC_PROXY_PLUGIN_ONLY + +_Pragma ("GCC diagnostic pop") + + +} // namespace IPC +} // namespace ARA + + +#endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE diff --git a/IPC/ARAIPCCFEncoding.cpp b/IPC/ARAIPCCFEncoding.cpp new file mode 100644 index 0000000..c33736c --- /dev/null +++ b/IPC/ARAIPCCFEncoding.cpp @@ -0,0 +1,326 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCCFEncoding.cpp +//! Implementation of ARAIPCMessageEn-/Decoder backed by CF(Mutable)Dictionary +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#include "ARAIPCCFEncoding.h" + + +#if ARA_ENABLE_IPC && defined (__APPLE__) + + +#include "ARA_Library/Debug/ARADebug.h" + +#include +#include +#include + + +namespace ARA { +namespace IPC { + + +#if defined (__GNUC__) + _Pragma ("GCC diagnostic push") + _Pragma ("GCC diagnostic ignored \"-Wold-style-cast\"") +#endif + + +// helper class to deal with CoreFoundation reference counting +class _CFReleaser +{ +public: + explicit _CFReleaser (CFStringRef ref) : _ref { ref } {} + _CFReleaser (const _CFReleaser& other) { _ref = (CFStringRef) CFRetain (other._ref); } + _CFReleaser (_CFReleaser&& other) { _ref = other._ref; other._ref = CFStringRef {}; } + ~_CFReleaser () { CFRelease (_ref); } + operator CFStringRef () { return _ref; } +private: + CFStringRef _ref; +}; + + +// wrap key value into CFString (no reference count transferred to caller) +static CFStringRef _getEncodedKey (MessageArgumentKey argKey) +{ + // \todo All plist formats available for CFPropertyListCreateData () in createEncodedMessage () need CFString keys. + // Once we switch to the more modern (NS)XPC API we shall be able to use CFNumber keys directly... + static std::map cache; + auto existingEntry { cache.find (argKey) }; + if (existingEntry != cache.end ()) + return existingEntry->second; + return cache.emplace (argKey, CFStringCreateWithCString (kCFAllocatorDefault, std::to_string (argKey).c_str (), kCFStringEncodingUTF8)).first->second; +} + + + +CFMessageEncoder::CFMessageEncoder () +: _dictionary { CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) } +{} + +CFMessageEncoder::~CFMessageEncoder () +{ + CFRelease (_dictionary); +} + +void CFMessageEncoder::appendInt32 (MessageArgumentKey argKey, int32_t argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &argValue) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendInt64 (MessageArgumentKey argKey, int64_t argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &argValue) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendSize (MessageArgumentKey argKey, size_t argValue) +{ + static_assert (sizeof (SInt64) == sizeof (size_t), "integer type needs adjustment for this compiler setup"); + + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &argValue) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendFloat (MessageArgumentKey argKey, float argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberFloatType, &argValue) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendDouble (MessageArgumentKey argKey, double argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberDoubleType, &argValue) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendString (MessageArgumentKey argKey, const char * argValue) +{ + auto argObject { CFStringCreateWithCString (kCFAllocatorDefault, argValue, kCFStringEncodingUTF8) }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void CFMessageEncoder::appendBytes (MessageArgumentKey argKey, const uint8_t * argValue, size_t argSize, bool copy) +{ + CFDataRef argObject; + if (copy) + argObject = CFDataCreate (kCFAllocatorDefault, argValue, (CFIndex) argSize); + else + argObject = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, argValue, (CFIndex) argSize, kCFAllocatorNull); + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +std::unique_ptr CFMessageEncoder::appendSubMessage (MessageArgumentKey argKey) +{ + auto argObject { CFMessageEncoder::create () }; + CFDictionarySetValue (_dictionary, _getEncodedKey (argKey), argObject->_dictionary); + return argObject; +} + +__attribute__((cf_returns_retained)) CFMutableDictionaryRef CFMessageEncoder::copyDictionary () const +{ + CFRetain (_dictionary); + return _dictionary; +} + +__attribute__((cf_returns_retained)) CFDataRef CFMessageEncoder::createMessageEncoderData () const +{ + if (CFDictionaryGetCount (_dictionary) == 0) + return nullptr; + return CFPropertyListCreateData (kCFAllocatorDefault, _dictionary, kCFPropertyListBinaryFormat_v1_0, 0, nullptr); +} + + +CFMessageDecoder::CFMessageDecoder (CFDictionaryRef dictionary, bool retain) +: _dictionary { dictionary } +{ + if (dictionary && retain) + CFRetain (dictionary); +} + +CFMessageDecoder::~CFMessageDecoder () +{ + if (_dictionary) + CFRelease (_dictionary); +} + +bool CFMessageDecoder::readInt32 (MessageArgumentKey argKey, int32_t* argValue) const +{ + CFNumberRef number {}; + if (_dictionary) + number = (CFNumberRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt32Type, argValue); + return true; +} + +bool CFMessageDecoder::readInt64 (MessageArgumentKey argKey, int64_t* argValue) const +{ + CFNumberRef number {}; + if (_dictionary) + number = (CFNumberRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt64Type, argValue); + return true; +} + +bool CFMessageDecoder::readSize (MessageArgumentKey argKey, size_t* argValue) const +{ + static_assert (sizeof (SInt64) == sizeof (size_t), "integer type needs adjustment for this compiler setup"); + + CFNumberRef number {}; + if (_dictionary) + number = (CFNumberRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt64Type, argValue); + return true; +} + +bool CFMessageDecoder::readFloat (MessageArgumentKey argKey, float* argValue) const +{ + CFNumberRef number {}; + if (_dictionary) + number = (CFNumberRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!number) + { + *argValue = 0.0f; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberFloatType, argValue); + return true; +} + +bool CFMessageDecoder::readDouble (MessageArgumentKey argKey, double* argValue) const +{ + CFNumberRef number {}; + if (_dictionary) + number = (CFNumberRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!number) + { + *argValue = 0.0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberDoubleType, argValue); + return true; +} + +bool CFMessageDecoder::readString (MessageArgumentKey argKey, const char ** argValue) const +{ + CFStringRef string {}; + if (_dictionary) + string = (CFStringRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!string) + { + *argValue = nullptr; + return false; + } + ARA_INTERNAL_ASSERT (string && (CFGetTypeID (string) == CFStringGetTypeID ())); + *argValue = CFStringGetCStringPtr (string, kCFStringEncodingUTF8); + if (!*argValue) // CFStringGetCStringPtr() may fail e.g. with chord names like "G/D" + { + const auto length { CFStringGetLength (string) }; + std::string temp; // \todo does not work: { static_cast (length), char { 0 } }; + temp.assign (static_cast (length) , char { 0 }); + [[maybe_unused]] const auto count { CFStringGetBytes (string, CFRangeMake (0, length), kCFStringEncodingUTF8, 0, false, (UInt8*)(&temp[0]), length, nullptr) }; + ARA_INTERNAL_ASSERT (count == length); + static std::set strings; // \todo static cache of "undecodeable" strings requires single-threaded use - maybe make iVar instead? + strings.insert (temp); + *argValue = strings.find (temp)->c_str (); + } + return true; +} + +bool CFMessageDecoder::readBytesSize (MessageArgumentKey argKey, size_t* argSize) const +{ + CFDataRef bytes {}; + if (_dictionary) + bytes = (CFDataRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)); + if (!bytes) + { + *argSize = 0; + return false; + } + ARA_INTERNAL_ASSERT (bytes && (CFGetTypeID (bytes) == CFDataGetTypeID ())); + *argSize = (size_t) CFDataGetLength (bytes); + return true; +} + +void CFMessageDecoder::readBytes (MessageArgumentKey argKey, uint8_t* argValue) const +{ + ARA_INTERNAL_ASSERT (_dictionary); + auto bytes { (CFDataRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)) }; + ARA_INTERNAL_ASSERT (bytes && (CFGetTypeID (bytes) == CFDataGetTypeID ())); + const auto length { CFDataGetLength (bytes) }; + CFDataGetBytes (bytes, CFRangeMake (0, length), argValue); +} + +std::unique_ptr CFMessageDecoder::readSubMessage (MessageArgumentKey argKey) const +{ + auto dictionary { (CFDictionaryRef) CFDictionaryGetValue (_dictionary, _getEncodedKey (argKey)) }; + ARA_INTERNAL_ASSERT (!dictionary || (CFGetTypeID (dictionary) == CFDictionaryGetTypeID ())); + if (dictionary == nullptr) + return nullptr; + return std::make_unique (dictionary); +} + +bool CFMessageDecoder::hasDataForKey (MessageArgumentKey argKey) const +{ + return CFDictionaryContainsKey (_dictionary, _getEncodedKey (argKey)); +} + +std::unique_ptr CFMessageDecoder::createWithMessageData (CFDataRef messageData) +{ + if (CFDataGetLength (messageData) == 0) + return nullptr; + + auto dictionary { (CFDictionaryRef) CFPropertyListCreateWithData (kCFAllocatorDefault, messageData, kCFPropertyListImmutable, nullptr, nullptr) }; + ARA_INTERNAL_ASSERT (dictionary && (CFGetTypeID (dictionary) == CFDictionaryGetTypeID ())); + return std::make_unique (dictionary, false); +} + +#if defined (__APPLE__) + _Pragma ("GCC diagnostic pop") +#endif + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC && defined (__APPLE__) diff --git a/IPC/ARAIPCCFEncoding.h b/IPC/ARAIPCCFEncoding.h new file mode 100644 index 0000000..7a9e94b --- /dev/null +++ b/IPC/ARAIPCCFEncoding.h @@ -0,0 +1,97 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCCFEncoding.h +//! Implementation of ARAIPCMessageEn-/Decoder backed by CF(Mutable)Dictionary +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCCFEncoding_h +#define ARAIPCCFEncoding_h + +#include "ARA_Library/IPC/ARAIPCMessage.h" + +#if ARA_ENABLE_IPC + + +#include +#include + + +//! @addtogroup ARA_Library_IPC +//! @{ + + +namespace ARA { +namespace IPC { + + +class CFMessageEncoder : public MessageEncoder +{ +public: + ~CFMessageEncoder () override; + + void appendInt32 (MessageArgumentKey argKey, int32_t argValue) override; + void appendInt64 (MessageArgumentKey argKey, int64_t argValue) override; + void appendSize (MessageArgumentKey argKey, size_t argValue) override; + void appendFloat (MessageArgumentKey argKey, float argValue) override; + void appendDouble (MessageArgumentKey argKey, double argValue) override; + void appendString (MessageArgumentKey argKey, const char * argValue) override; + void appendBytes (MessageArgumentKey argKey, const uint8_t * argValue, size_t argSize, bool copy) override; + std::unique_ptr appendSubMessage (MessageArgumentKey argKey) override; + + __attribute__((cf_returns_retained)) CFMutableDictionaryRef copyDictionary () const; + __attribute__((cf_returns_retained)) CFDataRef createMessageEncoderData () const; + + static std::unique_ptr create () { return std::unique_ptr { new CFMessageEncoder }; } + +private: + CFMessageEncoder (); + +private: + CFMutableDictionaryRef const _dictionary; +}; + + +class CFMessageDecoder : public MessageDecoder +{ +public: + CFMessageDecoder (CFDictionaryRef dictionary, bool retain = true); + ~CFMessageDecoder () override; + + bool readInt32 (MessageArgumentKey argKey, int32_t* argValue) const override; + bool readInt64 (MessageArgumentKey argKey, int64_t* argValue) const override; + bool readSize (MessageArgumentKey argKey, size_t* argValue) const override; + bool readFloat (MessageArgumentKey argKey, float* argValue) const override; + bool readDouble (MessageArgumentKey argKey, double* argValue) const override; + bool readString (MessageArgumentKey argKey, const char ** argValue) const override; + bool readBytesSize (MessageArgumentKey argKey, size_t* argSize) const override; + void readBytes (MessageArgumentKey argKey, uint8_t* argValue) const override; + std::unique_ptr readSubMessage (MessageArgumentKey argKey) const override; + bool hasDataForKey (MessageArgumentKey argKey) const override; + + static std::unique_ptr createWithMessageData (CFDataRef messageData); + +private: + CFDictionaryRef const _dictionary; +}; + + +} // namespace IPC +} // namespace ARA + +//! @} ARA_Library_IPC + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCCFEncoding_h diff --git a/IPC/ARAIPCConnection.cpp b/IPC/ARAIPCConnection.cpp new file mode 100644 index 0000000..aafc5a7 --- /dev/null +++ b/IPC/ARAIPCConnection.cpp @@ -0,0 +1,845 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCConnection.cpp +//! Connection to another process to pass messages to/receive messages from it +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#include "ARAIPCConnection.h" + + +#if ARA_ENABLE_IPC + + +#include "ARA_Library/Debug/ARADebug.h" +#include "ARA_Library/IPC/ARAIPCEncoding.h" + +#include +#include +#if __cplusplus >= 202002L + #include + #include +#endif + +#if defined (__APPLE__) + #include +#endif + + +#if ARA_ENABLE_DEBUG_OUTPUT && 0 + #define ARA_IPC_LOG(...) ARA_LOG ("ARA IPC " __VA_ARGS__) + #define ARA_IPC_DECODE_MESSAGE_FORMAT " %i=%s " + #define ARA_IPC_DECODE_SENT_MESSAGE_ARGS(messageID) messageID, (_debugAsHost) ? decodeHostMessageID (messageID) : decodePlugInMessageID (messageID) + #define ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS(messageID) messageID, (!_debugAsHost) ? decodeHostMessageID (messageID) : decodePlugInMessageID (messageID) + #define ARA_IPC_LABEL_THREAD_FORMAT " %sthread %p" + #define ARA_IPC_LABEL_THREAD_ARGS (getConnection ()->wasCreatedOnCurrentThread ()) ? "creation " : "other ", MessageDispatcher::getCurrentThread () +#else + #define ARA_IPC_LOG(...) ((void) 0) +#endif + + +// ARA IPC design overview +// +// ARA API calls can be stacked multiple times. +// For example, when the plug-in has completed analyzing an audio source, it will +// inform the host about the updated content information the next time the host +// calls notifyModelUpdates() by invoking notifyAudioSourceContentChanged(). +// From that method, the host now queries isAudioSourceContentAvailable() for the +// relevant content types and calls createAudioSourceContentReader() etc. to read +// the relevant content data. +// Such a stack of multiple messages is referred to as a "transaction" here. +// +// However, many IPC APIs including Audio Unit message channels cannot be stacked, +// i.e. each message has to completely be processed before the next one can be sent. +// Therefore, each ARA API calls needs to be split into two IPC messages: +// the actual call message that does not return any result just yet, and a matching +// reply message that returns the result (even if it is void, because the caller +// needs to know when the call has completed). +// After sending the actual call message, the sender loops while listening for +// incoming messages, which might either be a stacked callback that is processed +// accordingly, or the reply message which ends the loop. +// +// In this loop, extra efforts might be necessary to handle the IPC threading: +// When e.g. using Audio Unit AUMessageChannel, Grand Central Dispatch will deliver +// the incoming IPC messages on some undefined GDC worker thread, requiring a dispatch +// from the receiving thread to the target thread. This is implemented via +// a condition variable that the receive thread awakes when a message comes in. +// Instead of looping, a sending thread will wait on this condition for the reply. +// +// While most ARA communication is happening on the main thread, there are +// several calls that may be made from other threads. This poses several challenges +// when tunnelling everything through a single(threaded) IPC channel. +// +// First of all, there needs to be some locking mechanism around actually accessing +// the IPC. This adds a dependency between previously independent threads, so both +// hosts and plug-ins will need to carefully evaluate their existing ARA code to check +// for potential deadlocks or priority inversion caused by this. +// To reduce the potential impact of this, the implementation uses two IPC channels +// in parallel: one for all main thread communication (which can work lockless) and +// another one that is used for all other threads. +// +// Further, calls that are using IPC are no longer realtime safe. This means that calls +// like getPlaybackRegionHeadAndTailTime(), which could previously be executed on render +// threads, now need to be moved to other threads. Accordingly, hosts need to cache data +// like the head and tail times from the main thread and update them there whenever +// notifyPlaybackRegionContentChanged() is received. +// +// Another challenge when injecting IPC into the ARA communication is that for each +// message that comes in, an appropriate thread has to be selected to process it. +// When a new transaction is initiated, the implementation checks whether this is happening +// on the main thread or on any other thread, and chooses the appropriate channel accordingly. +// On the receiving side, calls coming in on the main thread channel are forwarded to the main +// thread (unless the receive code already runs there), and for the other threads the code is +// executed directly on the receiving thread. +// Because there's no mechanism to synchronize thread priorities between the host and the +// plug-in for the non-main-threads, plug-ins will no longer be able to rely on their thread +// priority configuration with respect to ARA calls. For this reason, it is recommended to +// restrict these calls to audio sample reading, and order these explicitly before making them. +// +// Replies or callbacks made when processing an IPC call are routed back to the originating +// thread in the sender. For non-main-thread communication, this is done by adding a token when +// sending a message which identifies the sending thread, and this token passed back alongside +// the reply or stacked callback to allow for proper dispatching from the IPC receive thread to +// the thread that initiated the transaction. +// Since the actual ARA code is agnostic to IPC being used, the receiving side uses +// thread local storage to make the sender's thread token available for all stacked calls +// that the ARA code might make in response to the message. +// +// Note that there is a crucial difference between the dispatch of a new transaction and +// the dispatch of any follow-up replies or callbacks in the transaction: the initial message +// is dispatched to a thread that is potentially executing other code as well in some form of +// run loop, whereas the follow-ups will be dispatched to a thread that is currently blocking +// inside ARA code. + + +namespace ARA { +namespace IPC { + + +#if defined (_WIN32) && !defined (__WINE__) + #define readThreadRef readInt32 + #define appendThreadRef appendInt32 +#else + #define readThreadRef readSize + #define appendThreadRef appendSize +#endif + +static bool _debugAsHost {}; + +//------------------------------------------------------------------------------ + +class MessageDispatcher +{ +public: // needs to be public for thread-local variables (which cannot be class members) +#if defined (_WIN32) && !defined (__WINE__) + using ThreadRef = int32_t; +#else + // Apple, Linux, and Wine: std::thread::id is pointer-sized. + using ThreadRef = size_t; +#endif + static constexpr ThreadRef _invalidThread { 0 }; + +public: + explicit MessageDispatcher (Connection* connection, std::unique_ptr && messageChannel); + +// protected: this does not work with thread_local... + struct PendingReplyHandler + { + ReplyHandler* const _replyHandler; + const PendingReplyHandler* _prevPendingReplyHandler; + }; + + static bool isReply (MessageID messageID) { return messageID == 0; } + + static ThreadRef getCurrentThread (); + +protected: + Connection* getConnection () const { return _connection; } + MessageChannel* getMessageChannel () const { return _messageChannel.get (); } + + void _sendMessage (MessageID messageID, std::unique_ptr && encoder, bool isNewTransaction); + bool _waitForMessage (); + void _handleReply (std::unique_ptr && decoder, ReplyHandler && replyHandler); + std::unique_ptr _handleReceivedMessage (MessageID messageID, std::unique_ptr && decoder); + +private: + Connection* const _connection; + const std::unique_ptr _messageChannel; +}; + + +MessageDispatcher::MessageDispatcher (Connection* connection, std::unique_ptr && messageChannel) +: _connection { connection }, + _messageChannel { std::move (messageChannel) } +{ + static_assert (sizeof (std::thread::id) == sizeof (ThreadRef), "the current implementation relies on a specific thread ID size"); +// unfortunately at least in clang std::thread::id's c'tor isn't constexpr +// static_assert (std::thread::id {} == *reinterpret_cast(&_invalidThread), "the current implementation relies on invalid thread IDs being 0"); + ARA_INTERNAL_ASSERT (std::thread::id {} == *reinterpret_cast(&_invalidThread)); +} + +MessageDispatcher::ThreadRef MessageDispatcher::getCurrentThread () +{ + const auto thisThread { std::this_thread::get_id () }; + const auto result { *reinterpret_cast (&thisThread) }; + ARA_INTERNAL_ASSERT (result != _invalidThread); + return result; +} + +void MessageDispatcher::_sendMessage (MessageID messageID, std::unique_ptr && encoder, [[maybe_unused]] bool isNewTransaction) +{ + if (isReply (messageID)) + ARA_IPC_LOG ("replies to message on" ARA_IPC_LABEL_THREAD_FORMAT, ARA_IPC_LABEL_THREAD_ARGS); + else + ARA_IPC_LOG ("sends" ARA_IPC_DECODE_MESSAGE_FORMAT "on" ARA_IPC_LABEL_THREAD_FORMAT "%s", + ARA_IPC_DECODE_SENT_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS, + (isNewTransaction) ? " (new transaction)" : ""); + + _messageChannel->sendMessage (messageID, std::move (encoder)); +} + +void MessageDispatcher::_handleReply (std::unique_ptr && decoder, ReplyHandler && replyHandler) +{ + ARA_IPC_LOG ("handles reply on" ARA_IPC_LABEL_THREAD_FORMAT, ARA_IPC_LABEL_THREAD_ARGS); + if (replyHandler) + replyHandler (decoder.get ()); + else + ARA_INTERNAL_ASSERT (!decoder || !decoder->hasDataForKey (0)); // replies should be empty when not handled (i.e. void functions) +} + +std::unique_ptr MessageDispatcher::_handleReceivedMessage (MessageID messageID, std::unique_ptr && decoder) +{ + ARA_INTERNAL_ASSERT (!isReply (messageID)); + + ARA_IPC_LOG ("handles" ARA_IPC_DECODE_MESSAGE_FORMAT "on" ARA_IPC_LABEL_THREAD_FORMAT, + ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS); + auto replyEncoder { _connection->createEncoder () }; + _connection->getMessageHandler () (messageID, decoder.get (), replyEncoder.get ()); + + return replyEncoder; +} + +//------------------------------------------------------------------------------ + +// helper for MainThreadMessageDispatcher: single-object message queue with semaphore to wait on +// Wine: std::binary_semaphore's try_acquire_for hangs (broken futex mapping). +// Use POSIX sem_t with sem_timedwait instead. +#if defined (__WINE__) +#include +#include +class WaitableSingleMessageQueue +{ + public: + WaitableSingleMessageQueue () { sem_init (&_sem, 0, 0); } + ~WaitableSingleMessageQueue () { sem_destroy (&_sem); } + + std::optional>> waitOnSemaphore (ARATimeDuration timeout) + { + bool didReceiveMessage; + if (timeout <= 0.0) + { + didReceiveMessage = (sem_trywait (&_sem) == 0); + } + else + { + struct timespec ts {}; + clock_gettime (CLOCK_REALTIME, &ts); + long long ns = static_cast (timeout * 1e9); + ts.tv_sec += ns / 1000000000LL; + ts.tv_nsec += ns % 1000000000LL; + if (ts.tv_nsec >= 1000000000LL) { ts.tv_sec++; ts.tv_nsec -= 1000000000LL; } + didReceiveMessage = (sem_timedwait (&_sem, &ts) == 0); + } + if (didReceiveMessage) + { + const auto messageID { _pendingMessageID }; + const auto messageDecoder { _pendingMessageDecoder.load (std::memory_order_acquire) }; + return std::make_pair (messageID, std::unique_ptr (messageDecoder)); + } + return {}; + } + + void signalSemaphore (MessageID messageID, std::unique_ptr && decoder) + { + _pendingMessageID = messageID; + _pendingMessageDecoder.store (decoder.release (), std::memory_order_release); + sem_post (&_sem); + } + + private: + MessageID _pendingMessageID { 0 }; + std::atomic _pendingMessageDecoder { nullptr }; + sem_t _sem; +}; +#else +class WaitableSingleMessageQueue +{ + public: + WaitableSingleMessageQueue () +#if __cplusplus >= 202002L + : _waitForMessageSemaphore { new std::binary_semaphore { 0 } } +#elif defined (_WIN32) + : _waitForMessageSemaphore { ::CreateSemaphore (nullptr, 0, LONG_MAX, nullptr) } +#elif defined (__APPLE__) + : _waitForMessageSemaphore { dispatch_semaphore_create (0) } +#else +#error "IPC not yet implemented for this platform" +#endif + {} + + ~WaitableSingleMessageQueue () + { +#if __cplusplus >= 202002L + delete static_cast (_waitForMessageSemaphore); +#elif defined (_WIN32) + ::CloseHandle (_waitForMessageSemaphore); +#elif defined (__APPLE__) + dispatch_release (static_cast (_waitForMessageSemaphore)); +#else +#error "IPC not yet implemented for this platform" +#endif + } + + std::optional>> waitOnSemaphore (ARATimeDuration timeout) + { + bool didReceiveMessage; +#if __cplusplus >= 202002L + didReceiveMessage = static_cast (_waitForMessageSemaphore)->try_acquire_for (std::chrono::duration { timeout }); +#elif defined (_WIN32) + didReceiveMessage = (::WaitForSingleObject (_waitForMessageSemaphore, static_cast (timeout * 1000.0 + 0.5)) == WAIT_OBJECT_0); +#elif defined (__APPLE__) + const auto deadline { dispatch_time (DISPATCH_TIME_NOW, static_cast (10e9 * timeout + 0.5)) }; + didReceiveMessage = (dispatch_semaphore_wait (static_cast (_waitForMessageSemaphore), deadline) == 0); +#else +#error "IPC not yet implemented for this platform" +#endif + if (didReceiveMessage) + { + const auto messageID { _pendingMessageID }; + const auto messageDecoder { _pendingMessageDecoder.load (std::memory_order_acquire) }; + return std::make_pair (messageID, std::unique_ptr (messageDecoder)); + } + else + { + return {}; + } + } + + void signalSemaphore (MessageID messageID, std::unique_ptr && decoder) + { + _pendingMessageID = messageID; + _pendingMessageDecoder.store (decoder.release (), std::memory_order_release); +#if __cplusplus >= 202002L + static_cast (_waitForMessageSemaphore)->release (); +#elif defined (_WIN32) + ::ReleaseSemaphore (_waitForMessageSemaphore, 1, nullptr); +#elif defined (__APPLE__) + dispatch_semaphore_signal (static_cast (_waitForMessageSemaphore)); +#else +#error "IPC not yet implemented for this platform" +#endif + } + + private: + MessageID _pendingMessageID { 0 }; // read/write _pendingMessageDecoder with proper barrier before/after reading/writing this + std::atomic _pendingMessageDecoder { nullptr }; + void* const _waitForMessageSemaphore; // concrete type is platform-dependent +}; +#endif // !defined(__WINE__) — close the #else block for the original WaitableSingleMessageQueue + +//------------------------------------------------------------------------------ + +// single-threaded variant for main thread communication only +class MainThreadMessageDispatcher : public MessageDispatcher +{ +public: + MainThreadMessageDispatcher (Connection* connection, std::unique_ptr && messageChannel) + : MessageDispatcher { connection, std::move (messageChannel) } + { + getMessageChannel ()->_receivedMessageRouter = [this] (MessageID messageID, std::unique_ptr && decoder) + { routeReceivedMessage (messageID, std::move (decoder)); }; + } + + void sendMessage (MessageID messageID, std::unique_ptr && encoder, ReplyHandler && replyHandler); + + void routeReceivedMessage (MessageID messageID, std::unique_ptr && decoder); + + void processPendingMessageIfNeeded () + { + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + auto receivedData { _messageQueue.waitOnSemaphore (0.0) }; + if (receivedData) + processMessage (receivedData->first, std::move (receivedData->second)); + } + +protected: + void processMessage (MessageID messageID, std::unique_ptr && decoder); + +private: + // key to indicate whether an outgoing call is made in response (reply or callback) to a + // currently handled incoming call, or a new call + // This distinction is necessary to deal with the decoupled main threads concurrency. + // To optimize for performance, it is only added when the call is a response, while being a + // new call is implicit. It is also not added to replies because they always are a response. + static constexpr MessageArgumentKey kIsResponseKey { -1 }; + +private: + int32_t _processingMessagesCount { 0 }; + + const PendingReplyHandler* _pendingReplyHandler { nullptr }; + + WaitableSingleMessageQueue _messageQueue {}; +}; + + +void MainThreadMessageDispatcher::sendMessage (MessageID messageID, std::unique_ptr && encoder, + ReplyHandler && replyHandler) +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (!isReply (messageID)); + + const auto previousPendingReplyHandler { _pendingReplyHandler }; + const PendingReplyHandler pendingReplyHandler { &replyHandler, previousPendingReplyHandler }; + _pendingReplyHandler = &pendingReplyHandler; + + const auto isResponse { _processingMessagesCount > 0 }; + if (isResponse) + encoder->appendInt32 (kIsResponseKey, 1); + + _sendMessage (messageID, std::move (encoder), !isResponse); + + while (previousPendingReplyHandler != _pendingReplyHandler) + { + if (getMessageChannel ()->receivesMessagesOnCurrentThread ()) + { + if (!getMessageChannel ()->waitForMessageOnCurrentThread ()) + getConnection ()->_callWaitForMessageDelegate (); + } + else + { + auto receivedData { _messageQueue.waitOnSemaphore (0.010) }; + if (receivedData ) + processMessage (receivedData->first, std::move (receivedData->second)); + else + getConnection ()->_callWaitForMessageDelegate (); + } + } +} + +void MainThreadMessageDispatcher::processMessage (MessageID messageID, std::unique_ptr && decoder) +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + if (isReply (messageID)) + { + ARA_INTERNAL_ASSERT (_pendingReplyHandler != nullptr); + _handleReply (std::move (decoder), std::move (*_pendingReplyHandler->_replyHandler)); + _pendingReplyHandler = _pendingReplyHandler->_prevPendingReplyHandler; + } + else + { + ++_processingMessagesCount; + auto replyEncoder { _handleReceivedMessage (messageID, std::move (decoder)) }; + --_processingMessagesCount; + _sendMessage (0, std::move (replyEncoder), false); + } +} + +void MainThreadMessageDispatcher::routeReceivedMessage (MessageID messageID, std::unique_ptr && decoder) +{ + const auto isResponse { isReply (messageID) || // replies implicitly are responses + (decoder && decoder->hasDataForKey (kIsResponseKey)) }; + + // if on creation thread, responses can be processed immediately, and + // new transaction can only be processed immediately when no other transaction is going on + const auto processSynchronously { getConnection ()->wasCreatedOnCurrentThread () && + (isResponse || (_pendingReplyHandler == nullptr)) }; + + if (processSynchronously) + { + if (isReply (messageID)) + ARA_IPC_LOG ("processes reply on" ARA_IPC_LABEL_THREAD_FORMAT, ARA_IPC_LABEL_THREAD_ARGS); + else + ARA_IPC_LOG ("processes" ARA_IPC_DECODE_MESSAGE_FORMAT "on" ARA_IPC_LABEL_THREAD_FORMAT, + ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS); + } + else + { + if (isReply (messageID)) + ARA_IPC_LOG ("dispatches reply from" ARA_IPC_LABEL_THREAD_FORMAT " to creation thread", ARA_IPC_LABEL_THREAD_ARGS); + else + ARA_IPC_LOG ("dispatches" ARA_IPC_DECODE_MESSAGE_FORMAT "from" ARA_IPC_LABEL_THREAD_FORMAT " to creation thread", + ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS); + } + + if (processSynchronously) + { + processMessage (messageID, std::move (decoder)); + } + else + { + // only new transactions must be dispatched, otherwise the target thread is already waiting for the message received signal + if (isResponse) + _messageQueue.signalSemaphore (messageID, std::move (decoder)); + else + getConnection ()->dispatchToCreationThread ([this, messageID, d = decoder.release ()] () + { processMessage (messageID, std::unique_ptr (d)); }); + } +} + +//------------------------------------------------------------------------------ + +// multi-threaded variant for all non-main thread communication +class OtherThreadsMessageDispatcher : public MessageDispatcher +{ +public: + OtherThreadsMessageDispatcher (Connection* connection, std::unique_ptr && messageChannel) + : MessageDispatcher { connection, std::move (messageChannel) } + { + getMessageChannel ()->_receivedMessageRouter = [this] (MessageID messageID, std::unique_ptr && decoder) + { routeReceivedMessage (messageID, std::move (decoder)); }; + } + + void sendMessage (MessageID messageID, std::unique_ptr && encoder, ReplyHandler && replyHandler); + + void routeReceivedMessage (MessageID messageID, std::unique_ptr && decoder); + +private: + // keys to store the threading information in the IPC messages + static constexpr MessageArgumentKey kSendThreadKey { -1 }; + static constexpr MessageArgumentKey kReceiveThreadKey { -2 }; + + struct RoutedMessage + { + MessageID _messageID { 0 }; + std::unique_ptr _decoder; + ThreadRef _targetThread { _invalidThread }; + }; + RoutedMessage* _getRoutedMessageForThread (ThreadRef thread); + + void _processReceivedMessage (MessageID messageID, std::unique_ptr && decoder); + +private: + // incoming data is stored in _routedMessages by the receive handler for the + // sending threads waiting to pick it up (signalled via _routeReceiveCondition) + std::condition_variable _routeReceiveCondition; + std::vector _routedMessages { 12 }; // we shouldn't use more than a handful of threads concurrently for the IPC + std::mutex _sendLock; + std::mutex _routeLock; +}; + +// actually "static" members of OtherThreadsMessageDispatcher, but for some reason C++ doesn't allow this... +thread_local OtherThreadsMessageDispatcher::ThreadRef _remoteTargetThread { 0 }; +thread_local const OtherThreadsMessageDispatcher::PendingReplyHandler* _pendingReplyHandler { nullptr }; + +void OtherThreadsMessageDispatcher::sendMessage (MessageID messageID, std::unique_ptr && encoder, + ReplyHandler && replyHandler) +{ + ARA_INTERNAL_ASSERT (!getConnection ()->wasCreatedOnCurrentThread ()); + + const auto currentThread { getCurrentThread () }; + encoder->appendThreadRef (kSendThreadKey, currentThread); + const auto isResponse { _remoteTargetThread != _invalidThread }; + if (isResponse) + encoder->appendThreadRef (kReceiveThreadKey, _remoteTargetThread); + + _sendLock.lock (); + _sendMessage (messageID, std::move (encoder), !isResponse); + _sendLock.unlock (); + + + if (getMessageChannel ()->receivesMessagesOnCurrentThread ()) + { + const auto previousPendingReplyHandler { _pendingReplyHandler }; + const PendingReplyHandler pendingReplyHandler { &replyHandler, previousPendingReplyHandler }; + _pendingReplyHandler = &pendingReplyHandler; + do + { + if (!getMessageChannel ()->waitForMessageOnCurrentThread ()) + getConnection ()->_callWaitForMessageDelegate (); + } while (_pendingReplyHandler != previousPendingReplyHandler); + } + else + { + while (true) + { + std::unique_lock lock { _routeLock }; + _routeReceiveCondition.wait (lock, [this, ¤tThread] + { return _getRoutedMessageForThread (currentThread) != nullptr; }); + RoutedMessage* message = _getRoutedMessageForThread (currentThread); + const auto receivedMessageID { message->_messageID }; + auto receivedDecoder { std::move (message->_decoder) }; + message->_targetThread = _invalidThread; + lock.unlock (); + + if (isReply (receivedMessageID)) + { + _handleReply (std::move (receivedDecoder), std::move (replyHandler)); + break; + } + else + { + _processReceivedMessage (receivedMessageID, std::move (receivedDecoder)); + } + } + } +} + +OtherThreadsMessageDispatcher::RoutedMessage* OtherThreadsMessageDispatcher::_getRoutedMessageForThread (ThreadRef thread) +{ + for (auto& message : _routedMessages) + { + if (message._targetThread == thread) + return &message; + } + return nullptr; +} + +void OtherThreadsMessageDispatcher::routeReceivedMessage (MessageID messageID, std::unique_ptr && decoder) +{ + ThreadRef targetThread; + if (decoder->readThreadRef (kReceiveThreadKey, &targetThread)) + { + ARA_INTERNAL_ASSERT (targetThread != _invalidThread); + if (targetThread == getCurrentThread ()) + { + if (isReply (messageID)) + { + ARA_IPC_LOG ("processes reply on" ARA_IPC_LABEL_THREAD_FORMAT, ARA_IPC_LABEL_THREAD_ARGS); + ARA_INTERNAL_ASSERT (_pendingReplyHandler != nullptr); + _handleReply (std::move (decoder), std::move (*_pendingReplyHandler->_replyHandler)); + _pendingReplyHandler = _pendingReplyHandler->_prevPendingReplyHandler; + } + else + { + ARA_IPC_LOG ("processes" ARA_IPC_DECODE_MESSAGE_FORMAT "on" ARA_IPC_LABEL_THREAD_FORMAT, + ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS); + _processReceivedMessage (messageID, std::move (decoder)); + } + } + else + { + if (isReply (messageID)) + ARA_IPC_LOG ("dispatches reply from" ARA_IPC_LABEL_THREAD_FORMAT " to sending thread %p", ARA_IPC_LABEL_THREAD_ARGS, targetThread); + else + ARA_IPC_LOG ("dispatches" ARA_IPC_DECODE_MESSAGE_FORMAT "from" ARA_IPC_LABEL_THREAD_FORMAT " to sending thread %p", + ARA_IPC_DECODE_RECEIVED_MESSAGE_ARGS (messageID), ARA_IPC_LABEL_THREAD_ARGS, targetThread); + + _routeLock.lock (); + RoutedMessage* message { _getRoutedMessageForThread (_invalidThread) }; + if (message == nullptr) + { + _routedMessages.push_back ({}); + message = &_routedMessages.back (); + } + message->_messageID = messageID; + message->_decoder = std::move (decoder); + message->_targetThread = targetThread; + _routeReceiveCondition.notify_all (); + _routeLock.unlock (); + } + } + else + { + ARA_INTERNAL_ASSERT (!isReply (messageID)); + _processReceivedMessage (messageID, std::move (decoder)); + } +} + +void OtherThreadsMessageDispatcher::_processReceivedMessage (MessageID messageID, std::unique_ptr && decoder) +{ + const auto previousRemoteTargetThread { _remoteTargetThread }; + ThreadRef remoteTargetThread; + [[maybe_unused]] const auto success { decoder->readThreadRef (kSendThreadKey, &remoteTargetThread) }; + ARA_INTERNAL_ASSERT (success); + ARA_INTERNAL_ASSERT (remoteTargetThread != _invalidThread); + _remoteTargetThread = remoteTargetThread; + + auto replyEncoder { _handleReceivedMessage (messageID, std::move (decoder)) }; + + replyEncoder->appendThreadRef (kReceiveThreadKey, remoteTargetThread); + + _sendLock.lock (); + _sendMessage (0, std::move (replyEncoder), false); + _sendLock.unlock (); + + _remoteTargetThread = previousRemoteTargetThread; +} + +//------------------------------------------------------------------------------ + +#if defined (_WIN32) && !defined (__WINE__) + +// from https://devblogs.microsoft.com/oldnewthing/20141015-00/?p=43843 +BOOL ConvertToRealHandle(HANDLE h, + BOOL bInheritHandle, + HANDLE *phConverted) +{ + return DuplicateHandle(GetCurrentProcess(), h, + GetCurrentProcess(), phConverted, + 0, bInheritHandle, DUPLICATE_SAME_ACCESS); +} + +HANDLE _GetRealCurrentThread () +{ + HANDLE currentThread {}; + [[maybe_unused]] const auto success { ConvertToRealHandle (::GetCurrentThread (), FALSE, ¤tThread) }; + ARA_INTERNAL_ASSERT (success); + return currentThread; +} + +void APCRouteNewTransactionFunc (ULONG_PTR parameter) +{ + auto funcPtr { reinterpret_cast (parameter) }; + (*funcPtr) (); + delete funcPtr; +} + +#elif defined (__APPLE__) + +void Connection::_performRunLoopSource (void* info) +{ + auto connection { reinterpret_cast (info) }; + connection->_mutex.lock (); + while (!connection->_queue.empty ()) + { + auto func { connection->_queue.front () }; + connection->_queue.pop (); + connection->_mutex.unlock (); + + func (); + + connection->_mutex.lock (); + } + connection->_mutex.unlock (); +} + +#endif + + +Connection::Connection (MessageEncoderFactory && messageEncoderFactory, MessageHandler&& messageHandler, + bool receiverEndianessMatches, WaitForMessageDelegate && waitForMessageDelegate) +: _messageEncoderFactory { std::move (messageEncoderFactory) }, + _messageHandler { std::move (messageHandler) }, + _receiverEndianessMatches { receiverEndianessMatches }, + _waitForMessageDelegate { std::move (waitForMessageDelegate) }, + _creationThreadID { std::this_thread::get_id () } +#if defined (_WIN32) && !defined (__WINE__) + , _creationThreadHandle { _GetRealCurrentThread () } +{} +#elif defined (__APPLE__) + , _creationThreadRunLoop { CFRunLoopGetCurrent () } +{ + CFRunLoopSourceContext context { 0, this, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, _performRunLoopSource }; + _runloopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 0, &context); + CFRunLoopAddSource (_creationThreadRunLoop, _runloopSource, kCFRunLoopCommonModes); +} +#else + // Linux: _queue, _mutex, and _condition all default-construct. +{} +#endif + +Connection::~Connection () +{ +#if defined (__APPLE__) + CFRunLoopRemoveSource (_creationThreadRunLoop, _runloopSource, kCFRunLoopCommonModes); + CFRelease (_runloopSource); +#endif +} + +void Connection::setMainThreadChannel (std::unique_ptr && messageChannel) +{ + ARA_INTERNAL_ASSERT (_mainThreadDispatcher == nullptr); + _mainThreadDispatcher = std::make_unique (this, std::move (messageChannel)); +} + +void Connection::setOtherThreadsChannel (std::unique_ptr && messageChannel) +{ + ARA_INTERNAL_ASSERT (_otherThreadsDispatcher == nullptr); + _otherThreadsDispatcher = std::make_unique (this, std::move (messageChannel)); +} + +void Connection::sendMessage (MessageID messageID, std::unique_ptr && encoder, ReplyHandler && replyHandler) +{ + ARA_INTERNAL_ASSERT ((_mainThreadDispatcher != nullptr) && (_otherThreadsDispatcher != nullptr) && (_messageHandler != nullptr)); + if (wasCreatedOnCurrentThread ()) + _mainThreadDispatcher->sendMessage (messageID, std::move (encoder), std::move (replyHandler)); + else + _otherThreadsDispatcher->sendMessage (messageID, std::move (encoder), std::move (replyHandler)); +} + +void Connection::dispatchToCreationThread (DispatchableFunction func) +{ +#if defined (_WIN32) && !defined (__WINE__) + auto funcPtr { new DispatchableFunction { func } }; + [[maybe_unused]] const auto result { ::QueueUserAPC (APCRouteNewTransactionFunc, _creationThreadHandle, reinterpret_cast (funcPtr)) }; + ARA_INTERNAL_ASSERT (result != 0); +#elif defined (__APPLE__) + _mutex.lock (); + _queue.emplace (std::move (func)); + _mutex.unlock (); + CFRunLoopSourceSignal (_runloopSource); + CFRunLoopWakeUp (_creationThreadRunLoop); +#else + // Linux: push onto the queue and wake the creation thread. + { + std::lock_guard lock { _mutex }; + _queue.emplace (std::move (func)); + } + _condition.notify_one (); +#endif +} + +void Connection::processPendingMessageOnCreationThreadIfNeeded () +{ +#if (!defined (_WIN32) || defined (__WINE__)) && !defined (__APPLE__) + // On Linux there is no run loop to fire dispatch functions automatically, + // so we must drain Connection::_queue here before letting the main thread + // dispatcher check for a pending IPC message. + // We hold the lock only while popping each item, then release it before + // executing so that dispatchToCreationThread() can push more items + // concurrently without deadlocking. + while (true) + { + DispatchableFunction func; + { + std::lock_guard lock { _mutex }; + if (_queue.empty ()) + break; + func = std::move (_queue.front ()); + _queue.pop (); + } + func (); + } +#endif + _mainThreadDispatcher->processPendingMessageIfNeeded (); +} + +void Connection::_callWaitForMessageDelegate () +{ + if (_waitForMessageDelegate) + _waitForMessageDelegate (); +} + +void Connection::_setDebugMessageHint (bool isHost) +{ + _debugAsHost = isHost; +} + + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCConnection.h b/IPC/ARAIPCConnection.h new file mode 100644 index 0000000..64be8d1 --- /dev/null +++ b/IPC/ARAIPCConnection.h @@ -0,0 +1,190 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCConnection.h +//! Connection to another process to pass messages to/receive messages from it +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCConnection_h +#define ARAIPCConnection_h + +#include "ARA_Library/IPC/ARAIPCMessage.h" +#include "ARA_Library/Debug/ARADebug.h" + +#if ARA_ENABLE_IPC + + +#if defined (_WIN32) && !defined (__WINE__) + #include +#elif defined (__APPLE__) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include + + +//! @addtogroup ARA_Library_IPC +//! @{ + +namespace ARA { +namespace IPC { + + +class MessageChannel; +class MessageDispatcher; +class MainThreadMessageDispatcher; +class OtherThreadsMessageDispatcher; + + +//! delegate function for processing messages received through an IPC connection +//! IPC connections will call this function for incoming messages after +//! filtering replies and routing them to the correct thread. +using MessageHandler = std::function; + +//! Reply Handler: a function passed to sendMessage() that is called to process the reply to a message +//! decoder will be nullptr if incoming message was empty +using ReplyHandler = std::function; + + +//! IPC message channel: primitive for sending and receiving messages +//! @{ +class MessageChannel +{ +public: + virtual ~MessageChannel () = default; + + //! implemented by subclasses to perform the actual message (or reply) sending + virtual void sendMessage (MessageID messageID, std::unique_ptr && encoder) = 0; + + //! implemented by subclasses to inform the message dispatching code of the threading requirements + virtual bool receivesMessagesOnCurrentThread () = 0; + + //! implemented by subclasses to receive messages if receivesMessagesOnCurrentThread() returns true + virtual bool waitForMessageOnCurrentThread () = 0; + +protected: + //! called by subclasses when messages arrive + void routeReceivedMessage (MessageID messageID, std::unique_ptr && decoder) + { + ARA_INTERNAL_ASSERT (_receivedMessageRouter); + _receivedMessageRouter (messageID, std::move (decoder)); + } + +private: + // configuration hook for the MessageDispatcher constructors + friend class MainThreadMessageDispatcher; + friend class OtherThreadsMessageDispatcher; + std::function &&)> _receivedMessageRouter {}; +}; +//! @} + + +//! IPC connection: client interface for sending and receiving messages, +//! utilizing potentially multiple MessageChannel instances +//! @{ +class Connection +{ +public: + using MessageEncoderFactory = std::function ()>; + using WaitForMessageDelegate = std::function; + Connection (MessageEncoderFactory && messageEncoderFactory, MessageHandler && messageHandler, + bool receiverEndianessMatches, WaitForMessageDelegate && waitForMessageDelegate = {}); + ~Connection (); + + //! set the message channel for all main thread communication + //! Must be done before sending or receiving the first message on any channel. + void setMainThreadChannel (std::unique_ptr && messageChannel); + + //! set the message channel for all non-main thread communication + //! Must be done before sending or receiving the first message on any channel. + void setOtherThreadsChannel (std::unique_ptr && messageChannel); + + const MessageHandler& getMessageHandler () const { return _messageHandler; } + + //! send an encoded messages to the receiving process + //! If an empty reply ("void") is expected, the replyHandler shall be omitted. + //! This method can be called from any thread, concurrent calls will be serialized. + //! The calling thread will be blocked until the receiver has processed the message and + //! returned a (potentially empty) reply, which will be forwarded to the replyHandler. + void sendMessage (MessageID messageID, std::unique_ptr && encoder, ReplyHandler && replyHandler = {}); + + //! message encoder factory for users of the connection + std::unique_ptr createEncoder () { return _messageEncoderFactory (); } + + //! indicate byte order mismatch between sending and receiving machine + bool receiverEndianessMatches () const { return _receiverEndianessMatches; } + + bool wasCreatedOnCurrentThread () const { return std::this_thread::get_id () == _creationThreadID; } + + //! if IPC messages that need to be processed on the creation thread are received on some + //! other thread due to the design of the underlying IPC APIs, then this dispatch allows them + //! to forward it to the creation thread + using DispatchableFunction = std::function; + void dispatchToCreationThread (DispatchableFunction func); + + //! spins message processing on the creation thread in case the thread is blocked by some outer loop + void processPendingMessageOnCreationThreadIfNeeded (); + + // internal API: called by message dispatchers when blocking the current thread in a receive loop + void _callWaitForMessageDelegate (); + + // internal API: debug output helper + static void _setDebugMessageHint (bool isHost); + +#if defined (__APPLE__) +private: + static void _performRunLoopSource (void* info); +#endif + +private: + const MessageEncoderFactory _messageEncoderFactory; + const MessageHandler _messageHandler; + const bool _receiverEndianessMatches; + const WaitForMessageDelegate _waitForMessageDelegate; + std::unique_ptr _mainThreadDispatcher {}; + std::unique_ptr _otherThreadsDispatcher {}; + std::thread::id const _creationThreadID; +#if defined (_WIN32) && !defined (__WINE__) + HANDLE const _creationThreadHandle; +#elif defined (__APPLE__) + CFRunLoopRef const _creationThreadRunLoop; + CFRunLoopSourceRef _runloopSource; + std::queue _queue; // \todo instead of locking, use a lockless concurrent queue, + std::recursive_mutex _mutex; // eg this one: https://github.com/hogliux/farbot +#else + // Linux: dispatch queue protected by mutex + condition variable. + // dispatchToCreationThread() pushes a function and notifies; + // processPendingMessageOnCreationThreadIfNeeded() drains it. + std::queue _queue; + std::mutex _mutex; + std::condition_variable _condition; +#endif +}; + +} // namespace IPC +} // namespace ARA + +//! @} ARA_Library_IPC + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCConnection_h diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h new file mode 100644 index 0000000..0136536 --- /dev/null +++ b/IPC/ARAIPCEncoding.h @@ -0,0 +1,1432 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCEncoding.h +//! Implementation helpers shared by both the ARA IPC proxy host and plug-in +//! Typically, this file is not included directly - either ARAIPCProxyHost.h +//! ARAIPCProxyPlugIn.h will be used instead. +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCEncoding_h +#define ARAIPCEncoding_h + +#include "ARA_Library/IPC/ARAIPCConnection.h" + +#if ARA_ENABLE_IPC + + +#include "ARA_Library/Debug/ARADebug.h" +#include "ARA_Library/Dispatch/ARAContentReader.h" +#include "ARA_Library/Utilities/ARAChannelFormat.h" + +#include +#include +#include +#include +#include +#include +#include + + +//! @addtogroup ARA_Library_IPC +//! @{ + +namespace ARA { +namespace IPC { + + +//------------------------------------------------------------------------------ +// wrapper factories to efficiently handle sending and receiving raw bytes +//------------------------------------------------------------------------------ + +// a read function based on a ptr+size pair or std::vector +// it returns pointer to read bytes from and byte count via reference +// copy should be set to false if the bytes are guaranteed to remain valid +// until the message has been sent - this is the case for all blocking sends, +// but not for non-blocking sends and depends on context for replies +class BytesEncoder : public std::function +{ +public: + BytesEncoder (const uint8_t* const bytes, const size_t size, const bool copy) + : std::function { + [bytes, size, copy] (const uint8_t*& bytesPtr, size_t& bytesSize, bool& bytesCopy) -> void + { + bytesPtr = bytes; + bytesSize = size; + bytesCopy = copy; + } } + {} + BytesEncoder (const std::vector& bytes, const bool copy) + : BytesEncoder { bytes.data (), bytes.size (), copy } + {} +}; + +// a write function based on a ptr+size pair or std::vector +// resizes to the desired byte count and returns pointer to write bytes to +class BytesDecoder : public std::function +{ +public: + BytesDecoder (uint8_t* const bytes, size_t& size) + : std::function { + [bytes, &size] (size_t& bytesSize) -> uint8_t* + { + if (bytesSize > size) + bytesSize = size; // if there is more data then we can take, clip + else + size = bytesSize; // otherwise store size + return bytes; + } } + {} + BytesDecoder (std::vector& bytes) + : std::function { + [&bytes] (size_t& size) -> uint8_t* + { + bytes.resize (size); + return bytes.data (); + } } + {} +}; + + +//------------------------------------------------------------------------------ +// wrapper factories to efficiently handle sending and receiving arrays +//------------------------------------------------------------------------------ + + +template +struct ArrayArgument +{ + static_assert (sizeof (ElementT) > sizeof (ARAByte), "byte-sized arrays should be sent as raw bytes"); + ElementT* elements; + size_t count; +}; + + +//------------------------------------------------------------------------------ +// various private helpers +//------------------------------------------------------------------------------ + +// private helper template to detect ARA ref types +template +inline constexpr bool _IsRefType { false }; +#define ARA_IPC_SPECIALIZE_FOR_REF_TYPE(Type) \ +template<> \ +inline constexpr bool _IsRefType { true }; +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAMusicalContextRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARARegionSequenceRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioSourceRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioModificationRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlaybackRegionRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAContentReaderRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARADocumentControllerRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlaybackRendererRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAEditorRendererRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAEditorViewRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlugInExtensionRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAMusicalContextHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARARegionSequenceHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioSourceHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioModificationHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlaybackRegionHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAContentReaderHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioAccessControllerHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAAudioReaderHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAArchivingControllerHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAArchiveReaderHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAArchiveWriterHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAContentAccessControllerHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAModelUpdateControllerHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAPlaybackControllerHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCProxyHostRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCProxyPlugInRef) +ARA_IPC_SPECIALIZE_FOR_REF_TYPE (ARAIPCPlugInInstanceRef) +#undef ARA_IPC_SPECIALIZE_FOR_REF_TYPE + + +//------------------------------------------------------------------------------ +// private primitive wrappers for MessageEn-/Decoder C API +//------------------------------------------------------------------------------ + + +// primitives for appending an argument to a message +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const int32_t argValue) +{ + encoder->appendInt32 (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const int64_t argValue) +{ + encoder->appendInt64 (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const size_t argValue) +{ + encoder->appendSize (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const float argValue) +{ + encoder->appendFloat (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const double argValue) +{ + encoder->appendDouble (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const char* const argValue) +{ + encoder->appendString (argKey, argValue); +} +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const BytesEncoder& argValue) +{ + const uint8_t* bytes; + size_t size; + bool copy; + argValue (bytes, size, copy); + encoder->appendBytes (argKey, bytes, size, copy); +} + +// primitives for reading an (optional) argument from a message +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, int32_t& argValue) +{ + return decoder->readInt32 (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, int64_t& argValue) +{ + return decoder->readInt64 (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, size_t& argValue) +{ + return decoder->readSize (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, float& argValue) +{ + return decoder->readFloat (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, double& argValue) +{ + return decoder->readDouble (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, const char*& argValue) +{ + return decoder->readString (argKey, &argValue); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, BytesDecoder& argValue) +{ + size_t receivedSize; + const auto found { decoder->readBytesSize (argKey, &receivedSize) }; + auto availableSize { receivedSize }; + const auto bytes { argValue (availableSize) }; + if (!found) + return false; + if (availableSize < receivedSize) + return false; + decoder->readBytes (argKey, bytes); + return true; +} + + +//------------------------------------------------------------------------------ +// overloads of the IPCMessageEn-/Decoder primitives for types that can be +// directly mapped to a primitive type +//------------------------------------------------------------------------------ + + +// templated overloads of the IPCMessageEn-/Decoder primitives for ARA (host) ref types, +// which are stored as size_t +template, bool> = true> +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const T argValue) +{ + encoder->appendSize (argKey, reinterpret_cast (argValue)); +} +template, bool> = true> +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, T& argValue) +{ + // \todo is there a safe/proper way across all compilers for this cast to avoid the copy? +// return decoder.readSize (argKey, *reinterpret_cast (&argValue)); + size_t tmp; + const auto success { decoder->readSize (argKey, &tmp) }; + argValue = reinterpret_cast (tmp); + return success; +} + +/* instead of using ARA_IPC_ENCODE_EMBEDDED_BYTES below, we could instead allow + sending arrays of ARABytes via this overload (seems simpler but less efficient): +// to read and write arrays of ARAByte (not raw bytes but e.g. ARAKeySignatureIntervalUsage), +// we use int32_t to keep the IPCMessageEn-/Decoder API small +inline void _appendToMessage (MessageEncoder* encoder, const MessageArgumentKey argKey, const ARAByte argValue) +{ + encoder->appendInt32 (argKey, static_cast (argValue)); +} +inline bool _readFromMessage (const MessageDecoder* decoder, const MessageArgumentKey argKey, ARAByte& argValue) +{ + int32_t tmp; + const auto result { decoder->readInt32 (argKey, tmp) }; + ARA_INTERNAL_ASSERT ((0 <= tmp) && (tmp <= static_cast (std::numeric_limits::max ()))); + argValue = static_cast (tmp); + return result; +} +*/ + + +//------------------------------------------------------------------------------ +// private helper templates to en-/decode ARA API values +// The mapping is 1:1 except for ARA (host)refs which are encoded as size_t, and aggregate types +// (i.e. ARA structs or std::vector<> of types other than ARAByte), which are expressed as sub-messages. +// En- and Decoding use the same implementation technique: +// To support using compound types (arrays, structs) both as indexed sub-message for call arguments +// when sending as well as as root message for replies, there's a templated _ValueEn-/Decoder struct +// for each type which provides an encode&append/read&decode call that extracts a sub-message if +// needed and then performs the en/decode via a separate plain en-/decoding call only available in +// compound types. The latter call will be used directly for compound data type replies. +// In order not to have to spell out _ValueEn-/Decoder<> explicitly, overloaded wrapper function +// templates _encodeAndAppend() and _readAndDecode() are provided. +//------------------------------------------------------------------------------ + + +// declarations of wrapper functions to implicitly deduce _ValueEn-/Decoder<> - +// they are defined further down below, after all specializations are defined +template +inline void _encodeAndAppend (MessageEncoder* encoder, const MessageArgumentKey argKey, const ValueT& argValue); +template +inline bool _readAndDecode (ValueT& result, const MessageDecoder* decoder, const MessageArgumentKey argKey); + + +// primary templates for basic types (numbers, strings, (host)refs and raw bytes) +template +struct _ValueEncoder +{ + static inline void encodeAndAppend (MessageEncoder* encoder, const MessageArgumentKey argKey, const ValueT& argValue) + { + _appendToMessage (encoder, argKey, argValue); + } +}; +template +struct _ValueDecoder +{ + static inline bool readAndDecode (ValueT& result, const MessageDecoder* decoder, const MessageArgumentKey argKey) + { + return _readFromMessage (decoder, argKey, result); + } +}; + + +// common base classes for en-/decoding compound types (arrays, structs) via nested messages, +// providing the generic encode&append/read&decode calls +template +struct _CompoundValueEncoderBase +{ + static inline void encodeAndAppend (MessageEncoder* encoder, const MessageArgumentKey argKey, const ValueT& argValue) + { + auto subEncoder { encoder->appendSubMessage (argKey) }; + ARA_INTERNAL_ASSERT (subEncoder != nullptr); + _ValueEncoder::encode (subEncoder.get (), argValue); + } +}; +template +struct _CompoundValueDecoderBase +{ + static inline bool readAndDecode (ValueT& result, const MessageDecoder* decoder, const MessageArgumentKey argKey) + { + auto subDecoder { decoder->readSubMessage (argKey) }; + if (subDecoder == nullptr) + return false; + const auto success { _ValueDecoder::decode (result, subDecoder.get ()) }; + return success; + } +}; + + +// specialization for encoding arrays (variable or fixed size) +template +struct _ValueEncoder> : public _CompoundValueEncoderBase> +{ + static inline void encode (MessageEncoder* encoder, const ArrayArgument& value) + { + ARA_INTERNAL_ASSERT (value.count <= static_cast (std::numeric_limits::max ())); + const auto count { static_cast (value.count) }; + _encodeAndAppend (encoder, 0, count); + for (auto i { 0 }; i < count; ++i) + _encodeAndAppend (encoder, i + 1, value.elements[static_cast (i)]); + } +}; + +// specialization for decoding fixed-size arrays +template +struct _ValueDecoder> : public _CompoundValueDecoderBase> +{ + static inline bool decode (ArrayArgument& result, const MessageDecoder* decoder) + { + bool success { true }; + MessageArgumentKey count; + success &= _readAndDecode (count, decoder, 0); + success &= (count == static_cast (result.count)); + if (count > static_cast (result.count)) + count = static_cast (result.count); + + for (auto i { 0 }; i < count; ++i) + success &= _readAndDecode (result.elements[static_cast (i)], decoder, i + 1); + return success; + } +}; + +// specialization for decoding variable arrays +template +struct _ValueDecoder> : public _CompoundValueDecoderBase> +{ + static inline bool decode (std::vector& result, const MessageDecoder* decoder) + { + bool success { true }; + MessageArgumentKey count; + success &= _readAndDecode (count, decoder, 0); + result.resize (static_cast (count)); + for (auto i { 0 }; i < count; ++i) + success &= _readAndDecode (result[static_cast (i)], decoder, i + 1); + return success; + } +}; + + +// specializations for en/decoding each ARA struct + +#define ARA_IPC_BEGIN_ENCODE(StructT) \ +template<> struct _ValueEncoder : public _CompoundValueEncoderBase \ +{ /* specialization for given struct */ \ + using StructType = StructT; \ + static inline void encode (MessageEncoder* encoder, const StructType& value) \ + { +#define ARA_IPC_ENCODE_MEMBER(member) \ + _encodeAndAppend (encoder, offsetof (StructType, member), value.member); +#define ARA_IPC_ENCODE_EMBEDDED_BYTES(member) \ + const BytesEncoder tmp_##member { reinterpret_cast (value.member), sizeof (value.member), true }; \ + _encodeAndAppend (encoder, offsetof (StructType, member), tmp_##member); +#define ARA_IPC_ENCODE_EMBEDDED_ARRAY(member) \ + const ArrayArgument> tmp_##member { value.member, std::extent_v }; \ + _encodeAndAppend (encoder, offsetof (StructType, member), tmp_##member); +#define ARA_IPC_ENCODE_VARIABLE_ARRAY(member, count) \ + if ((value.count > 0) && (value.member != nullptr)) { \ + const ArrayArgument> tmp_##member { value.member, value.count }; \ + _encodeAndAppend (encoder, offsetof (StructType, member), tmp_##member); \ + } +#define ARA_IPC_ENCODE_OPTIONAL_MEMBER(member) \ + if (value.member != nullptr) \ + ARA_IPC_ENCODE_MEMBER (member) +#define ARA_IPC_HAS_ADDENDUM_MEMBER(member) \ + /* \todo ARA_IMPLEMENTS_FIELD decorates the type with the ARA:: namespace, */ \ + /* this conflicts with decltype's result - this copied version drops the ARA:: */ \ + (value.structSize > offsetof (std::remove_reference_t, member)) +#define ARA_IPC_ENCODE_ADDENDUM_MEMBER(member) \ + if (ARA_IPC_HAS_ADDENDUM_MEMBER (member)) \ + ARA_IPC_ENCODE_MEMBER (member) +#define ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_MEMBER(member) \ + if (ARA_IPC_HAS_ADDENDUM_MEMBER (member)) \ + ARA_IPC_ENCODE_OPTIONAL_MEMBER (member) +#define ARA_IPC_ENCODE_OPTIONAL_STRUCT_PTR(member) \ + if (value.member != nullptr) \ + _encodeAndAppend (encoder, offsetof (StructType, member), *value.member); +#define ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_STRUCT_PTR(member) \ + if (ARA_IPC_HAS_ADDENDUM_MEMBER (member)) \ + ARA_IPC_ENCODE_OPTIONAL_STRUCT_PTR (member) +#define ARA_IPC_ENCODE_ADDENDUM_VARIABLE_ARRAY(member, count) \ + if (ARA_IPC_HAS_ADDENDUM_MEMBER (member)) \ + ARA_IPC_ENCODE_VARIABLE_ARRAY (member, count) +#define ARA_IPC_END_ENCODE \ + } \ +}; + + +#define ARA_IPC_BEGIN_DECODE(StructT) \ +template<> struct _ValueDecoder : public _CompoundValueDecoderBase \ +{ /* specialization for given struct */ \ + using StructType = StructT; \ + static inline bool decode (StructType& result, const MessageDecoder* decoder) \ + { \ + bool success { true }; +#define ARA_IPC_BEGIN_DECODE_SIZED(StructT) \ + ARA_IPC_BEGIN_DECODE (StructT) \ + result.structSize = k##StructT##MinSize; +#define ARA_IPC_DECODE_MEMBER(member) \ + success &= _readAndDecode (result.member, decoder, offsetof (StructType, member)); \ + ARA_INTERNAL_ASSERT (success); +#define ARA_IPC_DECODE_EMBEDDED_BYTES(member) \ + auto resultSize_##member { sizeof (result.member) }; \ + BytesDecoder tmp_##member { reinterpret_cast (result.member), resultSize_##member }; \ + success &= _readAndDecode (tmp_##member, decoder, offsetof (StructType, member)); \ + success &= (resultSize_##member == sizeof (result.member)); \ + ARA_INTERNAL_ASSERT (success); +#define ARA_IPC_DECODE_EMBEDDED_ARRAY(member) \ + ArrayArgument> tmp_##member { result.member, std::extent_v }; \ + success &= _readAndDecode (tmp_##member, decoder, offsetof (StructType, member)); \ + ARA_INTERNAL_ASSERT (success); +#define ARA_IPC_DECODE_VARIABLE_ARRAY(member, count, updateCount) \ + /* \todo the outer struct contains a pointer to the inner struct, so we need some */ \ + /* thread-safe place to store it - is there a better way to achieve this? */ \ + thread_local std::vector>> tmp_##member; \ + if (_readAndDecode (tmp_##member, decoder, offsetof (StructType, member))) { \ + result.member = tmp_##member.data (); \ + if (updateCount) { result.count = tmp_##member.size (); } \ + } else { \ + result.member = nullptr; \ + if (updateCount) { result.count = 0; } \ + } +#define ARA_IPC_DECODE_OPTIONAL_MEMBER(member) \ + if (!_readAndDecode (result.member, decoder, offsetof (StructType, member))) \ + result.member = nullptr; +#define ARA_IPC_UPDATE_STRUCT_SIZE_FOR_OPTIONAL(member) \ + /* \todo ARA_IMPLEMENTED_STRUCT_SIZE decorates the type with the ARA:: namespace, */ \ + /* conflicting with the local alias StructType - this copy simply drops the ARA:: */ \ + constexpr auto size { offsetof (StructType, member) + sizeof (static_cast (nullptr)->member) }; \ + result.structSize = std::max (result.structSize, size); +#define ARA_IPC_DECODE_ADDENDUM_MEMBER(member) \ + if (_readAndDecode (result.member, decoder, offsetof (StructType, member))) { \ + ARA_IPC_UPDATE_STRUCT_SIZE_FOR_OPTIONAL (member); \ + } +#define ARA_IPC_DECODE_OPTIONAL_ADDENDUM_MEMBER(member) \ + ARA_IPC_DECODE_ADDENDUM_MEMBER (member) \ + else { \ + result.member = nullptr; \ + } +#define ARA_IPC_DECODE_OPTIONAL_STRUCT_PTR(member, OPTIONAL_UPDATE_MACRO) \ + auto subDecoder_##member { decoder->readSubMessage (offsetof (StructType, member)) }; \ + if (subDecoder_##member != nullptr) { \ + /* \todo the outer struct contains a pointer to the inner struct, so we need some */\ + /* thread-safe place to store it - is there a better way to achieve this? */\ + thread_local std::remove_const_t> cache; \ + success &= _ValueDecoder::decode (cache, subDecoder_##member.get ()); \ + ARA_INTERNAL_ASSERT (success); \ + result.member = &cache; \ + { OPTIONAL_UPDATE_MACRO } \ + } \ + else { \ + result.member = nullptr; \ + } +#define ARA_IPC_DECODE_OPTIONAL_ADDENDUM_STRUCT_PTR(member) \ + ARA_IPC_DECODE_OPTIONAL_STRUCT_PTR (member, ARA_IPC_UPDATE_STRUCT_SIZE_FOR_OPTIONAL (member)) +#define ARA_IPC_END_DECODE \ + return success; \ + } \ +}; + + +ARA_IPC_BEGIN_ENCODE (ARAColor) + ARA_IPC_ENCODE_MEMBER (r) + ARA_IPC_ENCODE_MEMBER (g) + ARA_IPC_ENCODE_MEMBER (b) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAColor) + ARA_IPC_DECODE_MEMBER (r) + ARA_IPC_DECODE_MEMBER (g) + ARA_IPC_DECODE_MEMBER (b) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARADocumentProperties) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARADocumentProperties) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAMusicalContextProperties) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_MEMBER (name) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (orderIndex) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAMusicalContextProperties) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_MEMBER (name) + ARA_IPC_DECODE_ADDENDUM_MEMBER (orderIndex) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARARegionSequenceProperties) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) + ARA_IPC_ENCODE_MEMBER (orderIndex) + ARA_IPC_ENCODE_MEMBER (musicalContextRef) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_MEMBER (persistentID) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARARegionSequenceProperties) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) + ARA_IPC_DECODE_MEMBER (orderIndex) + ARA_IPC_DECODE_MEMBER (musicalContextRef) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_MEMBER (persistentID) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAAudioSourceProperties) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) + ARA_IPC_ENCODE_MEMBER (persistentID) + ARA_IPC_ENCODE_MEMBER (sampleCount) + ARA_IPC_ENCODE_MEMBER (sampleRate) + ARA_IPC_ENCODE_MEMBER (channelCount) + ARA_IPC_ENCODE_MEMBER (merits64BitSamples) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (channelArrangementDataType) + if (ARA_IPC_HAS_ADDENDUM_MEMBER (channelArrangement)) + { + // \todo potential endianess conversion is missing here if needed - but this is not known here, + // it must be performed in the calling code. + // this means the caller would need to copy the incoming data for mutation - + // maybe it would be better to move receiverEndianessMatches () from the sending side to + // the receiving side? We need to review how such a change would affect audio readers etc. + const auto size { ChannelFormat::getChannelArrangementDataSize (value.channelCount, value.channelArrangementDataType, value.channelArrangement) }; + if (size > 0) + { + const BytesEncoder tmp_channelArrangement { reinterpret_cast (value.channelArrangement), size, true }; + _encodeAndAppend (encoder, offsetof (ARAAudioSourceProperties, channelArrangement), tmp_channelArrangement); + } + } +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAAudioSourceProperties) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) + ARA_IPC_DECODE_MEMBER (persistentID) + ARA_IPC_DECODE_MEMBER (sampleCount) + ARA_IPC_DECODE_MEMBER (sampleRate) + ARA_IPC_DECODE_MEMBER (channelCount) + ARA_IPC_DECODE_MEMBER (merits64BitSamples) + ARA_IPC_DECODE_ADDENDUM_MEMBER (channelArrangementDataType) + if (result.structSize > offsetof (ARAAudioSourceProperties, channelArrangementDataType)) + { + ARA_IPC_UPDATE_STRUCT_SIZE_FOR_OPTIONAL (channelArrangement); + + if (result.channelArrangementDataType == kARAChannelArrangementUndefined) + { + result.channelArrangement = nullptr; + } + else + { + /* \todo the outer struct contains a pointer to the inner struct, so we need some */ + /* thread-safe place to store it - is there a better way to achieve this? */ + thread_local ARAByte cache[2016UL]; // some arbitrary space that can be conveniently allocated + auto resultSize_channelArrangement { sizeof (cache) }; + BytesDecoder tmp_channelArrangement { cache, resultSize_channelArrangement }; + if (_readAndDecode (tmp_channelArrangement, decoder, offsetof (ARAAudioSourceProperties, channelArrangement)) && + (resultSize_channelArrangement < sizeof (cache))) + { + result.channelArrangement = &cache; + } + else + { + result.channelArrangementDataType = kARAChannelArrangementUndefined; + result.channelArrangement = nullptr; + success = false; + } + } + } +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAAudioModificationProperties) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) + ARA_IPC_ENCODE_MEMBER (persistentID) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAAudioModificationProperties) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) + ARA_IPC_DECODE_MEMBER (persistentID) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAPlaybackRegionProperties) + ARA_IPC_ENCODE_MEMBER (transformationFlags) + ARA_IPC_ENCODE_MEMBER (startInModificationTime) + ARA_IPC_ENCODE_MEMBER (durationInModificationTime) + ARA_IPC_ENCODE_MEMBER (startInPlaybackTime) + ARA_IPC_ENCODE_MEMBER (durationInPlaybackTime) + if (!ARA_IPC_HAS_ADDENDUM_MEMBER (regionSequenceRef)) + ARA_IPC_ENCODE_MEMBER (musicalContextRef) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (regionSequenceRef) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_MEMBER (name) + ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAPlaybackRegionProperties) + ARA_IPC_DECODE_MEMBER (transformationFlags) + ARA_IPC_DECODE_MEMBER (startInModificationTime) + ARA_IPC_DECODE_MEMBER (durationInModificationTime) + ARA_IPC_DECODE_MEMBER (startInPlaybackTime) + ARA_IPC_DECODE_MEMBER (durationInPlaybackTime) + ARA_IPC_DECODE_OPTIONAL_MEMBER (musicalContextRef) + ARA_IPC_DECODE_ADDENDUM_MEMBER (regionSequenceRef) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_MEMBER (name) + ARA_IPC_DECODE_OPTIONAL_ADDENDUM_STRUCT_PTR (color) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentTimeRange) + ARA_IPC_ENCODE_MEMBER (start) + ARA_IPC_ENCODE_MEMBER (duration) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentTimeRange) + ARA_IPC_DECODE_MEMBER (start) + ARA_IPC_DECODE_MEMBER (duration) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentTempoEntry) + ARA_IPC_ENCODE_MEMBER (timePosition) + ARA_IPC_ENCODE_MEMBER (quarterPosition) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentTempoEntry) + ARA_IPC_DECODE_MEMBER (timePosition) + ARA_IPC_DECODE_MEMBER (quarterPosition) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentBarSignature) + ARA_IPC_ENCODE_MEMBER (numerator) + ARA_IPC_ENCODE_MEMBER (denominator) + ARA_IPC_ENCODE_MEMBER (position) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentBarSignature) + ARA_IPC_DECODE_MEMBER (numerator) + ARA_IPC_DECODE_MEMBER (denominator) + ARA_IPC_DECODE_MEMBER (position) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentNote) + ARA_IPC_ENCODE_MEMBER (frequency) + ARA_IPC_ENCODE_MEMBER (pitchNumber) + ARA_IPC_ENCODE_MEMBER (volume) + ARA_IPC_ENCODE_MEMBER (startPosition) + ARA_IPC_ENCODE_MEMBER (attackDuration) + ARA_IPC_ENCODE_MEMBER (noteDuration) + ARA_IPC_ENCODE_MEMBER (signalDuration) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentNote) + ARA_IPC_DECODE_MEMBER (frequency) + ARA_IPC_DECODE_MEMBER (pitchNumber) + ARA_IPC_DECODE_MEMBER (volume) + ARA_IPC_DECODE_MEMBER (startPosition) + ARA_IPC_DECODE_MEMBER (attackDuration) + ARA_IPC_DECODE_MEMBER (noteDuration) + ARA_IPC_DECODE_MEMBER (signalDuration) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentTuning) + ARA_IPC_ENCODE_MEMBER (concertPitchFrequency) + ARA_IPC_ENCODE_MEMBER (root) + ARA_IPC_ENCODE_EMBEDDED_ARRAY (tunings) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentTuning) + ARA_IPC_DECODE_MEMBER (concertPitchFrequency) + ARA_IPC_DECODE_MEMBER (root) + ARA_IPC_DECODE_EMBEDDED_ARRAY (tunings) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentKeySignature) + ARA_IPC_ENCODE_MEMBER (root) + ARA_IPC_ENCODE_EMBEDDED_BYTES (intervals) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) + ARA_IPC_ENCODE_MEMBER (position) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentKeySignature) + ARA_IPC_DECODE_MEMBER (root) + ARA_IPC_DECODE_EMBEDDED_BYTES (intervals) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) + ARA_IPC_DECODE_MEMBER (position) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentChord) + ARA_IPC_ENCODE_MEMBER (root) + ARA_IPC_ENCODE_MEMBER (bass) + ARA_IPC_ENCODE_EMBEDDED_BYTES (intervals) + ARA_IPC_ENCODE_OPTIONAL_MEMBER (name) + ARA_IPC_ENCODE_MEMBER (position) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentChord) + ARA_IPC_DECODE_MEMBER (root) + ARA_IPC_DECODE_MEMBER (bass) + ARA_IPC_DECODE_EMBEDDED_BYTES (intervals) + ARA_IPC_DECODE_OPTIONAL_MEMBER (name) + ARA_IPC_DECODE_MEMBER (position) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAContentLyricsEntry) + ARA_IPC_ENCODE_MEMBER (lyrics) + ARA_IPC_ENCODE_MEMBER (continuesPreviousWord) + ARA_IPC_ENCODE_MEMBER (language) + ARA_IPC_ENCODE_VARIABLE_ARRAY (phonemes, phonemeCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (phonemeOffsets, phonemeCount) + ARA_IPC_ENCODE_MEMBER (phonemesGrade) + ARA_IPC_ENCODE_MEMBER (position) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentLyricsEntry) + ARA_IPC_DECODE_MEMBER (lyrics) + ARA_IPC_DECODE_MEMBER (continuesPreviousWord) + ARA_IPC_DECODE_MEMBER (language) + ARA_IPC_DECODE_VARIABLE_ARRAY (phonemes, phonemeCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (phonemeOffsets, phonemeCount, false) + ARA_IPC_DECODE_MEMBER (phonemesGrade) + ARA_IPC_DECODE_MEMBER (position) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARARestoreObjectsFilter) + ARA_IPC_ENCODE_MEMBER (documentData) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioSourceArchiveIDs, audioSourceIDsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioSourceCurrentIDs, audioSourceIDsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioModificationArchiveIDs, audioModificationIDsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioModificationCurrentIDs, audioModificationIDsCount) + ARA_IPC_ENCODE_ADDENDUM_VARIABLE_ARRAY (regionSequenceArchiveIDs, regionSequenceIDsCount) + ARA_IPC_ENCODE_ADDENDUM_VARIABLE_ARRAY (regionSequenceCurrentIDs, regionSequenceIDsCount) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARARestoreObjectsFilter) + ARA_IPC_DECODE_MEMBER (documentData) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioSourceArchiveIDs, audioSourceIDsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioSourceCurrentIDs, audioSourceIDsCount, false) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioModificationArchiveIDs, audioModificationIDsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioModificationCurrentIDs, audioModificationIDsCount, false) + ARA_IPC_DECODE_VARIABLE_ARRAY (regionSequenceArchiveIDs, regionSequenceIDsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (regionSequenceCurrentIDs, regionSequenceIDsCount, false) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAStoreObjectsFilter) + ARA_IPC_ENCODE_MEMBER (documentData) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioSourceRefs, audioSourceRefsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (audioModificationRefs, audioModificationRefsCount) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAStoreObjectsFilter) + ARA_IPC_DECODE_MEMBER (documentData) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioSourceRefs, audioSourceRefsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (audioModificationRefs, audioModificationRefsCount, true) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAProcessingAlgorithmProperties) + ARA_IPC_ENCODE_MEMBER (persistentID) + ARA_IPC_ENCODE_MEMBER (name) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAProcessingAlgorithmProperties) + ARA_IPC_DECODE_MEMBER (persistentID) + ARA_IPC_DECODE_MEMBER (name) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAViewSelection) + ARA_IPC_ENCODE_VARIABLE_ARRAY (playbackRegionRefs, playbackRegionRefsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (regionSequenceRefs, regionSequenceRefsCount) + ARA_IPC_ENCODE_OPTIONAL_STRUCT_PTR (timeRange) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAViewSelection) + ARA_IPC_DECODE_VARIABLE_ARRAY (playbackRegionRefs, playbackRegionRefsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (regionSequenceRefs, regionSequenceRefsCount, true) + ARA_IPC_DECODE_OPTIONAL_STRUCT_PTR (timeRange, ) +ARA_IPC_END_DECODE + +ARA_IPC_BEGIN_ENCODE (ARAFactory) + ARA_IPC_ENCODE_MEMBER (lowestSupportedApiGeneration) + ARA_IPC_ENCODE_MEMBER (highestSupportedApiGeneration) + ARA_IPC_ENCODE_MEMBER (factoryID) + ARA_IPC_ENCODE_MEMBER (plugInName) + ARA_IPC_ENCODE_MEMBER (manufacturerName) + ARA_IPC_ENCODE_MEMBER (informationURL) + ARA_IPC_ENCODE_MEMBER (version) + ARA_IPC_ENCODE_MEMBER (documentArchiveID) + ARA_IPC_ENCODE_VARIABLE_ARRAY (compatibleDocumentArchiveIDs, compatibleDocumentArchiveIDsCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (analyzeableContentTypes, analyzeableContentTypesCount) + ARA_IPC_ENCODE_MEMBER (supportedPlaybackTransformationFlags) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsStoringAudioFileChunks) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsSampleBasedAudioSources) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsContentOnlyAudioSources) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (requiresPresetAudioSources) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE_SIZED (ARAFactory) + ARA_IPC_DECODE_MEMBER (lowestSupportedApiGeneration) + ARA_IPC_DECODE_MEMBER (highestSupportedApiGeneration) + ARA_IPC_DECODE_MEMBER (factoryID) + result.initializeARAWithConfiguration = nullptr; + result.uninitializeARA = nullptr; + ARA_IPC_DECODE_MEMBER (plugInName) + ARA_IPC_DECODE_MEMBER (manufacturerName) + ARA_IPC_DECODE_MEMBER (informationURL) + ARA_IPC_DECODE_MEMBER (version) + result.createDocumentControllerWithDocument = nullptr; + ARA_IPC_DECODE_MEMBER (documentArchiveID) + ARA_IPC_DECODE_VARIABLE_ARRAY (compatibleDocumentArchiveIDs, compatibleDocumentArchiveIDsCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (analyzeableContentTypes, analyzeableContentTypesCount, true) + ARA_IPC_DECODE_MEMBER (supportedPlaybackTransformationFlags) + ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsStoringAudioFileChunks) + ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsSampleBasedAudioSources) + ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsContentOnlyAudioSources) + ARA_IPC_DECODE_ADDENDUM_MEMBER (requiresPresetAudioSources) +ARA_IPC_END_DECODE + + +// ARADocumentControllerInterface::storeAudioSourceToAudioFileChunk() must return the documentArchiveID and the +// openAutomatically flag in addition to the return value, we need a special struct to encode this through IPC. +struct StoreAudioSourceToAudioFileChunkReply +{ + ARABool result; + ARAPersistentID documentArchiveID; + ARABool openAutomatically; +}; +ARA_IPC_BEGIN_ENCODE (StoreAudioSourceToAudioFileChunkReply) + ARA_IPC_ENCODE_MEMBER (result) + ARA_IPC_ENCODE_MEMBER (documentArchiveID) + ARA_IPC_ENCODE_MEMBER (openAutomatically) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (StoreAudioSourceToAudioFileChunkReply) + ARA_IPC_DECODE_MEMBER (result) + ARA_IPC_DECODE_MEMBER (documentArchiveID) + ARA_IPC_DECODE_MEMBER (openAutomatically) +ARA_IPC_END_DECODE + +// ARADocumentControllerInterface::getPlaybackRegionHeadAndTailTime() must return both head- and tailtime. +struct GetPlaybackRegionHeadAndTailTimeReply +{ + ARATimeDuration headTime; + ARATimeDuration tailTime; +}; +ARA_IPC_BEGIN_ENCODE (GetPlaybackRegionHeadAndTailTimeReply) + ARA_IPC_ENCODE_MEMBER (headTime) + ARA_IPC_ENCODE_MEMBER (tailTime) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (GetPlaybackRegionHeadAndTailTimeReply) + ARA_IPC_DECODE_MEMBER (headTime) + ARA_IPC_DECODE_MEMBER (tailTime) +ARA_IPC_END_DECODE + + +#undef ARA_IPC_BEGIN_ENCODE +#undef ARA_IPC_ENCODE_MEMBER +#undef ARA_IPC_ENCODE_EMBEDDED_BYES +#undef ARA_IPC_ENCODE_EMBEDDED_ARRAY +#undef ARA_IPC_ENCODE_VARIABLE_ARRAY +#undef ARA_IPC_ENCODE_OPTIONAL_MEMBER +#undef ARA_IPC_HAS_ADDENDUM_MEMBER +#undef ARA_IPC_ENCODE_ADDENDUM_MEMBER +#undef ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_MEMBER +#undef ARA_IPC_ENCODE_OPTIONAL_STRUCT_PTR +#undef ARA_IPC_ENCODE_OPTIONAL_ADDENDUM_STRUCT_PTR +#undef ARA_IPC_END_ENCODE + +#undef ARA_IPC_BEGIN_DECODE +#undef ARA_IPC_BEGIN_DECODE_SIZED +#undef ARA_IPC_DECODE_MEMBER +#undef ARA_IPC_DECODE_EMBEDDED_BYTES +#undef ARA_IPC_DECODE_EMBEDDED_ARRAY +#undef ARA_IPC_DECODE_VARIABLE_ARRAY +#undef ARA_IPC_DECODE_OPTIONAL_MEMBER +#undef ARA_IPC_UPDATE_STRUCT_SIZE_FOR_OPTIONAL +#undef ARA_IPC_DECODE_ADDENDUM_MEMBER +#undef ARA_IPC_DECODE_OPTIONAL_ADDENDUM_MEMBER +#undef ARA_IPC_DECODE_OPTIONAL_STRUCT_PTR +#undef ARA_IPC_DECODE_OPTIONAL_ADDENDUM_STRUCT_PTR +#undef ARA_IPC_END_DECODE + + +// actual definitions of wrapper functions to implicitly deduce _ValueEn-/Decoder<> - +// they are forward-declared above, before _ValueEn-/Decoder<> are defined +template +inline void _encodeAndAppend (MessageEncoder* encoder, const MessageArgumentKey argKey, const ValueT& argValue) +{ + return _ValueEncoder::encodeAndAppend (encoder, argKey, argValue); +} +template +inline bool _readAndDecode (ValueT& result, const MessageDecoder* decoder, const MessageArgumentKey argKey) +{ + return _ValueDecoder::readAndDecode (result, decoder, argKey); +} + + +//------------------------------------------------------------------------------ + +// private helper for decodeArguments() to deal with optional arguments +template +inline constexpr bool _IsOptionalArgument { false }; +template +inline constexpr bool _IsOptionalArgument> { true }; + +// private helpers for decodeArguments() to deal with the variable arguments one at a time +inline void _decodeArgumentsHelper (const MessageDecoder* /*decoder*/, const MessageArgumentKey /*argKey*/) +{ +} +template, bool> = true> +inline void _decodeArgumentsHelper (const MessageDecoder* decoder, const MessageArgumentKey argKey, ArgT& argValue, MoreArgs &... moreArgs) +{ + _readAndDecode (argValue, decoder, argKey); + _decodeArgumentsHelper (decoder, argKey + 1, moreArgs...); +} +template, bool> = true> +inline void _decodeArgumentsHelper (const MessageDecoder* decoder, const MessageArgumentKey argKey, ArgT& argValue, MoreArgs &... moreArgs) +{ + argValue.second = _readAndDecode (argValue.first, decoder, argKey); + _decodeArgumentsHelper (decoder, argKey + 1, moreArgs...); +} + +// private helper for ARA_IPC_METHOD_ID +template +constexpr MessageID _getInterfaceID (); +// host interface specializations +template<> +constexpr MessageID _getInterfaceID () { return 0; } +template<> +constexpr MessageID _getInterfaceID () { return 1; } +template<> +constexpr MessageID _getInterfaceID () { return 2; } +template<> +constexpr MessageID _getInterfaceID () { return 3; } +template<> +constexpr MessageID _getInterfaceID () { return 4; } +// plug-in interface specializations +template<> +constexpr MessageID _getInterfaceID () { return 0; } +template<> +constexpr MessageID _getInterfaceID () { return 1; } +template<> +constexpr MessageID _getInterfaceID () { return 2; } +template<> +constexpr MessageID _getInterfaceID () { return 3; } + + +//------------------------------------------------------------------------------ +// actual client API +//------------------------------------------------------------------------------ + + +// helper class wrapping MessageID to prevent implicit conversions to/from +// MessageID, so that they can be identified reliably in call signatures +// this is important e.g. for the various forms of RemoteCaller::remoteCall (). +class MethodID +{ +private: + constexpr MethodID (MessageID messageID) : _id { messageID } {} + +public: + template + static inline constexpr MethodID createWithARAInterfaceIDAndOffset () + { + static_assert (offset > 0, "offset 0 is never a valid function pointer"); + static_assert ((interfaceID < 8), "currently using only 3 bits for interface ID"); +#if defined (__i386__) || defined (_M_IX86) + static_assert ((sizeof (void*) == 4), "compiler settings imply 32 bit pointers"); + static_assert (((offset & 0x3FFFFFF4) == offset), "offset is misaligned or too large"); + return (offset << 1) + interfaceID; // lower 2 bits of offset are 0 due to alignment, must shift 1 bit to store interface ID +#else + static_assert ((sizeof (void*) == 8), "assuming 64 bit pointers per default"); + static_assert (((offset & 0x7FFFFFF8) == offset), "offset is misaligned or too large"); + return offset + interfaceID; // lower 3 bits of offset are 0 due to alignment, can be used to store interface ID +#endif + } + + template + static inline constexpr MethodID createWithARAGlobalMessageID () + { + static_assert (!isCustomMessageID (messageID), "must not be in custom message range"); + return messageID; + } + + template + static inline constexpr MethodID createWithCustomMessageID () + { + static_assert (isCustomMessageID (messageID), "must not be in reserved message range"); + return messageID; + } + + static inline constexpr bool isCustomMessageID (MessageID messageID) + { + return (messageID < kReservedMessageIDRangeStart) || (kReservedMessageIDRangeEnd <= messageID); + } + + constexpr MessageID getMessageID () const { return _id; } + +private: + const MessageID _id; +}; + +inline bool operator== (const MessageID messageID, const MethodID methodID) +{ + return (messageID == methodID.getMessageID ()); +} +inline bool operator== (const MethodID methodID, const MessageID messageID) +{ + return (messageID == methodID.getMessageID ()); +} +inline bool operator!= (const MessageID messageID, const MethodID methodID) +{ + return (messageID != methodID.getMessageID ()); +} +inline bool operator!= (const MethodID methodID, const MessageID messageID) +{ + return (messageID != methodID.getMessageID ()); +} + + +// create a MethodID for a given host-side or plug-in-side ARA method +#define ARA_IPC_METHOD_ID(StructT, member) MethodID::createWithARAInterfaceIDAndOffset <_getInterfaceID (), offsetof (StructT, member)> () + + +// "global" messages for startup and teardown that are not calculated based on interface structs +inline constexpr auto kGetFactoriesCountMethodID { MethodID::createWithARAGlobalMessageID<1> () }; +inline constexpr auto kGetFactoryMethodID { MethodID::createWithARAGlobalMessageID<2> () }; +inline constexpr auto kInitializeARAMethodID { MethodID::createWithARAGlobalMessageID<3> () }; +inline constexpr auto kCreateDocumentControllerMethodID { MethodID::createWithARAGlobalMessageID<4> () }; +inline constexpr auto kBindToDocumentControllerMethodID { MethodID::createWithARAGlobalMessageID<5> () }; +inline constexpr auto kCleanupBindingMethodID { MethodID::createWithARAGlobalMessageID<6> () }; +inline constexpr auto kUninitializeARAMethodID { MethodID::createWithARAGlobalMessageID<7> () }; + + +// private helpers for decodeHost/PlugInMessageID() +#define ARA_IPC_GLOBAL_MESSAGE_CASE(methodID) \ + case methodID.getMessageID (): return #methodID; + +#define ARA_IPC_INTERFACE_MESSAGE_CASE(StructT, member) \ + case ARA_IPC_METHOD_ID (StructT, member).getMessageID (): return (omitInterfaceName) ? #member : #StructT "::" #member; + +// for debugging: translate IDs of messages sent by the host back to human-readable text +inline const char* decodeHostMessageID (const MessageID messageID, const bool omitInterfaceName = true) +{ + switch (messageID) + { + ARA_IPC_GLOBAL_MESSAGE_CASE (kGetFactoriesCountMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kGetFactoryMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kInitializeARAMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kCreateDocumentControllerMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kBindToDocumentControllerMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kCleanupBindingMethodID); + ARA_IPC_GLOBAL_MESSAGE_CASE (kUninitializeARAMethodID); + + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyDocumentController) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getFactory) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, beginEditing) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, endEditing) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, notifyModelUpdates) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, beginRestoringDocumentFromArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, endRestoringDocumentFromArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, storeDocumentToArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateDocumentProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createMusicalContext) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateMusicalContextProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateMusicalContextContent) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyMusicalContext) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createAudioSource) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateAudioSourceProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateAudioSourceContent) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, enableAudioSourceSamplesAccess) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, deactivateAudioSourceForUndoHistory) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyAudioSource) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createAudioModification) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, cloneAudioModification) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateAudioModificationProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, deactivateAudioModificationForUndoHistory) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyAudioModification) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createPlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updatePlaybackRegionProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyPlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isAudioSourceContentAvailable) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isAudioSourceContentAnalysisIncomplete) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, requestAudioSourceContentAnalysis) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getAudioSourceContentGrade) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createAudioSourceContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isAudioModificationContentAvailable) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getAudioModificationContentGrade) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createAudioModificationContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isPlaybackRegionContentAvailable) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getPlaybackRegionContentGrade) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createPlaybackRegionContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getContentReaderEventCount) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getContentReaderDataForEvent) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, createRegionSequence) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, updateRegionSequenceProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, destroyRegionSequence) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getPlaybackRegionHeadAndTailTime) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, restoreObjectsFromArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, storeObjectsToArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getProcessingAlgorithmsCount) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getProcessingAlgorithmProperties) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, getProcessingAlgorithmForAudioSource) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, requestProcessingAlgorithmForAudioSource) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isLicensedForCapabilities) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, storeAudioSourceToAudioFileChunk) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackRendererInterface, addPlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackRendererInterface, removePlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorRendererInterface, addPlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorRendererInterface, removePlaybackRegion) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorRendererInterface, addRegionSequence) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorRendererInterface, removeRegionSequence) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorViewInterface, notifySelection) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorViewInterface, notifyHideRegionSequences) + } + if (MethodID::isCustomMessageID (messageID)) + return "custom message"; + ARA_INTERNAL_ASSERT (false); + return "unknown message"; +} + +// for debugging: translate IDs of messages sent by the host back to human-readable text +inline const char* decodePlugInMessageID (const MessageID messageID, const bool omitInterfaceName = true) +{ + switch (messageID) + { + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAAudioAccessControllerInterface, createAudioReaderForSource) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAAudioAccessControllerInterface, readAudioSamples) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAAudioAccessControllerInterface, destroyAudioReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, getArchiveSize) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, readBytesFromArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, writeBytesToArchive) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, notifyDocumentArchivingProgress) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, notifyDocumentUnarchivingProgress) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAArchivingControllerInterface, getDocumentArchiveID) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, isMusicalContextContentAvailable) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, getMusicalContextContentGrade) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, createMusicalContextContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, isAudioSourceContentAvailable) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, getAudioSourceContentGrade) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, createAudioSourceContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, getContentReaderEventCount) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, getContentReaderDataForEvent) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAContentAccessControllerInterface, destroyContentReader) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyAudioSourceAnalysisProgress) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyAudioSourceContentChanged) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyAudioModificationContentChanged) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyPlaybackRegionContentChanged) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyDocumentDataChanged) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackControllerInterface, requestStartPlayback) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackControllerInterface, requestStopPlayback) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackControllerInterface, requestSetPlaybackPosition) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackControllerInterface, requestSetCycleRange) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackControllerInterface, requestEnableCycle) + } + if (MethodID::isCustomMessageID (messageID)) + return "custom message"; + ARA_INTERNAL_ASSERT (false); + return "unknown message"; +} + +#undef ARA_IPC_GLOBAL_MESSAGE_CASE +#undef ARA_IPC_INTERFACE_MESSAGE_CASE + +// helper template to identify pointers to ARA structs in message arguments +template +inline constexpr auto _IsStructPointerArg = !_IsRefType && std::is_pointer_v && !std::is_same_v; + +template, int> = 0> +inline void _encodeAndAppendHelper (MessageEncoder* encoder, const MessageArgumentKey argKey, const ArgT& argValue) +{ + _encodeAndAppend (encoder, argKey, argValue); +} +template, int> = 0> +inline void _encodeAndAppendHelper (MessageEncoder* encoder, const MessageArgumentKey argKey, const ArgT& argValue) +{ + if (argValue != nullptr) + _encodeAndAppend (encoder, argKey, *argValue); +} +inline void _encodeAndAppendHelper (MessageEncoder* /*encoder*/, const MessageArgumentKey /*argKey*/, const std::nullptr_t& /*argValue*/) +{} + +// caller side: create a message with the specified arguments +template +inline void encodeArguments (MessageEncoder* encoder, const Args &... args) +{ + MessageArgumentKey key { 0 }; + ((_encodeAndAppendHelper(encoder, key++, args)), ...); +} + +// caller side: decode the received reply to a sent message +template || !std::is_pod_v, bool> = true> +inline bool decodeReply (RetT& result, const MessageDecoder* decoder) +{ + return _readAndDecode (result, decoder, 0); +} +template && std::is_pod_v, bool> = true> +inline bool decodeReply (RetT& result, const MessageDecoder* decoder) +{ + return _ValueDecoder::decode (result, decoder); +} + + +// callee side: decode the arguments of a received message +template +inline void decodeArguments (const MessageDecoder* decoder, Args &... args) +{ + ARA_INTERNAL_ASSERT (decoder != nullptr); + _decodeArgumentsHelper (decoder, 0, args...); +} + +// callee side: wrapper for optional method arguments: first is the argument value, second if it was present +template +using OptionalArgument = typename std::pair; + +// callee side: encode the reply to a received message +template || !std::is_pod_v, bool> = true> +inline void encodeReply (MessageEncoder* encoder, const ValueT& value) +{ + ARA_INTERNAL_ASSERT (encoder != nullptr); + _encodeAndAppend (encoder, 0, value); +} +template && std::is_pod_v, bool> = true> +inline void encodeReply (MessageEncoder* encoder, const ValueT& value) +{ + ARA_INTERNAL_ASSERT (encoder != nullptr); + _ValueEncoder::encode (encoder, value); +} + + +//------------------------------------------------------------------------------ +// support for content readers +//------------------------------------------------------------------------------ + +inline void encodeContentEvent (MessageEncoder* encoder, const ARAContentType type, const void* eventData) +{ + switch (type) + { + case kARAContentTypeNotes: encodeReply (encoder, *static_cast (eventData)); break; + case kARAContentTypeTempoEntries: encodeReply (encoder, *static_cast (eventData)); break; + case kARAContentTypeBarSignatures: encodeReply (encoder, *static_cast (eventData)); break; + case kARAContentTypeStaticTuning: encodeReply (encoder, *static_cast (eventData)); break; + case kARAContentTypeKeySignatures: encodeReply (encoder, *static_cast (eventData)); break; + case kARAContentTypeSheetChords: encodeReply (encoder, *static_cast (eventData)); break; + default: ARA_INTERNAL_ASSERT (false && "content type not implemented yet"); break; + } +} + +class ContentEventDecoder +{ +public: + ContentEventDecoder (ARAContentType type) + : _decoderFunction { _getDecoderFunctionForContentType (type) }, + _contentString { _findStringMember (type) } + {} + + const void* decode (const MessageDecoder* decoder) + { + (this->*_decoderFunction) (decoder); + return &_eventStorage; + } + +private: + using _DecoderFunction = void (ContentEventDecoder::*) (const MessageDecoder*); + + template + void _decodeContentEvent (const MessageDecoder* decoder) + { + decodeReply (*reinterpret_cast (&this->_eventStorage), decoder); + if (this->_contentString != nullptr) + { + this->_stringStorage.assign (*this->_contentString); + *this->_contentString = this->_stringStorage.c_str (); + } + } + + static inline constexpr _DecoderFunction _getDecoderFunctionForContentType (const ARAContentType type) + { + switch (type) + { + case kARAContentTypeNotes: return &ContentEventDecoder::_decodeContentEvent::DataType>; + case kARAContentTypeTempoEntries: return &ContentEventDecoder::_decodeContentEvent::DataType>; + case kARAContentTypeBarSignatures: return &ContentEventDecoder::_decodeContentEvent::DataType>; + case kARAContentTypeStaticTuning: return &ContentEventDecoder::_decodeContentEvent::DataType>; + case kARAContentTypeKeySignatures: return &ContentEventDecoder::_decodeContentEvent::DataType>; + case kARAContentTypeSheetChords: return &ContentEventDecoder::_decodeContentEvent::DataType>; + default: ARA_INTERNAL_ASSERT (false); return nullptr; + } + } + + static inline constexpr size_t _getStringMemberOffsetForContentType (const ARAContentType type) + { + switch (type) + { + case kARAContentTypeStaticTuning: return offsetof (ARAContentTuning, name); + case kARAContentTypeKeySignatures: return offsetof (ARAContentKeySignature, name); + case kARAContentTypeSheetChords: return offsetof (ARAContentChord, name); + default: return 0; + } + } + + inline ARAUtf8String* _findStringMember (const ARAContentType type) + { + const auto offset { ContentEventDecoder::_getStringMemberOffsetForContentType (type) }; + if (offset == 0) + return nullptr; + return reinterpret_cast (reinterpret_cast (&this->_eventStorage) + offset); + } + +private: + _DecoderFunction const _decoderFunction; // instead of performing the switch (type) for each event, + // we select a templated member function in the c'tor + union + { + ARAContentTempoEntry _tempoEntry; + ARAContentBarSignature _barSignature; + ARAContentNote _note; + ARAContentTuning _tuning; + ARAContentKeySignature _keySignature; + ARAContentChord _chord; + } _eventStorage {}; + + ARAUtf8String* _contentString {}; + std::string _stringStorage {}; + + ARA_DISABLE_COPY_AND_MOVE (ContentEventDecoder) +}; + +//------------------------------------------------------------------------------ +// implementation helpers +//------------------------------------------------------------------------------ + +// helper base class to create and send messages, decoding reply if applicable +// It's possible to specify a CustomDecodeFunction as reply type to access an un-decoded reply +// if needed (e.g. to deal with memory ownership issue). +class RemoteCaller +{ +public: + using CustomDecodeFunction = std::function; + + explicit RemoteCaller (Connection* connection) noexcept + : _connection { connection } + {} + + template + void remoteCall (const MethodID methodID, const Args &... args) const + { + auto encoder { _connection->createEncoder () }; + encodeArguments (encoder.get (), args...); + _connection->sendMessage (methodID.getMessageID (), std::move (encoder)); + } + + template + void remoteCall (RetT& result, const MethodID methodID, const Args &... args) const + { + auto encoder { _connection->createEncoder () }; + encodeArguments (encoder.get (), args...); + _connection->sendMessage (methodID.getMessageID (), std::move (encoder), + [&result] (const MessageDecoder* decoder) + { + ARA_INTERNAL_ASSERT (decoder); + decodeReply (result, decoder); + }); + } + + template + void remoteCall (CustomDecodeFunction& decodeFunction, const MethodID methodID, const Args &... args) const + { + auto encoder { _connection->createEncoder () }; + encodeArguments (encoder.get (), args...); + _connection->sendMessage (methodID.getMessageID (), std::move (encoder), + [&decodeFunction] (const MessageDecoder* decoder) + { + ARA_INTERNAL_ASSERT (decoder); + decodeFunction (decoder); + }); + } + + bool receiverEndianessMatches () const { return _connection->receiverEndianessMatches (); } + + Connection* getConnection () const { return _connection; } + +private: + Connection* const _connection; +}; + + +} // namespace IPC +} // namespace ARA + +//! @} ARA_Library_IPC + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCEncoding_h diff --git a/IPC/ARAIPCMessage.h b/IPC/ARAIPCMessage.h new file mode 100644 index 0000000..9b3bf54 --- /dev/null +++ b/IPC/ARAIPCMessage.h @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCMessage.h +//! Abstractions shared by both the ARA IPC proxy host and plug-in +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCMessage_h +#define ARAIPCMessage_h + +#include "ARA_Library/IPC/ARAIPC.h" + +#if ARA_ENABLE_IPC + + +#include + + +//! @addtogroup ARA_Library_IPC +//! @{ + +namespace ARA { +namespace IPC { + + +//! ID type for messages +typedef ARAInt32 MessageID; + +//! the implementation reserves this range of IDs, any custom messages must be >= kMessageIDRangeStart and < kMessageIDRangeEnd +constexpr MessageID kReservedMessageIDRangeStart { 1 }; +constexpr MessageID kReservedMessageIDRangeEnd { 8*16*16 - 1 }; + + +//! key type for message dictionaries - negative keys are reserved for the implementation +typedef ARAInt32 MessageArgumentKey; + + +//! Message Encoder +//! @{ +class MessageEncoder +{ +public: + virtual ~MessageEncoder () = default; + + //! number types + //! The "size" variant will also be used for the pointer-sized ARA (host) refs. + //@{ + virtual void appendInt32 (MessageArgumentKey argKey, int32_t argValue) = 0; + virtual void appendInt64 (MessageArgumentKey argKey, int64_t argValue) = 0; + virtual void appendSize (MessageArgumentKey argKey, size_t argValue) = 0; + virtual void appendFloat (MessageArgumentKey argKey, float argValue) = 0; + virtual void appendDouble (MessageArgumentKey argKey, double argValue) = 0; + //@} + + //! UTF8-encoded C strings + virtual void appendString (MessageArgumentKey argKey, const char* argValue) = 0; + + //! raw bytes + //! As optimization, disable copying if the memory containing the bytes stays + //! alive&unchanged until the message has been sent. + virtual void appendBytes (MessageArgumentKey argKey, const uint8_t* argValue, size_t argSize, bool copy) = 0; + + //! sub-messages to encode compound types + virtual std::unique_ptr appendSubMessage (MessageArgumentKey argKey) = 0; +}; +//! @} + + +//! Message Decoder +//! @{ +class MessageDecoder +{ +public: + virtual ~MessageDecoder () = default; + + //! number types + //! The "size" variant will also be used for the pointer-sized ARA (host) refs. + //! Will return false and set argValue to 0 if key not found. + //@{ + virtual bool readInt32 (MessageArgumentKey argKey, int32_t* argValue) const = 0; + virtual bool readInt64 (MessageArgumentKey argKey, int64_t* argValue) const = 0; + virtual bool readSize (MessageArgumentKey argKey, size_t* argValue) const = 0; + virtual bool readFloat (MessageArgumentKey argKey, float* argValue) const = 0; + virtual bool readDouble (MessageArgumentKey argKey, double* argValue) const = 0; + //@} + + //! UTF8-encoded C strings + //! Will return false and set argValue to NULL if key not found. + //! Note that received string data is only valid as long as the message that + //! provided them is alive. + virtual bool readString (MessageArgumentKey argKey, const char ** argValue) const = 0; + + //! raw bytes + //! first query size, then provide a buffer large enough to copy the bytes to. + //! readBytesSize () will return false and set argSize to 0 if key not found. + //@{ + virtual bool readBytesSize (MessageArgumentKey argKey, size_t* argSize) const = 0; + virtual void readBytes (MessageArgumentKey argKey, uint8_t* argValue) const = 0; + //@} + + //! sub-messages to decode compound types + //! returns nullptr if key not found or if the value for the key is not representing a sub-message + virtual std::unique_ptr readSubMessage (MessageArgumentKey argKey) const = 0; + + //! test whether a given argument is present in the message + virtual bool hasDataForKey (MessageArgumentKey argKey) const = 0; +}; +//! @} + + +} // namespace IPC +} // namespace ARA + +//! @} ARA_Library_IPC + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCMessage_h diff --git a/IPC/ARAIPCProxyHost.cpp b/IPC/ARAIPCProxyHost.cpp new file mode 100644 index 0000000..68eaa98 --- /dev/null +++ b/IPC/ARAIPCProxyHost.cpp @@ -0,0 +1,1461 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyHost.cpp +//! implementation of host-side ARA IPC proxy host +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#include "ARAIPCProxyHost.h" + + +#if ARA_ENABLE_IPC + +#include "ARA_Library/IPC/ARAIPCEncoding.h" +#include "ARA_Library/Dispatch/ARAHostDispatch.h" +#include "ARA_Library/Dispatch/ARAPlugInDispatch.h" +#include "ARA_Library/Utilities/ARAStdVectorUtilities.h" + +#include +#include +#include +#include + +#if defined (__WINE__) + // Wine: need Win32 APIs for the initializeARAWithConfiguration thread trick + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #include + #include + #include + #include +#endif + + +// (assert logger defined inside ARA::IPC namespace below) + +namespace ARA { +namespace IPC { + +// Logging assert function passed to initializeARAWithConfiguration. +// Melodyne fires ARA assertions even in Release builds — logging them +// reveals threading violations and other setup errors (per Stefan Gretscher). +// Must be ms_abi since Melodyne (MSVC PE) calls it with Windows calling convention. +static void __attribute__((ms_abi)) _araAssertLogger (ARA::ARAAssertCategory category, + const void* /*problematicArgument*/, + const char* diagnosis) noexcept +{ + std::fprintf (stderr, "[ARA_ASSERT] category=%d: %s\n", + (int)category, diagnosis ? diagnosis : "(null)"); + std::fflush (stderr); +} + +namespace ProxyHostImpl { + +class AudioAccessController; +class ArchivingController; +class ContentAccessController; +class ModelUpdateController; +class PlaybackController; +class DocumentController; + + +/*******************************************************************************/ + +struct RemoteAudioSource +{ + ARAAudioSourceHostRef mainHostRef; + ARAAudioSourceRef plugInRef; + ARAChannelCount channelCount; +}; +ARA_MAP_REF (RemoteAudioSource, ARAAudioSourceRef) +ARA_MAP_HOST_REF (RemoteAudioSource, ARAAudioSourceHostRef) + +struct RemoteAudioReader +{ + RemoteAudioSource* audioSource; + ARAAudioReaderHostRef mainHostRef; + size_t sampleSize; + void (*swapFunction) (void* buffer, ARASampleCount sampleCount); +}; +ARA_MAP_HOST_REF (RemoteAudioReader, ARAAudioReaderHostRef) + +struct RemoteContentReader +{ + ARAContentReaderRef plugInRef; + ARAContentType contentType; +}; +ARA_MAP_REF (RemoteContentReader, ARAContentReaderRef) + +struct RemoteHostContentReader +{ + RemoteHostContentReader (ARAContentReaderHostRef hostRef, ARAContentType type) + : remoteHostRef { hostRef }, decoder { type } {} + + ARAContentReaderHostRef remoteHostRef; + ContentEventDecoder decoder; +}; +ARA_MAP_HOST_REF (RemoteHostContentReader, ARAContentReaderHostRef) + + +/*******************************************************************************/ +//! Implementation of AudioAccessControllerInterface that channels all calls through IPC +class AudioAccessController : public Host::AudioAccessControllerInterface, protected RemoteCaller +{ +public: + AudioAccessController (Connection* connection, ARAAudioAccessControllerHostRef remoteHostRef) noexcept + : RemoteCaller { connection }, _remoteHostRef { remoteHostRef } {} + + ARAAudioReaderHostRef createAudioReaderForSource (ARAAudioSourceHostRef audioSourceHostRef, bool use64BitSamples) noexcept override; + bool readAudioSamples (ARAAudioReaderHostRef audioReaderHostRef, ARASamplePosition samplePosition, ARASampleCount samplesPerChannel, void* const buffers[]) noexcept override; + void destroyAudioReader (ARAAudioReaderHostRef audioReaderHostRef) noexcept override; + +private: + ARAAudioAccessControllerHostRef _remoteHostRef; +}; + +/*******************************************************************************/ + +static void _swap (float* ptr) +{ + auto asIntPtr { reinterpret_cast (ptr) }; +#if defined (_MSC_VER) + *asIntPtr = _byteswap_ulong (*asIntPtr); +#elif defined (__GNUC__) + *asIntPtr = __builtin_bswap32 (*asIntPtr); +#else + #error "not implemented for this compiler." +#endif +} +static void _swap (double* ptr) +{ + auto asIntPtr { reinterpret_cast (ptr) }; +#if defined (_MSC_VER) + *asIntPtr = _byteswap_uint64 (*asIntPtr); +#elif defined (__GNUC__) + *asIntPtr = __builtin_bswap64 (*asIntPtr); +#else + #error "not implemented for this compiler." +#endif +} + +template +void _swapBuffer (void* buffer, ARASampleCount sampleCount) +{ + for (auto i { 0 }; i < sampleCount; ++i) + _swap (static_cast (buffer) + i); +} + +ARAAudioReaderHostRef AudioAccessController::createAudioReaderForSource (ARAAudioSourceHostRef audioSourceHostRef, bool use64BitSamples) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto remoteAudioReader { new RemoteAudioReader }; + remoteAudioReader->audioSource = fromHostRef (audioSourceHostRef); + remoteAudioReader->sampleSize = (use64BitSamples) ? sizeof (double) : sizeof (float); + if (receiverEndianessMatches ()) + remoteAudioReader->swapFunction = nullptr; + else + remoteAudioReader->swapFunction = (use64BitSamples) ? &_swapBuffer : &_swapBuffer; + remoteCall (remoteAudioReader->mainHostRef, ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, createAudioReaderForSource), + _remoteHostRef, remoteAudioReader->audioSource->mainHostRef, use64BitSamples); + return toHostRef (remoteAudioReader); +} + +bool AudioAccessController::readAudioSamples (ARAAudioReaderHostRef audioReaderHostRef, ARASamplePosition samplePosition, + ARASampleCount samplesPerChannel, void* const buffers[]) noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto remoteAudioReader { fromHostRef (audioReaderHostRef) }; + const auto channelCount { static_cast (remoteAudioReader->audioSource->channelCount) }; + + // recursively limit message size to keep IPC responsive + if (samplesPerChannel > 8192) + { + const auto samplesPerChannel1 { samplesPerChannel / 2 }; + const auto result1 { readAudioSamples (audioReaderHostRef, samplePosition, samplesPerChannel1, buffers) }; + + const auto samplesPerChannel2 { samplesPerChannel - samplesPerChannel1 }; + std::vector buffers2; + buffers2.reserve (channelCount); + for (auto i { 0U }; i < channelCount; ++i) + buffers2.emplace_back (static_cast (buffers[i]) + static_cast (samplesPerChannel1) * remoteAudioReader->sampleSize); + + if (result1) + { + return readAudioSamples (audioReaderHostRef, samplePosition + samplesPerChannel1, samplesPerChannel2, buffers2.data ()); + } + else + { + for (auto i { 0U }; i < channelCount; ++i) + std::memset (buffers2[i], 0, static_cast (samplesPerChannel2) * remoteAudioReader->sampleSize); + return false; + } + } + + // custom decoding to deal with float data memory ownership + bool success { false }; + CustomDecodeFunction customDecode { + [&success, &remoteAudioReader, &samplesPerChannel, &channelCount, &buffers] (const MessageDecoder* decoder) -> void + { + const auto bufferSize { remoteAudioReader->sampleSize * static_cast (samplesPerChannel) }; + std::vector resultSizes; + std::vector decoders; + resultSizes.reserve (channelCount); + decoders.reserve (channelCount); + for (auto i { 0U }; i < channelCount; ++i) + { + resultSizes.emplace_back (bufferSize); + decoders.emplace_back (static_cast (buffers[i]), resultSizes[i]); + } + + ArrayArgument channelData { decoders.data (), decoders.size () }; + success = decodeReply (channelData, decoder); + if (success) + ARA_INTERNAL_ASSERT (channelData.count == channelCount); + + for (auto i { 0U }; i < channelCount; ++i) + { + if (success) + { + ARA_INTERNAL_ASSERT (resultSizes[i] == bufferSize); + if (remoteAudioReader->swapFunction) + remoteAudioReader->swapFunction (buffers[i], samplesPerChannel); + } + else + { + std::memset (buffers[i], 0, bufferSize); + } + } + + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, readAudioSamples), + _remoteHostRef, remoteAudioReader->mainHostRef, samplePosition, samplesPerChannel); + return success; +} + +void AudioAccessController::destroyAudioReader (ARAAudioReaderHostRef audioReaderHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto remoteAudioReader { fromHostRef (audioReaderHostRef) }; + remoteCall (ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, destroyAudioReader), _remoteHostRef, remoteAudioReader->mainHostRef); + delete remoteAudioReader; +} + + +/*******************************************************************************/ +//! Implementation of ArchivingControllerInterface that channels all calls through IPC +class ArchivingController : public Host::ArchivingControllerInterface, protected RemoteCaller +{ +public: + ArchivingController (Connection* connection, ARAArchivingControllerHostRef remoteHostRef) noexcept + : RemoteCaller { connection }, _remoteHostRef { remoteHostRef } {} + + ARASize getArchiveSize (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept override; + bool readBytesFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, ARASize position, ARASize length, ARAByte buffer[]) noexcept override; + bool writeBytesToArchive (ARAArchiveWriterHostRef archiveWriterHostRef, ARASize position, ARASize length, const ARAByte buffer[]) noexcept override; + void notifyDocumentArchivingProgress (float value) noexcept override; + void notifyDocumentUnarchivingProgress (float value) noexcept override; + ARAPersistentID getDocumentArchiveID (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept override; + +private: + ARAArchivingControllerHostRef _remoteHostRef; + std::string _archiveID; +}; + +/*******************************************************************************/ + +ARASize ArchivingController::getArchiveSize (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARASize size; + remoteCall (size, ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, getArchiveSize), _remoteHostRef, archiveReaderHostRef); + return size; +} + +bool ArchivingController::readBytesFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, ARASize position, ARASize length, ARAByte buffer[]) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + // recursively limit message size to keep IPC responsive + if (length > 131072) + { + const auto length1 { length / 2 }; + const auto result1 { readBytesFromArchive (archiveReaderHostRef, position, length1, buffer) }; + + const auto length2 { length - length1 }; + buffer += length1; + if (result1) + { + return readBytesFromArchive (archiveReaderHostRef, position + length1, length2, buffer); + } + else + { + std::memset (buffer, 0, length2); + return false; + } + } + + auto resultLength { length }; + BytesDecoder writer { buffer, resultLength }; + remoteCall (writer, ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, readBytesFromArchive), + _remoteHostRef, archiveReaderHostRef, position, length); + if (resultLength == length) + { + return true; + } + else + { + std::memset (buffer, 0, length); + return false; + } +} + +bool ArchivingController::writeBytesToArchive (ARAArchiveWriterHostRef archiveWriterHostRef, ARASize position, ARASize length, const ARAByte buffer[]) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + // recursively limit message size to keep IPC responsive + if (length > 131072) + { + const auto length1 { length / 2 }; + const auto result1 { writeBytesToArchive (archiveWriterHostRef, position, length1, buffer) }; + + const auto length2 { length - length1 }; + buffer += length1; + if (result1) + return writeBytesToArchive (archiveWriterHostRef, position + length1, length2, buffer); + else + return false; + } + + ARABool success; + remoteCall (success, ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, writeBytesToArchive), + _remoteHostRef, archiveWriterHostRef, position, BytesEncoder { buffer, length, false }); + return (success != kARAFalse); +} + +void ArchivingController::notifyDocumentArchivingProgress (float value) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentArchivingProgress), _remoteHostRef, value); +} + +void ArchivingController::notifyDocumentUnarchivingProgress (float value) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentUnarchivingProgress), _remoteHostRef, value); +} + +ARAPersistentID ArchivingController::getDocumentArchiveID (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + CustomDecodeFunction customDecode { + [this] (const MessageDecoder* decoder) -> void + { + ARAPersistentID persistentID; + decodeReply (persistentID, decoder); + _archiveID.assign (persistentID); + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, getDocumentArchiveID), _remoteHostRef, archiveReaderHostRef); + return _archiveID.c_str (); +} + + +/*******************************************************************************/ +//! Implementation of ContentAccessControllerInterface that channels all calls through IPC +class ContentAccessController : public Host::ContentAccessControllerInterface, protected RemoteCaller +{ +public: + ContentAccessController (Connection* connection, ARAContentAccessControllerHostRef remoteHostRef) noexcept + : RemoteCaller { connection }, _remoteHostRef { remoteHostRef } {} + + bool isMusicalContextContentAvailable (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type) noexcept override; + ARAContentGrade getMusicalContextContentGrade (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type) noexcept override; + ARAContentReaderHostRef createMusicalContextContentReader (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept override; + bool isAudioSourceContentAvailable (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type) noexcept override; + ARAContentGrade getAudioSourceContentGrade (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type) noexcept override; + ARAContentReaderHostRef createAudioSourceContentReader (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept override; + ARAInt32 getContentReaderEventCount (ARAContentReaderHostRef contentReaderHostRef) noexcept override; + const void* getContentReaderDataForEvent (ARAContentReaderHostRef contentReaderHostRef, ARAInt32 eventIndex) noexcept override; + void destroyContentReader (ARAContentReaderHostRef contentReaderHostRef) noexcept override; + +private: + ARAContentAccessControllerHostRef _remoteHostRef; +}; + +/*******************************************************************************/ + +bool ContentAccessController::isMusicalContextContentAvailable (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, isMusicalContextContentAvailable), + _remoteHostRef, musicalContextHostRef, type); + return (result != kARAFalse); +} + +ARAContentGrade ContentAccessController::getMusicalContextContentGrade (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARAContentGrade grade; + remoteCall (grade, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getMusicalContextContentGrade), + _remoteHostRef, musicalContextHostRef, type); + return grade; +} + +ARAContentReaderHostRef ContentAccessController::createMusicalContextContentReader (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARAContentReaderHostRef contentReaderHostRef; + remoteCall (contentReaderHostRef, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, createMusicalContextContentReader), + _remoteHostRef, musicalContextHostRef, type, range); + auto contentReader { new RemoteHostContentReader (contentReaderHostRef, type) }; + return toHostRef (contentReader); +} + +bool ContentAccessController::isAudioSourceContentAvailable (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, isAudioSourceContentAvailable), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, type); + return (result != kARAFalse); +} + +ARAContentGrade ContentAccessController::getAudioSourceContentGrade (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARAContentGrade grade; + remoteCall (grade, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getAudioSourceContentGrade), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, type); + return grade; +} + +ARAContentReaderHostRef ContentAccessController::createAudioSourceContentReader (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARAContentReaderHostRef contentReaderHostRef; + remoteCall (contentReaderHostRef, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, createAudioSourceContentReader), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, type, range); + auto contentReader { new RemoteHostContentReader (contentReaderHostRef, type) }; + return toHostRef (contentReader); +} + +ARAInt32 ContentAccessController::getContentReaderEventCount (ARAContentReaderHostRef contentReaderHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromHostRef (contentReaderHostRef) }; + ARAInt32 count; + remoteCall (count, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderEventCount), + _remoteHostRef, contentReader->remoteHostRef); + return count; +} + +const void* ContentAccessController::getContentReaderDataForEvent (ARAContentReaderHostRef contentReaderHostRef, ARAInt32 eventIndex) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromHostRef (contentReaderHostRef) }; + const void* result {}; + CustomDecodeFunction customDecode { + [&result, &contentReader] (const MessageDecoder* decoder) -> void + { + result = contentReader->decoder.decode (decoder); + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderDataForEvent), + _remoteHostRef, contentReader->remoteHostRef, eventIndex); + return result; +} + +void ContentAccessController::destroyContentReader (ARAContentReaderHostRef contentReaderHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromHostRef (contentReaderHostRef) }; + remoteCall (ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, destroyContentReader), _remoteHostRef, contentReader->remoteHostRef); + delete contentReader; +} + + +/*******************************************************************************/ +//! Implementation of ModelUpdateControllerInterface that channels all calls through IPC +class ModelUpdateController : public Host::ModelUpdateControllerInterface, protected RemoteCaller +{ +public: + ModelUpdateController (Connection* connection, ARAModelUpdateControllerHostRef remoteHostRef) noexcept + : RemoteCaller { connection }, _remoteHostRef { remoteHostRef } {} + + void notifyAudioSourceAnalysisProgress (ARAAudioSourceHostRef audioSourceHostRef, ARAAnalysisProgressState state, float value) noexcept override; + void notifyAudioSourceContentChanged (ARAAudioSourceHostRef audioSourceHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept override; + void notifyAudioModificationContentChanged (ARAAudioModificationHostRef audioModificationHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept override; + void notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept override; + void notifyDocumentDataChanged () noexcept override; + void notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept override; + +private: + ARAModelUpdateControllerHostRef _remoteHostRef; +}; + +/*******************************************************************************/ + +void ModelUpdateController::notifyAudioSourceAnalysisProgress (ARAAudioSourceHostRef audioSourceHostRef, ARAAnalysisProgressState state, float value) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceAnalysisProgress), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, state, value); +} + +void ModelUpdateController::notifyAudioSourceContentChanged (ARAAudioSourceHostRef audioSourceHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceContentChanged), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, range, scopeFlags); +} + +void ModelUpdateController::notifyAudioModificationContentChanged (ARAAudioModificationHostRef audioModificationHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioModificationContentChanged), + _remoteHostRef, audioModificationHostRef, range, scopeFlags); +} + +void ModelUpdateController::notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyPlaybackRegionContentChanged), + _remoteHostRef, playbackRegionHostRef, range, scopeFlags); +} + +void ModelUpdateController::notifyDocumentDataChanged () noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyDocumentDataChanged), + _remoteHostRef); +} + +void ModelUpdateController::notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged), + _remoteHostRef, regionSequenceHostRef); +} + + +/*******************************************************************************/ +//! Implementation of PlaybackControllerInterface that channels all calls through IPC +class PlaybackController : public Host::PlaybackControllerInterface, protected RemoteCaller +{ +public: + PlaybackController (Connection* connection, ARAPlaybackControllerHostRef remoteHostRef) noexcept + : RemoteCaller { connection }, _remoteHostRef { remoteHostRef } {} + + void requestStartPlayback () noexcept override; + void requestStopPlayback () noexcept override; + void requestSetPlaybackPosition (ARATimePosition timePosition) noexcept override; + void requestSetCycleRange (ARATimePosition startTime, ARATimeDuration duration) noexcept override; + void requestEnableCycle (bool enable) noexcept override; + +private: + ARAPlaybackControllerHostRef _remoteHostRef; +}; + +/*******************************************************************************/ + +void PlaybackController::requestStartPlayback () noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestStartPlayback), _remoteHostRef); +} + +void PlaybackController::requestStopPlayback () noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestStopPlayback), _remoteHostRef); +} + +void PlaybackController::requestSetPlaybackPosition (ARATimePosition timePosition) noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestSetPlaybackPosition), _remoteHostRef, timePosition); +} + +void PlaybackController::requestSetCycleRange (ARATimePosition startTime, ARATimeDuration duration) noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestSetCycleRange), _remoteHostRef, startTime, duration); +} + +void PlaybackController::requestEnableCycle (bool enable) noexcept +{ +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestEnableCycle), _remoteHostRef, (enable) ? kARATrue : kARAFalse); +} + + +/*******************************************************************************/ +//! Extension of Host::DocumentController that also stores the host instance visible to the plug-in +class DocumentController : public Host::DocumentController +{ +public: + explicit DocumentController (const Host::DocumentControllerHostInstance* hostInstance, const ARADocumentControllerInstance* instance) noexcept + : Host::DocumentController { instance }, + _hostInstance { hostInstance } + {} + + const Host::DocumentControllerHostInstance* getHostInstance () { return _hostInstance; } + +private: + const Host::DocumentControllerHostInstance* _hostInstance; +}; +ARA_MAP_REF (DocumentController, ARADocumentControllerRef) + + +/*******************************************************************************/ +//! Wrapper class for a plug-in extension instance that can forward remote calls to its sub-interfaces +class PlugInExtension +{ +public: + explicit PlugInExtension (const ARAPlugInExtensionInstance* instance) + : _playbackRenderer { instance }, + _editorRenderer { instance }, + _editorView { instance } + {} + + // Getters for ARA specific plug-in role interfaces + Host::PlaybackRenderer* getPlaybackRenderer () { return &_playbackRenderer; } + Host::EditorRenderer* getEditorRenderer () { return &_editorRenderer; } + Host::EditorView* getEditorView () { return &_editorView; } + +private: + Host::PlaybackRenderer _playbackRenderer; + Host::EditorRenderer _editorRenderer; + Host::EditorView _editorView; +}; +ARA_MAP_REF (PlugInExtension, ARAPlugInExtensionRef, ARAPlaybackRendererRef, ARAEditorRendererRef, ARAEditorViewRef) + + +/*******************************************************************************/ + +} // namespace ProxyHostImpl +using namespace ProxyHostImpl; + +/*******************************************************************************/ + + +std::vector _factories {}; +ARAIPCBindingHandler _bindingHandler {}; + +void ARAIPCProxyHostAddFactory (const ARAFactory* factory) +{ + ARA_INTERNAL_ASSERT (factory->highestSupportedApiGeneration >= kARAAPIGeneration_2_0_Final); + ARA_INTERNAL_ASSERT (!ARA::contains (_factories, factory)); + + _factories.emplace_back (factory); +} + +void ARAIPCProxyHostSetBindingHandler (ARAIPCBindingHandler handler) +{ + _bindingHandler = handler; +} + +static const ARAFactory* getFactoryWithID (ARAPersistentID factoryID) +{ + for (const auto& factory : _factories) + { + if (0 == std::strcmp (factory->factoryID, factoryID)) + return factory; + } + + ARA_INTERNAL_ASSERT (false && "provided factory ID not previously registered via ARAIPCProxyHostAddFactory()"); + return nullptr; +} + + +/*******************************************************************************/ + +ProxyHost::ProxyHost (std::unique_ptr && connection) +: RemoteCaller { connection.get () }, + _connection { std::move (connection) } +{ + Connection::_setDebugMessageHint (false); +} + +void ProxyHost::handleReceivedMessage (const MessageID messageID, const MessageDecoder* const decoder, + MessageEncoder* const replyEncoder) +{ + // ARAFactory + if (messageID == kGetFactoriesCountMethodID) + { + encodeReply (replyEncoder, _factories.size ()); + } + else if (messageID == kGetFactoryMethodID) + { + ARASize index; + decodeArguments (decoder, index); + ARA_INTERNAL_ASSERT (index < _factories.size ()); + encodeReply (replyEncoder, *_factories[index]); + } + else if (messageID == kInitializeARAMethodID) + { + ARAPersistentID factoryID; + // Use a logging assert so Melodyne's threading checks produce output. + // Cast to ARAAssertFunction (which has no ABI annotation under wineg++) + // — Melodyne will call it via ms_abi, which our function provides. + static ARAAssertFunction _assertFn = + reinterpret_cast(_araAssertLogger); + static ARA::SizedStruct<&ARA::ARAInterfaceConfiguration::assertFunctionAddress> interfaceConfig; + interfaceConfig = { kARAAPIGeneration_2_0_Final, &_assertFn }; + decodeArguments (decoder, factoryID, interfaceConfig.desiredApiGeneration); + ARA_INTERNAL_ASSERT (interfaceConfig.desiredApiGeneration >= kARAAPIGeneration_2_0_Final); + + if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + { +#if defined (__WINE__) + // Under wineg++, ARA_CALL is empty (no ms_abi attribute), but + // Melodyne.vst3 is a real Windows PE compiled with MSVC that + // expects its arguments in %rcx (ms_abi). Cast explicitly. + // + // The creation thread (pluginMainLoop) already has: + // - OleInitialize (STA) + // - Win32 message queue (PeekMessageW) + // - 32MB stack (STACK_SIZE_PARAM_IS_A_RESERVATION) + // Call initFn directly here — adding another thread creates a second + // STA which causes COM cross-apartment failures under Wine. + // + // Melodyne's std::thread objects may call their destructor from + // within the thread they represent, causing join()-on-self → + // EINVAL → std::system_error → std::terminate. Install a terminate + // handler that exits the crashing thread silently instead of + // aborting the whole process. The terminate handler must be set + // process-wide before initFn runs so Melodyne's threads use it. + auto oldTerminate = std::set_terminate([] () noexcept { + // A Melodyne thread (or our std::thread) has an unhandled + // exception. We can't continue safely, so exit cleanly with + // status 0 so the host doesn't treat it as a crash. + // Use _exit() to skip destructors and avoid re-entrancy. + std::fprintf(stderr, "[ProxyHost] terminate intercepted — exiting cleanly\n"); + std::fflush(stderr); + _exit(0); + }); + + // Also handle SIGSEGV from Wine's access violation handler — + // Melodyne's background threads may fault accessing stale data. + // The signal handler must use siglongjmp or just return to let + // the faulting thread exit. + struct sigaction sa_segv{}, old_segv{}; + sa_segv.sa_handler = [](int){ pthread_exit(nullptr); }; + sa_segv.sa_flags = SA_RESETHAND; + sigaction(SIGSEGV, &sa_segv, &old_segv); + + struct sigaction sa_abrt{}, old_abrt{}; + sa_abrt.sa_handler = [](int){ pthread_exit(nullptr); }; + sa_abrt.sa_flags = SA_RESETHAND; + sigaction(SIGABRT, &sa_abrt, &old_abrt); + + std::fprintf (stderr, "[ProxyHost] calling initializeARAWithConfiguration on creation thread\n"); + std::fflush (stderr); + using InitFn = void (__attribute__((ms_abi)) *) (const ARAInterfaceConfiguration*); + auto initFn = reinterpret_cast( + reinterpret_cast(factory->initializeARAWithConfiguration)); + try { + initFn (&interfaceConfig); + } catch (...) { + std::fprintf (stderr, "[ProxyHost] initFn exception swallowed\n"); + std::fflush (stderr); + } + std::fprintf (stderr, "[ProxyHost] initializeARAWithConfiguration returned\n"); + std::fflush (stderr); + + // Restore original terminate handler and signal handlers + std::set_terminate(oldTerminate); + sigaction(SIGSEGV, &old_segv, nullptr); + sigaction(SIGABRT, &old_abrt, nullptr); +#elif defined (_WIN32) + factory->initializeARAWithConfiguration (&interfaceConfig); +#else + factory->initializeARAWithConfiguration (&interfaceConfig); +#endif + } + } + else if (messageID == kCreateDocumentControllerMethodID) + { + ARAPersistentID factoryID; + ARAAudioAccessControllerHostRef audioAccessControllerHostRef; + ARAArchivingControllerHostRef archivingControllerHostRef; + ARABool provideContentAccessController; + ARAContentAccessControllerHostRef contentAccessControllerHostRef; + ARABool provideModelUpdateController; + ARAModelUpdateControllerHostRef modelUpdateControllerHostRef; + ARABool providePlaybackController; + ARAPlaybackControllerHostRef playbackControllerHostRef; + ARADocumentProperties properties; + decodeArguments (decoder, factoryID, + audioAccessControllerHostRef, archivingControllerHostRef, + provideContentAccessController, contentAccessControllerHostRef, + provideModelUpdateController, modelUpdateControllerHostRef, + providePlaybackController, playbackControllerHostRef, + properties); + + if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + { + const auto audioAccessController { new AudioAccessController { getConnection (), audioAccessControllerHostRef } }; + const auto archivingController { new ArchivingController { getConnection (), archivingControllerHostRef } }; + const auto contentAccessController { (provideContentAccessController != kARAFalse) ? new ContentAccessController { getConnection (), contentAccessControllerHostRef } : nullptr }; + const auto modelUpdateController { (provideModelUpdateController != kARAFalse) ? new ModelUpdateController { getConnection (), modelUpdateControllerHostRef } : nullptr }; + const auto playbackController { (providePlaybackController != kARAFalse) ? new PlaybackController { getConnection (), playbackControllerHostRef } : nullptr }; + + const auto hostInstance { new Host::DocumentControllerHostInstance { audioAccessController, archivingController, + contentAccessController, modelUpdateController, playbackController } }; + + auto documentControllerInstance { factory->createDocumentControllerWithDocument (hostInstance, &properties) }; + ARA_VALIDATE_API_CONDITION (documentControllerInstance != nullptr); + ARA_VALIDATE_API_INTERFACE (documentControllerInstance->documentControllerInterface, ARADocumentControllerInterface); + auto documentController { new DocumentController (hostInstance, documentControllerInstance) }; + encodeReply (replyEncoder, ARADocumentControllerRef { toRef (documentController) }); + } + } + else if (messageID == kBindToDocumentControllerMethodID) + { + ARAIPCPlugInInstanceRef plugInInstanceRef; + ARADocumentControllerRef controllerRef; + ARAPlugInInstanceRoleFlags knownRoles; + ARAPlugInInstanceRoleFlags assignedRoles; + decodeArguments (decoder, plugInInstanceRef, controllerRef, knownRoles, assignedRoles); + const auto plugInExtensionInstance { _bindingHandler (plugInInstanceRef, fromRef (controllerRef)->getRef (), knownRoles, assignedRoles) }; + const ARAPlugInExtensionRef plugInExtensionRef { toRef ( new PlugInExtension { plugInExtensionInstance } )}; + ARA_INTERNAL_ASSERT (plugInExtensionInstance->plugInExtensionRef == nullptr); // plugInExtensionRef must not be used when ARA 2 is active + const_cast (plugInExtensionInstance)->plugInExtensionRef = plugInExtensionRef; + encodeReply (replyEncoder, plugInExtensionRef ); + } + else if (messageID == kCleanupBindingMethodID) + { + ARAPlugInExtensionRef plugInExtensionRef; + decodeArguments (decoder, plugInExtensionRef); + delete fromRef (plugInExtensionRef); + } + else if (messageID == kUninitializeARAMethodID) + { + ARAPersistentID factoryID; + decodeArguments (decoder, factoryID); + + if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + factory->uninitializeARA (); + } + + //ARADocumentControllerInterface + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyDocumentController)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + auto documentController { fromRef (controllerRef) }; + documentController->destroyDocumentController (); + + delete documentController->getHostInstance ()->getPlaybackController (); + delete documentController->getHostInstance ()->getModelUpdateController (); + delete documentController->getHostInstance ()->getContentAccessController (); + delete documentController->getHostInstance ()->getArchivingController (); + delete documentController->getHostInstance ()->getAudioAccessController (); + delete documentController->getHostInstance (); + delete documentController; + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getFactory)) + { + ARA_INTERNAL_ASSERT (false && "should never be queried here but instead cached from companion API upon setup"); + + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + encodeReply (replyEncoder, *(fromRef (controllerRef)->getFactory ())); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, beginEditing)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->beginEditing (); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, endEditing)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->endEditing (); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, notifyModelUpdates)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->notifyModelUpdates (); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, restoreObjectsFromArchive)) + { + ARADocumentControllerRef controllerRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + OptionalArgument filter; + decodeArguments (decoder, controllerRef, archiveReaderHostRef, filter); + + encodeReply (replyEncoder, fromRef (controllerRef)->restoreObjectsFromArchive (archiveReaderHostRef, (filter.second) ? &filter.first : nullptr) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, storeObjectsToArchive)) + { + ARADocumentControllerRef controllerRef; + ARAArchiveWriterHostRef archiveWriterHostRef; + OptionalArgument filter; + decodeArguments (decoder, controllerRef, archiveWriterHostRef, filter); + + std::vector audioSourceRefs; + if (filter.second && (filter.first.audioSourceRefsCount > 0)) + { + audioSourceRefs.reserve (filter.first.audioSourceRefsCount); + for (auto i { 0U }; i < filter.first.audioSourceRefsCount; ++i) + audioSourceRefs.emplace_back (fromRef (filter.first.audioSourceRefs[i])->plugInRef); + + filter.first.audioSourceRefs = audioSourceRefs.data (); + } + + encodeReply (replyEncoder, fromRef (controllerRef)->storeObjectsToArchive (archiveWriterHostRef, (filter.second) ? &filter.first : nullptr) ? kARATrue : kARAFalse); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateDocumentProperties)) + { + ARADocumentControllerRef controllerRef; + ARADocumentProperties properties; + decodeArguments (decoder, controllerRef, properties); + + fromRef (controllerRef)->updateDocumentProperties (&properties); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createMusicalContext)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextHostRef hostRef; + ARAMusicalContextProperties properties; + decodeArguments (decoder, controllerRef, hostRef, properties); + + encodeReply (replyEncoder, fromRef (controllerRef)->createMusicalContext (hostRef, &properties)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextProperties)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextRef musicalContextRef; + ARAMusicalContextProperties properties; + decodeArguments (decoder, controllerRef, musicalContextRef, properties); + + fromRef (controllerRef)->updateMusicalContextProperties (musicalContextRef, &properties); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextContent)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextRef musicalContextRef; + OptionalArgument range; + ARAContentUpdateFlags flags; + decodeArguments (decoder, controllerRef, musicalContextRef, range, flags); + + fromRef (controllerRef)->updateMusicalContextContent (musicalContextRef, (range.second) ? &range.first : nullptr, flags); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyMusicalContext)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextRef musicalContextRef; + decodeArguments (decoder, controllerRef, musicalContextRef); + + fromRef (controllerRef)->destroyMusicalContext (musicalContextRef); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createRegionSequence)) + { + ARADocumentControllerRef controllerRef; + ARARegionSequenceHostRef hostRef; + ARARegionSequenceProperties properties; + decodeArguments (decoder, controllerRef, hostRef, properties); + + encodeReply (replyEncoder, fromRef (controllerRef)->createRegionSequence (hostRef, &properties)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateRegionSequenceProperties)) + { + ARADocumentControllerRef controllerRef; + ARARegionSequenceRef regionSequenceRef; + ARARegionSequenceProperties properties; + decodeArguments (decoder, controllerRef, regionSequenceRef, properties); + + fromRef (controllerRef)->updateRegionSequenceProperties (regionSequenceRef, &properties); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyRegionSequence)) + { + ARADocumentControllerRef controllerRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, controllerRef, regionSequenceRef); + + fromRef (controllerRef)->destroyRegionSequence (regionSequenceRef); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioSource)) + { + auto remoteAudioSource { new RemoteAudioSource }; + + ARADocumentControllerRef controllerRef; + ARAAudioSourceProperties properties; + decodeArguments (decoder, controllerRef, remoteAudioSource->mainHostRef, properties); + + remoteAudioSource->channelCount = properties.channelCount; + remoteAudioSource->plugInRef = fromRef (controllerRef)->createAudioSource (toHostRef (remoteAudioSource), &properties); + + encodeReply (replyEncoder, ARAAudioSourceRef { toRef (remoteAudioSource) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioSourceProperties)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAAudioSourceProperties properties; + decodeArguments (decoder, controllerRef, audioSourceRef, properties); + + fromRef (controllerRef)->updateAudioSourceProperties (fromRef (audioSourceRef)->plugInRef, &properties); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioSourceContent)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + OptionalArgument range; + ARAContentUpdateFlags flags; + decodeArguments (decoder, controllerRef, audioSourceRef, range, flags); + + fromRef (controllerRef)->updateAudioSourceContent (fromRef (audioSourceRef)->plugInRef, (range.second) ? &range.first : nullptr, flags); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, enableAudioSourceSamplesAccess)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARABool enable; + decodeArguments (decoder, controllerRef, audioSourceRef, enable); + + fromRef (controllerRef)->enableAudioSourceSamplesAccess (fromRef (audioSourceRef)->plugInRef, (enable) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, deactivateAudioSourceForUndoHistory)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARABool deactivate; + decodeArguments (decoder, controllerRef, audioSourceRef, deactivate); + + fromRef (controllerRef)->deactivateAudioSourceForUndoHistory (fromRef (audioSourceRef)->plugInRef, (deactivate) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, storeAudioSourceToAudioFileChunk)) + { + ARADocumentControllerRef controllerRef; + ARAArchiveWriterHostRef archiveWriterHostRef; + ARAAudioSourceRef audioSourceRef; + decodeArguments (decoder, controllerRef, archiveWriterHostRef, audioSourceRef); + + StoreAudioSourceToAudioFileChunkReply reply; + bool openAutomatically; + reply.result = (fromRef (controllerRef)->storeAudioSourceToAudioFileChunk (archiveWriterHostRef, fromRef (audioSourceRef)->plugInRef, + &reply.documentArchiveID, &openAutomatically)) ? kARATrue : kARAFalse; + reply.openAutomatically = (openAutomatically) ? kARATrue : kARAFalse; + encodeReply (replyEncoder, reply); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAnalysisIncomplete)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + encodeReply (replyEncoder, fromRef (controllerRef)->isAudioSourceContentAnalysisIncomplete (fromRef (audioSourceRef)->plugInRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, requestAudioSourceContentAnalysis)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + std::vector contentTypes; + decodeArguments (decoder, controllerRef, audioSourceRef, contentTypes); + + fromRef (controllerRef)->requestAudioSourceContentAnalysis (fromRef (audioSourceRef)->plugInRef, contentTypes.size (), contentTypes.data ()); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioSourceContentAvailable (fromRef (audioSourceRef)->plugInRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getAudioSourceContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + encodeReply (replyEncoder, fromRef (controllerRef)->getAudioSourceContentGrade (fromRef (audioSourceRef)->plugInRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioSourceContentReader)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + OptionalArgument range; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType, range); + + auto remoteContentReader { new RemoteContentReader }; + remoteContentReader->plugInRef = fromRef (controllerRef)->createAudioSourceContentReader (fromRef (audioSourceRef)->plugInRef, contentType, (range.second) ? &range.first : nullptr); + remoteContentReader->contentType = contentType; + encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyAudioSource)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + decodeArguments (decoder, controllerRef, audioSourceRef); + + auto remoteAudioSource { fromRef (audioSourceRef) }; + fromRef (controllerRef)->destroyAudioSource (remoteAudioSource->plugInRef); + + delete remoteAudioSource; + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAAudioModificationHostRef hostRef; + ARAAudioModificationProperties properties; + decodeArguments (decoder, controllerRef, audioSourceRef, hostRef, properties); + + encodeReply (replyEncoder, fromRef (controllerRef)->createAudioModification (fromRef (audioSourceRef)->plugInRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, cloneAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAAudioModificationHostRef hostRef; + ARAAudioModificationProperties properties; + decodeArguments (decoder, controllerRef, audioModificationRef, hostRef, properties); + + encodeReply (replyEncoder, fromRef (controllerRef)->cloneAudioModification (audioModificationRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioModificationProperties)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAAudioModificationProperties properties; + decodeArguments (decoder, controllerRef, audioModificationRef, properties); + + fromRef (controllerRef)->updateAudioModificationProperties (audioModificationRef, &properties); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + decodeArguments (decoder, controllerRef, audioModificationRef); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioModificationPreservingAudioSourceSignal (audioModificationRef)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, deactivateAudioModificationForUndoHistory)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARABool deactivate; + decodeArguments (decoder, controllerRef, audioModificationRef, deactivate); + + fromRef (controllerRef)->deactivateAudioModificationForUndoHistory (audioModificationRef, (deactivate) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioModificationContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioModificationRef, contentType); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioModificationContentAvailable (audioModificationRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getAudioModificationContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioModificationRef, contentType); + + encodeReply (replyEncoder, fromRef (controllerRef)->getAudioModificationContentGrade (audioModificationRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioModificationContentReader)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAContentType contentType; + OptionalArgument range; + decodeArguments (decoder, controllerRef, audioModificationRef, contentType, range); + + auto remoteContentReader { new RemoteContentReader }; + remoteContentReader->plugInRef = fromRef (controllerRef)->createAudioModificationContentReader (audioModificationRef, contentType, (range.second) ? &range.first : nullptr); + remoteContentReader->contentType = contentType; + encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + decodeArguments (decoder, controllerRef, audioModificationRef); + + fromRef (controllerRef)->destroyAudioModification (audioModificationRef); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createPlaybackRegion)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAPlaybackRegionHostRef hostRef; + ARAPlaybackRegionProperties properties; + decodeArguments (decoder, controllerRef, audioModificationRef, hostRef, properties); + + encodeReply (replyEncoder, fromRef (controllerRef)->createPlaybackRegion (audioModificationRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updatePlaybackRegionProperties)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAPlaybackRegionProperties properties; + decodeArguments (decoder, controllerRef, playbackRegionRef, properties); + + fromRef (controllerRef)->updatePlaybackRegionProperties (playbackRegionRef, &properties); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, controllerRef, playbackRegionRef); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isPlaybackRegionPreservingAudioSourceSignal (playbackRegionRef)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionHeadAndTailTime)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARABool wantsHeadTime; + ARABool wantsTailTime; + decodeArguments (decoder, controllerRef, playbackRegionRef, wantsHeadTime, wantsTailTime); + + GetPlaybackRegionHeadAndTailTimeReply reply { 0.0, 0.0 }; + fromRef (controllerRef)->getPlaybackRegionHeadAndTailTime (playbackRegionRef, (wantsHeadTime != kARAFalse) ? &reply.headTime : nullptr, + (wantsTailTime != kARAFalse) ? &reply.tailTime : nullptr); + encodeReply (replyEncoder, reply); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, playbackRegionRef, contentType); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isPlaybackRegionContentAvailable (playbackRegionRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, playbackRegionRef, contentType); + + encodeReply (replyEncoder, fromRef (controllerRef)->getPlaybackRegionContentGrade (playbackRegionRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createPlaybackRegionContentReader)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAContentType contentType; + OptionalArgument range; + decodeArguments (decoder, controllerRef, playbackRegionRef, contentType, range); + + auto remoteContentReader { new RemoteContentReader }; + remoteContentReader->plugInRef = fromRef (controllerRef)->createPlaybackRegionContentReader (playbackRegionRef, contentType, (range.second) ? &range.first : nullptr); + remoteContentReader->contentType = contentType; + encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyPlaybackRegion)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, controllerRef, playbackRegionRef); + + fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getContentReaderEventCount)) + { + ARADocumentControllerRef controllerRef; + ARAContentReaderRef contentReaderRef; + decodeArguments (decoder, controllerRef, contentReaderRef); + + encodeReply (replyEncoder, fromRef (controllerRef)->getContentReaderEventCount (fromRef (contentReaderRef)->plugInRef)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getContentReaderDataForEvent)) + { + ARADocumentControllerRef controllerRef; + ARAContentReaderRef contentReaderRef; + ARAInt32 eventIndex; + decodeArguments (decoder, controllerRef, contentReaderRef, eventIndex); + + auto remoteContentReader { fromRef (contentReaderRef) }; + const void* eventData { fromRef (controllerRef)->getContentReaderDataForEvent (remoteContentReader->plugInRef, eventIndex) }; + encodeContentEvent (replyEncoder, remoteContentReader->contentType, eventData); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyContentReader)) + { + ARADocumentControllerRef controllerRef; + ARAContentReaderRef contentReaderRef; + decodeArguments (decoder, controllerRef, contentReaderRef); + + auto remoteContentReader { fromRef (contentReaderRef) }; + fromRef (controllerRef)->destroyContentReader (remoteContentReader->plugInRef); + + delete remoteContentReader; + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmsCount)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + encodeReply (replyEncoder, fromRef (controllerRef)->getProcessingAlgorithmsCount ()); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmProperties)) + { + ARADocumentControllerRef controllerRef; + ARAInt32 algorithmIndex; + decodeArguments (decoder, controllerRef, algorithmIndex); + + encodeReply (replyEncoder, *(fromRef (controllerRef)->getProcessingAlgorithmProperties (algorithmIndex))); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmForAudioSource)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + decodeArguments (decoder, controllerRef, audioSourceRef); + + encodeReply (replyEncoder, fromRef (controllerRef)->getProcessingAlgorithmForAudioSource (fromRef (audioSourceRef)->plugInRef)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, requestProcessingAlgorithmForAudioSource)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAInt32 algorithmIndex; + decodeArguments (decoder, controllerRef, audioSourceRef, algorithmIndex); + + fromRef (controllerRef)->requestProcessingAlgorithmForAudioSource (fromRef (audioSourceRef)->plugInRef, algorithmIndex); + } + + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isLicensedForCapabilities)) + { + ARADocumentControllerRef controllerRef; + ARABool runModalActivationDialogIfNeeded; + std::vector types; + ARAPlaybackTransformationFlags transformationFlags; + decodeArguments (decoder, controllerRef, runModalActivationDialogIfNeeded, types, transformationFlags); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isLicensedForCapabilities ((runModalActivationDialogIfNeeded != kARAFalse), + types.size (), types.data (), transformationFlags)) ? kARATrue : kARAFalse); + } + + // ARAPlaybackRendererInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackRendererInterface, addPlaybackRegion)) + { + ARAPlaybackRendererRef playbackRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, playbackRendererRef, playbackRegionRef); + + fromRef (playbackRendererRef)->getPlaybackRenderer ()->addPlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackRendererInterface, removePlaybackRegion)) + { + ARAPlaybackRendererRef playbackRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, playbackRendererRef, playbackRegionRef); + + fromRef (playbackRendererRef)->getPlaybackRenderer ()->removePlaybackRegion (playbackRegionRef); + } + + // ARAEditorRendererInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorRendererInterface, addPlaybackRegion)) + { + ARAEditorRendererRef editorRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, editorRendererRef, playbackRegionRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->addPlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorRendererInterface, removePlaybackRegion)) + { + ARAEditorRendererRef editorRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, editorRendererRef, playbackRegionRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->removePlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorRendererInterface, addRegionSequence)) + { + ARAEditorRendererRef editorRendererRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, editorRendererRef, regionSequenceRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->addRegionSequence (regionSequenceRef); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorRendererInterface, removeRegionSequence)) + { + ARAEditorRendererRef editorRendererRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, editorRendererRef, regionSequenceRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->removeRegionSequence (regionSequenceRef); + } + + // ARAEditorViewInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorViewInterface, notifySelection)) + { + ARAEditorViewRef editorViewRef; + ARAViewSelection selection; + decodeArguments (decoder, editorViewRef, selection); + + fromRef (editorViewRef)->getEditorView ()->notifySelection (&selection); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAEditorViewInterface, notifyHideRegionSequences)) + { + ARAEditorViewRef editorViewRef; + std::vector regionSequenceRefs; + decodeArguments (decoder, editorViewRef, regionSequenceRefs); + + fromRef (editorViewRef)->getEditorView ()->notifyHideRegionSequences (regionSequenceRefs.size (), regionSequenceRefs.data ()); + } + else + { + ARA_INTERNAL_ASSERT (false && "unhandled message ID"); + } +} + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCProxyHost.h b/IPC/ARAIPCProxyHost.h new file mode 100644 index 0000000..06f7271 --- /dev/null +++ b/IPC/ARAIPCProxyHost.h @@ -0,0 +1,79 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyHost.h +//! implementation of host-side ARA IPC proxy host +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCProxyHost_h +#define ARAIPCProxyHost_h + +#if defined(__cplusplus) +#include "ARA_Library/IPC/ARAIPCConnection.h" +#include "ARA_Library/IPC/ARAIPCEncoding.h" +#else +#include "ARA_Library/IPC/ARAIPC.h" +#endif + +#if ARA_ENABLE_IPC + + +//! @addtogroup ARA_Library_IPC +//! @{ + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { + +//! plug-in side implementation of MessageHandler +//! the plug-in uses the C interface below, but this class will be subclassed by specialized implementations +class ProxyHost : public RemoteCaller +{ +protected: + explicit ProxyHost (std::unique_ptr && connection); + +public: + void handleReceivedMessage (const MessageID messageID, const MessageDecoder* const decoder, + MessageEncoder* const replyEncoder); +private: + const std::unique_ptr _connection; +}; +#endif + + +//! callback that the proxy uses to execute the binding of an opaque companion API plug-in instance to the given document controller +typedef const ARAPlugInExtensionInstance * (*ARAIPCBindingHandler) (ARAIPCPlugInInstanceRef plugInInstanceRef, + ARADocumentControllerRef controllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles); + +//! static configuration: add the ARA factories that the proxy host will wrap +void ARAIPCProxyHostAddFactory(const ARAFactory * factory); + +//! static configuration: set the callback to execute the binding of companion API plug-in instances to ARA document controllers +void ARAIPCProxyHostSetBindingHandler(ARAIPCBindingHandler handler); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + +//! @} ARA_Library_IPC + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCProxyHost_h diff --git a/IPC/ARAIPCProxyPlugIn.cpp b/IPC/ARAIPCProxyPlugIn.cpp new file mode 100644 index 0000000..6b71eb4 --- /dev/null +++ b/IPC/ARAIPCProxyPlugIn.cpp @@ -0,0 +1,1871 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyPlugIn.cpp +//! implementation of host-side ARA IPC proxy plug-in +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#include "ARAIPCProxyPlugIn.h" + + +#if ARA_ENABLE_IPC + +#include "ARA_Library/IPC/ARAIPCEncoding.h" +#include "ARA_Library/Dispatch/ARAPlugInDispatch.h" +#include "ARA_Library/Dispatch/ARAHostDispatch.h" + +#include +#include +#include +#include + + +/*******************************************************************************/ +// configuration switches for debug output +// each can be defined as a nonzero integer to enable the associated logging + +// log each entry from the host into the document controller (except for notifyModelUpdates (), which is called too often) +#ifndef ARA_ENABLE_HOST_ENTRY_LOG + #define ARA_ENABLE_HOST_ENTRY_LOG 0 +#endif + +// log the creation and destruction of plug-in objects +#ifndef ARA_ENABLE_OBJECT_LIFETIME_LOG + #define ARA_ENABLE_OBJECT_LIFETIME_LOG 0 +#endif + +// conditional logging helper functions based on the above switches +#if ARA_ENABLE_HOST_ENTRY_LOG + #define ARA_LOG_HOST_ENTRY(object) ARA_LOG ("Host calls into %s (%p)", __FUNCTION__, object); +#else + #define ARA_LOG_HOST_ENTRY(object) ((void) 0) +#endif + +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + #define ARA_LOG_MODELOBJECT_LIFETIME(message, object) ARA_LOG ("Plug success: document controller %p %s %p", object->getDocumentController (), message, object) +#else + #define ARA_LOG_MODELOBJECT_LIFETIME(message, object) ((void) 0) +#endif + + +/*******************************************************************************/ + +namespace ARA { +namespace IPC { +namespace ProxyPlugInImpl { + +struct AudioSource; +struct ContentReader; +struct HostContentReader; +struct HostAudioReader; +class DocumentController; +class PlaybackRenderer; +class EditorRenderer; +class EditorView; +class PlugInExtension; + + +/*******************************************************************************/ +// ObjectRef validation helper class - empty class unless ARA_VALIDATE_API_CALLS is enabled + +template +class InstanceValidator +{ +#if ARA_VALIDATE_API_CALLS +protected: + inline InstanceValidator () noexcept + { + auto result { _instances.insert (this) }; + ARA_INTERNAL_ASSERT (result.second); + } + + inline ~InstanceValidator () + { + auto it { _instances.find (this) }; + ARA_INTERNAL_ASSERT (it != _instances.end ()); + _instances.erase (it); + } + +public: + static inline bool isValid (const InstanceValidator* instance) + { + return _instances.find (instance) != _instances.end (); + } + +private: + static std::set _instances; +#endif +}; + +#if ARA_VALIDATE_API_CALLS +template +std::set*> InstanceValidator::_instances; + +template +inline bool isValidInstance (const SubclassT* instance) +{ + return InstanceValidator::isValid (instance); +} +#endif + + +/*******************************************************************************/ + +struct AudioSource : public InstanceValidator +{ + AudioSource (ARAAudioSourceHostRef hostRef, ARAAudioSourceRef remoteRef, ARAChannelCount channelCount +#if ARA_VALIDATE_API_CALLS + , ARASampleCount sampleCount, ARASampleRate sampleRate +#endif + ) + : _hostRef { hostRef }, _remoteRef { remoteRef }, _channelCount { channelCount } +#if ARA_VALIDATE_API_CALLS + , _sampleCount { sampleCount }, _sampleRate { sampleRate } +#endif + {} + + ARAAudioSourceHostRef _hostRef; + ARAAudioSourceRef _remoteRef; + ARAChannelCount _channelCount; +#if ARA_VALIDATE_API_CALLS + ARASampleCount _sampleCount; + ARASampleRate _sampleRate; +#endif +}; +ARA_MAP_REF (AudioSource, ARAAudioSourceRef) +ARA_MAP_HOST_REF (AudioSource, ARAAudioSourceHostRef) + +struct ContentReader : public InstanceValidator +{ + ContentReader (ARAContentReaderRef remoteRef, ARAContentType type) + : _remoteRef { remoteRef }, _decoder { type } + {} + + ARAContentReaderRef _remoteRef; + ContentEventDecoder _decoder; +}; +ARA_MAP_REF (ContentReader, ARAContentReaderRef) + +struct HostContentReader +{ + ARAContentReaderHostRef hostRef; + ARAContentType contentType; +}; +ARA_MAP_HOST_REF (HostContentReader, ARAContentReaderHostRef) + +struct HostAudioReader +{ + AudioSource* audioSource; + ARAAudioReaderHostRef hostRef; + size_t sampleSize; +}; +ARA_MAP_HOST_REF (HostAudioReader, ARAAudioReaderHostRef) + + +/*******************************************************************************/ +// Implementation of DocumentControllerInterface that channels all calls through IPC + +class DocumentController : public PlugIn::DocumentControllerInterface, public RemoteCaller, public InstanceValidator +{ +public: + DocumentController (Connection* connection, const ARAFactory* factory, const ARADocumentControllerHostInstance* instance, const ARADocumentProperties* properties) noexcept; + +public: + template + using PropertiesPtr = PlugIn::PropertiesPtr; + + // Destruction + void destroyDocumentController () noexcept override; + + // Factory + const ARAFactory* getFactory () const noexcept override; + + // Update Management + void beginEditing () noexcept override; + void endEditing () noexcept override; + void notifyModelUpdates () noexcept override; + + // Archiving + bool restoreObjectsFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, const ARARestoreObjectsFilter* filter) noexcept override; + bool storeObjectsToArchive (ARAArchiveWriterHostRef archiveWriterHostRef, const ARAStoreObjectsFilter* filter) noexcept override; + bool storeAudioSourceToAudioFileChunk (ARAArchiveWriterHostRef archiveWriterHostRef, ARAAudioSourceRef audioSourceRef, ARAPersistentID* documentArchiveID, bool* openAutomatically) noexcept override; + + // Document Management + void updateDocumentProperties (PropertiesPtr properties) noexcept override; + + // Musical Context Management + ARAMusicalContextRef createMusicalContext (ARAMusicalContextHostRef hostRef, PropertiesPtr properties) noexcept override; + void updateMusicalContextProperties (ARAMusicalContextRef musicalContextRef, PropertiesPtr properties) noexcept override; + void updateMusicalContextContent (ARAMusicalContextRef musicalContextRef, const ARAContentTimeRange* range, ContentUpdateScopes flags) noexcept override; + void destroyMusicalContext (ARAMusicalContextRef musicalContextRef) noexcept override; + + // Region Sequence Management + ARARegionSequenceRef createRegionSequence (ARARegionSequenceHostRef hostRef, PropertiesPtr properties) noexcept override; + void updateRegionSequenceProperties (ARARegionSequenceRef regionSequence, PropertiesPtr properties) noexcept override; + void destroyRegionSequence (ARARegionSequenceRef regionSequence) noexcept override; + + // Audio Source Management + ARAAudioSourceRef createAudioSource (ARAAudioSourceHostRef hostRef, PropertiesPtr properties) noexcept override; + void updateAudioSourceProperties (ARAAudioSourceRef audioSourceRef, PropertiesPtr properties) noexcept override; + void updateAudioSourceContent (ARAAudioSourceRef audioSourceRef, const ARAContentTimeRange* range, ContentUpdateScopes flags) noexcept override; + void enableAudioSourceSamplesAccess (ARAAudioSourceRef audioSourceRef, bool enable) noexcept override; + void deactivateAudioSourceForUndoHistory (ARAAudioSourceRef audioSourceRef, bool deactivate) noexcept override; + void destroyAudioSource (ARAAudioSourceRef audioSourceRef) noexcept override; + + // Audio Modification Management + ARAAudioModificationRef createAudioModification (ARAAudioSourceRef audioSourceRef, ARAAudioModificationHostRef hostRef, PropertiesPtr properties) noexcept override; + ARAAudioModificationRef cloneAudioModification (ARAAudioModificationRef audioModificationRef, ARAAudioModificationHostRef hostRef, PropertiesPtr properties) noexcept override; + void updateAudioModificationProperties (ARAAudioModificationRef audioModificationRef, PropertiesPtr properties) noexcept override; + bool isAudioModificationPreservingAudioSourceSignal (ARAAudioModificationRef audioModificationRef) noexcept override; + void deactivateAudioModificationForUndoHistory (ARAAudioModificationRef audioModificationRef, bool deactivate) noexcept override; + void destroyAudioModification (ARAAudioModificationRef audioModificationRef) noexcept override; + + // Playback Region Management + ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept override; + void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept override; + bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept override; + void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept override; + void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override; + + // Content Reader Management + bool isAudioSourceContentAvailable (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept override; + ARAContentGrade getAudioSourceContentGrade (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept override; + ARAContentReaderRef createAudioSourceContentReader (ARAAudioSourceRef audioSourceRef, ARAContentType type, const ARAContentTimeRange* range) noexcept override; + + bool isAudioModificationContentAvailable (ARAAudioModificationRef audioModificationRef, ARAContentType type) noexcept override; + ARAContentGrade getAudioModificationContentGrade (ARAAudioModificationRef audioModificationRef, ARAContentType type) noexcept override; + ARAContentReaderRef createAudioModificationContentReader (ARAAudioModificationRef audioModificationRef, ARAContentType type, const ARAContentTimeRange* range) noexcept override; + + bool isPlaybackRegionContentAvailable (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type) noexcept override; + ARAContentGrade getPlaybackRegionContentGrade (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type) noexcept override; + ARAContentReaderRef createPlaybackRegionContentReader (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type, const ARAContentTimeRange* range) noexcept override; + + ARAInt32 getContentReaderEventCount (ARAContentReaderRef contentReaderRef) noexcept override; + const void* getContentReaderDataForEvent (ARAContentReaderRef contentReaderRef, ARAInt32 eventIndex) noexcept override; + void destroyContentReader (ARAContentReaderRef contentReaderRef) noexcept override; + + // Controlling Analysis + bool isAudioSourceContentAnalysisIncomplete (ARAAudioSourceRef audioSourceRef, ARAContentType contentType) noexcept override; + void requestAudioSourceContentAnalysis (ARAAudioSourceRef audioSourceRef, ARASize contentTypesCount, const ARAContentType contentTypes[]) noexcept override; + + ARAInt32 getProcessingAlgorithmsCount () noexcept override; + const ARAProcessingAlgorithmProperties* getProcessingAlgorithmProperties (ARAInt32 algorithmIndex) noexcept override; + ARAInt32 getProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef) noexcept override; + void requestProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef, ARAInt32 algorithmIndex) noexcept override; + + // License Management + bool isLicensedForCapabilities (bool runModalActivationDialogIfNeeded, ARASize contentTypesCount, const ARAContentType contentTypes[], ARAPlaybackTransformationFlags transformationFlags) noexcept override; + + // Accessors for Proxy + const ARADocumentControllerInstance* getInstance () const noexcept { return &_instance; } + ARADocumentControllerRef getRemoteRef () const noexcept { return _remoteRef; } + + // Host Interface Access + PlugIn::HostAudioAccessController* getHostAudioAccessController () noexcept { return &_hostAudioAccessController; } + PlugIn::HostArchivingController* getHostArchivingController () noexcept { return &_hostArchivingController; } + PlugIn::HostContentAccessController* getHostContentAccessController () noexcept { return (_hostContentAccessController.isProvided ()) ? &_hostContentAccessController : nullptr; } + PlugIn::HostModelUpdateController* getHostModelUpdateController () noexcept { return (_hostModelUpdateController.isProvided ()) ? &_hostModelUpdateController : nullptr; } + PlugIn::HostPlaybackController* getHostPlaybackController () noexcept { return (_hostPlaybackController.isProvided ()) ? &_hostPlaybackController : nullptr; } + +private: + void destroyIfUnreferenced () noexcept; + + friend class PlugInExtension; + void addPlugInExtension (PlugInExtension* plugInExtension) noexcept { _plugInExtensions.insert (plugInExtension); } + void removePlugInExtension (PlugInExtension* plugInExtension) noexcept { _plugInExtensions.erase (plugInExtension); if (_plugInExtensions.empty ()) destroyIfUnreferenced (); } + +private: + const ARAFactory* const _factory; + + PlugIn::HostAudioAccessController _hostAudioAccessController; + PlugIn::HostArchivingController _hostArchivingController; + PlugIn::HostContentAccessController _hostContentAccessController; + PlugIn::HostModelUpdateController _hostModelUpdateController; + PlugIn::HostPlaybackController _hostPlaybackController; + + PlugIn::DocumentControllerInstance _instance; + + ARADocumentControllerRef _remoteRef; + + bool _hasBeenDestroyed { false }; + + ARAProcessingAlgorithmProperties _processingAlgorithmData { 0, nullptr, nullptr }; + struct + { + std::string persistentID; + std::string name; + } _processingAlgorithmStrings; + + std::set _plugInExtensions; + + ARA_HOST_MANAGED_OBJECT (DocumentController) +}; +ARA_MAP_HOST_REF (DocumentController, ARAAudioAccessControllerHostRef, ARAArchivingControllerHostRef, + ARAContentAccessControllerHostRef, ARAModelUpdateControllerHostRef, ARAPlaybackControllerHostRef) + + +/*******************************************************************************/ + +DocumentController::DocumentController (Connection* connection, const ARAFactory* factory, const ARADocumentControllerHostInstance* instance, const ARADocumentProperties* properties) noexcept +: RemoteCaller { connection }, + _factory { factory }, + _hostAudioAccessController { instance }, + _hostArchivingController { instance }, + _hostContentAccessController { instance }, + _hostModelUpdateController { instance }, + _hostPlaybackController { instance }, + _instance { this } +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAAudioAccessControllerHostRef audioAccessControllerHostRef { toHostRef (this) }; + ARAArchivingControllerHostRef archivingControllerHostRef { toHostRef (this) }; + ARAContentAccessControllerHostRef contentAccessControllerHostRef { toHostRef (this) }; + ARAModelUpdateControllerHostRef modelUpdateControllerHostRef { toHostRef (this) }; + ARAPlaybackControllerHostRef playbackControllerHostRef { toHostRef (this) }; + remoteCall (_remoteRef, kCreateDocumentControllerMethodID, _factory->factoryID, + audioAccessControllerHostRef, archivingControllerHostRef, + (_hostContentAccessController.isProvided ()) ? kARATrue : kARAFalse, contentAccessControllerHostRef, + (_hostModelUpdateController.isProvided ()) ? kARATrue : kARAFalse, modelUpdateControllerHostRef, + (_hostPlaybackController.isProvided ()) ? kARATrue : kARAFalse, playbackControllerHostRef, + properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create document controller", _remoteRef); +} + +void DocumentController::destroyDocumentController () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy document controller", _remoteRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyDocumentController), _remoteRef); + + _hasBeenDestroyed = true; + + destroyIfUnreferenced (); +} + +void DocumentController::destroyIfUnreferenced () noexcept +{ + // still in use by host? + if (!_hasBeenDestroyed) + return; + + // still referenced from plug-in instances? + if (!_plugInExtensions.empty ()) + return; + + delete this; +} + +/*******************************************************************************/ + +const ARAFactory* DocumentController::getFactory () const noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + return _factory; +} + +/*******************************************************************************/ + +void DocumentController::beginEditing () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, beginEditing), _remoteRef); +} + +void DocumentController::endEditing () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, endEditing), _remoteRef); +} + +void DocumentController::notifyModelUpdates () noexcept +{ +#if ARA_ENABLE_HOST_ENTRY_LOG + static int logCount { 0 }; + constexpr int maxLogCount { 3 }; + if ((++logCount) <= maxLogCount) + { + ARA_LOG_HOST_ENTRY (this); + if (logCount >= maxLogCount) + ARA_LOG ("notifyModelUpdates () called %i times, will now suppress logging future calls to it", maxLogCount); + } +#endif + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + if (!_hostModelUpdateController.isProvided ()) + return; + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, notifyModelUpdates), _remoteRef); +} + +bool DocumentController::restoreObjectsFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, const ARARestoreObjectsFilter* filter) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool success; + remoteCall (success, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, restoreObjectsFromArchive), _remoteRef, archiveReaderHostRef, filter); + return (success != kARAFalse); +} + +bool DocumentController::storeObjectsToArchive (ARAArchiveWriterHostRef archiveWriterHostRef, const ARAStoreObjectsFilter* filter) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAStoreObjectsFilter tempFilter; + std::vector remoteAudioSourceRefs; + if ((filter != nullptr) && (filter->audioSourceRefsCount > 0)) + { + remoteAudioSourceRefs.reserve (filter->audioSourceRefsCount); + for (auto i { 0U }; i < filter->audioSourceRefsCount; ++i) + remoteAudioSourceRefs.emplace_back (fromRef (filter->audioSourceRefs[i])->_remoteRef); + + tempFilter = *filter; + tempFilter.audioSourceRefs = remoteAudioSourceRefs.data (); + filter = &tempFilter; + } + + ARABool success; + remoteCall (success, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, storeObjectsToArchive), _remoteRef, archiveWriterHostRef, filter); + return (success!= kARAFalse); +} + +bool DocumentController::storeAudioSourceToAudioFileChunk (ARAArchiveWriterHostRef archiveWriterHostRef, ARAAudioSourceRef audioSourceRef, ARAPersistentID* documentArchiveID, bool* openAutomatically) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + ARA_INTERNAL_ASSERT (documentArchiveID != nullptr); + ARA_INTERNAL_ASSERT (openAutomatically != nullptr); + + bool success { false }; + CustomDecodeFunction customDecode { + [this, &success, &documentArchiveID, &openAutomatically] (const MessageDecoder* decoder) -> void + { + StoreAudioSourceToAudioFileChunkReply reply; + decodeReply (reply, decoder); + + // find ID string in factory because our return value is a temporary copy + if (0 == std::strcmp (reply.documentArchiveID, _factory->documentArchiveID)) + { + *documentArchiveID = _factory->documentArchiveID; + } + else + { + *documentArchiveID = nullptr; + for (auto i { 0U }; i < _factory->compatibleDocumentArchiveIDsCount; ++i) + { + if (0 == std::strcmp (reply.documentArchiveID, _factory->compatibleDocumentArchiveIDs[i])) + { + *documentArchiveID = _factory->compatibleDocumentArchiveIDs[i]; + break; + } + } + ARA_INTERNAL_ASSERT (*documentArchiveID != nullptr); + } + + *openAutomatically = (reply.openAutomatically != kARAFalse); + success = (reply.result != kARAFalse); + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, storeAudioSourceToAudioFileChunk), + _remoteRef, archiveWriterHostRef, audioSource->_remoteRef); + return success; +} + +void DocumentController::updateDocumentProperties (PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARADocumentPropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateDocumentProperties), _remoteRef, *properties); +} + +/*******************************************************************************/ + +ARAMusicalContextRef DocumentController::createMusicalContext (ARAMusicalContextHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAMusicalContextPropertiesMinSize); + + ARAMusicalContextRef musicalContextRef; + remoteCall (musicalContextRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createMusicalContext), _remoteRef, hostRef, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create musical context", musicalContextRef); + return musicalContextRef; +} + +void DocumentController::updateMusicalContextProperties (ARAMusicalContextRef musicalContextRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (musicalContextRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAMusicalContextPropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextProperties), _remoteRef, musicalContextRef, *properties); +} + +void DocumentController::updateMusicalContextContent (ARAMusicalContextRef musicalContextRef, const ARAContentTimeRange* range, ContentUpdateScopes flags) noexcept +{ + ARA_LOG_HOST_ENTRY (musicalContextRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextContent), _remoteRef, musicalContextRef, range, flags); +} + +void DocumentController::destroyMusicalContext (ARAMusicalContextRef musicalContextRef) noexcept +{ + ARA_LOG_HOST_ENTRY (musicalContextRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy musical context", musicalContextRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyMusicalContext), _remoteRef, musicalContextRef); +} + +/*******************************************************************************/ + +ARARegionSequenceRef DocumentController::createRegionSequence (ARARegionSequenceHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARARegionSequencePropertiesMinSize); + + ARARegionSequenceRef regionSequenceRef; + remoteCall (regionSequenceRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createRegionSequence), _remoteRef, hostRef, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create region sequence", regionSequenceRef); + return regionSequenceRef; +} + +void DocumentController::updateRegionSequenceProperties (ARARegionSequenceRef regionSequenceRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (regionSequenceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARARegionSequencePropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateRegionSequenceProperties), _remoteRef, regionSequenceRef, *properties); +} + +void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (regionSequenceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy region sequence", regionSequenceRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyRegionSequence), _remoteRef, regionSequenceRef); +} + +/*******************************************************************************/ + +ARAAudioSourceRef DocumentController::createAudioSource (ARAAudioSourceHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAAudioSourcePropertiesMinSize); + + auto audioSource { new AudioSource { hostRef, nullptr, properties->channelCount +#if ARA_VALIDATE_API_CALLS + , properties->sampleCount, properties->sampleRate +#endif + } }; + + remoteCall (audioSource->_remoteRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioSource), + _remoteRef, ARAAudioSourceHostRef { toHostRef (audioSource) }, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create audio source", audioSourceRef); + return toRef (audioSource); +} + +void DocumentController::updateAudioSourceProperties (ARAAudioSourceRef audioSourceRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAAudioSourcePropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioSourceProperties), _remoteRef, audioSource->_remoteRef, *properties); +} + +void DocumentController::updateAudioSourceContent (ARAAudioSourceRef audioSourceRef, const ARAContentTimeRange* range, ContentUpdateScopes flags) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioSourceContent), _remoteRef, audioSource->_remoteRef, range, flags); +} + +void DocumentController::enableAudioSourceSamplesAccess (ARAAudioSourceRef audioSourceRef, bool enable) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, enableAudioSourceSamplesAccess), _remoteRef, audioSource->_remoteRef, (enable) ? kARATrue : kARAFalse); +} + +void DocumentController::deactivateAudioSourceForUndoHistory (ARAAudioSourceRef audioSourceRef, bool deactivate) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, deactivateAudioSourceForUndoHistory), _remoteRef, audioSource->_remoteRef, (deactivate) ? kARATrue : kARAFalse); +} + +void DocumentController::destroyAudioSource (ARAAudioSourceRef audioSourceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy audio source", audioSource->_remoteRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyAudioSource), _remoteRef, audioSource->_remoteRef); + delete audioSource; +} + +/*******************************************************************************/ + +ARAAudioModificationRef DocumentController::createAudioModification (ARAAudioSourceRef audioSourceRef, ARAAudioModificationHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAAudioModificationPropertiesMinSize); + + ARAAudioModificationRef audioModificationRef; + remoteCall (audioModificationRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioModification), + _remoteRef, audioSource->_remoteRef, hostRef, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create audio modification", audioModificationRef); + return audioModificationRef; +} + +ARAAudioModificationRef DocumentController::cloneAudioModification (ARAAudioModificationRef srcAudioModificationRef, ARAAudioModificationHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (srcAudioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAAudioModificationPropertiesMinSize); + + ARAAudioModificationRef clonedAudioModificationRef; + remoteCall (clonedAudioModificationRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, cloneAudioModification), + _remoteRef, srcAudioModificationRef, hostRef, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create cloned audio modification", clonedAudioModificationRef); + return clonedAudioModificationRef; +} + +void DocumentController::updateAudioModificationProperties (ARAAudioModificationRef audioModificationRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAAudioModificationPropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updateAudioModificationProperties), _remoteRef, audioModificationRef, *properties); +} + +bool DocumentController::isAudioModificationPreservingAudioSourceSignal (ARAAudioModificationRef audioModificationRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal), _remoteRef, audioModificationRef); + return (result != kARAFalse); +} + +void DocumentController::deactivateAudioModificationForUndoHistory (ARAAudioModificationRef audioModificationRef, bool deactivate) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, deactivateAudioModificationForUndoHistory), _remoteRef, audioModificationRef, (deactivate) ? kARATrue : kARAFalse); +} + +void DocumentController::destroyAudioModification (ARAAudioModificationRef audioModificationRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy audio modification", audioModification); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyAudioModification), _remoteRef, audioModificationRef); +} + +/*******************************************************************************/ + +ARAPlaybackRegionRef DocumentController::createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this));ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAPlaybackRegionPropertiesMinSize); + + ARAPlaybackRegionRef playbackRegionRef; + remoteCall (playbackRegionRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createPlaybackRegion), + _remoteRef, audioModificationRef, hostRef, *properties); + + ARA_LOG_MODELOBJECT_LIFETIME ("did create playback region", playbackRegionRef); + return playbackRegionRef; +} + +void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (properties != nullptr); + ARA_INTERNAL_ASSERT (properties->structSize >= ARA::kARAPlaybackRegionPropertiesMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updatePlaybackRegionProperties), _remoteRef, playbackRegionRef, *properties); +} + +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal), _remoteRef, playbackRegionRef); + return (result != kARAFalse); +} + +void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); +// this function can be called from other threads! +// ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (headTime != nullptr); + ARA_INTERNAL_ASSERT (tailTime != nullptr); + + GetPlaybackRegionHeadAndTailTimeReply reply; + remoteCall (reply, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionHeadAndTailTime), + _remoteRef, playbackRegionRef, (headTime != nullptr) ? kARATrue : kARAFalse, (tailTime != nullptr) ? kARATrue : kARAFalse); + if (headTime != nullptr) + *headTime = reply.headTime; + if (tailTime != nullptr) + *tailTime = reply.tailTime; +} + +void DocumentController::destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy playback region", playbackRegionRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyPlaybackRegion), _remoteRef, playbackRegionRef); +} + +/*******************************************************************************/ + +bool DocumentController::isAudioSourceContentAvailable (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAvailable), _remoteRef, audioSource->_remoteRef, type); + return (result != kARAFalse); +} + +ARAContentGrade DocumentController::getAudioSourceContentGrade (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARAContentGrade grade; + remoteCall (grade, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getAudioSourceContentGrade), _remoteRef, audioSource->_remoteRef, type); + return grade; +} + +ARAContentReaderRef DocumentController::createAudioSourceContentReader (ARAAudioSourceRef audioSourceRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioSourceContentReader), + _remoteRef, audioSource->_remoteRef, type, range); + + auto contentReader { new ContentReader { contentReaderRef, type } }; +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + ARA_LOG ("Plug success: did create content reader %p for audio source %p", contentReaderRef, audioSourceRef); +#endif + return toRef (contentReader); +} + +/*******************************************************************************/ + +bool DocumentController::isAudioModificationContentAvailable (ARAAudioModificationRef audioModificationRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioModificationContentAvailable), _remoteRef, audioModificationRef, type); + return (result != kARAFalse); +} + +ARAContentGrade DocumentController::getAudioModificationContentGrade (ARAAudioModificationRef audioModificationRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAContentGrade grade; + remoteCall (grade, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getAudioModificationContentGrade), _remoteRef, audioModificationRef, type); + return grade; +} + +ARAContentReaderRef DocumentController::createAudioModificationContentReader (ARAAudioModificationRef audioModificationRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createAudioModificationContentReader), + _remoteRef, audioModificationRef, type, range); + + auto contentReader { new ContentReader { contentReaderRef, type } }; +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + ARA_LOG ("Plug success: did create content reader %p for audio modification %p", contentReaderRef, audioModificationRef); +#endif + return toRef (contentReader); +} + +/*******************************************************************************/ + +bool DocumentController::isPlaybackRegionContentAvailable (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionContentAvailable), _remoteRef, playbackRegionRef, type); + return (result != kARAFalse); +} + +ARAContentGrade DocumentController::getPlaybackRegionContentGrade (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAContentGrade grade; + remoteCall (grade, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionContentGrade), _remoteRef, playbackRegionRef, type); + return grade; +} + +ARAContentReaderRef DocumentController::createPlaybackRegionContentReader (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, createPlaybackRegionContentReader), + _remoteRef, playbackRegionRef, type, range); + + auto contentReader { new ContentReader { contentReaderRef, type } }; +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + ARA_LOG ("Plug success: did create content reader %p for playback region %p", contentReaderRef, playbackRegionRef); +#endif + return toRef (contentReader); +} + +/*******************************************************************************/ + +ARAInt32 DocumentController::getContentReaderEventCount (ARAContentReaderRef contentReaderRef) noexcept +{ + ARA_LOG_HOST_ENTRY (contentReaderRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (contentReader)); + + ARAInt32 count; + remoteCall (count, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getContentReaderEventCount), _remoteRef, contentReader->_remoteRef); + return count; +} + +const void* DocumentController::getContentReaderDataForEvent (ARAContentReaderRef contentReaderRef, ARAInt32 eventIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (contentReaderRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (contentReader)); + + const void* result {}; + CustomDecodeFunction customDecode { + [&result, &contentReader] (const MessageDecoder* decoder) -> void + { + result = contentReader->_decoder.decode (decoder); + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getContentReaderDataForEvent), + _remoteRef, contentReader->_remoteRef, eventIndex); + return result; +} + +void DocumentController::destroyContentReader (ARAContentReaderRef contentReaderRef) noexcept +{ + ARA_LOG_HOST_ENTRY (contentReaderRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (contentReader)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy content reader", contentReader->remoteRef); + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, destroyContentReader), _remoteRef, contentReader->_remoteRef); + + delete contentReader; +} + +/*******************************************************************************/ + +bool DocumentController::isAudioSourceContentAnalysisIncomplete (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAnalysisIncomplete), + _remoteRef, audioSource->_remoteRef, type); + return (result != kARAFalse); +} + +void DocumentController::requestAudioSourceContentAnalysis (ARAAudioSourceRef audioSourceRef, ARASize contentTypesCount, const ARAContentType contentTypes[]) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + const ArrayArgument types { contentTypes, contentTypesCount }; + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, requestAudioSourceContentAnalysis), _remoteRef, audioSource->_remoteRef, types); +} + +ARAInt32 DocumentController::getProcessingAlgorithmsCount () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARAInt32 count; + remoteCall (count, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmsCount), _remoteRef); + return count; +} + +const ARAProcessingAlgorithmProperties* DocumentController::getProcessingAlgorithmProperties (ARAInt32 algorithmIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + CustomDecodeFunction customDecode { + [this] (const MessageDecoder* decoder) -> void + { + ARAProcessingAlgorithmProperties reply; + decodeReply (reply, decoder); + _processingAlgorithmStrings.persistentID = reply.persistentID; + _processingAlgorithmStrings.name = reply.name; + _processingAlgorithmData = reply; + _processingAlgorithmData.persistentID = _processingAlgorithmStrings.persistentID.c_str (); + _processingAlgorithmData.name = _processingAlgorithmStrings.name.c_str (); + } }; + remoteCall (customDecode, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmProperties), _remoteRef, algorithmIndex); + return &_processingAlgorithmData; +} + +ARAInt32 DocumentController::getProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + ARAInt32 result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmForAudioSource), _remoteRef, audioSource->_remoteRef); + return result; +} + +void DocumentController::requestProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef, ARAInt32 algorithmIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_INTERNAL_ASSERT (isValidInstance (audioSource)); + + remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, requestProcessingAlgorithmForAudioSource), _remoteRef, audioSource->_remoteRef, algorithmIndex); +} + +/*******************************************************************************/ + +bool DocumentController::isLicensedForCapabilities (bool runModalActivationDialogIfNeeded, ARASize contentTypesCount, const ARAContentType contentTypes[], ARAPlaybackTransformationFlags transformationFlags) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + const ArrayArgument types { contentTypes, contentTypesCount }; + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isLicensedForCapabilities), + _remoteRef, (runModalActivationDialogIfNeeded) ? kARATrue : kARAFalse, types, transformationFlags); + return (result != kARAFalse); +} + + +/*******************************************************************************/ +// Implementation of PlaybackRendererInterface that channels all calls through IPC + +class PlaybackRenderer : public PlugIn::PlaybackRendererInterface, protected RemoteCaller, public InstanceValidator +{ +public: + explicit PlaybackRenderer (Connection* connection, ARAPlaybackRendererRef remoteRef) noexcept + : RemoteCaller { connection }, + _remoteRef { remoteRef } + {} + + // Inherited public interface used by the C++ dispatcher, to be called by the ARAPlugInDispatch code exclusively. + void addPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackRendererInterface, addPlaybackRegion), _remoteRef, playbackRegionRef); + } + void removePlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAPlaybackRendererInterface, removePlaybackRegion), _remoteRef, playbackRegionRef); + } + +private: + ARAPlaybackRendererRef const _remoteRef; + + ARA_HOST_MANAGED_OBJECT (PlaybackRenderer) +}; + + +/*******************************************************************************/ +// Implementation of EditorRendererInterface that channels all calls through IPC + +class EditorRenderer : public PlugIn::EditorRendererInterface, protected RemoteCaller, public InstanceValidator +{ +public: + explicit EditorRenderer (Connection* connection, ARAEditorRendererRef remoteRef) noexcept + : RemoteCaller { connection }, + _remoteRef { remoteRef } + {} + + // Inherited public interface used by the C++ dispatcher, to be called by the ARAPlugInDispatch code exclusively. + void addPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAEditorRendererInterface, addPlaybackRegion), _remoteRef, playbackRegionRef); + } + void removePlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAEditorRendererInterface, removePlaybackRegion), _remoteRef, playbackRegionRef); + } + + void addRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAEditorRendererInterface, addRegionSequence), _remoteRef, regionSequenceRef); + } + void removeRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + remoteCall (ARA_IPC_METHOD_ID (ARAEditorRendererInterface, removeRegionSequence), _remoteRef, regionSequenceRef); + } + +private: + ARAEditorRendererRef const _remoteRef; + + ARA_HOST_MANAGED_OBJECT (EditorRenderer) +}; + + +/*******************************************************************************/ +// Implementation of EditorRendererInterface that channels all calls through IPC + +class EditorView : public PlugIn::EditorViewInterface, protected RemoteCaller, public InstanceValidator +{ +public: + explicit EditorView (Connection* connection, ARAEditorViewRef remoteRef) noexcept + : RemoteCaller { connection }, + _remoteRef { remoteRef } + {} + + // Inherited public interface used by the C++ dispatcher, to be called by the ARAPlugInDispatch code exclusively. + void notifySelection (SizedStructPtr selection) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + ARA_INTERNAL_ASSERT (selection != nullptr); + ARA_INTERNAL_ASSERT (selection->structSize >= ARA::kARAViewSelectionMinSize); + + remoteCall (ARA_IPC_METHOD_ID (ARAEditorViewInterface, notifySelection), _remoteRef, *selection); + } + void notifyHideRegionSequences (ARASize regionSequenceRefsCount, const ARARegionSequenceRef regionSequenceRefs[]) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + const ArrayArgument sequences { regionSequenceRefs, regionSequenceRefsCount }; + remoteCall (ARA_IPC_METHOD_ID (ARAEditorViewInterface, notifyHideRegionSequences), _remoteRef, sequences); + } + +private: + ARAEditorViewRef const _remoteRef; + + ARA_HOST_MANAGED_OBJECT (EditorView) +}; + + +/*******************************************************************************/ +// implementation of ARAPlugInExtensionInstance that uses the above instance role classes + +class PlugInExtension : public PlugIn::PlugInExtensionInstance, public RemoteCaller +{ +public: + PlugInExtension (Connection* connection, ARAPlugInExtensionRef remoteExtensionRef, + ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles) noexcept + : PlugIn::PlugInExtensionInstance { (((knownRoles & kARAPlaybackRendererRole) == 0) || ((assignedRoles & kARAPlaybackRendererRole) != 0)) ? + new PlaybackRenderer (connection, reinterpret_cast (remoteExtensionRef)) : nullptr, + (((knownRoles & kARAEditorRendererRole) == 0) || ((assignedRoles & kARAEditorRendererRole) != 0)) ? + new EditorRenderer (connection, reinterpret_cast (remoteExtensionRef)) : nullptr, + (((knownRoles & kARAEditorViewRole) == 0) || ((assignedRoles & kARAEditorViewRole) != 0)) ? + new EditorView (connection, reinterpret_cast (remoteExtensionRef)) : nullptr }, + RemoteCaller { connection }, + _documentController { PlugIn::fromRef (documentControllerRef) } + { + plugInExtensionRef = remoteExtensionRef; // we re-use this deprecated ivar to store the remote extension + + ARA_LOG_HOST_ENTRY (this); + ARA_INTERNAL_ASSERT (isValidInstance (_documentController)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + _documentController->addPlugInExtension (this); + +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + ARA_LOG ("Plug success: did create plug-in extension %p (playbackRenderer %p, editorRenderer %p, editorView %p)", this, getPlaybackRenderer (), getEditorRenderer (), getEditorView ()); +#endif + } + + ~PlugInExtension () noexcept + { + ARA_LOG_HOST_ENTRY (this); +#if ARA_ENABLE_OBJECT_LIFETIME_LOG + ARA_LOG ("Plug success: will destroy plug-in extension %p (playbackRenderer %p, editorRenderer %p, editorView %p)", this, getPlaybackRenderer (), getEditorRenderer (), getEditorView ()); +#endif + + remoteCall (kCleanupBindingMethodID, plugInExtensionRef); + + _documentController->removePlugInExtension (this); + + delete getEditorView (); + delete getEditorRenderer (); + delete getPlaybackRenderer (); + } + +private: + DocumentController* const _documentController; + + ARA_HOST_MANAGED_OBJECT (PlugInExtension) +}; + + +/*******************************************************************************/ + +struct RemoteFactory +{ + ARAFactory _factory; + struct + { + std::string factoryID; + std::string plugInName; + std::string manufacturerName; + std::string informationURL; + std::string version; + std::string documentArchiveID; + } _strings; + std::vector _compatibleIDStrings; + std::vector _compatibleIDs; + std::vector _analyzableTypes; +}; + +std::map _factories {}; + + +/*******************************************************************************/ + +} // namespace ProxyPlugInImpl +using namespace ProxyPlugInImpl; + +/*******************************************************************************/ + +#if defined (__GNUC__) + _Pragma ("GCC diagnostic push") + _Pragma ("GCC diagnostic ignored \"-Wunused-function\"") +#endif + +ARA_MAP_IPC_REF (ProxyPlugIn, ARAIPCProxyPlugInRef) + +#if defined (__GNUC__) + _Pragma ("GCC diagnostic pop") +#endif + + +size_t ARAIPCProxyPlugInGetFactoriesCount (ARAIPCProxyPlugInRef proxyPlugInRef) +{ + size_t count; + fromIPCRef (proxyPlugInRef)->remoteCall (count, kGetFactoriesCountMethodID); + ARA_INTERNAL_ASSERT (count > 0); + return count; +} + +const ARAFactory* ARAIPCProxyPlugInGetFactoryAtIndex (ARAIPCProxyPlugInRef proxyPlugInRef, size_t index) +{ + RemoteFactory remoteFactory; + RemoteCaller::CustomDecodeFunction customDecode { + [&remoteFactory] (const MessageDecoder* decoder) -> void + { + decodeReply (remoteFactory._factory, decoder); + + ARA_VALIDATE_API_ARGUMENT (&remoteFactory._factory, remoteFactory._factory.highestSupportedApiGeneration >= kARAAPIGeneration_2_0_Final); + + remoteFactory._strings.factoryID = remoteFactory._factory.factoryID; + remoteFactory._factory.factoryID = remoteFactory._strings.factoryID.c_str (); + + remoteFactory._strings.plugInName = remoteFactory._factory.plugInName; + remoteFactory._factory.plugInName = remoteFactory._strings.plugInName.c_str (); + remoteFactory._strings.manufacturerName = remoteFactory._factory.manufacturerName; + remoteFactory._factory.manufacturerName = remoteFactory._strings.manufacturerName.c_str (); + remoteFactory._strings.informationURL = remoteFactory._factory.informationURL; + remoteFactory._factory.informationURL = remoteFactory._strings.informationURL.c_str (); + remoteFactory._strings.version = remoteFactory._factory.version; + remoteFactory._factory.version = remoteFactory._strings.version.c_str (); + + remoteFactory._strings.documentArchiveID = remoteFactory._factory.documentArchiveID; + remoteFactory._factory.documentArchiveID = remoteFactory._strings.documentArchiveID.c_str (); + + remoteFactory._compatibleIDStrings.reserve (remoteFactory._factory.compatibleDocumentArchiveIDsCount); + remoteFactory._compatibleIDs.reserve (remoteFactory._factory.compatibleDocumentArchiveIDsCount); + for (auto i { 0U }; i < remoteFactory._factory.compatibleDocumentArchiveIDsCount; ++i) + { + remoteFactory._compatibleIDStrings.emplace_back (remoteFactory._factory.compatibleDocumentArchiveIDs[i]); + remoteFactory._compatibleIDs.emplace_back (remoteFactory._compatibleIDStrings[i].c_str ()); + } + remoteFactory._factory.compatibleDocumentArchiveIDs = remoteFactory._compatibleIDs.data (); + + remoteFactory._analyzableTypes.reserve (remoteFactory._factory.analyzeableContentTypesCount); + for (auto i { 0U }; i < remoteFactory._factory.analyzeableContentTypesCount; ++i) + remoteFactory._analyzableTypes.emplace_back (remoteFactory._factory.analyzeableContentTypes[i]); + remoteFactory._factory.analyzeableContentTypes = remoteFactory._analyzableTypes.data (); + } }; + + fromIPCRef (proxyPlugInRef)->remoteCall (customDecode, kGetFactoryMethodID, index); + + const auto result { _factories.insert (std::make_pair (remoteFactory._strings.factoryID, remoteFactory)) }; + if (result.second) + { + result.first->second._factory.factoryID = result.first->second._strings.factoryID.c_str (); + + result.first->second._factory.factoryID = result.first->second._strings.factoryID.c_str (); + result.first->second._factory.plugInName = result.first->second._strings.plugInName.c_str (); + result.first->second._factory.manufacturerName = result.first->second._strings.manufacturerName.c_str (); + result.first->second._factory.informationURL = result.first->second._strings.informationURL.c_str (); + result.first->second._factory.version = result.first->second._strings.version.c_str (); + + result.first->second._factory.documentArchiveID = result.first->second._strings.documentArchiveID.c_str (); + + for (auto i { 0U }; i < result.first->second._compatibleIDStrings.size (); ++i) + result.first->second._compatibleIDs[i] = result.first->second._compatibleIDStrings[i].c_str (); + result.first->second._factory.compatibleDocumentArchiveIDs = result.first->second._compatibleIDs.data (); + + result.first->second._factory.analyzeableContentTypes = result.first->second._analyzableTypes.data (); + } + return &result.first->second._factory; +} + +void ARAIPCProxyPlugInInitializeARA (ARAIPCProxyPlugInRef proxyPlugInRef, const ARAPersistentID factoryID, ARAAPIGeneration desiredApiGeneration) +{ + ARA_INTERNAL_ASSERT (desiredApiGeneration >= kARAAPIGeneration_2_0_Final); + fromIPCRef (proxyPlugInRef)->remoteCall (kInitializeARAMethodID, factoryID, desiredApiGeneration); +} + +const ARADocumentControllerInstance* ARAIPCProxyPlugInCreateDocumentControllerWithDocument ( + ARAIPCProxyPlugInRef proxyPlugInRef, const ARAPersistentID factoryID, + const ARADocumentControllerHostInstance* hostInstance, const ARADocumentProperties* properties) +{ + const auto cached { _factories.find (std::string { factoryID }) }; + ARA_INTERNAL_ASSERT (cached != _factories.end ()); + if (cached == _factories.end ()) + return nullptr; + + auto result { new DocumentController { fromIPCRef (proxyPlugInRef)->getConnection (), &cached->second._factory, hostInstance, properties } }; + return result->getInstance (); +} + +const ARAPlugInExtensionInstance* ARAIPCProxyPlugInBindToDocumentController (ARAIPCPlugInInstanceRef remoteRef, ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles) +{ + auto documentController { static_cast (PlugIn::fromRef (documentControllerRef)) }; + const auto remoteDocumentControllerRef { documentController->getRemoteRef () }; + + size_t remoteExtensionRef {}; + documentController->remoteCall (remoteExtensionRef, kBindToDocumentControllerMethodID, remoteRef, remoteDocumentControllerRef, knownRoles, assignedRoles); + + return new PlugInExtension { documentController->getConnection (), reinterpret_cast (remoteExtensionRef), documentControllerRef, knownRoles, assignedRoles }; +} + +void ARAIPCProxyPlugInCleanupBinding (const ARAPlugInExtensionInstance* plugInExtensionInstance) +{ + delete static_cast (plugInExtensionInstance); +} + +void ARAIPCProxyPlugInUninitializeARA (ARAIPCProxyPlugInRef proxyPlugInRef, const ARAPersistentID factoryID) +{ + fromIPCRef (proxyPlugInRef)->remoteCall (kUninitializeARAMethodID, factoryID); +} + + +/*******************************************************************************/ + +ProxyPlugIn::ProxyPlugIn (std::unique_ptr && connection) +: RemoteCaller (connection.get ()), + _connection (std::move (connection)) +{ + Connection::_setDebugMessageHint (true); +} + +void ProxyPlugIn::handleReceivedMessage (const MessageID messageID, const MessageDecoder* const decoder, + MessageEncoder* const replyEncoder) +{ + // ARAAudioAccessControllerInterface + if (messageID == ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, createAudioReaderForSource)) + { + ARAAudioAccessControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + ARABool use64BitSamples; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, use64BitSamples); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + auto reader { new HostAudioReader { audioSource, nullptr, (use64BitSamples != kARAFalse) ? sizeof (double) : sizeof (float) } }; + reader->hostRef = documentController->getHostAudioAccessController ()->createAudioReaderForSource (audioSource->_hostRef, (use64BitSamples) ? kARATrue : kARAFalse); + ARAAudioReaderHostRef audioReaderHostRef { toHostRef (reader) }; + encodeReply (replyEncoder, audioReaderHostRef); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, readAudioSamples)) + { + ARAAudioAccessControllerHostRef controllerHostRef; + ARAAudioReaderHostRef audioReaderHostRef; + ARASamplePosition samplePosition; + ARASampleCount samplesPerChannel; + decodeArguments (decoder, controllerHostRef, audioReaderHostRef, samplePosition, samplesPerChannel); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + auto reader { fromHostRef (audioReaderHostRef) }; + + // \todo using static (plus not copy bytes) here assumes single-threaded callbacks, but currently this is a valid requirement + static std::vector bufferData; + const auto channelCount { static_cast (reader->audioSource->_channelCount) }; + const auto bufferSize { reader->sampleSize * static_cast (samplesPerChannel) }; + const auto allBuffersSize { channelCount * bufferSize }; + if (bufferData.size () < allBuffersSize) + bufferData.resize (allBuffersSize); + + static std::vector sampleBuffers; + static std::vector encoders; + if (sampleBuffers.size () < channelCount) + sampleBuffers.resize (channelCount, nullptr); + if (encoders.size () < channelCount) + encoders.resize (channelCount, { nullptr, 0, false }); + for (auto i { 0U }; i < channelCount; ++i) + { + const auto buffer { bufferData.data () + i * bufferSize }; + sampleBuffers[i] = buffer; + encoders[i] = { buffer, bufferSize, false }; + } + + if (documentController->getHostAudioAccessController ()->readAudioSamples (reader->hostRef, samplePosition, samplesPerChannel, sampleBuffers.data ())) + encodeReply (replyEncoder, ArrayArgument { encoders.data (), encoders.size () }); + // else send empty reply as indication of failure + } + else if (messageID == ARA_IPC_METHOD_ID (ARAAudioAccessControllerInterface, destroyAudioReader)) + { + ARAAudioAccessControllerHostRef controllerHostRef; + ARAAudioReaderHostRef audioReaderHostRef; + decodeArguments (decoder, controllerHostRef, audioReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto reader { fromHostRef (audioReaderHostRef) }; + + documentController->getHostAudioAccessController ()->destroyAudioReader (reader->hostRef); + delete reader; + } + + // ARAArchivingControllerInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, getArchiveSize)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + decodeArguments (decoder, controllerHostRef, archiveReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + encodeReply (replyEncoder, documentController->getHostArchivingController ()->getArchiveSize (archiveReaderHostRef)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, readBytesFromArchive)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + ARASize position; + ARASize length; + decodeArguments (decoder, controllerHostRef, archiveReaderHostRef, position, length); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + // \todo using static here assumes single-threaded callbacks, but currently this is a valid requirement + static std::vector bytes; + bytes.resize (length); + if (!documentController->getHostArchivingController ()->readBytesFromArchive (archiveReaderHostRef, position, length, bytes.data ())) + bytes.clear (); + encodeReply (replyEncoder, BytesEncoder { bytes, false }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, writeBytesToArchive)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveWriterHostRef archiveWriterHostRef; + ARASize position; + std::vector bytes; + BytesDecoder writer { bytes }; + decodeArguments (decoder, controllerHostRef, archiveWriterHostRef, position, writer); + ARA_INTERNAL_ASSERT (bytes.size () > 0); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + encodeReply (replyEncoder, documentController->getHostArchivingController ()->writeBytesToArchive (archiveWriterHostRef, position, bytes.size (), bytes.data ())); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentArchivingProgress)) + { + ARAArchivingControllerHostRef controllerHostRef; + float value; + decodeArguments (decoder, controllerHostRef, value); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostArchivingController ()->notifyDocumentArchivingProgress (value); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentUnarchivingProgress)) + { + ARAArchivingControllerHostRef controllerHostRef; + float value; + decodeArguments (decoder, controllerHostRef, value); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostArchivingController ()->notifyDocumentUnarchivingProgress (value); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAArchivingControllerInterface, getDocumentArchiveID)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + decodeArguments (decoder, controllerHostRef, archiveReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + encodeReply (replyEncoder, documentController->getHostArchivingController ()->getDocumentArchiveID (archiveReaderHostRef)); + } + + // ARAContentAccessControllerInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, isMusicalContextContentAvailable)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAMusicalContextHostRef musicalContextHostRef; + ARAContentType contentType; + decodeArguments (decoder, controllerHostRef, musicalContextHostRef, contentType); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + encodeReply (replyEncoder, (documentController->getHostContentAccessController ()->isMusicalContextContentAvailable (musicalContextHostRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getMusicalContextContentGrade)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAMusicalContextHostRef musicalContextHostRef; + ARAContentType contentType; + decodeArguments (decoder, controllerHostRef, musicalContextHostRef, contentType); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getMusicalContextContentGrade (musicalContextHostRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, createMusicalContextContentReader)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAMusicalContextHostRef musicalContextHostRef; + ARAContentType contentType; + OptionalArgument range; + decodeArguments (decoder, controllerHostRef, musicalContextHostRef, contentType, range); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + auto hostContentReader { new HostContentReader }; + hostContentReader->hostRef = documentController->getHostContentAccessController ()->createMusicalContextContentReader (musicalContextHostRef, contentType, (range.second) ? &range.first : nullptr); + hostContentReader->contentType = contentType; + + encodeReply (replyEncoder, ARAContentReaderHostRef { toHostRef (hostContentReader) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, isAudioSourceContentAvailable)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + ARAContentType contentType; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, contentType); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + encodeReply (replyEncoder, (documentController->getHostContentAccessController ()->isAudioSourceContentAvailable (audioSource->_hostRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getAudioSourceContentGrade)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + ARAContentType contentType; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, contentType); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getAudioSourceContentGrade (audioSource->_hostRef, contentType)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, createAudioSourceContentReader)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + ARAContentType contentType; + OptionalArgument range; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, contentType, range); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + auto hostContentReader { new HostContentReader }; + hostContentReader->hostRef = documentController->getHostContentAccessController ()->createAudioSourceContentReader (audioSource->_hostRef, contentType, (range.second) ? &range.first : nullptr); + hostContentReader->contentType = contentType; + encodeReply (replyEncoder, ARAContentReaderHostRef { toHostRef (hostContentReader) }); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderEventCount)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAContentReaderHostRef contentReaderHostRef; + decodeArguments (decoder, controllerHostRef, contentReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto hostContentReader { fromHostRef (contentReaderHostRef) }; + + encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getContentReaderEventCount (hostContentReader->hostRef)); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderDataForEvent)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAContentReaderHostRef contentReaderHostRef; + ARAInt32 eventIndex; + decodeArguments (decoder, controllerHostRef, contentReaderHostRef, eventIndex); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto hostContentReader { fromHostRef (contentReaderHostRef) }; + + const void* eventData { documentController->getHostContentAccessController ()->getContentReaderDataForEvent (hostContentReader->hostRef, eventIndex) }; + encodeContentEvent (replyEncoder, hostContentReader->contentType, eventData); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAContentAccessControllerInterface, destroyContentReader)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAContentReaderHostRef contentReaderHostRef; + decodeArguments (decoder, controllerHostRef, contentReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto hostContentReader { fromHostRef (contentReaderHostRef) }; + + documentController->getHostContentAccessController ()->destroyContentReader (hostContentReader->hostRef); + delete hostContentReader; + } + + // ARAModelUpdateControllerInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceAnalysisProgress)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + ARAAnalysisProgressState state; + float value; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, state, value); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + documentController->getHostModelUpdateController ()->notifyAudioSourceAnalysisProgress (audioSource->_hostRef, state, value); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceContentChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioSourceHostRef audioSourceHostRef; + OptionalArgument range; + ARAContentUpdateFlags scopeFlags; + decodeArguments (decoder, controllerHostRef, audioSourceHostRef, range, scopeFlags); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + auto audioSource { fromHostRef (audioSourceHostRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSourceHostRef, isValidInstance (audioSource)); + + documentController->getHostModelUpdateController ()->notifyAudioSourceContentChanged (audioSource->_hostRef, (range.second) ? &range.first : nullptr, scopeFlags); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioModificationContentChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAAudioModificationHostRef audioModificationHostRef; + OptionalArgument range; + ARAContentUpdateFlags scopeFlags; + decodeArguments (decoder, controllerHostRef, audioModificationHostRef, range, scopeFlags); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostModelUpdateController ()->notifyAudioModificationContentChanged (audioModificationHostRef, (range.second) ? &range.first : nullptr, scopeFlags); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyPlaybackRegionContentChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARAPlaybackRegionHostRef playbackRegionHostRef; + OptionalArgument range; + ARAContentUpdateFlags scopeFlags; + decodeArguments (decoder, controllerHostRef, playbackRegionHostRef, range, scopeFlags); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostModelUpdateController ()->notifyPlaybackRegionContentChanged (playbackRegionHostRef, (range.second) ? &range.first : nullptr, scopeFlags); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyDocumentDataChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + decodeArguments (decoder, controllerHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostModelUpdateController ()->notifyDocumentDataChanged (); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARARegionSequenceHostRef regionSequenceHostRef; + decodeArguments (decoder, controllerHostRef, regionSequenceHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostModelUpdateController ()->notifyRegionSequenceDataChanged (regionSequenceHostRef); + } + + // ARAPlaybackControllerInterface + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestStartPlayback)) + { + ARAPlaybackControllerHostRef controllerHostRef; + decodeArguments (decoder, controllerHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostPlaybackController ()->requestStartPlayback (); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestStopPlayback)) + { + ARAPlaybackControllerHostRef controllerHostRef; + decodeArguments (decoder, controllerHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostPlaybackController ()->requestStopPlayback (); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestSetPlaybackPosition)) + { + ARAPlaybackControllerHostRef controllerHostRef; + ARATimePosition timePosition; + decodeArguments (decoder, controllerHostRef, timePosition); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostPlaybackController ()->requestSetPlaybackPosition (timePosition); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestSetCycleRange)) + { + ARAPlaybackControllerHostRef controllerHostRef; + ARATimePosition startTime; + ARATimeDuration duration; + decodeArguments (decoder, controllerHostRef, startTime, duration); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostPlaybackController ()->requestSetCycleRange (startTime, duration); + } + else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestEnableCycle)) + { + ARAPlaybackControllerHostRef controllerHostRef; + ARABool enable; + decodeArguments (decoder, controllerHostRef, enable); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostPlaybackController ()->requestEnableCycle (enable != kARAFalse); + } + else + { + ARA_INTERNAL_ASSERT (false && "unhandled message ID"); + } +} + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCProxyPlugIn.h b/IPC/ARAIPCProxyPlugIn.h new file mode 100644 index 0000000..3c34274 --- /dev/null +++ b/IPC/ARAIPCProxyPlugIn.h @@ -0,0 +1,96 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyPlugIn.h +//! implementation of host-side ARA IPC proxy plug-in +//! \project ARA SDK Library +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! http://www.apache.org/licenses/LICENSE-2.0 +//! +//! Unless required by applicable law or agreed to in writing, software +//! distributed under the License is distributed on an "AS IS" BASIS, +//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//! See the License for the specific language governing permissions and +//! limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef ARAIPCProxyPlugIn_h +#define ARAIPCProxyPlugIn_h + +#if defined(__cplusplus) +#include "ARA_Library/IPC/ARAIPCConnection.h" +#include "ARA_Library/IPC/ARAIPCEncoding.h" +#else +#include "ARA_Library/IPC/ARAIPC.h" +#endif + + +#if ARA_ENABLE_IPC + + +//! @addtogroup ARA_Library_IPC +//! @{ + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { + +//! host side implementation of MessageHandler +//! the host uses the C interface below, but this class will be subclassed by specialized implementations +class ProxyPlugIn : public RemoteCaller +{ +public: + ProxyPlugIn (std::unique_ptr && connection); + + static void handleReceivedMessage (const MessageID messageID, const MessageDecoder* const decoder, + MessageEncoder* const replyEncoder); + +private: + const std::unique_ptr _connection; +}; +#endif + + +//! counts the factories available through the given message channel +size_t ARAIPCProxyPlugInGetFactoriesCount(ARAIPCProxyPlugInRef proxyPlugInRef); + +//! get a static copy of the remote factory data, with all function calls removed +//! index must be smaller than the result of ARAIPCProxyPlugInGetFactoriesCount() +const ARAFactory * ARAIPCProxyPlugInGetFactoryAtIndex(ARAIPCProxyPlugInRef proxyPlugInRef, size_t index); + +//! proxy initialization call, to be used instead of ARAFactory.initializeARAWithConfiguration() +// \todo we're currently not supporting propagating ARA assertions through IPC... +void ARAIPCProxyPlugInInitializeARA(ARAIPCProxyPlugInRef proxyPlugInRef, const ARAPersistentID factoryID, ARAAPIGeneration desiredApiGeneration); + +//! proxy document controller creation call, to be used instead of ARAFactory.createDocumentControllerWithDocument() +const ARADocumentControllerInstance * ARAIPCProxyPlugInCreateDocumentControllerWithDocument(ARAIPCProxyPlugInRef proxyPlugInRef, + const ARAPersistentID factoryID, + const ARADocumentControllerHostInstance * hostInstance, + const ARADocumentProperties * properties); + +//! create the proxy plug-in extension when performing the binding to the remote plug-in instance +const ARAPlugInExtensionInstance * ARAIPCProxyPlugInBindToDocumentController(ARAIPCPlugInInstanceRef remoteRef, ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles); + +//! trigger proper teardown of proxy plug-in extension upon destroying a remote plug-in instance that has been bound to ARA +void ARAIPCProxyPlugInCleanupBinding(const ARAPlugInExtensionInstance * plugInExtension); + +//! proxy uninitialization call, to be used instead of ARAFactory.uninitializeARA() +void ARAIPCProxyPlugInUninitializeARA(ARAIPCProxyPlugInRef proxyPlugInRef, const ARAPersistentID factoryID); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + +//! @} ARA_Library_IPC + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCProxyPlugIn_h diff --git a/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index 5a90e10..f0b020c 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -2,7 +2,7 @@ //! \file ARAPlug.cpp //! implementation of base classes for ARA plug-ins //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -113,7 +113,7 @@ struct SortByOrderIndex /*******************************************************************************/ // stream operator for color (r,g,b) -std::ostream& operator<< (std::ostream& oss, const ARAColor& color) +[[maybe_unused]] static std::ostream& operator<< (std::ostream& oss, const ARAColor& color) { oss << "(" << color.r << "," << color.g << "," << color.b << ")"; return oss; @@ -150,14 +150,14 @@ std::ostream& operator<< (std::ostream& oss, const OptionalPropertygetHostRef () << "):" << playbackRegion->getName () << ", playback time:" << playbackRegion->getStartInPlaybackTime () << " to " << playbackRegion->getEndInPlaybackTime (); if (detailed) { oss << ", modification:" << playbackRegion->getStartInAudioModificationTime () << " to " << playbackRegion->getEndInAudioModificationTime () - << ", time-stretching:" << (playbackRegion->isTimestretchEnabled () ? (playbackRegion->isTimeStretchReflectingTempo () ? "musical" : "linear") : "off)") + << ", time-stretching:" << (playbackRegion->isTimestretchEnabled () ? (playbackRegion->isTimestretchReflectingTempo () ? "musical" : "linear") : "off)") << ", content based fades:" << (playbackRegion->hasContentBasedFadeAtHead () ? (playbackRegion->hasContentBasedFadeAtTail () ? "both" : "head only") : (playbackRegion->hasContentBasedFadeAtTail () ? "tail only" : "none")) << ", regionSequence:" << playbackRegion->getRegionSequence ()->getName () << ", color:" << playbackRegion->getColor (); @@ -165,7 +165,7 @@ void logToStream (const PlaybackRegion* playbackRegion, std::ostringstream& oss, oss << "\n"; } -void logToStream (const AudioModification* audioModification, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) +[[maybe_unused]] static void logToStream (const AudioModification* audioModification, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) { oss << indentation << audioModification << "(" << audioModification->getHostRef () << "):" << audioModification->getName () << ", ID: \"" << audioModification->getPersistentID () << "\""; @@ -179,7 +179,7 @@ void logToStream (const AudioModification* audioModification, std::ostringstream } } -void logToStream (const AudioSource* audioSource, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) +[[maybe_unused]] static void logToStream (const AudioSource* audioSource, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) { oss << indentation << audioSource << "(" << audioSource->getHostRef () << "):" << audioSource->getName () << ", ID: \"" << audioSource->getPersistentID () << "\"\n"; if (detailed) @@ -193,7 +193,7 @@ void logToStream (const AudioSource* audioSource, std::ostringstream& oss, bool } } -void logToStream (const RegionSequence* regionSequence, std::ostringstream& oss, bool detailed, bool /*recursive*/, std::string indentation) +[[maybe_unused]] static void logToStream (const RegionSequence* regionSequence, std::ostringstream& oss, bool detailed, bool /*recursive*/, std::string indentation) { oss << indentation << regionSequence << "(" << regionSequence->getHostRef () << "):" << regionSequence->getName (); if (detailed) @@ -223,7 +223,7 @@ void logToStream (const RegionSequence* regionSequence, std::ostringstream& oss, } } -void logToStream (const MusicalContext* musicalContext, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) +[[maybe_unused]] static void logToStream (const MusicalContext* musicalContext, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) { oss << indentation << musicalContext << "(" << musicalContext->getHostRef () << "):" << musicalContext->getName (); if (detailed) @@ -239,7 +239,7 @@ void logToStream (const MusicalContext* musicalContext, std::ostringstream& oss, } } -void logToStream (const Document* document, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) +[[maybe_unused]] static void logToStream (const Document* document, std::ostringstream& oss, bool detailed, bool recursive, std::string indentation) { oss << indentation << document << ":" << document->getName () << "\n"; @@ -299,11 +299,8 @@ bool AnalysisProgressTracker::decodeIsProgressing (float encodedProgress) noexce bool AnalysisProgressTracker::updateProgress (ARAAnalysisProgressState state, float progress) noexcept { -#if __cplusplus >= 201703L static_assert (decltype (_encodedProgress)::is_always_lock_free); -#else - ARA_INTERNAL_ASSERT (_encodedProgress.is_lock_free ()); -#endif + ARA_INTERNAL_ASSERT (0.0f <= progress); ARA_INTERNAL_ASSERT (progress <= 1.0f); @@ -433,17 +430,17 @@ const OptionalProperty& MusicalContext::getEffectiveName () const void MusicalContext::updateProperties (PropertiesPtr properties) noexcept { - if (properties.implements ()) + if (properties.implements<&ARAMusicalContextProperties::name> ()) _name = properties->name; else _name = nullptr; - if (properties.implements ()) + if (properties.implements<&ARAMusicalContextProperties::orderIndex> ()) _orderIndex = properties->orderIndex; else _orderIndex = 0; // for position, we have no markup for "unknown" position - we'll just remain unsorted - if (properties.implements ()) + if (properties.implements<&ARAMusicalContextProperties::color> ()) _color = properties->color; else _color = nullptr; @@ -474,7 +471,7 @@ void RegionSequence::updateProperties (PropertiesPtrname; _orderIndex = properties->orderIndex; - if (properties.implements ()) + if (properties.implements<&ARARegionSequenceProperties::color> ()) _color = properties->color; else _color = nullptr; @@ -482,6 +479,18 @@ void RegionSequence::updateProperties (PropertiesPtrmusicalContextRef) }; ARA_VALIDATE_API_ARGUMENT (properties->musicalContextRef, getDocumentController ()->isValidMusicalContext (musicalContext)); setMusicalContext (musicalContext); + + if (properties.implements<&ARARegionSequenceProperties::persistentID> ()) + { + ARA_VALIDATE_API_ARGUMENT (properties->persistentID, properties->persistentID != nullptr); + ARA_VALIDATE_API_ARGUMENT (properties->persistentID, std::strlen (properties->persistentID) > 0); + _persistentID = properties->persistentID; + } + else + { + ARA_VALIDATE_API_ARGUMENT (properties, getDocumentController ()->getUsedApiGeneration () < kARAAPIGeneration_3_0_Draft); + _persistentID = nullptr; + } } void RegionSequence::setMusicalContext (MusicalContext* musicalContext) noexcept @@ -518,11 +527,15 @@ void AudioSource::updateProperties (PropertiesPtr prop ARA_VALIDATE_API_ARGUMENT (properties->persistentID, std::strlen (properties->persistentID) > 0); _persistentID = properties->persistentID; + [[maybe_unused]] const auto supportsContentOnlyAudioSources { getDocumentController ()->getFactory ()->supportsContentOnlyAudioSources != kARAFalse }; + ARA_VALIDATE_API_ARGUMENT (properties, properties->sampleCount >= ((supportsContentOnlyAudioSources) ? 0 : 1)); _sampleCount = properties->sampleCount; + ARA_VALIDATE_API_ARGUMENT (properties, properties->sampleRate > 0.0); _sampleRate = properties->sampleRate; _merits64BitSamples = (properties->merits64BitSamples != kARAFalse); - if (properties.implements ()) + ARA_VALIDATE_API_ARGUMENT (properties, properties->channelCount >= ((supportsContentOnlyAudioSources) ? 0 : 1)); + if (properties.implements<&ARAAudioSourceProperties::channelArrangement> ()) _channelFormat.update (properties->channelCount, properties->channelArrangementDataType, properties->channelArrangement); else _channelFormat.update (properties->channelCount, kARAChannelArrangementUndefined, nullptr); @@ -615,39 +628,20 @@ void PlaybackRegion::updateProperties (PropertiesPtrtransformationFlags & kARAPlaybackTransformationContentBasedFadeAtHead) != 0); _contentBasedFadeAtTail = ((properties->transformationFlags & kARAPlaybackTransformationContentBasedFadeAtTail) != 0); - if (properties.implements ()) + if (properties.implements<&ARAPlaybackRegionProperties::name> ()) _name = properties->name; else _name = nullptr; - if (properties.implements ()) + if (properties.implements<&ARAPlaybackRegionProperties::color> ()) _color = properties->color; else _color = nullptr; -#if ARA_SUPPORT_VERSION_1 - if (properties.implements ()) - { - ARA_VALIDATE_API_STATE (_musicalContext == nullptr); - auto regionSequence { fromRef (properties->regionSequenceRef) }; - ARA_VALIDATE_API_ARGUMENT (properties->regionSequenceRef, getDocumentController ()->isValidRegionSequence (regionSequence)); - setRegionSequence (regionSequence); - } - else - { - ARA_VALIDATE_API_STATE (DocumentController::getUsedApiGeneration () < kARAAPIGeneration_2_0_Draft); - ARA_VALIDATE_API_STATE (getRegionSequence () == nullptr); - - auto musicalContext { fromRef (properties->musicalContextRef) }; - ARA_VALIDATE_API_ARGUMENT (properties->musicalContextRef, getDocumentController ()->isValidMusicalContext (musicalContext)); - _musicalContext = musicalContext; - } -#else - ARA_VALIDATE_API_ARGUMENT (properties, properties.implements ()); + ARA_VALIDATE_API_ARGUMENT (properties, properties.implements<&ARAPlaybackRegionProperties::regionSequenceRef> ()); auto regionSequence { fromRef (properties->regionSequenceRef) }; ARA_VALIDATE_API_ARGUMENT (properties->regionSequenceRef, getDocumentController ()->isValidRegionSequence (regionSequence)); setRegionSequence (regionSequence); -#endif } bool PlaybackRegion::intersectsWithAudioModificationTimeRange (ARAContentTimeRange range) const noexcept @@ -706,23 +700,32 @@ void PlaybackRegion::setRegionSequence (RegionSequence* regionSequence) noexcept /*******************************************************************************/ -RestoreObjectsFilter::RestoreObjectsFilter (const ARARestoreObjectsFilter* filter, Document* document) noexcept +RestoreObjectsFilter::RestoreObjectsFilter (const SizedStructPtr filter, Document* document) noexcept : _filter { filter } { for (const auto& audioSource : document->getAudioSources ()) { - auto audioSourceID { audioSource->getPersistentID ().c_str () }; + const auto audioSourceID { audioSource->getPersistentID ().c_str () }; ARA_VALIDATE_API_STATE (_audioSourcesByID.count (audioSourceID) == 0); // make sure all current audio source persistentIDs are unique _audioSourcesByID[audioSourceID] = audioSource; for (const auto& audioModification : audioSource->getAudioModifications ()) { - auto audioModificationID { audioModification->getPersistentID ().c_str () }; + const auto audioModificationID { audioModification->getPersistentID ().c_str () }; ARA_VALIDATE_API_STATE (_audioModificationsByID.count (audioModificationID) == 0); // make sure all current audio modification persistentIDs are unique _audioModificationsByID[audioModificationID] = audioModification; } } + for (const auto& regionSequence : document->getRegionSequences ()) + { + if (const auto& regionSequenceID { regionSequence->getPersistentID () }) + { + ARA_VALIDATE_API_STATE (_regionSequencesByID.count (regionSequenceID) == 0); // make sure all current region sequence persistentIDs are unique + _regionSequencesByID[regionSequenceID] = regionSequence; + } + } + if (filter) { decltype (_audioSourcesByID) audioSourcesByMappedIDs; @@ -750,6 +753,22 @@ RestoreObjectsFilter::RestoreObjectsFilter (const ARARestoreObjectsFilter* filte audioModificationsByMappedIDs[audioModificationArchiveID] = it->second; } _audioModificationsByID = std::move (audioModificationsByMappedIDs); + + if (filter.implements<&ARARestoreObjectsFilter::regionSequenceIDsCount> ()) + { + decltype (_regionSequencesByID) regionSequencesByMappedIDs; + for (ARASize i { 0 }; i < filter->regionSequenceIDsCount; ++i) + { + auto regionSequenceArchiveID { filter->regionSequenceArchiveIDs[i] }; + ARA_VALIDATE_API_STATE (regionSequencesByMappedIDs.count (regionSequenceArchiveID) == 0); // make sure audio Modification persistentIDs in filter are unique + auto regionSequenceCurrentID { (filter->regionSequenceCurrentIDs != nullptr) ? filter->regionSequenceCurrentIDs[i] : regionSequenceArchiveID }; + + const auto it { _regionSequencesByID.find (regionSequenceCurrentID) }; + if (it != _regionSequencesByID.end ()) + regionSequencesByMappedIDs[regionSequenceArchiveID] = it->second; + } + _regionSequencesByID = std::move (regionSequencesByMappedIDs); + } } } @@ -772,9 +791,15 @@ AudioModification* RestoreObjectsFilter::getAudioModificationToRestoreStateWithI return (it != _audioModificationsByID.end ()) ? it->second : nullptr; } +RegionSequence* RestoreObjectsFilter::getRegionSequenceToRestoreStateWithID (ARAPersistentID regionSequenceID) const noexcept +{ + const auto it { _regionSequencesByID.find (regionSequenceID) }; + return (it != _regionSequencesByID.end ()) ? it->second : nullptr; +} + /*******************************************************************************/ -StoreObjectsFilter::StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noexcept +StoreObjectsFilter::StoreObjectsFilter (const SizedStructPtr filter) noexcept : _filter { filter } { ARA_INTERNAL_ASSERT (filter != nullptr); @@ -782,6 +807,12 @@ StoreObjectsFilter::StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noe _audioSourcesToStore.push_back (fromRef (_filter->audioSourceRefs[i])); for (ARASize i { 0 }; i < _filter->audioModificationRefsCount; ++i) _audioModificationsToStore.push_back (fromRef (_filter->audioModificationRefs[i])); + + if (filter.implements<&ARAStoreObjectsFilter::regionSequenceRefs> ()) + { + for (ARASize i { 0 }; i < _filter->regionSequenceRefsCount; ++i) + _regionSequencesToStore.push_back (fromRef (_filter->regionSequenceRefs[i])); + } } StoreObjectsFilter::StoreObjectsFilter (const Document* document) noexcept @@ -791,6 +822,12 @@ StoreObjectsFilter::StoreObjectsFilter (const Document* document) noexcept _audioModificationsToStore.reserve (_audioSourcesToStore.size ()); for (const auto& audioSource : _audioSourcesToStore) _audioModificationsToStore.insert (_audioModificationsToStore.end (), audioSource->getAudioModifications ().begin (), audioSource->getAudioModifications ().end ()); + + for (const auto& regionSequence : document->getRegionSequences ()) + { + if (regionSequence->getPersistentID ()) + _regionSequencesToStore.emplace_back (regionSequence); + } } bool StoreObjectsFilter::shouldStoreDocumentData () const noexcept @@ -1115,10 +1152,24 @@ void DocumentController::notifyModelUpdates () noexcept hostModelUpdateController->notifyPlaybackRegionContentChanged (playbackRegionUpdate.first->getHostRef (), nullptr, playbackRegionUpdate.second); _playbackRegionContentUpdates.clear (); + if (_regionSequenceDataUpdates.size () > 0) + { + if (hostModelUpdateController->supportsNotifyRegionSequenceDataChanged ()) + { + for (const auto& regionSequence : _regionSequenceDataUpdates) + hostModelUpdateController->notifyRegionSequenceDataChanged (regionSequence->getHostRef ()); + } + else + { + _documentDataChanged = true; // aka notifyDocumentDataChanged() + } + _regionSequenceDataUpdates.clear (); + } + if (_documentDataChanged) hostModelUpdateController->notifyDocumentDataChanged (); _documentDataChanged = false; - + didNotifyModelUpdates (); } @@ -1146,7 +1197,7 @@ bool DocumentController::restoreObjectsFromArchive (ARAArchiveReaderHostRef arch } #endif - const RestoreObjectsFilter restoreObjectsFilter (filter, getDocument ()); + const RestoreObjectsFilter restoreObjectsFilter { filter, getDocument () }; return doRestoreObjectsFromArchive (&archiveReader, &restoreObjectsFilter); } @@ -1211,10 +1262,11 @@ bool DocumentControllerDelegate::doStoreAudioSourceToAudioFileChunk (HostArchive *openAutomatically = false; ARAAudioSourceRef audioSourceRef { toRef (audioSource) }; - const ARA::SizedStruct filter { ARA::kARATrue, - 1U, &audioSourceRef, - 0U, nullptr - }; + const SizedStruct<&ARAStoreObjectsFilter::regionSequenceRefs> filter { kARATrue, + 1U, &audioSourceRef, + 0U, nullptr, + 0U, nullptr + }; const StoreObjectsFilter storeObjectsFilter { &filter }; return doStoreObjectsToArchive (archiveWriter, &storeObjectsFilter); } @@ -1282,7 +1334,7 @@ void DocumentController::updateMusicalContextProperties (ARAMusicalContextRef mu ARA_VALIDATE_API_ARGUMENT (musicalContextRef, isValidMusicalContext (musicalContext)); ARA_VALIDATE_API_STRUCT_PTR (properties, ARAMusicalContextProperties); - if (properties.implements ()) + if (properties.implements<&ARAMusicalContextProperties::orderIndex> ()) { if (properties->orderIndex != musicalContext->getOrderIndex ()) { @@ -1443,6 +1495,9 @@ void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSeque ARA_LOG_MODELOBJECT_LIFETIME ("will destroy region sequence", regionSequence); willDestroyRegionSequence (regionSequence); + + _regionSequenceDataUpdates.erase (regionSequence); + doDestroyRegionSequence (regionSequence); } @@ -1707,11 +1762,7 @@ ARAPlaybackRegionRef DocumentController::createPlaybackRegion (ARAAudioModificat didAddPlaybackRegionToAudioModification (audioModification, playbackRegion); - auto regionSequence { playbackRegion->getRegionSequence () }; -#if ARA_SUPPORT_VERSION_1 - if (regionSequence) -#endif - didAddPlaybackRegionToRegionSequence (regionSequence, playbackRegion); + didAddPlaybackRegionToRegionSequence (playbackRegion->getRegionSequence (), playbackRegion); ARA_LOG_MODELOBJECT_LIFETIME ("did create playback region", playbackRegion); return toRef (playbackRegion); @@ -1741,17 +1792,23 @@ void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef pl playbackRegion->updateProperties (properties); didUpdatePlaybackRegionProperties (playbackRegion); -#if ARA_SUPPORT_VERSION_1 - if (newSequence) -#endif - { - if (currentSequence != newSequence) - didAddPlaybackRegionToRegionSequence (newSequence, playbackRegion); - } + if (currentSequence != newSequence) + didAddPlaybackRegionToRegionSequence (newSequence, playbackRegion); ARA_LOG_PROPERTY_CHANGES ("did update properties of playback region", playbackRegion); } +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidDocumentController (this)); + ARA_VALIDATE_API_THREAD (wasCreatedOnCurrentThread ()); + + auto playbackRegion { fromRef (playbackRegionRef) }; + ARA_VALIDATE_API_ARGUMENT (playbackRegionRef, isValidPlaybackRegion (playbackRegion)); + return doIsPlaybackRegionPreservingAudioSourceSignal (playbackRegion); +} + void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { ARA_LOG_HOST_ENTRY (playbackRegionRef); @@ -1786,10 +1843,7 @@ void DocumentController::destroyPlaybackRegion (ARAPlaybackRegionRef playbackReg ARA_VALIDATE_API_STATE (!contains (playbackRenderer->getPlaybackRegions (), playbackRegion)); #endif -#if ARA_SUPPORT_VERSION_1 - if (playbackRegion->getRegionSequence ()) -#endif - willRemovePlaybackRegionFromRegionSequence (playbackRegion->getRegionSequence (), playbackRegion); + willRemovePlaybackRegionFromRegionSequence (playbackRegion->getRegionSequence (), playbackRegion); willRemovePlaybackRegionFromAudioModification (playbackRegion->getAudioModification (), playbackRegion); @@ -2314,6 +2368,12 @@ void DocumentController::notifyPlaybackRegionContentChanged (PlaybackRegion* pla _playbackRegionContentUpdates[playbackRegion] += scopeFlags; } +void DocumentController::notifyRegionSequenceDataChanged (RegionSequence* regionSequence) noexcept +{ + if (getHostModelUpdateController ()) + _regionSequenceDataUpdates.insert (regionSequence); +} + void DocumentController::notifyDocumentDataChanged () noexcept { _documentDataChanged = true; @@ -2401,7 +2461,7 @@ void HostArchiveWriter::notifyDocumentArchivingProgress (float value) noexcept /*******************************************************************************/ -std::vector _convertPlaybackRegionsArray (const DocumentController* ARA_MAYBE_UNUSED_ARG (documentController), ARASize playbackRegionsCount, const ARAPlaybackRegionRef playbackRegionRefs[]) +static std::vector _convertPlaybackRegionsArray ([[maybe_unused]] const DocumentController* documentController, ARASize playbackRegionsCount, const ARAPlaybackRegionRef playbackRegionRefs[]) { std::vector playbackRegions; if (playbackRegionsCount > 0) @@ -2419,7 +2479,7 @@ std::vector _convertPlaybackRegionsArray (const DocumentControl return playbackRegions; } -std::vector _convertRegionSequencesArray (const DocumentController* ARA_MAYBE_UNUSED_ARG (documentController), ARASize regionSequenceRefsCount, const ARARegionSequenceRef regionSequenceRefs[]) +static std::vector _convertRegionSequencesArray ([[maybe_unused]] const DocumentController* documentController, ARASize regionSequenceRefsCount, const ARARegionSequenceRef regionSequenceRefs[]) { std::vector regionSequences; if (regionSequenceRefsCount > 0) @@ -2480,7 +2540,7 @@ std::vector ViewSelection::getEffectiveRegionSequences () const return result; } -ARAContentTimeRange getUnionTimeRangeOfPlaybackRegions (std::vector const& playbackRegions) noexcept +static ARAContentTimeRange getUnionTimeRangeOfPlaybackRegions (std::vector const& playbackRegions) noexcept { ARA_INTERNAL_ASSERT (!playbackRegions.empty ()); @@ -2791,13 +2851,16 @@ PlugInEntry::PlugInEntry (const FactoryConfig* factoryConfig, factoryConfig->getDocumentArchiveID (), factoryConfig->getCompatibleDocumentArchiveIDsCount (), factoryConfig->getCompatibleDocumentArchiveIDs (), factoryConfig->getAnalyzeableContentTypesCount (), factoryConfig->getAnalyzeableContentTypes (), factoryConfig->getSupportedPlaybackTransformationFlags (), - (factoryConfig->supportsStoringAudioFileChunks ()) ? kARATrue : kARAFalse + (factoryConfig->supportsStoringAudioFileChunks ()) ? kARATrue : kARAFalse, + (factoryConfig->supportsSampleBasedAudioSources ()) ? kARATrue : kARAFalse, + (factoryConfig->supportsContentOnlyAudioSources ()) ? kARATrue : kARAFalse, + (factoryConfig->requiresPresetAudioSources ()) ? kARATrue : kARAFalse } { #if ARA_CPU_ARM ARA_INTERNAL_ASSERT (_factory.lowestSupportedApiGeneration >= kARAAPIGeneration_2_0_Final); #else - ARA_INTERNAL_ASSERT (_factory.lowestSupportedApiGeneration >= kARAAPIGeneration_1_0_Draft); + ARA_INTERNAL_ASSERT (_factory.lowestSupportedApiGeneration >= kARAAPIGeneration_2_0_Draft); #endif ARA_INTERNAL_ASSERT (_factory.highestSupportedApiGeneration >= _factory.lowestSupportedApiGeneration); @@ -2879,7 +2942,7 @@ const ARADocumentControllerInstance* PlugInEntry::createDocumentControllerWithDo ARA_VALIDATE_API_INTERFACE (hostInstance->audioAccessControllerInterface, ARAAudioAccessControllerInterface); ARA_VALIDATE_API_INTERFACE (hostInstance->archivingControllerInterface, ARAArchivingControllerInterface); if (_usedApiGeneration >= kARAAPIGeneration_2_0_Final) - ARA_VALIDATE_API_ARGUMENT (hostInstance->archivingControllerInterface, SizedStructPtr (hostInstance->archivingControllerInterface).implements ()); + ARA_VALIDATE_API_ARGUMENT (hostInstance->archivingControllerInterface, SizedStructPtr (hostInstance->archivingControllerInterface).implements<&ARAArchivingControllerInterface::getDocumentArchiveID> ()); if (hostInstance->contentAccessControllerInterface) ARA_VALIDATE_API_INTERFACE (hostInstance->contentAccessControllerInterface, ARAContentAccessControllerInterface); if (hostInstance->modelUpdateControllerInterface) diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index 9975d76..df95120 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -2,7 +2,7 @@ //! \file ARAPlug.h //! implementation of base classes for ARA plug-ins //! \project ARA SDK Library -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -182,9 +182,9 @@ class OptionalProperty inline bool operator!= (const OptionalProperty& other) const noexcept { return !(*this == other._data); } private: - template::value, bool>::type = true> + template, bool> = true> static constexpr inline size_t getAllocSize (const T* /*value*/) noexcept { return sizeof (T); } - template::value, bool>::type = true> + template, bool> = true> static inline size_t getAllocSize (const ARAUtf8String value) noexcept { return std::strlen (value) + 1; } private: @@ -380,6 +380,7 @@ class RegionSequence const OptionalProperty& getName () const noexcept { return _name; } //!< See ARARegionSequenceProperties::name. ARAInt32 getOrderIndex () const noexcept { return _orderIndex; } //!< See ARARegionSequenceProperties::orderIndex. const OptionalProperty& getColor () const noexcept { return _color; } //!< See ARARegionSequenceProperties::color. + ARA_DRAFT const OptionalProperty& getPersistentID () const noexcept { return _persistentID; } //!< See ARARegionSequenceProperties::persistentID. //@} //! @name Region Sequence Relationships @@ -422,6 +423,7 @@ class RegionSequence OptionalProperty _name; ARAInt32 _orderIndex { 0 }; OptionalProperty _color; + OptionalProperty _persistentID; std::vector _playbackRegions; ARA_HOST_MANAGED_OBJECT (RegionSequence) @@ -623,7 +625,7 @@ class PlaybackRegion ARASamplePosition getEndInPlaybackSamples (ARASampleRate playbackSampleRate) const noexcept; //!< Playback end time in samples, derived using underlying AudioSource sample rate. bool isTimestretchEnabled () const noexcept { return _timestretchEnabled; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretch`. - bool isTimeStretchReflectingTempo () const noexcept { return _timestretchReflectingTempo; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretchReflectingTempo`. + bool isTimestretchReflectingTempo () const noexcept { return _timestretchReflectingTempo; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretchReflectingTempo`. bool hasContentBasedFadeAtHead () const noexcept { return _contentBasedFadeAtHead; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationContentBasedFadeAtHead`. bool hasContentBasedFadeAtTail () const noexcept { return _contentBasedFadeAtTail; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationContentBasedFadeAtTail`. @@ -656,10 +658,6 @@ class PlaybackRegion template AudioModification_t* getAudioModification () const noexcept { return static_cast (this->_audioModification); } -#if ARA_SUPPORT_VERSION_1 - template - MusicalContext_t* getMusicalContext () const noexcept { return static_cast ((this->_regionSequence) ? this->_regionSequence->getMusicalContext () : this->_musicalContext); } -#endif //! Retrieve the current underlying RegionSequence instance. template RegionSequence_t* getRegionSequence () const noexcept { return static_cast (this->_regionSequence); } @@ -679,9 +677,6 @@ class PlaybackRegion ARATimePosition _startInPlaybackTime { 0.0 }; ARATimeDuration _durationInPlaybackTime { 0.0 }; RegionSequence* _regionSequence { nullptr }; -#if ARA_SUPPORT_VERSION_1 - MusicalContext* _musicalContext { nullptr }; -#endif bool _timestretchEnabled { false }; bool _timestretchReflectingTempo { false }; bool _contentBasedFadeAtHead { false }; @@ -739,7 +734,7 @@ class RestoreObjectsFilter }; public: - RestoreObjectsFilter (const ARARestoreObjectsFilter* filter, Document* document) noexcept; + RestoreObjectsFilter (const SizedStructPtr filter, Document* document) noexcept; //! @name Filter Queries //! Use these functions to filter and map the objects restored during DocumentController::doRestoreObjectsFromArchive(). @@ -753,12 +748,17 @@ class RestoreObjectsFilter AudioModification* getAudioModificationToRestoreStateWithID (ARAPersistentID archivedAudioModificationID) const noexcept; template AudioModification_t* getAudioModificationToRestoreStateWithID (ARAPersistentID archivedAudioModificationID) const noexcept { return static_cast (getAudioModificationToRestoreStateWithID (archivedAudioModificationID)); } + + ARA_DRAFT RegionSequence* getRegionSequenceToRestoreStateWithID (ARAPersistentID archivedRegionSequenceID) const noexcept; + template + ARA_DRAFT RegionSequence_t* getRegionSequenceToRestoreStateWithID (ARAPersistentID archivedRegionSequenceID) const noexcept { return static_cast (getRegionSequenceToRestoreStateWithID (archivedRegionSequenceID)); } //@} private: const ARARestoreObjectsFilter* _filter; std::map _audioSourcesByID; std::map _audioModificationsByID; + std::map _regionSequencesByID; }; @@ -767,7 +767,9 @@ class RestoreObjectsFilter class StoreObjectsFilter { public: - explicit StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noexcept; + //! use this c'tor when host-provided filter is not a nullptr + explicit StoreObjectsFilter (const SizedStructPtr filter) noexcept; + //! use this c'tor when host-provided filter is a nullptr explicit StoreObjectsFilter (const Document* document) noexcept; //! @name Filter Queries @@ -779,12 +781,16 @@ class StoreObjectsFilter std::vector const& getAudioSourcesToStore () const noexcept { return vector_cast (_audioSourcesToStore); } template std::vector const& getAudioModificationsToStore () const noexcept { return vector_cast (_audioModificationsToStore); } + + template + ARA_DRAFT std::vector const& getRegionSequencesToStore () const noexcept { return vector_cast (_regionSequencesToStore); } //@} private: const ARAStoreObjectsFilter* _filter; std::vector _audioSourcesToStore; std::vector _audioModificationsToStore; + std::vector _regionSequencesToStore; }; //! @} ARA_Library_ARAPlug_Utility_Classes @@ -976,6 +982,8 @@ class DocumentControllerDelegate virtual void willUpdatePlaybackRegionProperties (PlaybackRegion* playbackRegion, PropertiesPtr newProperties) noexcept {} //! Override to customize post-update behavior of updatePlaybackRegionProperties(). virtual void didUpdatePlaybackRegionProperties (PlaybackRegion* playbackRegion) noexcept {} + //! Override to implement isPlaybackRegionPreservingAudioSourceSignal(). + ARA_DRAFT virtual bool doIsPlaybackRegionPreservingAudioSourceSignal (PlaybackRegion* playbackRegion) noexcept { return false; } //! Override to define a content based fade for \p playbackRegion by assigning positive values to \p headTime and/or \p tailTime - see getPlaybackRegionHeadAndTailTime(). virtual void doGetPlaybackRegionHeadAndTailTime (const PlaybackRegion* playbackRegion, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { *headTime = 0.0; *tailTime = 0.0; } //! Override to customize behavior before \p playbackRegion is destroyed during destroyPlaybackRegion(). @@ -1171,6 +1179,7 @@ class DocumentController : public DocumentControllerInterface, // Playback Region Management ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept override; void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept override; + ARA_DRAFT bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept override; void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept override; void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override; @@ -1267,15 +1276,20 @@ class DocumentController : public DocumentControllerInterface, //! @name Sending content updates to the host //! The implementation will internally enqueue the updates and later send them to the host -//! from notifyModelUpdates (). +//! from notifyModelUpdates(). //! Note that while the ARA API allows for specifying affected time ranges for content updates, //! this feature is not yet supported in our current plug-in implementation (since most hosts //! do not evaluate this either). +//! Since older ARA 2.x hosts will not yet support notifyRegionSequenceDataChanged(), the implementation +//! will eventually fall back to calling notifyDocumentDataChanged() instead if needed. This allows +//! plug-ins to consistently utilize the new update APIs (but they will still need to branch out for +//! old hosts/archives in their implementation of doStoreObjectsToArchive()/doRestoreObjectsFromArchive()). //@{ void notifyAudioSourceContentChanged (AudioSource* audioSource, ContentUpdateScopes scopeFlags) noexcept; void notifyAudioModificationContentChanged (AudioModification* audioModification, ContentUpdateScopes scopeFlags) noexcept; void notifyPlaybackRegionContentChanged (PlaybackRegion* playbackRegion, ContentUpdateScopes scopeFlags) noexcept; void notifyDocumentDataChanged () noexcept; + ARA_DRAFT void notifyRegionSequenceDataChanged (RegionSequence* regionSequence) noexcept; //@} // Helper for analysis requests. @@ -1350,6 +1364,7 @@ class DocumentController : public DocumentControllerInterface, std::map _audioSourceContentUpdates; std::map _audioModificationContentUpdates; std::map _playbackRegionContentUpdates; + std::set _regionSequenceDataUpdates; bool _documentDataChanged { false }; std::atomic_flag _analysisProgressIsSynced {}; // { true } would be better but C++ standard only allows for default-init to false @@ -1381,9 +1396,9 @@ class DocumentController : public DocumentControllerInterface, //! Internal helper template class for HostContentReader. template #if ARA_VALIDATE_API_CALLS -using HostContentReaderBase = ARA::ContentReader>; +using HostContentReaderBase = ARA::ContentReader>; #else -using HostContentReaderBase = ARA::ContentReader>; +using HostContentReaderBase = ARA::ContentReader>; #endif /*******************************************************************************/ @@ -1793,16 +1808,15 @@ class FactoryConfig virtual ~FactoryConfig () = default; //! \copydoc ARAFactory::lowestSupportedApiGeneration - virtual ARAAPIGeneration getLowestSupportedApiGeneration () const noexcept -#if ARA_SUPPORT_VERSION_1 - { return kARAAPIGeneration_1_0_Final; } -#elif ARA_CPU_ARM - { return kARAAPIGeneration_2_0_Final; } + virtual ARAAPIGeneration getLowestSupportedApiGeneration () const noexcept { return (supportsSampleBasedAudioSources ()) ? +#if ARA_CPU_ARM + kARAAPIGeneration_2_0_Final : #else - { return kARAAPIGeneration_2_0_Draft; } + kARAAPIGeneration_2_0_Draft : #endif + kARAAPIGeneration_3_0_Draft; } //! \copydoc ARAFactory::highestSupportedApiGeneration - virtual ARAAPIGeneration getHighestSupportedApiGeneration () const noexcept { return kARAAPIGeneration_2_X_Draft; } + virtual ARAAPIGeneration getHighestSupportedApiGeneration () const noexcept { return kARAAPIGeneration_3_0_Draft; } virtual DocumentController* createDocumentController (const PlugInEntry* entry, const ARADocumentControllerHostInstance* instance) const noexcept = 0; virtual void destroyDocumentController (DocumentController* documentController) const noexcept { delete documentController; } @@ -1835,6 +1849,15 @@ class FactoryConfig //! \copydoc ARAFactory::supportsStoringAudioFileChunks virtual bool supportsStoringAudioFileChunks () const noexcept { return false; } + + //! \copydoc ARAFactory::supportsSampleBasedAudioSources + virtual bool supportsSampleBasedAudioSources () const noexcept { return true; } + + //! \copydoc ARAFactory::supportsContentOnlyAudioSources + virtual bool supportsContentOnlyAudioSources () const noexcept { return false; } + + //! \copydoc ARAFactory::requiresPresetAudioSources + virtual bool requiresPresetAudioSources () const noexcept { return false; } }; @@ -1952,7 +1975,7 @@ class PlugInEntry private: const FactoryConfig* const _factoryConfig; - const SizedStruct _factory; + const SizedStruct<&ARAFactory::requiresPresetAudioSources> _factory; ARAAPIGeneration _usedApiGeneration { 0 }; ARA_DISABLE_COPY_AND_MOVE (PlugInEntry) diff --git a/README.md b/README.md index 0abf2e1..0a969a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ARA Audio Random Access: Implementation Library -Copyright (c) 2012-2025, [Celemony Software GmbH](https://www.celemony.com), All Rights Reserved. +Copyright (c) 2012-2026, [Celemony Software GmbH](https://www.celemony.com), All Rights Reserved. Published under the Apache 2.0 license. The ARA_Library Git repository contains an extensive set of C++ classes and utility functions to ease diff --git a/Utilities/ARAChannelFormat.cpp b/Utilities/ARAChannelFormat.cpp index b58056d..e9416c5 100644 --- a/Utilities/ARAChannelFormat.cpp +++ b/Utilities/ARAChannelFormat.cpp @@ -2,7 +2,7 @@ //! \file ARAChannelFormat.cpp //! utility class dealing with the companion-API-dependent surround channel arrangements //! \project ARA SDK Library -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/Utilities/ARAChannelFormat.h b/Utilities/ARAChannelFormat.h index 26d1614..8d5a81a 100644 --- a/Utilities/ARAChannelFormat.h +++ b/Utilities/ARAChannelFormat.h @@ -2,7 +2,7 @@ //! \file ARAChannelFormat.h //! utility class dealing with the companion-API-dependent surround channel arrangements //! \project ARA SDK Library -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/Utilities/ARAPitchInterpretation.cpp b/Utilities/ARAPitchInterpretation.cpp index d15b639..1c5c86f 100644 --- a/Utilities/ARAPitchInterpretation.cpp +++ b/Utilities/ARAPitchInterpretation.cpp @@ -3,7 +3,7 @@ //! classes to find proper names for pitches, ARAContentChord and ARAContentKeySignature //! if not provided by the API partner //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/Utilities/ARAPitchInterpretation.h b/Utilities/ARAPitchInterpretation.h index 1e312a2..9804cc6 100644 --- a/Utilities/ARAPitchInterpretation.h +++ b/Utilities/ARAPitchInterpretation.h @@ -3,7 +3,7 @@ //! classes to find proper names for pitches, ARAContentChord and ARAContentKeySignature //! if not provided by the API partner //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/Utilities/ARASamplePositionConversion.h b/Utilities/ARASamplePositionConversion.h index 696c8dc..1036e9c 100644 --- a/Utilities/ARASamplePositionConversion.h +++ b/Utilities/ARASamplePositionConversion.h @@ -3,7 +3,7 @@ //! convenience functions to ensure consistent conversion from //! continuous time to discrete sample positions and vice versa. //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/Utilities/ARAStdVectorUtilities.h b/Utilities/ARAStdVectorUtilities.h index 240b020..3db36f2 100644 --- a/Utilities/ARAStdVectorUtilities.h +++ b/Utilities/ARAStdVectorUtilities.h @@ -4,7 +4,7 @@ //! STL's std::vector, e.g. when searching derived class pointers //! in base class vectors and vice versa //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -34,13 +34,13 @@ namespace ARA { //! Up-casting vectors of base class pointers to vectors of derived class pointers. //@{ -template ::value || std::is_convertible::value), bool>::type = true> +template || std::is_convertible_v), bool> = true> inline std::vector& vector_cast (std::vector& container) noexcept { return reinterpret_cast&> (container); } -template ::value || std::is_convertible::value), bool>::type = true> +template || std::is_convertible_v), bool> = true> inline std::vector const& vector_cast (std::vector const& container) noexcept { return reinterpret_cast const&> (container); @@ -53,7 +53,7 @@ inline std::vector const& vector_cast (std::vector const& container) noexc //! Returns true if found & erased, otherwise false. //@{ -template ::value || std::is_convertible::value, bool>::type = true> +template || std::is_convertible_v, bool> = true> inline bool find_erase (std::vector& container, const U& element) noexcept { const auto it { std::find (container.begin (), container.end (), element) }; @@ -70,7 +70,7 @@ inline bool find_erase (std::vector& container, const U& element) noexcept //! Determine if an element exists in a vector. //@{ -template ::value || std::is_convertible::value, bool>::type = true> +template || std::is_convertible_v, bool> = true> inline bool contains (std::vector const& container, const U& element) noexcept { return std::find (container.begin (), container.end (), element) != container.end (); diff --git a/Utilities/ARATimelineConversion.h b/Utilities/ARATimelineConversion.h index 57e7a90..88b6d7f 100644 --- a/Utilities/ARATimelineConversion.h +++ b/Utilities/ARATimelineConversion.h @@ -3,7 +3,7 @@ //! classes to effectively iterate over tempo and signature content readers //! and convert between in seconds or quarters and between quarters and beats //! \project ARA SDK Library -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at