diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e2653f..b1fdcc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,19 @@ function(configure_ARA_Library_target target) PRIVATE -DNOMINMAX=1 ) + elseif(APPLE) + if(XCODE) + set_target_properties(${target} PROPERTIES + XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN YES + XCODE_ATTRIBUTE_GCC_INLINES_ARE_PRIVATE_EXTERN YES + ) + else() + set_target_properties(${target} PROPERTIES + C_VISIBILITY_PRESET hidden + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + ) + endif() elseif(UNIX AND NOT APPLE) set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON @@ -99,7 +112,7 @@ endfunction() # ====================== -# files used in both targets +# files used in both host and plug-in target set(ARA_Library_Common_Files "${CMAKE_CURRENT_SOURCE_DIR}/Debug/ARADebug.h" "${CMAKE_CURRENT_SOURCE_DIR}/Debug/ARADebug.c" @@ -150,4 +163,36 @@ add_library(ARA_PlugIn_Library ${ARA_LIBRARY_TARGET_TYPE} "${CMAKE_CURRENT_SOURCE_DIR}/PlugIn/ARAPlug.h" "${CMAKE_CURRENT_SOURCE_DIR}/PlugIn/ARAPlug.cpp" ) + configure_ARA_Library_target(ARA_PlugIn_Library) + +# ====================== + +add_library(ARA_IPC_Library ${ARA_LIBRARY_TARGET_TYPE} + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPC.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCEncoding.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCLockingContext.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCLockingContext.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyHost.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyHost.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyPlugIn.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyPlugIn.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyHost.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCProxyHost.cpp" +) + +if(APPLE) + target_sources(ARA_IPC_Library PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCAudioUnit_v3.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCAudioUnit_v3.m" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCCFEncoding.h" + "${CMAKE_CURRENT_SOURCE_DIR}/IPC/ARAIPCCFEncoding.cpp" + ) +endif() + +target_link_libraries(ARA_IPC_Library PRIVATE + ARA_Host_Library + ARA_PlugIn_Library +) + +configure_ARA_Library_target(ARA_IPC_Library) diff --git a/ChangeLog.txt b/ChangeLog.txt index ef5714d..c0e3ab7 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,12 @@ +This is a development build of the ARA Library 2.3. +=== PRELIMINARY - DO NOT USE FOR SHIPPING PRODUCTS! === + + +Changes since previous releases: +- 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 + + === ARA SDK 2.2 release (aka 2.2.001) (2022/11/07) === - added surround support for audio sources - added API to determine whether an audio modification actually modifies the underlying audio source diff --git a/Debug/ARADebug.h b/Debug/ARADebug.h index 4593eb4..5c0aafb 100644 --- a/Debug/ARADebug.h +++ b/Debug/ARADebug.h @@ -69,7 +69,8 @@ extern "C" // logs to the debugger console and/or to the error output (e.g. stderr) void ARADebugMessage(ARADebugLevel level, const char * file, int line, const char * text, ...); // this logging can be prefixed by a custom static string if desired, which is useful e.g. - // if multiple plug-ins are build using the same compiled library, or in the IPC Demo example + // if multiple plug-ins are build using the same compiled library, or if using the IPC + // capabilities of the ARATestHost example // typically, this setup call is made once when loading the final binary and passes its name void ARASetupDebugMessagePrefix(const char * prefix); #if defined(__cplusplus) diff --git a/Dispatch/ARADispatchBase.h b/Dispatch/ARADispatchBase.h index 2a56824..c5f2644 100644 --- a/Dispatch/ARADispatchBase.h +++ b/Dispatch/ARADispatchBase.h @@ -31,6 +31,36 @@ #include "ARA_API/ARAInterface.h" +/*******************************************************************************/ +/** Optional ARA 1 backwards compatibility. + Hosts and plug-ins can choose support ARA 1 hosts in addition to ARA 2 hosts. + This feature is being phased out as all vendors move to ARA 2, and is not + available on architectures where ARA 1 was not available, such as ARM. + + For hosts, using ARA 1 plug-ins through the implementation provided here + imposes several implicit restrictions: + - each plug-in instance assumes all possible roles (see ARAPlugInInstanceRoleFlags) + - each plug-in instance only is associated with at most one playback region at any time + - the ARA 1 API is mapped to PlaybackRenderer*, the other interfaces will not be provided + - archiving must use ARA 1 style monolithic persistency calls + + Plug-ins will have to implement a several fallbacks in order to work in ARA 1 hosts, + in addition to the support provided by this implementation they need to: + - create dummy region sequences for the playback regions, utilizing the + context information provided via the companion APIs + - implicitly derive ARA selection state from companion API actions +*/ +/*******************************************************************************/ + +#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 { /*******************************************************************************/ diff --git a/Dispatch/ARAHostDispatch.h b/Dispatch/ARAHostDispatch.h index 2e1d44c..6cce269 100644 --- a/Dispatch/ARAHostDispatch.h +++ b/Dispatch/ARAHostDispatch.h @@ -30,25 +30,6 @@ namespace Host { //! @addtogroup ARA_Library_Host_Dispatch //! @{ -/*******************************************************************************/ -/** Optional ARA 1 backwards compatibility. - Host can choose support ARA 1 plug-ins in addition to ARA 2 plug-ins. - This results in several restrictions being implicitly imposed when using such plug-ins - through the implementation provided here: - - each plug-in instance assumes all possible roles (see ARAPlugInInstanceRoleFlags) - - each plug-in instance only is associated with at most one playback region at any time - - the ARA 1 API is mapped to PlaybackRenderer*, the other interfaces will not be provided - - archiving must use ARA 1 style monolithic persistency calls -*/ -/*******************************************************************************/ - -#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 /*******************************************************************************/ /** Type safe conversions to/from host ref: toHostRef () and fromHostRef<> (). diff --git a/Dispatch/ARAPlugInDispatch.h b/Dispatch/ARAPlugInDispatch.h index 374ea32..888b9a1 100644 --- a/Dispatch/ARAPlugInDispatch.h +++ b/Dispatch/ARAPlugInDispatch.h @@ -27,24 +27,6 @@ namespace PlugIn { //! @addtogroup ARA_Library_PlugIn_Dispatch //! @{ -/*******************************************************************************/ -/** Optional ARA 1 backwards compatibility. - Plug-ins can choose support ARA 1 hosts in addition to ARA 2 hosts. - Plug-ins will have to implement a lot of fallbacks in addition to the support - provided by this implementation: - - create dummy region sequences for the playback regions, utilizing the - context information provided via the companion APIs - - implicitly derive ARA selection state from companion API actions -*/ -/*******************************************************************************/ -#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 - /*******************************************************************************/ /** Type safe conversions to/from ref: toRef () and fromRef<> (). This macro defines custom overloads of the toRef () and fromRef<> () conversion functions diff --git a/IPC/ARAIPC.h b/IPC/ARAIPC.h new file mode 100644 index 0000000..bcbb685 --- /dev/null +++ b/IPC/ARAIPC.h @@ -0,0 +1,218 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPC.h +//! Abstractions 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-2022, 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 ARAIPC_h +#define ARAIPC_h + + +#include "ARA_API/ARAInterface.h" + + +//! @addtogroup ARA_Library_IPC +//! @{ + +//! switch to bypass all IPC code +#if !defined (ARA_ENABLE_IPC) + #if defined (__APPLE__) || defined (_WIN32) + #define ARA_ENABLE_IPC 1 + #else + #define ARA_ENABLE_IPC 0 + #endif +#endif + + +#if ARA_ENABLE_IPC + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +//! ID type for messages, IDs must be >= kARAIPCMessageIDRangeStart and < kARAIPCMessageIDRangeEnd +typedef ARAInt32 ARAIPCMessageID; +#if defined(__cplusplus) + constexpr ARAIPCMessageID kARAIPCMessageIDRangeStart { 1 }; + constexpr ARAIPCMessageID kARAIPCMessageIDRangeEnd { 8*16*16 - 1 }; +#else + #define kARAIPCMessageIDRangeStart ((ARAIPCMessageID) 1) + #define kARAIPCMessageIDRangeEnd ((ARAIPCMessageID) 8*16*16 - 1) +#endif + + +//! key type for message dictionaries - negative keys are reserved for the implementation +typedef ARAInt32 ARAIPCMessageKey; + + +//! Message Encoder +//! @{ +typedef struct ARAIPCMessageEncoderImplementation * ARAIPCMessageEncoderRef; + +typedef struct ARAIPCMessageEncoderInterface +{ + //! destructor + void (ARA_CALL *destroyEncoder) (ARAIPCMessageEncoderRef encoderRef); + + //! number types + //! The size variant will also be used for the pointer-sized ARA (host) refs. + //@{ + void (ARA_CALL *appendInt32) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, int32_t argValue); + void (ARA_CALL *appendInt64) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, int64_t argValue); + void (ARA_CALL *appendSize) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, size_t argValue); + void (ARA_CALL *appendFloat) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, float argValue); + void (ARA_CALL *appendDouble) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, double argValue); + //@} + + //! UTF8-encoded C strings + void (ARA_CALL *appendString) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, const char * argValue); + + //! raw bytes + //! As optimization, disable copying if the memory containing the bytes stays + //! alive&unchanged until the message has been sent. + void (ARA_CALL *appendBytes) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, const uint8_t * argValue, size_t argSize, bool copy); + + //! sub-messages to encode compound types + //! The caller is responsible for deleting the encoder after use. + ARAIPCMessageEncoderRef (ARA_CALL *appendSubMessage) (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey); +} ARAIPCMessageEncoderInterface; + +typedef struct ARAIPCMessageEncoder +{ + ARAIPCMessageEncoderRef ref; + const ARAIPCMessageEncoderInterface * methods; // this preferably would be called "interface", but there's a system-defined macro in Windows with that name... +} ARAIPCMessageEncoder; +//! @} + + +//! Message Decoder +//! @{ +typedef struct ARAIPCMessageDecoderImplementation * ARAIPCMessageDecoderRef; + +typedef struct ARAIPCMessageDecoderInterface +{ + //! destructor + void (ARA_CALL *destroyDecoder) (ARAIPCMessageDecoderRef messageDecoderRef); + + //! only for debugging/validation: test if the message contains any key/value pairs + bool (ARA_CALL *isEmpty) (ARAIPCMessageDecoderRef messageDecoderRef); + + //! 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. + //@{ + bool (ARA_CALL *readInt32) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, int32_t * argValue); + bool (ARA_CALL *readInt64) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, int64_t * argValue); + bool (ARA_CALL *readSize) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, size_t * argValue); + bool (ARA_CALL *readFloat) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, float * argValue); + bool (ARA_CALL *readDouble) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, double * argValue); + //@} + + //! UTF8-encoded C strings + //! Will return false and set argValue to NULL if key not found. + bool (ARA_CALL *readString) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, const char ** argValue); + + //! 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. + //@{ + bool (ARA_CALL *readBytesSize) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, size_t * argSize); + void (ARA_CALL *readBytes) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, uint8_t * argValue); + //@} + + //! 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 + //! The caller is responsible for deleting the encoder after use. + ARAIPCMessageDecoderRef (ARA_CALL *readSubMessage) (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey); +} ARAIPCMessageDecoderInterface; + +typedef struct ARAIPCMessageDecoder +{ + ARAIPCMessageDecoderRef ref; + const ARAIPCMessageDecoderInterface * methods; // this preferably would be called "interface", but there's a system-defined macro in Windows with that name... +} ARAIPCMessageDecoder; +//! @} + + +//! Message Receiver: a function that receives a message readable through the decoder, optionally creating a reply +//! Not using the replyEncoder will return a valid empty message to the sender (useful for void calls). +//! Depending on the underlying implementation, replyEncoder may be nullptr if no reply has been +//! requested by the sender, but providing a dummy encoder in this case is valid too. +//! The sender thread will be blocked until the (possibly empty) reply has been received. +//! A receive function can be called from any thread, but not concurrently. +typedef void (ARA_CALL *ARAIPCMessageReceiver) (const ARAIPCMessageID messageID, const ARAIPCMessageDecoder decoder, ARAIPCMessageEncoder * replyEncoder); + +//! Reply Handler: a function that is called to process the reply to a message +typedef void (ARA_CALL *ARAIPCReplyHandler) (const ARAIPCMessageDecoder decoder, void * userData); + +//! Message Sender: gateway for sending messages +//! @{ +typedef struct ARAIPCMessageSenderImplementation * ARAIPCMessageSenderRef; + +typedef struct ARAIPCMessageSenderInterface +{ + //! generate an encoder to encode a new message + //! An encoder can be reused if the same message is sent several times, + //! but it must not be modified after sending. + //! The caller is responsible for deleting the encoder after use. + ARAIPCMessageEncoder (ARA_CALL *createEncoder) (ARAIPCMessageSenderRef messageSenderRef); + + //! send function: send message create using the encoder, blocking until a reply has been received. + //! If an empty reply ("void") is expected, the replyHandler should be nullptr. + //! A send function can be called from any thread, but not concurrently. + void (ARA_CALL *sendMessage) (const bool stackable, ARAIPCMessageSenderRef messageSenderRef, ARAIPCMessageID messageID, + const ARAIPCMessageEncoder * encoder, ARAIPCReplyHandler * const replyHandler, void * replyHandlerUserData); + + //! Test if the receiver runs on a different architecture with different endianess. + bool (ARA_CALL *receiverEndianessMatches) (ARAIPCMessageSenderRef messageSenderRef); +} ARAIPCMessageSenderInterface; + +typedef struct ARAIPCMessageSender +{ + ARAIPCMessageSenderRef ref; + const ARAIPCMessageSenderInterface * methods; // this preferably would be called "interface", but there's a system-defined macro in Windows with that name... +} ARAIPCMessageSender; +//! @} + + +//! Companion API: opaque encapsulation +//! @{ +//! to keep the IPC decoupled from the Companion API in use, the IPC code uses an opaque token to represent a plug-in instance +typedef size_t ARAIPCPlugInInstanceRef; + +//! 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); +//! @} + + +#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..266e271 --- /dev/null +++ b/IPC/ARAIPCAudioUnit_v3.h @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCAudioUnit_v3.h +//! Implementation of ARA IPC message sending through AUMessageChannel +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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" + + +// 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" +#import "ARA_Library/IPC/ARAIPCLockingContext.h" + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +API_AVAILABLE_BEGIN(macos(13.0)) + + +//! host side: initialize the message sender used for all factory-related messaging for all +//! Audio Units that have the same component as the provided audioUnit +//! will return false if the Audio Unit does not implement ARAAudioUnit or [AUAudioUnit messageChannelFor:], +//! leaving the channel uninitalized +//! this sender can be used for the calls to ARAIPCAUProxyPlugInGetFactory(), ARAIPCProxyPlugInInitializeARA(), +//! ARAIPCProxyPlugInCreateDocumentControllerWithDocument() and ARAIPCProxyPlugInUninitializeARA() +bool ARA_CALL ARAIPCAUProxyPlugInInitializeFactoryMessageSender(ARAIPCMessageSender * _Nonnull messageSender, AUAudioUnit * _Nonnull audioUnit, ARAIPCLockingContextRef _Nonnull lockingContextRef); + +//! host side: get the ARA factory for the audio unit +//! will return NULL if the Audio Unit does not implement ARAAudioUnit or [AUAudioUnit messageChannelFor:] +const ARAFactory * _Nonnull ARA_CALL ARAIPCAUProxyPlugInGetFactory(ARAIPCMessageSender messageSender); + +//! host side: create the plug-in extension when performing the binding to the remote plug-in instance +//! also initialzies the messageSender which remains valid until ARAIPCAUProxyPlugInCleanupBinding() is called +//! the document controller must be created through a factory obtained through ARAIPCAUProxyPlugInGetFactory() +//! will return NULL if the Audio Unit does not implement ARAAudioUnit or [AUAudioUnit messageChannelFor:], +//! leaving messageSender uninitalized in that case +const ARAPlugInExtensionInstance * _Nullable ARA_CALL ARAIPCAUProxyPlugInBindToDocumentController(AUAudioUnit * _Nonnull audioUnit, + ARAIPCLockingContextRef _Nonnull lockingContextRef, + ARADocumentControllerRef _Nonnull documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles, + ARAIPCMessageSender * _Nonnull messageSender); + +//! host side: trigger proper teardown of proxy plug-in extension when Companion API instance is destroyed +//! the messageSender must have been initialized by ARAIPCAUProxyPlugInBindToDocumentController() and will be uninitialized +void ARA_CALL ARAIPCAUProxyPlugInCleanupBinding(ARAIPCMessageSender messageSender); + +//! host side: uninitialize the sender set up in ARAIPCAUProxyPlugInInitializeFactoryMessageSender() +void ARA_CALL ARAIPCAUProxyPlugInUninitializeFactoryMessageSender(ARAIPCMessageSender messageSender); + + +//! plug-in side: static configuration: add the ARA factories that the IPC shall handle +void ARA_CALL ARAIPCAUProxyHostAddFactory(const ARAFactory * _Nonnull factory); + +//! callback that the proxy uses to execute the binding of an AUAudioUnit to the given document controller +typedef const ARAPlugInExtensionInstance * _Nonnull (^ARAIPCAUBindingHandler)(AUAudioUnit * _Nonnull audioUnit, ARADocumentControllerRef _Nonnull controllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles); + +//! callback that the proxy uses to ensure synchronous destruction of a an AUAudioUnit bound to ARA +//! this extra hook is necessary because the regular teardown via dealloc is delayed in macOS 13 on +//! the remote end, causing race conditions with ARA teardown methods send after plug-in destruction +typedef void (^ARAIPCAUDestructionHandler)(AUAudioUnit * _Nonnull audioUnit); + +//! plug-in side: static configuration: after adding all factories, initialize the IPC (before allocating any instances) +void ARA_CALL ARAIPCAUProxyHostInitialize(NSObject * _Nonnull factoryMessageChannel, + ARAIPCAUBindingHandler _Nonnull bindingHandler, ARAIPCAUDestructionHandler _Nonnull destructionHandler); + +//! plug-in side: implementation for AUMessageChannel -callAudioUnit: +NSDictionary * _Nonnull ARA_CALL ARAIPCAUProxyHostCommandHandler (AUAudioUnit * _Nullable audioUnit, NSDictionary * _Nonnull wrappedMessage); + +//! plug-in side: trigger proper teardown of proxy plug-in extension when Companion API instance is destroyed +void ARA_CALL ARAIPCAUProxyHostCleanupBinding(const ARAPlugInExtensionInstance * _Nonnull plugInExtensionInstance); + +//! plug-in side: static cleanup upon shutdown +void ARA_CALL ARAIPCAUProxyHostUninitalize(void); + + +API_AVAILABLE_END + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +#endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE diff --git a/IPC/ARAIPCAudioUnit_v3.m b/IPC/ARAIPCAudioUnit_v3.m new file mode 100644 index 0000000..87dd4ad --- /dev/null +++ b/IPC/ARAIPCAudioUnit_v3.m @@ -0,0 +1,383 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCAudioUnit_v3.m +//! Implementation of ARA IPC message sending through AUMessageChannel +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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/ARAIPCCFEncoding.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 + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +API_AVAILABLE_BEGIN(macos(13.0)) + + +// custom IPC message to read the remote instance ref +const ARAIPCMessageID kARAIPCGetRemoteInstanceRef = -1; + +// custom IPC message to force synchronous IPC shutdown despite the OS shutting down asynchronously +const ARAIPCMessageID kARAIPCDestroyRemoteInstance = -2; + + +// "base class" layout used in plug-in and host +typedef struct ARAIPCMessageSenderImplementation +{ + NSObject * __strong messageChannel; + ARAIPCLockingContextRef lockingContextRef; +} ARAIPCMessageSenderImplementation; + +// "subclass" used for plug-in extension proxies with added fields +typedef struct ARAIPCAUProxyPlugInExtensionMessageSenderImplementation +{ + ARAIPCMessageSenderImplementation base; + ARAIPCPlugInInstanceRef remoteInstanceRef; // stores "ref" for the remote ARAIPCAUAudioUnit instance + const ARAPlugInExtensionInstance * plugInExtensionInstance; // stored only for proper cleanup +} ARAIPCAUProxyPlugInExtensionMessageSenderImplementation; + + +// message sender implementations for host and plug-in +ARAIPCMessageEncoder ARA_CALL ARAIPCAUCreateEncoder(ARAIPCMessageSenderRef ARA_MAYBE_UNUSED_ARG(messageSenderRef)) +{ + return ARAIPCCFCreateMessageEncoder(); +} + +void ARA_CALL ARAIPCAUProxyPlugInSendMessage(const bool stackable, ARAIPCMessageSenderRef messageSenderRef, ARAIPCMessageID messageID, const ARAIPCMessageEncoder * encoder, + ARAIPCReplyHandler * const replyHandler, void * replyHandlerUserData) +{ + @autoreleasepool + { + NSDictionary * message = CFBridgingRelease(ARAIPCCFCopyMessageEncoderDictionaryAddingMessageID(encoder->ref, messageID)); + const ARAIPCLockingContextMessageSendingToken lockToken = ARAIPCLockContextBeforeSendingMessage(messageSenderRef->lockingContextRef, stackable); + if (replyHandler) + { + CFDictionaryRef _Nonnull reply = (__bridge CFDictionaryRef)[messageSenderRef->messageChannel callAudioUnit:message]; + ARAIPCMessageDecoder replyDecoder = ARAIPCCFCreateMessageDecoderWithDictionary(reply); + (*replyHandler)(replyDecoder, replyHandlerUserData); + replyDecoder.methods->destroyDecoder(replyDecoder.ref); + } + else + { + NSDictionary * _Nonnull reply = [messageSenderRef->messageChannel callAudioUnit:message]; + ARA_INTERNAL_ASSERT([reply count] == 0); + } + ARAIPCUnlockContextAfterSendingMessage(messageSenderRef->lockingContextRef, lockToken); + } +} + +void ARA_CALL ARAIPCAUProxyHostSendMessage(const bool stackable, ARAIPCMessageSenderRef messageSenderRef, ARAIPCMessageID messageID, const ARAIPCMessageEncoder * encoder, + ARAIPCReplyHandler * const replyHandler, void * replyHandlerUserData) +{ + @autoreleasepool + { + CallHostBlock callHostBlock = messageSenderRef->messageChannel.callHostBlock; + if (callHostBlock) + { + NSDictionary * message = CFBridgingRelease(ARAIPCCFCopyMessageEncoderDictionaryAddingMessageID(encoder->ref, messageID)); + const ARAIPCLockingContextMessageSendingToken lockToken = ARAIPCLockContextBeforeSendingMessage(messageSenderRef->lockingContextRef, stackable); + if (replyHandler) + { + CFDictionaryRef _Nullable reply = (__bridge CFDictionaryRef)callHostBlock(message); + ARAIPCMessageDecoder replyDecoder = ARAIPCCFCreateMessageDecoderWithDictionary(reply); + (*replyHandler)(replyDecoder, replyHandlerUserData); + replyDecoder.methods->destroyDecoder(replyDecoder.ref); + } + else + { + NSDictionary * _Nullable reply = callHostBlock(message); + ARA_INTERNAL_ASSERT([reply count] == 0); + } + ARAIPCUnlockContextAfterSendingMessage(messageSenderRef->lockingContextRef, lockToken); + } + else + { + ARA_INTERNAL_ASSERT(false && "trying to send IPC message while host has not set callHostBlock"); + if (replyHandler) + { + ARAIPCMessageDecoder replyDecoder = ARAIPCCFCreateMessageDecoderWithDictionary(NULL); + (*replyHandler)(replyDecoder, replyHandlerUserData); + replyDecoder.methods->destroyDecoder(replyDecoder.ref); + } + } + } +} + +bool ARA_CALL ARAIPCAUReceiverEndianessMatches(ARAIPCMessageSenderRef ARA_MAYBE_UNUSED_ARG(messageSenderRef)) +{ + // \todo shouldn't the AUMessageChannel provide this information? + return true; +} + +ARAIPCMessageSender ARA_CALL ARAIPCAUInitializeSender(ARAIPCMessageSenderImplementation * ref, const ARAIPCMessageSenderInterface * methods, + NSObject * _Nonnull messageChannel, ARAIPCLockingContextRef _Nonnull lockingContextRef) +{ + ARAIPCMessageSender sender; + sender.methods = methods; + + sender.ref = ref; + ARA_INTERNAL_ASSERT(sender.ref != NULL); +#if __has_feature(objc_arc) + memset((void*)ref, 0, sizeof(((ARAIPCMessageSenderImplementation *)NULL)->messageChannel)); // partially clear malloced memory or else ARC will try to release some "old object" upon assign + sender.ref->messageChannel = messageChannel; +#else + sender.ref->messageChannel = [messageChannel retain]; +#endif + sender.ref->lockingContextRef = lockingContextRef; + + return sender; +} + +void ARA_CALL ARAIPCAUDestroyMessageSender(ARAIPCMessageSender messageSender) +{ +#if __has_feature(objc_arc) + messageSender.ref->messageChannel = nil; +#else + [messageSender.ref->messageChannel release]; +#endif + free(messageSender.ref); +} + + + +void ARA_CALL ARAIPCAUGetRemoteInstanceReplyHandler(const ARAIPCMessageDecoder decoder, void * _Nullable userData) +{ + ARA_INTERNAL_ASSERT(!decoder.methods->isEmpty(decoder.ref)); + bool ARA_MAYBE_UNUSED_VAR(success) = decoder.methods->readSize(decoder.ref, 0, (ARAIPCPlugInInstanceRef*)userData); + ARA_INTERNAL_ASSERT(success); +} + +ARAIPCMessageSender ARA_CALL ARAIPCAUProxyPlugInCreateMessageSender(NSObject * _Nonnull messageChannel, + ARAIPCMessageSenderRef messageSenderRef, + ARAIPCLockingContextRef _Nonnull lockingContextRef) +{ + static const ARAIPCMessageSenderInterface senderInterface = { ARAIPCAUCreateEncoder, ARAIPCAUProxyPlugInSendMessage, ARAIPCAUReceiverEndianessMatches }; + ARAIPCMessageSender sender = ARAIPCAUInitializeSender(messageSenderRef, &senderInterface, messageChannel, lockingContextRef); + + // \todo we happen to use the same message block with the same locking context for both the + // ARA_AUDIOUNIT_FACTORY_CUSTOM_MESSAGES_UTI and all ARA_AUDIOUNIT_PLUGINEXTENSION_CUSTOM_MESSAGES_UTI uses, + // but it is generally unclear here how to set the correct message block if the channel is shared. + messageChannel.callHostBlock = ^NSDictionary * _Nullable (NSDictionary * _Nonnull message) + { + ARAIPCMessageDecoder messageDecoder = ARAIPCCFCreateMessageDecoderWithDictionary((__bridge CFDictionaryRef)message); + ARAIPCMessageID messageID = ARAIPCCFGetMessageIDFromDictionary(messageDecoder.ref); + + ARAIPCMessageEncoder replyEncoder = ARAIPCCFCreateMessageEncoder(); + + const ARAIPCLockingContextMessageHandlingToken lockToken = ARAIPCLockContextBeforeHandlingMessage(lockingContextRef); + ARAIPCProxyPlugInCallbacksDispatcher(messageID, &messageDecoder, &replyEncoder); + ARAIPCUnlockContextAfterHandlingMessage(lockingContextRef, lockToken); + + messageDecoder.methods->destroyDecoder(messageDecoder.ref); + + NSDictionary * replyDictionary = CFBridgingRelease(ARAIPCCFCopyMessageEncoderDictionary(replyEncoder.ref)); + replyEncoder.methods->destroyEncoder(replyEncoder.ref); + return replyDictionary; + }; + + return sender; +} + +id _Nullable ARA_CALL ARAIPCAUGetMessageChannel(AUAudioUnit * _Nonnull audioUnit, NSString * _Nonnull identifier) +{ + // AUAudioUnits created before macOS 13 will not know about this API yet + if (![audioUnit respondsToSelector:@selector(messageChannelFor:)]) + return nil; + + return [(AUAudioUnit *) audioUnit messageChannelFor:identifier]; +} + +bool ARA_CALL ARAIPCAUProxyPlugInInitializeFactoryMessageSender(ARAIPCMessageSender * _Nonnull messageSender, + AUAudioUnit * _Nonnull audioUnit, ARAIPCLockingContextRef _Nonnull lockingContextRef) +{ + id _Nullable messageChannel = ARAIPCAUGetMessageChannel(audioUnit, ARA_AUDIOUNIT_FACTORY_CUSTOM_MESSAGES_UTI); + if (!messageChannel) + return false; + + ARAIPCMessageSenderImplementation * messageSenderRef = malloc(sizeof(ARAIPCMessageSenderImplementation)); + *messageSender = ARAIPCAUProxyPlugInCreateMessageSender((NSObject * _Nonnull)messageChannel, messageSenderRef, lockingContextRef); + return true; +} + +const ARAFactory * _Nonnull ARA_CALL ARAIPCAUProxyPlugInGetFactory(ARAIPCMessageSender messageSender) +{ + ARA_VALIDATE_API_CONDITION(ARAIPCProxyPlugInGetFactoriesCount(messageSender) == 1); + const ARAFactory * result = ARAIPCProxyPlugInGetFactoryAtIndex(messageSender, 0); + ARA_VALIDATE_API_CONDITION(result != NULL); + return result; +} + +const ARAPlugInExtensionInstance * _Nullable ARA_CALL ARAIPCAUProxyPlugInBindToDocumentController(AUAudioUnit * _Nonnull audioUnit, + ARAIPCLockingContextRef _Nonnull lockingContextRef, + ARADocumentControllerRef _Nonnull documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles, + ARAIPCMessageSender * _Nonnull messageSender) +{ + id _Nullable messageChannel = ARAIPCAUGetMessageChannel(audioUnit, ARA_AUDIOUNIT_PLUGINEXTENSION_CUSTOM_MESSAGES_UTI); + if (!messageChannel) + return NULL; + + ARAIPCAUProxyPlugInExtensionMessageSenderImplementation * messageSenderRef = malloc(sizeof(ARAIPCAUProxyPlugInExtensionMessageSenderImplementation)); + messageSenderRef->plugInExtensionInstance = NULL; + *messageSender = ARAIPCAUProxyPlugInCreateMessageSender((NSObject * _Nonnull)messageChannel, (ARAIPCMessageSenderImplementation *)messageSenderRef, lockingContextRef); + + ARAIPCMessageEncoder encoder = messageSender->methods->createEncoder(messageSender->ref); + ARAIPCReplyHandler replyHandler = ARAIPCAUGetRemoteInstanceReplyHandler; + messageSender->methods->sendMessage(false, messageSender->ref, kARAIPCGetRemoteInstanceRef, &encoder, &replyHandler, &messageSenderRef->remoteInstanceRef); + encoder.methods->destroyEncoder(encoder.ref); + + messageSenderRef->plugInExtensionInstance = ARAIPCProxyPlugInBindToDocumentController(messageSenderRef->remoteInstanceRef, *messageSender, documentControllerRef, knownRoles, assignedRoles); + return messageSenderRef->plugInExtensionInstance; +} + +void ARA_CALL ARAIPCAUProxyPlugInCleanupBinding(ARAIPCMessageSender messageSender) +{ + ARAIPCAUProxyPlugInExtensionMessageSenderImplementation * messageSenderRef = (ARAIPCAUProxyPlugInExtensionMessageSenderImplementation *)messageSender.ref; + if (messageSenderRef->plugInExtensionInstance) + { + ARAIPCMessageEncoder encoder = messageSender.methods->createEncoder(messageSender.ref); + encoder.methods->appendSize(encoder.ref, 0, messageSenderRef->remoteInstanceRef); + messageSender.methods->sendMessage(false, messageSender.ref, kARAIPCDestroyRemoteInstance, &encoder, NULL, NULL); + encoder.methods->destroyEncoder(encoder.ref); + + ARAIPCProxyPlugInCleanupBinding(messageSenderRef->plugInExtensionInstance); + } + + ARAIPCAUDestroyMessageSender(messageSender); +} + +void ARA_CALL ARAIPCAUProxyPlugInUninitializeFactoryMessageSender(ARAIPCMessageSender messageSender) +{ + ARAIPCAUDestroyMessageSender(messageSender); +} + + + +ARAIPCLockingContextRef _sharedPlugInLockingContextRef; +ARAIPCMessageSender _sharedPlugInCallbacksSender; + +void ARA_CALL ARAIPCAUProxyHostAddFactory(const ARAFactory * _Nonnull factory) +{ + ARAIPCProxyHostAddFactory(factory); +} + +ARAIPCAUBindingHandler _bindingHandler = nil; +ARAIPCAUDestructionHandler _destructionHandler = nil; + +const ARAPlugInExtensionInstance * ARA_CALL ARAIPCAUBindingHandlerWrapper(ARAIPCPlugInInstanceRef plugInInstanceRef, ARADocumentControllerRef controllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles) +{ + AUAudioUnit * audioUnit = (__bridge AUAudioUnit *)(void *)plugInInstanceRef; + return _bindingHandler (audioUnit, controllerRef, knownRoles, assignedRoles); +} + +void ARA_CALL ARAIPCAUProxyHostInitialize(NSObject * _Nonnull factoryMessageChannel, ARAIPCAUBindingHandler _Nonnull bindingHandler, ARAIPCAUDestructionHandler _Nonnull destructionHandler) +{ + _sharedPlugInLockingContextRef = ARAIPCCreateLockingContext(); + + static const ARAIPCMessageSenderInterface senderInterface = { ARAIPCAUCreateEncoder, ARAIPCAUProxyHostSendMessage, ARAIPCAUReceiverEndianessMatches }; + _sharedPlugInCallbacksSender = ARAIPCAUInitializeSender(malloc(sizeof(ARAIPCMessageSenderImplementation)), &senderInterface, factoryMessageChannel, _sharedPlugInLockingContextRef); + ARAIPCProxyHostSetPlugInCallbacksSender(_sharedPlugInCallbacksSender); + +#if __has_feature(objc_arc) + _bindingHandler = bindingHandler; + _destructionHandler = destructionHandler; +#else + _bindingHandler = [bindingHandler retain]; + _destructionHandler = [destructionHandler retain]; +#endif + ARAIPCProxyHostSetBindingHandler(ARAIPCAUBindingHandlerWrapper); +} + +NSDictionary * _Nonnull ARA_CALL ARAIPCAUProxyHostCommandHandler (AUAudioUnit * _Nullable audioUnit, NSDictionary * _Nonnull message) +{ + ARAIPCMessageDecoder messageDecoder = ARAIPCCFCreateMessageDecoderWithDictionary((__bridge CFDictionaryRef)message); + ARAIPCMessageID messageID = ARAIPCCFGetMessageIDFromDictionary(messageDecoder.ref); + + ARAIPCMessageEncoder replyEncoder = ARAIPCCFCreateMessageEncoder(); + + if (messageID == kARAIPCGetRemoteInstanceRef) + { + replyEncoder.methods->appendSize(replyEncoder.ref, 0, (ARAIPCPlugInInstanceRef)audioUnit); + } + else if (messageID == kARAIPCDestroyRemoteInstance) + { + ARA_INTERNAL_ASSERT(!messageDecoder.methods->isEmpty(messageDecoder.ref)); + ARAIPCPlugInInstanceRef plugInInstanceRef; + bool ARA_MAYBE_UNUSED_VAR(success) = messageDecoder.methods->readSize(messageDecoder.ref, 0, &plugInInstanceRef); + ARA_INTERNAL_ASSERT(success); + + AUAudioUnit * audioUnit = (__bridge AUAudioUnit *)(void *)plugInInstanceRef; + _destructionHandler (audioUnit); + } + else + { + const ARAIPCLockingContextMessageHandlingToken lockToken = ARAIPCLockContextBeforeHandlingMessage(_sharedPlugInLockingContextRef); + ARAIPCProxyHostCommandHandler(messageID, &messageDecoder, &replyEncoder); + ARAIPCUnlockContextAfterHandlingMessage(_sharedPlugInLockingContextRef, lockToken); + } + + messageDecoder.methods->destroyDecoder(messageDecoder.ref); + + NSDictionary * replyDictionary = CFBridgingRelease(ARAIPCCFCopyMessageEncoderDictionary(replyEncoder.ref)); + replyEncoder.methods->destroyEncoder(replyEncoder.ref); + return replyDictionary; +} + +void ARA_CALL ARAIPCAUProxyHostCleanupBinding(const ARAPlugInExtensionInstance * _Nonnull plugInExtensionInstance) +{ + ARAIPCProxyHostCleanupBinding(plugInExtensionInstance); +} + +void ARA_CALL ARAIPCAUProxyHostUninitalize(void) +{ +#if __has_feature(objc_arc) + _bindingHandler = nil; + _destructionHandler = nil; +#else + [_bindingHandler release]; + [_destructionHandler release]; +#endif + ARAIPCAUDestroyMessageSender(_sharedPlugInCallbacksSender); + ARAIPCDestroyLockingContext(_sharedPlugInLockingContextRef); +} + + +API_AVAILABLE_END + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +#endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE diff --git a/IPC/ARAIPCCFEncoding.cpp b/IPC/ARAIPCCFEncoding.cpp new file mode 100644 index 0000000..93aae33 --- /dev/null +++ b/IPC/ARAIPCCFEncoding.cpp @@ -0,0 +1,423 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCCFEncoding.cpp +//! Implementation of ARAIPCMessageEn-/Decoder backed by CF(Mutable)Dictionary +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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 { +extern "C" { + + +#if defined (__GNUC__) + _Pragma ("GCC diagnostic push") + _Pragma ("GCC diagnostic ignored \"-Wold-style-cast\"") +#endif + + +// the message ID will be added to the underlying dictionary with a key that does not conflict with other message keys +constexpr ARAIPCMessageKey messageIDKey { -1 }; + + +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) +CFStringRef ARA_CALL ARAIPCCFMessageGetEncodedKey (ARAIPCMessageKey argKey, bool isInternalCall = false) +{ + if (!isInternalCall) + ARA_INTERNAL_ASSERT (argKey >= 0); + + // \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; +} + + + +inline ARAIPCMessageEncoderRef ARAIPCCFMessageToEncoderRef (CFMutableDictionaryRef dictionary) +{ + return (ARAIPCMessageEncoderRef) dictionary; +} + +inline CFMutableDictionaryRef ARAIPCCFMessageFromEncoderRef (ARAIPCMessageEncoderRef messageEncoderRef) +{ + return (CFMutableDictionaryRef) messageEncoderRef; +} + +void ARA_CALL ARAIPCCFMessageDestroyEncoder (ARAIPCMessageEncoderRef messageEncoderRef) +{ + if (messageEncoderRef) + CFRelease (ARAIPCCFMessageFromEncoderRef (messageEncoderRef)); +} + +void ARA_CALL ARAIPCCFMessageAppendInt32 (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, int32_t argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &argValue) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendInt64 (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, int64_t argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt64Type, &argValue) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendSize (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey 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 (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendFloat (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, float argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberFloatType, &argValue) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendDouble (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, double argValue) +{ + auto argObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberDoubleType, &argValue) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendString (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey, const char* argValue) +{ + auto argObject { CFStringCreateWithCString (kCFAllocatorDefault, argValue, kCFStringEncodingUTF8) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +void ARA_CALL ARAIPCCFMessageAppendBytes (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey 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 (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + CFRelease (argObject); +} + +ARAIPCMessageEncoderRef ARA_CALL ARAIPCCFMessageAppendSubMessage (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageKey argKey) +{ + auto argObject { CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) }; + CFDictionarySetValue (ARAIPCCFMessageFromEncoderRef (messageEncoderRef), ARAIPCCFMessageGetEncodedKey (argKey), argObject); + return (ARAIPCMessageEncoderRef) argObject; +} + +ARAIPCMessageEncoder ARAIPCCFCreateMessageEncoder (void) +{ + static const ARA::IPC::ARAIPCMessageEncoderInterface encoderMethods + { + ARAIPCCFMessageDestroyEncoder, + ARAIPCCFMessageAppendInt32, + ARAIPCCFMessageAppendInt64, + ARAIPCCFMessageAppendSize, + ARAIPCCFMessageAppendFloat, + ARAIPCCFMessageAppendDouble, + ARAIPCCFMessageAppendString, + ARAIPCCFMessageAppendBytes, + ARAIPCCFMessageAppendSubMessage + }; + + return { ARAIPCCFMessageToEncoderRef (CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), &encoderMethods }; +} + +__attribute__((cf_returns_retained)) CFDictionaryRef ARAIPCCFCopyMessageEncoderDictionary (ARAIPCMessageEncoderRef messageEncoderRef) +{ + const auto dictionary { ARAIPCCFMessageFromEncoderRef (messageEncoderRef) }; + ARA_INTERNAL_ASSERT (dictionary); + CFRetain (dictionary); + return dictionary; +} + +__attribute__((cf_returns_retained)) CFDictionaryRef ARAIPCCFCopyMessageEncoderDictionaryAddingMessageID (ARAIPCMessageEncoderRef messageEncoderRef, ARAIPCMessageID messageIDValue) +{ + static_assert (std::is_same::value, "encoding needs to be adopted here if key type is changed"); + auto messageIDObject { CFNumberCreate (kCFAllocatorDefault, kCFNumberSInt32Type, &messageIDValue) }; + auto dictionary { ARAIPCCFMessageFromEncoderRef (messageEncoderRef) }; + ARA_INTERNAL_ASSERT (dictionary); + CFDictionarySetValue (dictionary, ARAIPCCFMessageGetEncodedKey (messageIDKey, true), messageIDObject); + CFRelease (messageIDObject); + CFRetain (dictionary); + return dictionary; +} + +__attribute__((cf_returns_retained)) CFDataRef ARAIPCCFCreateMessageEncoderData (ARAIPCMessageEncoderRef messageEncoderRef) +{ + const auto dictionary { ARAIPCCFMessageFromEncoderRef (messageEncoderRef) }; + if ((!dictionary) || (CFDictionaryGetCount (dictionary) == 0)) + return nullptr; + return CFPropertyListCreateData (kCFAllocatorDefault, dictionary, kCFPropertyListBinaryFormat_v1_0, 0, nullptr); +} + + + +inline ARAIPCMessageDecoderRef ARAIPCCFMessageToDecoderRef (CFDictionaryRef dictionary) +{ + return (ARAIPCMessageDecoderRef) dictionary; +} + +inline CFDictionaryRef ARAIPCCFMessageFromDecoderRef (ARAIPCMessageDecoderRef messageDecoderRef) +{ + return (CFDictionaryRef) messageDecoderRef; +} + +void ARA_CALL ARAIPCCFMessageDestroyDecoder (ARAIPCMessageDecoderRef messageDecoderRef) +{ + if (messageDecoderRef) + CFRelease (ARAIPCCFMessageFromDecoderRef (messageDecoderRef)); +} + +bool ARA_CALL ARAIPCCFMessageIsEmpty (ARAIPCMessageDecoderRef messageDecoderRef) +{ + return (!messageDecoderRef) || (CFDictionaryGetCount (ARAIPCCFMessageFromDecoderRef (messageDecoderRef)) == 0); +} + +bool ARA_CALL ARAIPCCFMessageReadInt32 (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, int32_t* argValue) +{ + CFNumberRef number {}; + if (messageDecoderRef) + number = (CFNumberRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt32Type, argValue); + return true; +} + +bool ARA_CALL ARAIPCCFMessageReadInt64 (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, int64_t* argValue) +{ + CFNumberRef number {}; + if (messageDecoderRef) + number = (CFNumberRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt64Type, argValue); + return true; +} + +bool ARA_CALL ARAIPCCFMessageReadSize (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, size_t* argValue) +{ + static_assert (sizeof (SInt64) == sizeof (size_t), "integer type needs adjustment for this compiler setup"); + + CFNumberRef number {}; + if (messageDecoderRef) + number = (CFNumberRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!number) + { + *argValue = 0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberSInt64Type, argValue); + return true; +} + +bool ARA_CALL ARAIPCCFMessageReadFloat (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, float* argValue) +{ + CFNumberRef number {}; + if (messageDecoderRef) + number = (CFNumberRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!number) + { + *argValue = 0.0f; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberFloatType, argValue); + return true; +} + +bool ARA_CALL ARAIPCCFMessageReadDouble (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, double* argValue) +{ + CFNumberRef number {}; + if (messageDecoderRef) + number = (CFNumberRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!number) + { + *argValue = 0.0; + return false; + } + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + CFNumberGetValue (number, kCFNumberDoubleType, argValue); + return true; +} + +bool ARA_CALL ARAIPCCFMessageReadString (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, const char** argValue) +{ + CFStringRef string {}; + if (messageDecoderRef) + string = (CFStringRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (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 } ); + CFIndex ARA_MAYBE_UNUSED_VAR (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 ARA_CALL ARAIPCCFMessageReadBytesSize (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, size_t* argSize) +{ + CFDataRef bytes {}; + if (messageDecoderRef) + bytes = (CFDataRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)); + if (!bytes) + { + *argSize = 0; + return false; + } + ARA_INTERNAL_ASSERT (bytes && (CFGetTypeID (bytes) == CFDataGetTypeID ())); + *argSize = (size_t) CFDataGetLength (bytes); + return true; + +} + +void ARA_CALL ARAIPCCFMessageReadBytes (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey, uint8_t* argValue) +{ + ARA_INTERNAL_ASSERT (messageDecoderRef); + auto bytes { (CFDataRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)) }; + ARA_INTERNAL_ASSERT (bytes && (CFGetTypeID (bytes) == CFDataGetTypeID ())); + const auto length { CFDataGetLength (bytes) }; + CFDataGetBytes (bytes, CFRangeMake (0, length), argValue); +} + +ARAIPCMessageDecoderRef ARA_CALL ARAIPCCFMessageReadSubMessage (ARAIPCMessageDecoderRef messageDecoderRef, ARAIPCMessageKey argKey) +{ + auto dictionary { (CFDictionaryRef) CFDictionaryGetValue (ARAIPCCFMessageFromDecoderRef (messageDecoderRef), ARAIPCCFMessageGetEncodedKey (argKey)) }; + ARA_INTERNAL_ASSERT (!dictionary || (CFGetTypeID (dictionary) == CFDictionaryGetTypeID ())); + if (dictionary) + CFRetain (dictionary); + return ARAIPCCFMessageToDecoderRef (dictionary); +} + +ARAIPCMessageDecoder ARAIPCCFCreateMessageDecoderWithRetainedDictionary (CFDictionaryRef __attribute__((cf_consumed)) messageDictionary) +{ + static const ARA::IPC::ARAIPCMessageDecoderInterface decoderMethods + { + ARAIPCCFMessageDestroyDecoder, + ARAIPCCFMessageIsEmpty, + ARAIPCCFMessageReadInt32, + ARAIPCCFMessageReadInt64, + ARAIPCCFMessageReadSize, + ARAIPCCFMessageReadFloat, + ARAIPCCFMessageReadDouble, + ARAIPCCFMessageReadString, + ARAIPCCFMessageReadBytesSize, + ARAIPCCFMessageReadBytes, + ARAIPCCFMessageReadSubMessage + }; + + return { ARAIPCCFMessageToDecoderRef (messageDictionary), &decoderMethods }; +} + +ARAIPCMessageDecoder ARAIPCCFCreateMessageDecoderWithDictionary (CFDictionaryRef messageDictionary) +{ + if (messageDictionary) + CFRetain (messageDictionary); + + return ARAIPCCFCreateMessageDecoderWithRetainedDictionary (messageDictionary); +} + +ARAIPCMessageID ARAIPCCFGetMessageIDFromDictionary (ARAIPCMessageDecoderRef messageDecoderRef) +{ + const auto dictionary { ARAIPCCFMessageFromDecoderRef (messageDecoderRef) }; + ARA_INTERNAL_ASSERT (dictionary != nullptr); + const auto number { (CFNumberRef) CFDictionaryGetValue (dictionary, ARAIPCCFMessageGetEncodedKey (messageIDKey, true)) }; + ARA_INTERNAL_ASSERT (number != nullptr); + ARA_INTERNAL_ASSERT (CFGetTypeID (number) == CFNumberGetTypeID ()); + static_assert (std::is_same::value, "decoding needs to be adopted here if key type is changed"); + ARAIPCMessageID result; + CFNumberGetValue (number, kCFNumberSInt32Type, &result); + return result; +} + +ARAIPCMessageDecoder ARAIPCCFCreateMessageDecoder (CFDataRef messageData) +{ + if (CFDataGetLength (messageData) == 0) + return ARAIPCCFCreateMessageDecoderWithRetainedDictionary (nullptr); + + auto dictionary { (CFDictionaryRef) CFPropertyListCreateWithData (kCFAllocatorDefault, messageData, kCFPropertyListImmutable, nullptr, nullptr) }; + ARA_INTERNAL_ASSERT (dictionary && (CFGetTypeID (dictionary) == CFDictionaryGetTypeID ())); + auto result { ARAIPCCFCreateMessageDecoderWithRetainedDictionary (dictionary) }; + return result; +} + + +#if defined (__APPLE__) + _Pragma ("GCC diagnostic pop") +#endif + +} // extern "C" +} // 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..4b64e22 --- /dev/null +++ b/IPC/ARAIPCCFEncoding.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCCFEncoding.h +//! Implementation of ARAIPCMessageEn-/Decoder backed by CF(Mutable)Dictionary +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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/ARAIPC.h" + + +#if ARA_ENABLE_IPC && defined(__APPLE__) + +#include + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +ARAIPCMessageEncoder ARAIPCCFCreateMessageEncoder(void); +__attribute__((cf_returns_retained)) CFDictionaryRef ARAIPCCFCopyMessageEncoderDictionary(ARAIPCMessageEncoderRef messageEncoder); +__attribute__((cf_returns_retained)) CFDictionaryRef ARAIPCCFCopyMessageEncoderDictionaryAddingMessageID(ARAIPCMessageEncoderRef messageEncoder, ARAIPCMessageID messageIDValue); +__attribute__((cf_returns_retained)) CFDataRef ARAIPCCFCreateMessageEncoderData(ARAIPCMessageEncoderRef messageEncoderRef); + +ARAIPCMessageDecoder ARAIPCCFCreateMessageDecoderWithDictionary(CFDictionaryRef messageDictionary); +ARAIPCMessageID ARAIPCCFGetMessageIDFromDictionary(ARAIPCMessageDecoderRef messageDecoderRef); +ARAIPCMessageDecoder ARAIPCCFCreateMessageDecoder(CFDataRef messageData); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +#endif // ARA_ENABLE_IPC && defined(__APPLE__) + +#endif // ARAIPCCFEncoding_h diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h new file mode 100644 index 0000000..05cc953 --- /dev/null +++ b/IPC/ARAIPCEncoding.h @@ -0,0 +1,1328 @@ +//------------------------------------------------------------------------------ +//! \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 Examples +//! \copyright Copyright (c) 2021-2022, 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/ARAIPC.h" + + +#if ARA_ENABLE_IPC && defined(__cplusplus) + +#include "ARA_Library/Debug/ARADebug.h" +#include "ARA_Library/Dispatch/ARAContentReader.h" +#include "ARA_Library/Utilities/ARAChannelArrangement.h" + +#include +#include +#include +#include +#include +#include +#include + + +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 +struct _IsRefType +{ + static constexpr bool value { false }; +}; +#define ARA_IPC_SPECIALIZE_FOR_REF_TYPE(Type) \ +template<> \ +struct _IsRefType \ +{ \ + static constexpr bool value { 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) +#undef ARA_IPC_SPECIALIZE_FOR_REF_TYPE + + +// helper template to identify pointers to ARA structs in message arguments +template +struct _IsStructPointerArg +{ + struct _False + { + static constexpr bool value { false }; + }; + struct _True + { + static constexpr bool value { true }; + }; + using type = typename std::conditional::value && + std::is_pointer::value && !std::is_same::value, _True, _False>::type; +}; + + +//------------------------------------------------------------------------------ +// private primitive wrappers for MessageEn-/Decoder C API +//------------------------------------------------------------------------------ + + +// primitives for appending an argument to a message +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const int32_t argValue) +{ + encoder.methods->appendInt32 (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const int64_t argValue) +{ + encoder.methods->appendInt64 (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const size_t argValue) +{ + encoder.methods->appendSize (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const float argValue) +{ + encoder.methods->appendFloat (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const double argValue) +{ + encoder.methods->appendDouble (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const char* const argValue) +{ + encoder.methods->appendString (encoder.ref, argKey, argValue); +} +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const BytesEncoder& argValue) +{ + const uint8_t* bytes; + size_t size; + bool copy; + argValue (bytes, size, copy); + encoder.methods->appendBytes (encoder.ref, argKey, bytes, size, copy); +} + +// primitives for reading an (optional) argument from a message +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, int32_t& argValue) +{ + return decoder.methods->readInt32 (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, int64_t& argValue) +{ + return decoder.methods->readInt64 (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, size_t& argValue) +{ + return decoder.methods->readSize (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, float& argValue) +{ + return decoder.methods->readFloat (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, double& argValue) +{ + return decoder.methods->readDouble (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, const char*& argValue) +{ + return decoder.methods->readString (decoder.ref, argKey, &argValue); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, BytesDecoder& argValue) +{ + size_t receivedSize; + const auto found { decoder.methods->readBytesSize (decoder.ref, argKey, &receivedSize) }; + auto availableSize { receivedSize }; + const auto bytes { argValue (availableSize) }; + if (!found) + return false; + if (availableSize < receivedSize) + return false; + decoder.methods->readBytes (decoder.ref, 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::value, bool>::type = true> +inline void _appendToMessage (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const T argValue) +{ + encoder.methods->appendSize (encoder.ref, argKey, reinterpret_cast (argValue)); +} +template::value, bool>::type = true> +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey 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.methods->readSize (decoder.ref, 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 (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ARAByte argValue) +{ + encoder.appendInt32 (argKey, static_cast (argValue)); +} +inline bool _readFromMessage (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey 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 (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ValueT& argValue); +template +inline bool _readAndDecode (ValueT& result, const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey); + + +// primary templates for basic types (numbers, strings, (host)refs and raw bytes) +template +struct _ValueEncoder +{ + static inline void encodeAndAppend (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ValueT& argValue) + { + _appendToMessage (encoder, argKey, argValue); + } +}; +template +struct _ValueDecoder +{ + static inline bool readAndDecode (ValueT& result, const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey 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 (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ValueT& argValue) + { + auto subEncoderRef { encoder.methods->appendSubMessage (encoder.ref, argKey) }; + ARA_INTERNAL_ASSERT (subEncoderRef != nullptr); + ARAIPCMessageEncoder subEncoder { subEncoderRef, encoder.methods }; + _ValueEncoder::encode (subEncoder, argValue); + encoder.methods->destroyEncoder (subEncoderRef); + } +}; +template +struct _CompoundValueDecoderBase +{ + static inline bool readAndDecode (ValueT& result, const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey) + { + auto subDecoderRef { decoder.methods->readSubMessage (decoder.ref, argKey) }; + if (subDecoderRef == nullptr) + return false; + ARAIPCMessageDecoder subDecoder { subDecoderRef, decoder.methods }; + const auto success { _ValueDecoder::decode (result, subDecoder) }; + decoder.methods->destroyDecoder (subDecoderRef); + return success; + } +}; + + +// specialization for encoding arrays (variable or fixed size) +template +struct _ValueEncoder> : public _CompoundValueEncoderBase> +{ + static inline void encode (ARAIPCMessageEncoder& 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 ARAIPCMessageDecoder& decoder) + { + bool success { true }; + ARAIPCMessageKey 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 ARAIPCMessageDecoder& decoder) + { + bool success { true }; + ARAIPCMessageKey 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 (ARAIPCMessageEncoder& 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::type> tmp_##member { value.member, std::extent::value }; \ + _encodeAndAppend (encoder, offsetof (StructType, member), tmp_##member); +#define ARA_IPC_ENCODE_VARIABLE_ARRAY(member, count) \ + if ((value.count > 0) && (value.member != nullptr)) { \ + const ArrayArgument::type> 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::type, 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_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 ARAIPCMessageDecoder& 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::type> tmp_##member { result.member, std::extent::value }; \ + 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 array, so we need some */ \ + /* place to store it - this static only works as long as this is single-threaded! */ \ + static std::vector::type>::type> 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 subDecoderRef_##member { decoder.methods->readSubMessage (decoder.ref, offsetof (StructType, member)) }; \ + if (subDecoderRef_##member != nullptr) { \ + ARAIPCMessageDecoder subDecoder { subDecoderRef_##member, decoder.methods }; \ + /* \todo the outer struct contains a pointer to the inner struct, so we need some */\ + /* place to store it - this static only works as long as this is single-threaded! */\ + static std::remove_const::type>::type cache; \ + success &= _ValueDecoder::decode (cache, subDecoder); \ + ARA_INTERNAL_ASSERT (success); \ + result.member = &cache; \ + { OPTIONAL_UPDATE_MACRO } \ + decoder.methods->destroyDecoder (subDecoderRef_##member); \ + } \ + 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_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_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 ChannelArrangement channelArrangement { value.channelArrangementDataType, value.channelArrangement }; + const auto size { channelArrangement.getDataSize () }; + 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 */ + /* place to store it - this static only works as long as this is single-threaded! */ + static union + { + ARAInt64 speakerArrangement; + ARAInt32 stemFormat; + ARAByte audioChannelLayoutBytes[2016UL]; // some arbitrary limit that can be conveniently allocated + } cache; + auto resultSize_channelArrangement { sizeof (cache.audioChannelLayoutBytes) }; + BytesDecoder tmp_channelArrangement { cache.audioChannelLayoutBytes, resultSize_channelArrangement }; + if (_readAndDecode (tmp_channelArrangement, decoder, offsetof (ARAAudioSourceProperties, channelArrangement)) && + (resultSize_channelArrangement < sizeof (cache.audioChannelLayoutBytes))) + { + result.channelArrangement = &cache.audioChannelLayoutBytes; + } + 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 (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_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_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_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_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 (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ValueT& argValue) +{ + return _ValueEncoder::encodeAndAppend (encoder, argKey, argValue); +} +template +inline bool _readAndDecode (ValueT& result, const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey) +{ + return _ValueDecoder::readAndDecode (result, decoder, argKey); +} + + +//------------------------------------------------------------------------------ + +// private helper for decodeArguments() to deal with optional arguments +template +struct _IsOptionalArgument +{ + static constexpr bool value { false }; +}; +template +struct _IsOptionalArgument> +{ + static constexpr bool value { true }; +}; + +// private helpers for encodeArguments() and decodeArguments() to deal with the variable arguments one at a time +inline void _encodeArgumentsHelper (ARAIPCMessageEncoder& /*encoder*/, const ARAIPCMessageKey /*argKey*/) +{ +} +template::type::value, bool>::type = true> +inline void _encodeArgumentsHelper (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ArgT& argValue, const MoreArgs &... moreArgs) +{ + _encodeAndAppend (encoder, argKey, argValue); + _encodeArgumentsHelper (encoder, argKey + 1, moreArgs...); +} +template::type::value, bool>::type = true> +inline void _encodeArgumentsHelper (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const ArgT& argValue, const MoreArgs &... moreArgs) +{ + if (argValue != nullptr) + _encodeAndAppend (encoder, argKey, *argValue); + _encodeArgumentsHelper (encoder, argKey + 1, moreArgs...); +} +template +inline void _encodeArgumentsHelper (ARAIPCMessageEncoder& encoder, const ARAIPCMessageKey argKey, const std::nullptr_t& /*argValue*/, const MoreArgs &... moreArgs) +{ + _encodeArgumentsHelper (encoder, argKey + 1, moreArgs...); +} + +inline void _decodeArgumentsHelper (const ARAIPCMessageDecoder& /*decoder*/, const ARAIPCMessageKey /*argKey*/) +{ +} +template::value, bool>::type = true> +inline void _decodeArgumentsHelper (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, ArgT& argValue, MoreArgs &... moreArgs) +{ + _readAndDecode (argValue, decoder, argKey); + _decodeArgumentsHelper (decoder, argKey + 1, moreArgs...); +} +template::value, bool>::type = true> +inline void _decodeArgumentsHelper (const ARAIPCMessageDecoder& decoder, const ARAIPCMessageKey argKey, ArgT& argValue, MoreArgs &... moreArgs) +{ + argValue.second = _readAndDecode (argValue.first, decoder, argKey); + _decodeArgumentsHelper (decoder, argKey + 1, moreArgs...); +} + +// private helpers for ARA_IPC_HOST_METHOD_ID and ARA_IPC_PLUGIN_METHOD_ID +template +constexpr ARAIPCMessageID _getHostInterfaceID (); +template<> +constexpr ARAIPCMessageID _getHostInterfaceID () { return 0; } +template<> +constexpr ARAIPCMessageID _getHostInterfaceID () { return 1; } +template<> +constexpr ARAIPCMessageID _getHostInterfaceID () { return 2; } +template<> +constexpr ARAIPCMessageID _getHostInterfaceID () { return 3; } +template<> +constexpr ARAIPCMessageID _getHostInterfaceID () { return 4; } + +template +constexpr ARAIPCMessageID _getPlugInInterfaceID (); +template<> +constexpr ARAIPCMessageID _getPlugInInterfaceID () { return 0; } +template<> +constexpr ARAIPCMessageID _getPlugInInterfaceID () { return 1; } +template<> +constexpr ARAIPCMessageID _getPlugInInterfaceID () { return 2; } +template<> +constexpr ARAIPCMessageID _getPlugInInterfaceID () { return 3; } + + +//------------------------------------------------------------------------------ +// actual client API +//------------------------------------------------------------------------------ + + +// helper class wrapping ARAIPCMessageID to prevent implicit conversions to/from +// ARAIPCMessageID, 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 (ARAIPCMessageID 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 createWithARASetupMethodID () + { + static_assert ((methodID >= kARAIPCMessageIDRangeStart) && (methodID < kARAIPCMessageIDRangeEnd), "must be in valid ARA message ID range"); + return methodID; + } + + template + static inline constexpr MethodID createWithNonARAMethodID () + { + static_assert ((methodID < kARAIPCMessageIDRangeStart) || (methodID >= kARAIPCMessageIDRangeStart), "must not be in valid ARA message range"); + return methodID; + } + + constexpr ARAIPCMessageID getMessageID () const { return _id; } +private: + const ARAIPCMessageID _id; +}; + +inline bool operator== (const ARAIPCMessageID messageID, const MethodID methodID) +{ + return (messageID == methodID.getMessageID ()); +} +inline bool operator== (const MethodID methodID, const ARAIPCMessageID messageID) +{ + return (messageID == methodID.getMessageID ()); +} + + +// create a MethodID for a given host-side or plug-in-side ARA method +#define ARA_IPC_HOST_METHOD_ID(StructT, member) MethodID::createWithARAInterfaceIDAndOffset <_getHostInterfaceID (), offsetof (StructT, member)> () +#define ARA_IPC_PLUGIN_METHOD_ID(StructT, member) MethodID::createWithARAInterfaceIDAndOffset <_getPlugInInterfaceID (), offsetof (StructT, member)> () + + +// "global" messages for startup and teardown that are not calculated based on interface structs +constexpr auto kGetFactoriesCountMethodID { MethodID::createWithARASetupMethodID<1> () }; +constexpr auto kGetFactoryMethodID { MethodID::createWithARASetupMethodID<2> () }; +constexpr auto kInitializeARAMethodID { MethodID::createWithARASetupMethodID<3> () }; +constexpr auto kCreateDocumentControllerMethodID { MethodID::createWithARASetupMethodID<4> () }; +constexpr auto kBindToDocumentControllerMethodID { MethodID::createWithARASetupMethodID<5> () }; +constexpr auto kUninitializeARAMethodID { MethodID::createWithARASetupMethodID<6> () }; + + +// caller side: create a message with the specified arguments +template +inline void encodeArguments (ARAIPCMessageEncoder& encoder, const Args &... args) +{ + _encodeArgumentsHelper (encoder, 0, args...); +} + +// caller side: decode the received reply to a sent message +template::value || !std::is_pod::value, bool>::type = true> +inline bool decodeReply (RetT& result, const ARAIPCMessageDecoder& decoder) +{ + return _readAndDecode (result, decoder, 0); +} +template::value && std::is_pod::value, bool>::type = true> +inline bool decodeReply (RetT& result, const ARAIPCMessageDecoder& decoder) +{ + return _ValueDecoder::decode (result, decoder); +} + + +// callee side: decode the arguments of a received message +template +inline void decodeArguments (const ARAIPCMessageDecoder* 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::value || !std::is_pod::value, bool>::type = true> +inline void encodeReply (ARAIPCMessageEncoder* encoder, const ValueT& value) +{ + ARA_INTERNAL_ASSERT (encoder != nullptr); + _encodeAndAppend (*encoder, 0, value); +} +template::value && std::is_pod::value, bool>::type = true> +inline void encodeReply (ARAIPCMessageEncoder* encoder, const ValueT& value) +{ + ARA_INTERNAL_ASSERT (encoder != nullptr); + _ValueEncoder::encode (*encoder, value); +} + + +// for debugging only: decoding method IDs +inline const char* _decodeMessageID (std::map& cache, const char* interfaceName, const ARAIPCMessageID messageID) +{ + auto it { cache.find (messageID) }; + if (it == cache.end ()) + it = cache.emplace (messageID, std::string { interfaceName } + " method " + std::to_string (messageID >> 3)).first; + return it->second.c_str(); +} +inline const char* decodeHostMessageID (const ARAIPCMessageID messageID) +{ + static std::map cache; + const char* interfaceName; + switch (messageID & 0x7) + { + case 0: interfaceName = "ARAAudioAccessControllerInterface"; break; + case 1: interfaceName = "ARAArchivingControllerInterface"; break; + case 2: interfaceName = "ARAContentAccessControllerInterface"; break; + case 3: interfaceName = "ARAModelUpdateControllerInterface"; break; + case 4: interfaceName = "ARAPlaybackControllerInterface"; break; + default: ARA_INTERNAL_ASSERT (false); interfaceName = "(unknown)"; break; + } + return _decodeMessageID (cache, interfaceName, messageID); +} +inline const char* decodePlugInMessageID (const ARAIPCMessageID messageID) +{ + static std::map cache; + const char* interfaceName; + switch (messageID & 0x7) + { + case 0: interfaceName = "ARADocumentControllerInterface"; break; + case 1: interfaceName = "ARAPlaybackRendererInterface"; break; + case 2: interfaceName = "ARAEditorRendererInterface"; break; + case 3: interfaceName = "ARAEditorViewInterface"; break; + default: ARA_INTERNAL_ASSERT (false); interfaceName = "(unknown)"; break; + } + return _decodeMessageID (cache, interfaceName, messageID); +} + +//------------------------------------------------------------------------------ +// support for content readers +//------------------------------------------------------------------------------ + +inline void encodeContentEvent (ARAIPCMessageEncoder* 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 ARAIPCMessageDecoder& decoder) + { + (this->*_decoderFunction) (decoder); + return &_eventStorage; + } + +private: + using _DecoderFunction = void (ContentEventDecoder::*) (const ARAIPCMessageDecoder&); + + template + void _decodeContentEvent (const ARAIPCMessageDecoder& decoder) + { + decodeReply (*reinterpret_cast (&this->_eventStorage), decoder); + if (this->_contentString != nullptr) + { + this->_stringStorage.assign (*this->_contentString); + *this->_contentString = this->_stringStorage.c_str (); + } + } + + static inline +#if __cplusplus >= 201402L + constexpr +#endif + _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 +#if __cplusplus >= 201402L + constexpr +#endif + 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; + + RemoteCaller (ARAIPCMessageSender sender) noexcept : _sender { sender } {} + + template + void remoteCall (const bool stackable, const MethodID methodID, const Args &... args) + { + auto encoder { _sender.methods->createEncoder (_sender.ref) }; + encodeArguments (encoder, args...); + _sender.methods->sendMessage (stackable, _sender.ref, methodID.getMessageID (), &encoder, nullptr, nullptr); + encoder.methods->destroyEncoder (encoder.ref); + } + + template + void remoteCall (RetT& result, const bool stackable, const MethodID methodID, const Args &... args) + { + auto encoder { _sender.methods->createEncoder (_sender.ref) }; + encodeArguments (encoder, args...); + ARAIPCReplyHandler replyHandler { [] (const ARAIPCMessageDecoder decoder, void* userData) -> void + { + ARA_INTERNAL_ASSERT (!decoder.methods->isEmpty (decoder.ref)); + decodeReply (*reinterpret_cast (userData), decoder); + } }; + _sender.methods->sendMessage (stackable, _sender.ref, methodID.getMessageID (), &encoder, &replyHandler, &result); + encoder.methods->destroyEncoder (encoder.ref); + } + template + void remoteCall (CustomDecodeFunction& decodeFunction, const bool stackable, const MethodID methodID, const Args &... args) + { + auto encoder { _sender.methods->createEncoder (_sender.ref) }; + encodeArguments (encoder, args...); + ARAIPCReplyHandler replyHandler { [] (const ARAIPCMessageDecoder decoder, void* userData) -> void + { + ARA_INTERNAL_ASSERT (!decoder.methods->isEmpty (decoder.ref)); + (*reinterpret_cast (userData)) (decoder); + } }; + _sender.methods->sendMessage (stackable, _sender.ref, methodID.getMessageID (), &encoder, &replyHandler, &decodeFunction); + encoder.methods->destroyEncoder (encoder.ref); + } + + bool receiverEndianessMatches () { return _sender.methods->receiverEndianessMatches (_sender.ref); } + +private: + ARAIPCMessageSender _sender; +}; + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC && defined(__cplusplus) + +#endif // ARAIPCEncoding_h diff --git a/IPC/ARAIPCLockingContext.cpp b/IPC/ARAIPCLockingContext.cpp new file mode 100644 index 0000000..e874496 --- /dev/null +++ b/IPC/ARAIPCLockingContext.cpp @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCLockingContext.cpp +//! Implementation of locking IPC access to make it appear single-threaded +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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 "ARAIPCLockingContext.h" + + +#if ARA_ENABLE_IPC + + +#include "ARA_Library/Debug/ARADebug.h" + +#include +#include + + +namespace ARA { +namespace IPC { +extern "C" { + + +struct ARAIPCLockingContextImplementation +{ + std::mutex sendMutex {}; + std::mutex handleMutex {}; + bool sendIsStackable {}; +}; + + +thread_local ARAIPCLockingContextRef _currentHandlingLockingContextRef {}; + + +ARAIPCLockingContextRef ARA_CALL ARAIPCCreateLockingContext (void) +{ + return new ARAIPCLockingContextImplementation; +} + +void ARA_CALL ARAIPCDestroyLockingContext (ARAIPCLockingContextRef lockingContextRef) +{ + delete lockingContextRef; +} + +ARAIPCLockingContextMessageSendingToken ARA_CALL ARAIPCLockContextBeforeSendingMessage (ARAIPCLockingContextRef lockingContextRef, bool stackable) +{ + const auto useLock { !lockingContextRef->sendIsStackable || (_currentHandlingLockingContextRef != lockingContextRef) }; + if (useLock) + { + lockingContextRef->sendMutex.lock (); + lockingContextRef->sendIsStackable = stackable; + } + return useLock; +} + +void ARA_CALL ARAIPCUnlockContextAfterSendingMessage (ARAIPCLockingContextRef lockingContextRef, ARAIPCLockingContextMessageSendingToken lockToken) +{ + if (lockToken) + { + lockingContextRef->sendIsStackable = false; + lockingContextRef->sendMutex.unlock (); + } +} + +ARAIPCLockingContextMessageHandlingToken ARA_CALL ARAIPCLockContextBeforeHandlingMessage (ARAIPCLockingContextRef lockingContextRef) +{ + if (_currentHandlingLockingContextRef != lockingContextRef) + lockingContextRef->handleMutex.lock (); + const auto result { _currentHandlingLockingContextRef }; + _currentHandlingLockingContextRef = lockingContextRef; + return result; +} + +void ARA_CALL ARAIPCUnlockContextAfterHandlingMessage (ARAIPCLockingContextRef lockingContextRef, ARAIPCLockingContextMessageHandlingToken lockToken) +{ + ARA_INTERNAL_ASSERT (_currentHandlingLockingContextRef == lockingContextRef); + if (lockingContextRef != lockToken) + lockingContextRef->handleMutex.unlock (); + _currentHandlingLockingContextRef = lockToken; +} + + +} // extern "C" +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCLockingContext.h b/IPC/ARAIPCLockingContext.h new file mode 100644 index 0000000..79efa4b --- /dev/null +++ b/IPC/ARAIPCLockingContext.h @@ -0,0 +1,95 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCLockingContext.h +//! Implementation of locking IPC access to make it appear single-threaded +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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 ARAIPCLockingContext_h +#define ARAIPCLockingContext_h + + +#include "ARA_Library/IPC/ARAIPC.h" + + +//! @addtogroup ARA_Library_IPC +//! @{ + +#if ARA_ENABLE_IPC + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + +//! Locking Context +//! The underlying IPC implementation must channel several threads through one or more single-threaded +//! IPC channel, requiring proper locking. At the same time, sending and receiving messages may be +//! distributed across different threads, and may be stacked, so proper locking depends on the specific +//! calls - for example, the host might call ARADocumentControllerInterface::notifyModelUpdates(), +//! causing the plug-in to respond with ARAModelUpdateControllerInterface.notifyAudioSourceContentChanged(), +//! which in turn triggers a host call to ARADocumentControllerInterface::isAudioSourceContentAvailable() etc. +//! At the same time, the host may make render calls through the Companion API and another thread in +//! the plug-in might be call ARAAudioAccessControllerInterface::readAudioSamples(). +//! To handle this properly, the locking strategy is separated into this LockingContext class. +//! All communication that interacts in the non-IPC ARA world must use the same locking context, +//! i.e. all calls to/from a given document controller and all plug-in extensions bound to it. +//! \todo Are all Companion API calls independent of ARA, or might there be the need in certain +//! situations to also lock around some Companion API calls? +//! \todo It is possible for a LockingContext instance to be used across several communication +//! channels if the IPC is distributed accordingly. +//! @{ + +//! opaque token representing an instance of a LockingContext +typedef struct ARAIPCLockingContextImplementation * ARAIPCLockingContextRef; + +//! creation and destruction of LockingContext instances +//@{ +ARAIPCLockingContextRef ARA_CALL ARAIPCCreateLockingContext (void); +void ARA_CALL ARAIPCDestroyLockingContext (ARAIPCLockingContextRef lockingContextRef); +//@} + +//! locking around sending a message through the associated IPC channel(s) +//! The opaque token returned by the locking call must be passed on to the unlocking call. +//@{ +typedef bool ARAIPCLockingContextMessageSendingToken; +ARAIPCLockingContextMessageSendingToken ARA_CALL ARAIPCLockContextBeforeSendingMessage (ARAIPCLockingContextRef lockingContextRef, bool stackable); +void ARA_CALL ARAIPCUnlockContextAfterSendingMessage (ARAIPCLockingContextRef lockingContextRef, ARAIPCLockingContextMessageSendingToken lockToken); +//@} + +//! locking around handling a message received through the associated IPC channel(s) +//! The opaque token returned by the locking call must be passed on to the unlocking call. +//@{ +typedef ARAIPCLockingContextRef ARAIPCLockingContextMessageHandlingToken; +ARAIPCLockingContextMessageHandlingToken ARA_CALL ARAIPCLockContextBeforeHandlingMessage (ARAIPCLockingContextRef lockingContextRef); +void ARA_CALL ARAIPCUnlockContextAfterHandlingMessage (ARAIPCLockingContextRef lockingContextRef, ARAIPCLockingContextMessageHandlingToken lockToken); +//@} + +//! @} + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +#endif // ARA_ENABLE_IPC + +//! @} ARA_Library_IPC + +#endif // ARAIPCLockingContext_h diff --git a/IPC/ARAIPCProxyHost.cpp b/IPC/ARAIPCProxyHost.cpp new file mode 100644 index 0000000..5c1c293 --- /dev/null +++ b/IPC/ARAIPCProxyHost.cpp @@ -0,0 +1,1295 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyHost.cpp +//! implementation of host-side ARA IPC proxy host +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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" + +#if ARA_VALIDATE_API_CALLS + #include "ARA_Library/Debug/ARAContentValidator.h" + #include "ARA_Library/Utilities/ARAStdVectorUtilities.h" +#endif + +#include +#include +#include + + +#if ARA_SUPPORT_VERSION_1 + #error "The ARA IPC proxy host implementation does not support ARA 1." +#endif + + +namespace ARA { +namespace IPC { +namespace ProxyHost { + +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, public RemoteCaller +{ +public: + AudioAccessController (ARAIPCMessageSender sender, ARAAudioAccessControllerHostRef remoteHostRef) noexcept + : RemoteCaller { sender }, _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; +}; + +/*******************************************************************************/ + +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 +} +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 +{ + 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, false, ARA_IPC_HOST_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 +{ + 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 }; + RemoteCaller::CustomDecodeFunction customDecode { [&success, &remoteAudioReader, &samplesPerChannel, &channelCount, &buffers] (const ARAIPCMessageDecoder& 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, false, ARA_IPC_HOST_METHOD_ID (ARAAudioAccessControllerInterface, readAudioSamples), + _remoteHostRef, remoteAudioReader->mainHostRef, samplePosition, samplesPerChannel); + return success; +} + +void AudioAccessController::destroyAudioReader (ARAAudioReaderHostRef audioReaderHostRef) noexcept +{ + auto remoteAudioReader { fromHostRef (audioReaderHostRef) }; + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAAudioAccessControllerInterface, destroyAudioReader), _remoteHostRef, remoteAudioReader->mainHostRef); + delete remoteAudioReader; +} + + +/*******************************************************************************/ +//! Implementation of ArchivingControllerInterface that channels all calls through IPC +class ArchivingController : public Host::ArchivingControllerInterface, public RemoteCaller +{ +public: + ArchivingController (ARAIPCMessageSender sender, ARAArchivingControllerHostRef remoteHostRef) noexcept + : RemoteCaller { sender }, _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 +{ + ARASize size; + remoteCall (size, false, ARA_IPC_HOST_METHOD_ID (ARAArchivingControllerInterface, getArchiveSize), _remoteHostRef, archiveReaderHostRef); + return size; +} + +bool ArchivingController::readBytesFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, ARASize position, ARASize length, ARAByte buffer[]) noexcept +{ + // 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, false, ARA_IPC_HOST_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 +{ + // 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, false, ARA_IPC_HOST_METHOD_ID (ARAArchivingControllerInterface, writeBytesToArchive), + _remoteHostRef, archiveWriterHostRef, position, BytesEncoder { buffer, length, false }); + return (success != kARAFalse); +} + +void ArchivingController::notifyDocumentArchivingProgress (float value) noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentArchivingProgress), _remoteHostRef, value); +} + +void ArchivingController::notifyDocumentUnarchivingProgress (float value) noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAArchivingControllerInterface, notifyDocumentUnarchivingProgress), _remoteHostRef, value); +} + +ARAPersistentID ArchivingController::getDocumentArchiveID (ARAArchiveReaderHostRef archiveReaderHostRef) noexcept +{ + RemoteCaller::CustomDecodeFunction customDecode { [this] (const ARAIPCMessageDecoder& decoder) -> void + { + ARAPersistentID persistentID; + decodeReply (persistentID, decoder); + _archiveID.assign (persistentID); + } }; + remoteCall (customDecode, false, ARA_IPC_HOST_METHOD_ID (ARAArchivingControllerInterface, getDocumentArchiveID), _remoteHostRef, archiveReaderHostRef); + return _archiveID.c_str(); +} + + +/*******************************************************************************/ +//! Implementation of ContentAccessControllerInterface that channels all calls through IPC +class ContentAccessController : public Host::ContentAccessControllerInterface, public RemoteCaller +{ +public: + ContentAccessController (ARAIPCMessageSender sender, ARAContentAccessControllerHostRef remoteHostRef) noexcept + : RemoteCaller { sender }, _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 +{ + ARABool result; + remoteCall (result, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, isMusicalContextContentAvailable), + _remoteHostRef, musicalContextHostRef, type); + return (result != kARAFalse); +} + +ARAContentGrade ContentAccessController::getMusicalContextContentGrade (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type) noexcept +{ + ARAContentGrade grade; + remoteCall (grade, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, getMusicalContextContentGrade), + _remoteHostRef, musicalContextHostRef, type); + return grade; +} + +ARAContentReaderHostRef ContentAccessController::createMusicalContextContentReader (ARAMusicalContextHostRef musicalContextHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARAContentReaderHostRef contentReaderHostRef; + remoteCall (contentReaderHostRef, false, ARA_IPC_HOST_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 +{ + ARABool result; + remoteCall (result, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, isAudioSourceContentAvailable), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, type); + return (result != kARAFalse); +} + +ARAContentGrade ContentAccessController::getAudioSourceContentGrade (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type) noexcept +{ + ARAContentGrade grade; + remoteCall (grade, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, getAudioSourceContentGrade), + _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, type); + return grade; +} + +ARAContentReaderHostRef ContentAccessController::createAudioSourceContentReader (ARAAudioSourceHostRef audioSourceHostRef, ARAContentType type, const ARAContentTimeRange* range) noexcept +{ + ARAContentReaderHostRef contentReaderHostRef; + remoteCall (contentReaderHostRef, false, ARA_IPC_HOST_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 +{ + const auto contentReader { fromHostRef (contentReaderHostRef) }; + ARAInt32 count; + remoteCall (count, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderEventCount), + _remoteHostRef, contentReader->remoteHostRef); + return count; +} + +const void* ContentAccessController::getContentReaderDataForEvent (ARAContentReaderHostRef contentReaderHostRef, ARAInt32 eventIndex) noexcept +{ + const auto contentReader { fromHostRef (contentReaderHostRef) }; + const void* result {}; + RemoteCaller::CustomDecodeFunction customDecode { [&result, &contentReader] (const ARAIPCMessageDecoder& decoder) -> void + { + result = contentReader->decoder.decode (decoder); + } }; + remoteCall (customDecode, false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, getContentReaderDataForEvent), + _remoteHostRef, contentReader->remoteHostRef, eventIndex); + return result; +} + +void ContentAccessController::destroyContentReader (ARAContentReaderHostRef contentReaderHostRef) noexcept +{ + const auto contentReader { fromHostRef (contentReaderHostRef) }; + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAContentAccessControllerInterface, destroyContentReader), _remoteHostRef, contentReader->remoteHostRef); + delete contentReader; +} + + +/*******************************************************************************/ +//! Implementation of ModelUpdateControllerInterface that channels all calls through IPC +class ModelUpdateController : public Host::ModelUpdateControllerInterface, public RemoteCaller +{ +public: + ModelUpdateController (ARAIPCMessageSender sender, ARAModelUpdateControllerHostRef remoteHostRef) noexcept + : RemoteCaller { sender }, _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; + +private: + ARAModelUpdateControllerHostRef _remoteHostRef; +}; + +/*******************************************************************************/ + +void ModelUpdateController::notifyAudioSourceAnalysisProgress (ARAAudioSourceHostRef audioSourceHostRef, ARAAnalysisProgressState state, float value) noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceAnalysisProgress), _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, state, value); +} + +void ModelUpdateController::notifyAudioSourceContentChanged (ARAAudioSourceHostRef audioSourceHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + remoteCall (true, ARA_IPC_HOST_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioSourceContentChanged), _remoteHostRef, fromHostRef (audioSourceHostRef)->mainHostRef, range, scopeFlags); +} + +void ModelUpdateController::notifyAudioModificationContentChanged (ARAAudioModificationHostRef audioModificationHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + remoteCall (true, ARA_IPC_HOST_METHOD_ID (ARAModelUpdateControllerInterface, notifyAudioModificationContentChanged), _remoteHostRef, audioModificationHostRef, range, scopeFlags); +} + +void ModelUpdateController::notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept +{ + remoteCall (true, ARA_IPC_HOST_METHOD_ID (ARAModelUpdateControllerInterface, notifyPlaybackRegionContentChanged), _remoteHostRef, playbackRegionHostRef, range, scopeFlags); +} + + +/*******************************************************************************/ +//! Implementation of PlaybackControllerInterface that channels all calls through IPC +class PlaybackController : public Host::PlaybackControllerInterface, public RemoteCaller +{ +public: + PlaybackController (ARAIPCMessageSender sender, ARAPlaybackControllerHostRef remoteHostRef) noexcept + : RemoteCaller { sender }, _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 +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAPlaybackControllerInterface, requestStartPlayback), _remoteHostRef); +} + +void PlaybackController::requestStopPlayback () noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAPlaybackControllerInterface, requestStopPlayback), _remoteHostRef); +} + +void PlaybackController::requestSetPlaybackPosition (ARATimePosition timePosition) noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAPlaybackControllerInterface, requestSetPlaybackPosition), _remoteHostRef, timePosition); +} + +void PlaybackController::requestSetCycleRange (ARATimePosition startTime, ARATimeDuration duration) noexcept +{ + remoteCall (false, ARA_IPC_HOST_METHOD_ID (ARAPlaybackControllerInterface, requestSetCycleRange), _remoteHostRef, startTime, duration); +} + +void PlaybackController::requestEnableCycle (bool enable) noexcept +{ + remoteCall (false, ARA_IPC_HOST_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 ProxyHost +using namespace ProxyHost; + +/*******************************************************************************/ + + +std::vector _factories {}; +ARAIPCMessageSender _plugInCallbacksSender {}; +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 ARAIPCProxyHostSetPlugInCallbacksSender (ARAIPCMessageSender plugInCallbacksSender) +{ + _plugInCallbacksSender = plugInCallbacksSender; +} + +void ARAIPCProxyHostSetBindingHandler(ARAIPCBindingHandler handler) +{ + _bindingHandler = handler; +} + +void ARAIPCProxyHostCleanupBinding (const ARA::ARAPlugInExtensionInstance* plugInExtensionInstance) +{ + delete fromRef (plugInExtensionInstance->plugInExtensionRef); +} + +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; +} + +void ARAIPCProxyHostCommandHandler (const ARAIPCMessageID messageID, const ARAIPCMessageDecoder* const decoder, ARAIPCMessageEncoder* const replyEncoder) +{ +// ARA_LOG ("ARAIPCProxyHostCommandHandler received message %s", decodePlugInMessageID (messageID)); + + // ARAFactory + if (messageID == kGetFactoriesCountMethodID) + { + return encodeReply (replyEncoder, _factories.size ()); + } + else if (messageID == kGetFactoryMethodID) + { + ARASize index; + decodeArguments (decoder, index); + ARA_INTERNAL_ASSERT (index < _factories.size ()); + return encodeReply (replyEncoder, *_factories[index]); + } + else if (messageID == kInitializeARAMethodID) + { + ARAPersistentID factoryID; + ARA::SizedStruct interfaceConfig = { kARAAPIGeneration_2_0_Final, nullptr }; + decodeArguments (decoder, factoryID, interfaceConfig.desiredApiGeneration); + ARA_INTERNAL_ASSERT (interfaceConfig.desiredApiGeneration >= kARAAPIGeneration_2_0_Final); + + if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + factory->initializeARAWithConfiguration(&interfaceConfig); + } + 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 { _plugInCallbacksSender, audioAccessControllerHostRef } }; + const auto archivingController { new ArchivingController { _plugInCallbacksSender, archivingControllerHostRef } }; + const auto contentAccessController { (provideContentAccessController != kARAFalse) ? new ContentAccessController { _plugInCallbacksSender, contentAccessControllerHostRef } : nullptr }; + const auto modelUpdateController { (provideModelUpdateController != kARAFalse) ? new ModelUpdateController { _plugInCallbacksSender, modelUpdateControllerHostRef } : nullptr }; + const auto playbackController { (providePlaybackController != kARAFalse) ? new PlaybackController { _plugInCallbacksSender, 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) }; + return 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) }; + return encodeReply (replyEncoder, ARAPlugInExtensionRef { toRef (new PlugInExtension { plugInExtensionInstance })}); + } + else if (messageID == kUninitializeARAMethodID) + { + ARAPersistentID factoryID; + decodeArguments (decoder, factoryID); + + if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + factory->uninitializeARA(); + } + + //ARADocumentControllerInterface + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_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); + + return encodeReply (replyEncoder, *(fromRef (controllerRef)->getFactory ())); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, beginEditing)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->beginEditing (); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, endEditing)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->endEditing (); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, notifyModelUpdates)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + fromRef (controllerRef)->notifyModelUpdates (); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, restoreObjectsFromArchive)) + { + ARADocumentControllerRef controllerRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + OptionalArgument filter; + decodeArguments (decoder, controllerRef, archiveReaderHostRef, filter); + + return encodeReply (replyEncoder, fromRef (controllerRef)->restoreObjectsFromArchive (archiveReaderHostRef, (filter.second) ? &filter.first : nullptr) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_PLUGIN_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 (); + } + + return encodeReply (replyEncoder, fromRef (controllerRef)->storeObjectsToArchive (archiveWriterHostRef, (filter.second) ? &filter.first : nullptr) ? kARATrue : kARAFalse); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateDocumentProperties)) + { + ARADocumentControllerRef controllerRef; + ARADocumentProperties properties; + decodeArguments (decoder, controllerRef, properties); + + fromRef (controllerRef)->updateDocumentProperties (&properties); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, createMusicalContext)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextHostRef hostRef; + ARAMusicalContextProperties properties; + decodeArguments (decoder, controllerRef, hostRef, properties); + + return encodeReply (replyEncoder, fromRef (controllerRef)->createMusicalContext (hostRef, &properties)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyMusicalContext)) + { + ARADocumentControllerRef controllerRef; + ARAMusicalContextRef musicalContextRef; + decodeArguments (decoder, controllerRef, musicalContextRef); + + fromRef (controllerRef)->destroyMusicalContext (musicalContextRef); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, createRegionSequence)) + { + ARADocumentControllerRef controllerRef; + ARARegionSequenceHostRef hostRef; + ARARegionSequenceProperties properties; + decodeArguments (decoder, controllerRef, hostRef, properties); + + return encodeReply (replyEncoder, fromRef (controllerRef)->createRegionSequence (hostRef, &properties)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyRegionSequence)) + { + ARADocumentControllerRef controllerRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, controllerRef, regionSequenceRef); + + fromRef (controllerRef)->destroyRegionSequence (regionSequenceRef); + } + + else if (messageID == ARA_IPC_PLUGIN_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); + + return encodeReply (replyEncoder, ARAAudioSourceRef { toRef (remoteAudioSource) }); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_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_PLUGIN_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_PLUGIN_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_PLUGIN_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; + return encodeReply (replyEncoder, reply); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAnalysisIncomplete)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + return encodeReply (replyEncoder, fromRef (controllerRef)->isAudioSourceContentAnalysisIncomplete (fromRef (audioSourceRef)->plugInRef, contentType)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioSourceContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + return encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioSourceContentAvailable (fromRef (audioSourceRef)->plugInRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getAudioSourceContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioSourceRef, contentType); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getAudioSourceContentGrade (fromRef (audioSourceRef)->plugInRef, contentType)); + } + else if (messageID == ARA_IPC_PLUGIN_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; + return encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, createAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + ARAAudioModificationHostRef hostRef; + ARAAudioModificationProperties properties; + decodeArguments (decoder, controllerRef, audioSourceRef, hostRef, properties); + + return encodeReply (replyEncoder, fromRef (controllerRef)->createAudioModification (fromRef (audioSourceRef)->plugInRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, cloneAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAAudioModificationHostRef hostRef; + ARAAudioModificationProperties properties; + decodeArguments (decoder, controllerRef, audioModificationRef, hostRef, properties); + + return encodeReply (replyEncoder, fromRef (controllerRef)->cloneAudioModification (audioModificationRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + decodeArguments (decoder, controllerRef, audioModificationRef); + + return encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioModificationPreservingAudioSourceSignal (audioModificationRef)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioModificationContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioModificationRef, contentType); + + return encodeReply (replyEncoder, (fromRef (controllerRef)->isAudioModificationContentAvailable (audioModificationRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getAudioModificationContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, audioModificationRef, contentType); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getAudioModificationContentGrade (audioModificationRef, contentType)); + } + else if (messageID == ARA_IPC_PLUGIN_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; + return encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyAudioModification)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + decodeArguments (decoder, controllerRef, audioModificationRef); + + fromRef (controllerRef)->destroyAudioModification (audioModificationRef); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, createPlaybackRegion)) + { + ARADocumentControllerRef controllerRef; + ARAAudioModificationRef audioModificationRef; + ARAPlaybackRegionHostRef hostRef; + ARAPlaybackRegionProperties properties; + decodeArguments (decoder, controllerRef, audioModificationRef, hostRef, properties); + + return encodeReply (replyEncoder, fromRef (controllerRef)->createPlaybackRegion (audioModificationRef, hostRef, &properties)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_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); + return encodeReply (replyEncoder, reply); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionContentAvailable)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, playbackRegionRef, contentType); + + return encodeReply (replyEncoder, (fromRef (controllerRef)->isPlaybackRegionContentAvailable (playbackRegionRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionContentGrade)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + ARAContentType contentType; + decodeArguments (decoder, controllerRef, playbackRegionRef, contentType); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getPlaybackRegionContentGrade (playbackRegionRef, contentType)); + } + else if (messageID == ARA_IPC_PLUGIN_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; + return encodeReply (replyEncoder, ARAContentReaderRef { toRef (remoteContentReader) }); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyPlaybackRegion)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, controllerRef, playbackRegionRef); + + fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + } + + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getContentReaderEventCount)) + { + ARADocumentControllerRef controllerRef; + ARAContentReaderRef contentReaderRef; + decodeArguments (decoder, controllerRef, contentReaderRef); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getContentReaderEventCount (fromRef (contentReaderRef)->plugInRef)); + } + else if (messageID == ARA_IPC_PLUGIN_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) }; + return encodeContentEvent (replyEncoder, remoteContentReader->contentType, eventData); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmsCount)) + { + ARADocumentControllerRef controllerRef; + decodeArguments (decoder, controllerRef); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getProcessingAlgorithmsCount ()); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmProperties)) + { + ARADocumentControllerRef controllerRef; + ARAInt32 algorithmIndex; + decodeArguments (decoder, controllerRef, algorithmIndex); + + return encodeReply (replyEncoder, *(fromRef (controllerRef)->getProcessingAlgorithmProperties (algorithmIndex))); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmForAudioSource)) + { + ARADocumentControllerRef controllerRef; + ARAAudioSourceRef audioSourceRef; + decodeArguments (decoder, controllerRef, audioSourceRef); + + return encodeReply (replyEncoder, fromRef (controllerRef)->getProcessingAlgorithmForAudioSource (fromRef (audioSourceRef)->plugInRef)); + } + else if (messageID == ARA_IPC_PLUGIN_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_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isLicensedForCapabilities)) + { + ARADocumentControllerRef controllerRef; + ARABool runModalActivationDialogIfNeeded; + std::vector types; + ARAPlaybackTransformationFlags transformationFlags; + decodeArguments (decoder, controllerRef, runModalActivationDialogIfNeeded, types, transformationFlags); + + return encodeReply (replyEncoder, (fromRef (controllerRef)->isLicensedForCapabilities ((runModalActivationDialogIfNeeded != kARAFalse), + types.size(), types.data (), transformationFlags)) ? kARATrue : kARAFalse); + } + + // ARAPlaybackRendererInterface + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAPlaybackRendererInterface, addPlaybackRegion)) + { + ARAPlaybackRendererRef playbackRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, playbackRendererRef, playbackRegionRef); + + fromRef (playbackRendererRef)->getPlaybackRenderer ()->addPlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAPlaybackRendererInterface, removePlaybackRegion)) + { + ARAPlaybackRendererRef playbackRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, playbackRendererRef, playbackRegionRef); + + fromRef (playbackRendererRef)->getPlaybackRenderer ()->removePlaybackRegion (playbackRegionRef); + } + + // ARAEditorRendererInterface + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, addPlaybackRegion)) + { + ARAEditorRendererRef editorRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, editorRendererRef, playbackRegionRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->addPlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, removePlaybackRegion)) + { + ARAEditorRendererRef editorRendererRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, editorRendererRef, playbackRegionRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->removePlaybackRegion (playbackRegionRef); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, addRegionSequence)) + { + ARAEditorRendererRef editorRendererRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, editorRendererRef, regionSequenceRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->addRegionSequence (regionSequenceRef); + } + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, removeRegionSequence)) + { + ARAEditorRendererRef editorRendererRef; + ARARegionSequenceRef regionSequenceRef; + decodeArguments (decoder, editorRendererRef, regionSequenceRef); + + fromRef (editorRendererRef)->getEditorRenderer ()->removeRegionSequence (regionSequenceRef); + } + + // ARAEditorViewInterface + else if (messageID == ARA_IPC_PLUGIN_METHOD_ID (ARAEditorViewInterface, notifySelection)) + { + ARAEditorViewRef editorViewRef; + ARAViewSelection selection; + decodeArguments (decoder, editorViewRef, selection); + + fromRef (editorViewRef)->getEditorView ()->notifySelection (&selection); + } + else if (messageID == ARA_IPC_PLUGIN_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"); + } + + // all calls that create a reply return early from their respective if (). +// ARA_INTERNAL_ASSERT ((replyEncoder == nullptr) || replyEncoder->methods->isEmpty (replyEncoder->ref)); +} + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCProxyHost.h b/IPC/ARAIPCProxyHost.h new file mode 100644 index 0000000..aaa657a --- /dev/null +++ b/IPC/ARAIPCProxyHost.h @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyHost.h +//! implementation of host-side ARA IPC proxy host +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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 + +#include "ARA_Library/IPC/ARAIPC.h" + + +#if ARA_ENABLE_IPC + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +//! static configuration: add the ARA factories that the proxy host will wrap +void ARAIPCProxyHostAddFactory(const ARAFactory * factory); + +//! static configuration: set sender that the proxy host will use to perform callbacks received from the plug-in +void ARAIPCProxyHostSetPlugInCallbacksSender(ARAIPCMessageSender plugInCallbacksSender); + +//! static configuration: set the callback to execute the binding of Companion API plug-in instances to ARA document controllers +void ARAIPCProxyHostSetBindingHandler(ARAIPCBindingHandler handler); + +//! static dispatcher: the host command handler that controls the proxy host +void ARAIPCProxyHostCommandHandler(const ARAIPCMessageID messageID, const ARAIPCMessageDecoder * decoder, ARAIPCMessageEncoder * replyEncoder); + +//! trigger proper teardown of proxy plug-in extension when destroying Companion API plug-in instances that have been bound to ARA +void ARAIPCProxyHostCleanupBinding(const ARAPlugInExtensionInstance * plugInExtensionInstance); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCProxyHost_h diff --git a/IPC/ARAIPCProxyPlugIn.cpp b/IPC/ARAIPCProxyPlugIn.cpp new file mode 100644 index 0000000..07e3054 --- /dev/null +++ b/IPC/ARAIPCProxyPlugIn.cpp @@ -0,0 +1,1757 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyPlugIn.cpp +//! implementation of host-side ARA IPC proxy plug-in +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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" + +#if ARA_VALIDATE_API_CALLS + #include "ARA_Library/Debug/ARAContentValidator.h" +#endif + +#include +#include +#include +#include + + +#if ARA_SUPPORT_VERSION_1 + #error "The ARA IPC proxy plug-in implementation does not support ARA 1." +#endif + + +/*******************************************************************************/ +// 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 ProxyPlugIn { + +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 +}; + +template +std::set*> InstanceValidator::_instances; + +template +inline bool isValidInstance (const SubclassT* instance) +{ + return InstanceValidator::isValid (instance); +} + + +/*******************************************************************************/ + +struct AudioSource +#if ARA_VALIDATE_API_CALLS + : public InstanceValidator +#endif +{ +#if ARA_VALIDATE_API_CALLS + AudioSource (ARAAudioSourceHostRef hostRef_, ARAAudioSourceRef remoteRef_, ARAChannelCount channelCount_, + ARASampleCount sampleCount_, ARASampleRate sampleRate_) + : hostRef { hostRef_ }, remoteRef { remoteRef_ }, channelCount { channelCount_ }, + 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 +#if ARA_VALIDATE_API_CALLS + : public InstanceValidator +#endif +{ +#if ARA_VALIDATE_API_CALLS + ContentReader (ARAContentReaderRef remoteRef_, ARAContentType type_) + : remoteRef { remoteRef_ }, decoder { type_ } + {} +#endif + + 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, protected RemoteCaller, public InstanceValidator +{ +public: + DocumentController (ARAIPCMessageSender sender, 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; + 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 (ARAIPCMessageSender sender, const ARAFactory* factory, const ARADocumentControllerHostInstance* instance, const ARADocumentProperties* properties) noexcept +: RemoteCaller { sender }, + _factory { factory }, + _hostAudioAccessController { instance }, + _hostArchivingController { instance }, + _hostContentAccessController { instance }, + _hostModelUpdateController { instance }, + _hostPlaybackController { instance }, + _instance { this } +{ + ARAAudioAccessControllerHostRef audioAccessControllerHostRef { toHostRef (this) }; + ARAArchivingControllerHostRef archivingControllerHostRef { toHostRef (this) }; + ARAContentAccessControllerHostRef contentAccessControllerHostRef { toHostRef (this) }; + ARAModelUpdateControllerHostRef modelUpdateControllerHostRef { toHostRef (this) }; + ARAPlaybackControllerHostRef playbackControllerHostRef { toHostRef (this) }; + remoteCall (_remoteRef, false, 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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy document controller", _remoteRef); + remoteCall (false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + return _factory; +} + +/*******************************************************************************/ + +void DocumentController::beginEditing () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, beginEditing), _remoteRef); +} + +void DocumentController::endEditing () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + if (!_hostModelUpdateController.isProvided ()) + return; + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, notifyModelUpdates), _remoteRef); +} + +bool DocumentController::restoreObjectsFromArchive (ARAArchiveReaderHostRef archiveReaderHostRef, const ARARestoreObjectsFilter* filter) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARABool success; + remoteCall (success, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + 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, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + ARA_VALIDATE_API_ARGUMENT (documentArchiveID, documentArchiveID != nullptr); + ARA_VALIDATE_API_ARGUMENT (openAutomatically, openAutomatically != nullptr); + + bool success { false }; + RemoteCaller::CustomDecodeFunction customDecode { [this, &success, &documentArchiveID, &openAutomatically] (const ARAIPCMessageDecoder& 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, true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, storeAudioSourceToAudioFileChunk), + _remoteRef, archiveWriterHostRef, audioSource->remoteRef); + return success; +} + +void DocumentController::updateDocumentProperties (PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARADocumentProperties); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateDocumentProperties), _remoteRef, *properties); +} + +/*******************************************************************************/ + +ARAMusicalContextRef DocumentController::createMusicalContext (ARAMusicalContextHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAMusicalContextProperties); + + ARAMusicalContextRef musicalContextRef; + remoteCall (musicalContextRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAMusicalContextProperties); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextProperties), _remoteRef, musicalContextRef, *properties); +} + +void DocumentController::updateMusicalContextContent (ARAMusicalContextRef musicalContextRef, const ARAContentTimeRange* range, ContentUpdateScopes flags) noexcept +{ + ARA_LOG_HOST_ENTRY (musicalContextRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateMusicalContextContent), _remoteRef, musicalContextRef, range, flags); +} + +void DocumentController::destroyMusicalContext (ARAMusicalContextRef musicalContextRef) noexcept +{ + ARA_LOG_HOST_ENTRY (musicalContextRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy musical context", musicalContextRef); + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyMusicalContext), _remoteRef, musicalContextRef); +} + +/*******************************************************************************/ + +ARARegionSequenceRef DocumentController::createRegionSequence (ARARegionSequenceHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARARegionSequenceProperties); + + ARARegionSequenceRef regionSequenceRef; + remoteCall (regionSequenceRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARARegionSequenceProperties); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateRegionSequenceProperties), _remoteRef, regionSequenceRef, *properties); +} + +void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (regionSequenceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy region sequence", regionSequenceRef); + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyRegionSequence), _remoteRef, regionSequenceRef); +} + +/*******************************************************************************/ + +ARAAudioSourceRef DocumentController::createAudioSource (ARAAudioSourceHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAAudioSourceProperties); + + auto audioSource { new AudioSource { hostRef, nullptr, properties->channelCount +#if ARA_VALIDATE_API_CALLS + , properties->sampleCount, properties->sampleRate +#endif + } }; + + remoteCall (audioSource->remoteRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAAudioSourceProperties); + + remoteCall (true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateAudioSourceContent), _remoteRef, audioSource->remoteRef, range, flags); +} + +void DocumentController::enableAudioSourceSamplesAccess (ARAAudioSourceRef audioSourceRef, bool enable) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, enableAudioSourceSamplesAccess), _remoteRef, audioSource->remoteRef, (enable) ? kARATrue : kARAFalse); +} + +void DocumentController::deactivateAudioSourceForUndoHistory (ARAAudioSourceRef audioSourceRef, bool deactivate) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, deactivateAudioSourceForUndoHistory), _remoteRef, audioSource->remoteRef, (deactivate) ? kARATrue : kARAFalse); +} + +void DocumentController::destroyAudioSource (ARAAudioSourceRef audioSourceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy audio source", audioSource->remoteRef); + remoteCall (false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAAudioModificationProperties); + + ARAAudioModificationRef audioModificationRef; + remoteCall (audioModificationRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAAudioModificationProperties); + + ARAAudioModificationRef clonedAudioModificationRef; + remoteCall (clonedAudioModificationRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAAudioModificationProperties); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updateAudioModificationProperties), _remoteRef, audioModificationRef, *properties); +} + +bool DocumentController::isAudioModificationPreservingAudioSourceSignal (ARAAudioModificationRef audioModificationRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal), _remoteRef, audioModificationRef); + return (result != kARAFalse); +} + +void DocumentController::deactivateAudioModificationForUndoHistory (ARAAudioModificationRef audioModificationRef, bool deactivate) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, deactivateAudioModificationForUndoHistory), _remoteRef, audioModificationRef, (deactivate) ? kARATrue : kARAFalse); +} + +void DocumentController::destroyAudioModification (ARAAudioModificationRef audioModificationRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy audio modification", audioModification); + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyAudioModification), _remoteRef, audioModificationRef); +} + +/*******************************************************************************/ + +ARAPlaybackRegionRef DocumentController::createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAPlaybackRegionProperties); + + ARAPlaybackRegionRef playbackRegionRef; + remoteCall (playbackRegionRef, true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (properties, ARAPlaybackRegionProperties); + + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, updatePlaybackRegionProperties), _remoteRef, playbackRegionRef, *properties); +} + +void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_ARGUMENT (headTime, headTime != nullptr); + ARA_VALIDATE_API_ARGUMENT (tailTime, tailTime != nullptr); + + GetPlaybackRegionHeadAndTailTimeReply reply; + remoteCall (reply, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy playback region", playbackRegionRef); + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyPlaybackRegion), _remoteRef, playbackRegionRef); +} + +/*******************************************************************************/ + +bool DocumentController::isAudioSourceContentAvailable (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARAContentGrade grade; + remoteCall (grade, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isAudioModificationContentAvailable), _remoteRef, audioModificationRef, type); + return (result != kARAFalse); +} + +ARAContentGrade DocumentController::getAudioModificationContentGrade (ARAAudioModificationRef audioModificationRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioModificationRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARAContentGrade grade; + remoteCall (grade, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionContentAvailable), _remoteRef, playbackRegionRef, type); + return (result != kARAFalse); +} + +ARAContentGrade DocumentController::getPlaybackRegionContentGrade (ARAPlaybackRegionRef playbackRegionRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARAContentGrade grade; + remoteCall (grade, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARAContentReaderRef contentReaderRef; + remoteCall (contentReaderRef, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_VALIDATE_API_ARGUMENT (contentReader, isValidInstance (contentReader)); + + ARAInt32 count; + remoteCall (count, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getContentReaderEventCount), _remoteRef, contentReader->remoteRef); + return count; +} + +const void* DocumentController::getContentReaderDataForEvent (ARAContentReaderRef contentReaderRef, ARAInt32 eventIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (contentReaderRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_VALIDATE_API_ARGUMENT (contentReader, isValidInstance (contentReader)); + + const void* result {}; + RemoteCaller::CustomDecodeFunction customDecode { [&result, &contentReader] (const ARAIPCMessageDecoder& decoder) -> void + { + result = contentReader->decoder.decode (decoder); + } }; + remoteCall (customDecode, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getContentReaderDataForEvent), + _remoteRef, contentReader->remoteRef, eventIndex); + return result; +} + +void DocumentController::destroyContentReader (ARAContentReaderRef contentReaderRef) noexcept +{ + ARA_LOG_HOST_ENTRY (contentReaderRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto contentReader { fromRef (contentReaderRef) }; + ARA_VALIDATE_API_ARGUMENT (contentReader, isValidInstance (contentReader)); + + ARA_LOG_MODELOBJECT_LIFETIME ("will destroy content reader", contentReader->remoteRef); + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, destroyContentReader), _remoteRef, contentReader->remoteRef); + + delete contentReader; +} + +/*******************************************************************************/ + +bool DocumentController::isAudioSourceContentAnalysisIncomplete (ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + const ArrayArgument types { contentTypes, contentTypesCount }; + remoteCall (true, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, requestAudioSourceContentAnalysis), _remoteRef, audioSource->remoteRef, types); +} + +ARAInt32 DocumentController::getProcessingAlgorithmsCount () noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + ARAInt32 count; + remoteCall (count, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmsCount), _remoteRef); + return count; +} + +const ARAProcessingAlgorithmProperties* DocumentController::getProcessingAlgorithmProperties (ARAInt32 algorithmIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + RemoteCaller::CustomDecodeFunction customDecode { [this] (const ARAIPCMessageDecoder& 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, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmProperties), _remoteRef, algorithmIndex); + return &_processingAlgorithmData; +} + +ARAInt32 DocumentController::getProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + ARAInt32 result; + remoteCall (result, false, ARA_IPC_PLUGIN_METHOD_ID (ARADocumentControllerInterface, getProcessingAlgorithmForAudioSource), _remoteRef, audioSource->remoteRef); + return result; +} + +void DocumentController::requestProcessingAlgorithmForAudioSource (ARAAudioSourceRef audioSourceRef, ARAInt32 algorithmIndex) noexcept +{ + ARA_LOG_HOST_ENTRY (audioSourceRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + const auto audioSource { fromRef (audioSourceRef) }; + ARA_VALIDATE_API_ARGUMENT (audioSource, isValidInstance (audioSource)); + + remoteCall (true, ARA_IPC_PLUGIN_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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + const ArrayArgument types { contentTypes, contentTypesCount }; + ARABool result; + remoteCall (result, false, ARA_IPC_PLUGIN_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 (ARAIPCMessageSender sender, ARAPlaybackRendererRef remoteRef) noexcept + : RemoteCaller { sender }, + _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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARAPlaybackRendererInterface, addPlaybackRegion), _remoteRef, playbackRegionRef); + } + void removePlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_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 (ARAIPCMessageSender sender, ARAEditorRendererRef remoteRef) noexcept + : RemoteCaller { sender }, + _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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, addPlaybackRegion), _remoteRef, playbackRegionRef); + } + void removePlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, removePlaybackRegion), _remoteRef, playbackRegionRef); + } + + void addRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARAEditorRendererInterface, addRegionSequence), _remoteRef, regionSequenceRef); + } + void removeRegionSequence (ARARegionSequenceRef regionSequenceRef) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + remoteCall (false, ARA_IPC_PLUGIN_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 (ARAIPCMessageSender sender, ARAEditorViewRef remoteRef) noexcept + : RemoteCaller { sender }, + _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_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + ARA_VALIDATE_API_STRUCT_PTR (selection, ARAViewSelection); + + remoteCall (false, ARA_IPC_PLUGIN_METHOD_ID (ARAEditorViewInterface, notifySelection), _remoteRef, *selection); + } + void notifyHideRegionSequences (ARASize regionSequenceRefsCount, const ARARegionSequenceRef regionSequenceRefs[]) noexcept override + { + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (this, isValidInstance (this)); + + const ArrayArgument sequences { regionSequenceRefs, regionSequenceRefsCount }; + remoteCall (false, ARA_IPC_PLUGIN_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: + PlugInExtension (ARAIPCMessageSender sender, ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles, + size_t remotePlugInExtensionRef) noexcept + : PlugIn::PlugInExtensionInstance { (((knownRoles & kARAPlaybackRendererRole) == 0) || ((assignedRoles & kARAPlaybackRendererRole) != 0)) ? + new PlaybackRenderer (sender, reinterpret_cast (remotePlugInExtensionRef)) : nullptr, + (((knownRoles & kARAEditorRendererRole) == 0) || ((assignedRoles & kARAEditorRendererRole) != 0)) ? + new EditorRenderer (sender, reinterpret_cast (remotePlugInExtensionRef)) : nullptr, + (((knownRoles & kARAEditorViewRole) == 0) || ((assignedRoles & kARAEditorViewRole) != 0)) ? + new EditorView (sender, reinterpret_cast (remotePlugInExtensionRef)) : nullptr }, + _documentController { PlugIn::fromRef (documentControllerRef) } + { + plugInExtensionRef = reinterpret_cast (remotePlugInExtensionRef); + + ARA_LOG_HOST_ENTRY (this); + ARA_VALIDATE_API_ARGUMENT (documentControllerRef, isValidInstance (_documentController)); + + _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 + + _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 ProxyPlugIn +using namespace ProxyPlugIn; + +/*******************************************************************************/ + + +size_t ARAIPCProxyPlugInGetFactoriesCount (ARAIPCMessageSender hostCommandsSender) +{ + size_t count; + RemoteCaller { hostCommandsSender }.remoteCall (count, false, kGetFactoriesCountMethodID); + ARA_INTERNAL_ASSERT (count > 0); + return count; +} + +const ARAFactory* ARAIPCProxyPlugInGetFactoryAtIndex (ARAIPCMessageSender hostCommandsSender, size_t index) +{ + RemoteFactory remoteFactory; + RemoteCaller::CustomDecodeFunction customDecode { [&remoteFactory] (const ARAIPCMessageDecoder& 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 (); + } }; + + RemoteCaller { hostCommandsSender }.remoteCall (customDecode, false, 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 (ARAIPCMessageSender hostCommandsSender, const ARAPersistentID factoryID, ARAAPIGeneration desiredApiGeneration) +{ + ARA_INTERNAL_ASSERT (desiredApiGeneration >= kARAAPIGeneration_2_0_Final); + RemoteCaller { hostCommandsSender }.remoteCall (false, kInitializeARAMethodID, factoryID, desiredApiGeneration); +} + +const ARADocumentControllerInstance* ARAIPCProxyPlugInCreateDocumentControllerWithDocument ( + ARAIPCMessageSender hostCommandsSender, 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 { hostCommandsSender, &cached->second._factory, hostInstance, properties} }; + return result->getInstance (); +} + +const ARAPlugInExtensionInstance* ARAIPCProxyPlugInBindToDocumentController (ARAIPCPlugInInstanceRef remoteRef, ARAIPCMessageSender sender, ARADocumentControllerRef documentControllerRef, + ARAPlugInInstanceRoleFlags knownRoles, ARAPlugInInstanceRoleFlags assignedRoles) +{ + const auto remoteDocumentControllerRef { static_cast (PlugIn::fromRef (documentControllerRef))->getRemoteRef () }; + + size_t remoteExtensionRef {}; + RemoteCaller::CustomDecodeFunction customDecode { [&remoteExtensionRef] (const ARAIPCMessageDecoder& decoder) -> void + { + decoder.methods->readSize (decoder.ref, 0, &remoteExtensionRef); + } }; + RemoteCaller { sender }.remoteCall (customDecode, false, kBindToDocumentControllerMethodID, remoteRef, remoteDocumentControllerRef, knownRoles, assignedRoles); + + return new PlugInExtension { sender, documentControllerRef, knownRoles, assignedRoles, remoteExtensionRef }; +} + +void ARAIPCProxyPlugInCleanupBinding (const ARAPlugInExtensionInstance* plugInExtensionInstance) +{ + delete static_cast (plugInExtensionInstance); +} + +void ARAIPCProxyPlugInUninitializeARA (ARAIPCMessageSender hostCommandsSender, const ARAPersistentID factoryID) +{ + RemoteCaller { hostCommandsSender }.remoteCall (false, kUninitializeARAMethodID, factoryID); +} + +void ARAIPCProxyPlugInCallbacksDispatcher (const ARAIPCMessageID messageID, const ARAIPCMessageDecoder* const decoder, ARAIPCMessageEncoder* const replyEncoder) +{ +// ARA_LOG ("plugInCallbackDispatcher received message %s", decodeHostMessageID (messageID)); + + // ARAAudioAccessControllerInterface + if (messageID == ARA_IPC_HOST_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) }; + return encodeReply (replyEncoder, audioReaderHostRef); + } + else if (messageID == ARA_IPC_HOST_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 ())) + return encodeReply (replyEncoder, ArrayArgument { encoders.data (), encoders.size () }); + else + return; // send empty reply as indication of failure + } + else if (messageID == ARA_IPC_HOST_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_HOST_METHOD_ID (ARAArchivingControllerInterface, getArchiveSize)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + decodeArguments (decoder, controllerHostRef, archiveReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + return encodeReply (replyEncoder, documentController->getHostArchivingController ()->getArchiveSize (archiveReaderHostRef)); + } + else if (messageID == ARA_IPC_HOST_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 (); + return encodeReply (replyEncoder, BytesEncoder { bytes, false }); + } + else if (messageID == ARA_IPC_HOST_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)); + + return encodeReply (replyEncoder, documentController->getHostArchivingController ()->writeBytesToArchive (archiveWriterHostRef, position, bytes.size (), bytes.data ())); + } + else if (messageID == ARA_IPC_HOST_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_HOST_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_HOST_METHOD_ID (ARAArchivingControllerInterface, getDocumentArchiveID)) + { + ARAArchivingControllerHostRef controllerHostRef; + ARAArchiveReaderHostRef archiveReaderHostRef; + decodeArguments (decoder, controllerHostRef, archiveReaderHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + return encodeReply (replyEncoder, documentController->getHostArchivingController ()->getDocumentArchiveID (archiveReaderHostRef)); + } + + // ARAContentAccessControllerInterface + else if (messageID == ARA_IPC_HOST_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)); + + return encodeReply (replyEncoder, (documentController->getHostContentAccessController ()->isMusicalContextContentAvailable (musicalContextHostRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_HOST_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)); + + return encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getMusicalContextContentGrade (musicalContextHostRef, contentType)); + } + else if (messageID == ARA_IPC_HOST_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; + + return encodeReply (replyEncoder, ARAContentReaderHostRef { toHostRef (hostContentReader) }); + } + else if (messageID == ARA_IPC_HOST_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)); + + return encodeReply (replyEncoder, (documentController->getHostContentAccessController ()->isAudioSourceContentAvailable (audioSource->hostRef, contentType)) ? kARATrue : kARAFalse); + } + else if (messageID == ARA_IPC_HOST_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)); + + return encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getAudioSourceContentGrade (audioSource->hostRef, contentType)); + } + else if (messageID == ARA_IPC_HOST_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; + return encodeReply (replyEncoder, ARAContentReaderHostRef { toHostRef (hostContentReader) }); + } + else if (messageID == ARA_IPC_HOST_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) }; + + return encodeReply (replyEncoder, documentController->getHostContentAccessController ()->getContentReaderEventCount (hostContentReader->hostRef)); + } + else if (messageID == ARA_IPC_HOST_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) }; + return encodeContentEvent (replyEncoder, hostContentReader->contentType, eventData); + } + else if (messageID == ARA_IPC_HOST_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_HOST_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_HOST_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_HOST_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_HOST_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); + } + + // ARAPlaybackControllerInterface + else if (messageID == ARA_IPC_HOST_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_HOST_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_HOST_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_HOST_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_HOST_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"); + } + + // all calls that create a reply return early from their respective if (). +// it is valid to provide a dummy replyEncoder if no reply has been requested. +// ARA_INTERNAL_ASSERT (replyEncoder == nullptr); +} + +} // namespace IPC +} // namespace ARA + +#endif // ARA_ENABLE_IPC diff --git a/IPC/ARAIPCProxyPlugIn.h b/IPC/ARAIPCProxyPlugIn.h new file mode 100644 index 0000000..c5e85d6 --- /dev/null +++ b/IPC/ARAIPCProxyPlugIn.h @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +//! \file ARAIPCProxyPlugIn.h +//! implementation of host-side ARA IPC proxy plug-in +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2021-2022, 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 + + +#include "ARA_Library/IPC/ARAIPC.h" + +#if ARA_ENABLE_IPC + + +#if defined(__cplusplus) +namespace ARA { +namespace IPC { +extern "C" { +#endif + + +//! counts the factories available through the message channel the sender is accessing +size_t ARAIPCProxyPlugInGetFactoriesCount(ARAIPCMessageSender hostCommandsSender); + +//! 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(ARAIPCMessageSender hostCommandsSender, size_t index); + +//! proxy initialization call, to be used instead of ARAFactory.initializeARAWithConfiguration() +// \todo we're currently not supporting propagating ARA assertions through IPC, +// because it would require to make all calls stackable... +void ARAIPCProxyPlugInInitializeARA(ARAIPCMessageSender hostCommandsSender, const ARAPersistentID factoryID, ARAAPIGeneration desiredApiGeneration); + +//! proxy document controller creation call, to be used instead of ARAFactory.createDocumentControllerWithDocument() +const ARADocumentControllerInstance * ARAIPCProxyPlugInCreateDocumentControllerWithDocument(ARAIPCMessageSender hostCommandsSender, + const ARAPersistentID factoryID, + const ARADocumentControllerHostInstance * hostInstance, + const ARADocumentProperties * properties); + +//! static handler of received messages +void ARAIPCProxyPlugInCallbacksDispatcher(const ARAIPCMessageID messageID, const ARAIPCMessageDecoder * decoder, ARAIPCMessageEncoder * replyEncoder); + +//! create the proxy plug-in extension when performing the binding to the remote plug-in instance +const ARAPlugInExtensionInstance * ARAIPCProxyPlugInBindToDocumentController(ARAIPCPlugInInstanceRef remoteRef, + ARAIPCMessageSender sender, 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(ARAIPCMessageSender hostCommandsSender, const ARAPersistentID factoryID); + + +#if defined(__cplusplus) +} // extern "C" +} // namespace IPC +} // namespace ARA +#endif + + +#endif // ARA_ENABLE_IPC + +#endif // ARAIPCProxyPlugIn_h diff --git a/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index f1b43fb..533c560 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -931,7 +931,7 @@ void DocumentController::initializeDocument (const ARADocumentProperties* proper ARA_LOG_MODELOBJECT_LIFETIME ("did create document", getDocument ()); willUpdateDocumentProperties (_document, properties); - getDocument ()->updateProperties (properties); + _document->updateProperties (properties); didUpdateDocumentProperties (_document); } @@ -2815,6 +2815,9 @@ void PlugInEntry::uninitializeARA () noexcept const ARADocumentControllerInstance* PlugInEntry::createDocumentControllerWithDocument (const ARADocumentControllerHostInstance* hostInstance, const ARADocumentProperties* properties) noexcept { ARA_LOG_HOST_ENTRY (nullptr); + + ARA_VALIDATE_API_STATE ((_usedApiGeneration != 0) && "ARAFactory::initializeARAWithConfiguration () not called!"); + ARA_VALIDATE_API_STRUCT_PTR (hostInstance, ARADocumentControllerHostInstance); ARA_VALIDATE_API_INTERFACE (hostInstance->audioAccessControllerInterface, ARAAudioAccessControllerInterface); ARA_VALIDATE_API_INTERFACE (hostInstance->archivingControllerInterface, ARAArchivingControllerInterface); diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index eec4b33..13e7706 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -1355,7 +1355,7 @@ class DocumentController : public DocumentControllerInterface, std::map _audioSourceContentUpdates; std::map _audioModificationContentUpdates; std::map _playbackRegionContentUpdates; - std::atomic_flag _analysisProgressIsSynced/* { true } C++ standard only allows for default-init to false */; + std::atomic_flag _analysisProgressIsSynced = ATOMIC_FLAG_INIT; bool _isHostEditingDocument { false };