diff --git a/CMakeLists.txt b/CMakeLists.txt index de840eb..212deed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # ====================== # ARA Examples CMake Integration # -# Copyright (c) 2020-2025, Celemony Software GmbH, All Rights Reserved. +# Copyright (c) 2020-2026, Celemony Software GmbH, All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,6 +68,19 @@ if(APPLE AND NOT CMAKE_OSX_ARCHITECTURES) endif() +# ====================== +# configure deployment target for Apple platforms + +if(NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET} OR ENV{MACOSX_DEPLOYMENT_TARGET} VERSION_LESS "10.15") + if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "macOS deployment target") + endif() +endif() +if (CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "10.15") + message(FATAL_ERROR "CMAKE_OSX_DEPLOYMENT_TARGET must be '10.15' or higher.") +endif() + + # ====================== # ARA SDK paths @@ -163,6 +176,8 @@ set(CMAKE_SUPPRESS_REGENERATION ON) set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY ON) set(CMAKE_XCODE_GENERATE_SCHEME ${ARA_SETUP_DEBUGGING}) +# this triggers a warning when using CLAP because they are using install() +# \todo there seems to be no way to silence the warning? set(CMAKE_SKIP_INSTALL_RULES YES) set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -208,7 +223,7 @@ if(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD) endif() string(REGEX MATCH "[Cc]\\+\\+" temp CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD) - if (CMAKE_CXX_EXTENSIONS) + if(CMAKE_CXX_EXTENSIONS) if((temp AND NOT CMAKE_CXX_EXTENSIONS) OR (NOT temp AND CMAKE_CXX_EXTENSIONS)) message(FATAL_ERROR "Conflicting C++ standards set in general versus Xcode-specific configuration.") @@ -224,21 +239,17 @@ if(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD) endif() if(CMAKE_CXX_STANDARD) - if(NOT(CMAKE_CXX_STANDARD VERSION_GREATER_EQUAL 11 AND + if(NOT(CMAKE_CXX_STANDARD VERSION_GREATER_EQUAL 17 AND CMAKE_CXX_STANDARD VERSION_LESS 98)) - message(FATAL_ERROR "This project require C++11 or newer.") + message(FATAL_ERROR "This project require C++17 or newer.") endif() else() - if(MSVC) - set(CMAKE_CXX_STANDARD 14) - else() - set(CMAKE_CXX_STANDARD 11) - endif() + set(CMAKE_CXX_STANDARD 17) endif() if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif() -set(CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) # ====================== @@ -261,13 +272,17 @@ set(CXX_STANDARD_REQUIRED ON) if(WIN32) # no special setup for Windows needed at this point. elseif(APPLE) - if (CMAKE_OSX_DEPLOYMENT_TARGET AND - CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "10.9") - message(FATAL_ERROR "CMAKE_OSX_DEPLOYMENT_TARGET must be '10.9' or higher.") - endif() - if(NOT CMAKE_OSX_SYSROOT MATCHES ".+[Mm][Aa][Cc][Oo][Ss][X?x?]?[0123456789]+\.[0123456789]+\.sdk") - message(FATAL_ERROR "CMake support for Apple platforms is currently limited to macOS.") + # CMAKE_OSX_SYSROOT also applies to other SDKs like iOS, tvOS, visionOS, or watchOS + if(CMAKE_OSX_SYSROOT AND (NOT CMAKE_OSX_SYSROOT STREQUAL "")) + if(NOT CMAKE_OSX_SYSROOT MATCHES ".+[Mm][Aa][Cc][Oo][Ss][X?x?]?[0123456789]+\.[0123456789]+\.sdk") + message(FATAL_ERROR "CMake support for Apple platforms is currently limited to macOS.") + endif() + execute_process(COMMAND xcrun --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-version OUTPUT_VARIABLE mac_os_sdk_version OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + execute_process(COMMAND xcrun --sdk macosx --show-sdk-version OUTPUT_VARIABLE mac_os_sdk_version OUTPUT_STRIP_TRAILING_WHITESPACE) endif() + set(CMAKE_OSX_SYSROOT "macosx${mac_os_sdk_version}") + unset(mac_os_sdk_version) elseif(UNIX) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) else() @@ -295,6 +310,15 @@ if(MSVC) $,/ZI,/Zi> /diagnostics:column ) + + # /Zc:__cplusplus, added in Visual Studio 2017 version 15.7, is required to make __cplusplus + # accurate, see https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + if(MSVC_VERSION GREATER_EQUAL 1914) + add_compile_options( + /Zc:__cplusplus + ) + endif() + elseif(GCC_STYLE_COMPILER) add_compile_options( $,-O0,-Os> @@ -483,7 +507,6 @@ if(ARA_ENABLE_AUDIO_UNIT) ) set_target_properties(AudioUnitSDK PROPERTIES - CXX_STANDARD 17 FOLDER "3rdParty" XCODE_GENERATE_SCHEME OFF XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH $,YES,NO> @@ -866,7 +889,7 @@ function(configure_ARA_Examples_target target) # language standards target_compile_features(${target} PRIVATE - cxx_std_11 + cxx_std_14 c_std_11 ) @@ -990,7 +1013,7 @@ function(ara_configure_binary_package target extension apple_plist_file) list(APPEND target_resources ${apple_plist_file} ) - if (NOT ${extension} MATCHES "framework") + if(NOT ${extension} MATCHES "framework") list(APPEND target_resources "${CMAKE_CURRENT_SOURCE_DIR}/ExamplesCommon/macOS/ARAExamples.icns" ) @@ -1110,8 +1133,7 @@ endif() # ARA_Library (including ARA_API) message(STATUS "Importing ARA_Library (including ARA_API)") add_subdirectory("${ARA_LIBRARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ARA_Library.build" EXCLUDE_FROM_ALL) -#set_target_properties(ARA_Host_Library ARA_PlugIn_Library ARA_IPC_Library PROPERTIES -set_target_properties(ARA_Host_Library ARA_PlugIn_Library PROPERTIES +set_target_properties(ARA_Host_Library ARA_PlugIn_Library ARA_IPC_Library PROPERTIES FOLDER "ARA_Library" XCODE_GENERATE_SCHEME OFF XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH $,YES,NO> @@ -1119,8 +1141,7 @@ set_target_properties(ARA_Host_Library ARA_PlugIn_Library PROPERTIES # for debug builds, these are enabled automatically by the ARA SDK, as is appropriate for actual products # - but for our examples/tests here, we want to enable these also for release builds -#foreach(target ARA_Host_Library ARA_PlugIn_Library ARA_IPC_Library) -foreach(target ARA_Host_Library ARA_PlugIn_Library) +foreach(target ARA_Host_Library ARA_PlugIn_Library ARA_IPC_Library) target_compile_definitions(${target} PUBLIC -DARA_VALIDATE_API_CALLS=1 -DARA_ENABLE_INTERNAL_ASSERTS=1 @@ -1232,8 +1253,7 @@ target_link_libraries(ARATestPlugInCommon PUBLIC ) # \todo add IPC implementation for Linux -#if(APPLE OR WIN32) -if(0) +if(APPLE OR WIN32) target_link_libraries(ARATestPlugInCommon PUBLIC ARA_IPC_Library ) @@ -1262,7 +1282,7 @@ endif() # ====================== # audio plug-in target VST3 Test Plug-In -if (ARA_ENABLE_VST3) +if(ARA_ENABLE_VST3) if(APPLE) add_library(ARATestPlugInVST3 MODULE "") @@ -1346,7 +1366,6 @@ if(ARA_ENABLE_AUDIO_UNIT) ) set_target_properties(ARATestPlugInAudioUnit PROPERTIES - CXX_STANDARD 17 FOLDER "Examples/ARATestPlugIn" OUTPUT_NAME "ARATestPlugIn" ) @@ -1392,8 +1411,7 @@ endif(ARA_ENABLE_AUDIO_UNIT) # ====================== # audio plug-in target Audio Unit v3 App Extension Test Plug-In, including the required dummy app -#if(APPLE) -if(FALSE) +if(APPLE) add_library(ARATestPlugInAUv3Framework SHARED "${CMAKE_CURRENT_SOURCE_DIR}/TestPlugIn/AudioUnit_v3/Framework/BufferedAudioBus.hpp" @@ -1544,7 +1562,7 @@ endif(APPLE) if(ARA_ENABLE_CLAP) - if (APPLE) + if(APPLE) add_library(ARATestPlugInCLAP MODULE "") else() add_library(ARATestPlugInCLAP SHARED "") @@ -1659,7 +1677,7 @@ target_link_libraries(ARAMiniHost PRIVATE ARA_API ARAExamplesCommon ARA_Host_Library -# ARA_IPC_Library + ARA_IPC_Library ) if(ARA_ENABLE_VST3) @@ -1780,8 +1798,7 @@ elseif(UNIX) endif() # \todo add IPC implementation for Linux -#if(APPLE OR WIN32) -if(FALSE) +if(APPLE OR WIN32) target_sources(ARATestHost PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/TestHost/IPC/IPCMessageChannel.h" "${CMAKE_CURRENT_SOURCE_DIR}/TestHost/IPC/IPCMessageChannel.cpp" @@ -1807,9 +1824,9 @@ if(ARA_SETUP_DEBUGGING) if(ARA_ENABLE_AUDIO_UNIT) add_dependencies(ARATestHost ARATestPlugInAudioUnit) endif() -# if(APPLE) -# add_dependencies(ARATestHost ARATestPlugInAUv3App) -# endif() + if(APPLE) + add_dependencies(ARATestHost ARATestPlugInAUv3App) + endif() if(ARA_ENABLE_CLAP) add_dependencies(ARATestHost ARATestPlugInCLAP) endif() diff --git a/ChangeLog.txt b/ChangeLog.txt index df66adf..597e83a 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,24 @@ -Proposed Features which have been postponed to a later subrelease: -- Audio Unit version 3 support (App Extension, incl. IPC when not loaded in-process) -- generic ARA IPC implementation in ARA_Library +This is a development build of the ARA Examples 3.0. +=== PRELIMINARY - DO NOT USE FOR SHIPPING PRODUCTS! === + + +Changes since previous releases: +- initial draft of ARA generator plug-ins that operate merely based on content descriptions (no sample input) +- initial draft of lyrics-related content types, tailored to support singing voice synthesis +- initial draft of region sequence persistency +- added isPlaybackRegionPreservingAudioSourceSignal(), superseding isAudioModificationPreservingAudioSourceSignal() +- initial draft of Audio Unit version 3 support (App Extension) + Note that macOS code signing is required for proper loading in sandboxes such as App Extensions, + configured via CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY and CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM. +- initial draft of generic ARA IPC implementation in ARA_Library +- IPCDemo refactored and merged into ARATestHost, utilizing new ARA_IPC library draft +- IPC now also supports using VST3 or CLAP (was previously limited to Audio Units) +- IPC now also works on Windows (was previously limited to macOS) +- IPC now also supports using pugixml as encoder (was previously limited to the macOS-only CFDictionary) +- TestHost now uses a separate region sequence for each file, allowing to test alignment-related plug-ins properly +- dropped support for ARA 1 and 2.0 Draft APIs +- C++17 is now consistently required on all platforms +- fixed inconsistent spelling of "timestretch" in various identifiers === ARA SDK 2.3 release (aka 2.3.001) (2025/11/07) === diff --git a/ExamplesCommon/Archives/Archives.cpp b/ExamplesCommon/Archives/Archives.cpp index 120d3af..eeb3dcc 100644 --- a/ExamplesCommon/Archives/Archives.cpp +++ b/ExamplesCommon/Archives/Archives.cpp @@ -2,7 +2,7 @@ //! \file Archives.cpp //! archive classes used by e.g. ARATestHost to save/restore plug-in state //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/ExamplesCommon/Archives/Archives.h b/ExamplesCommon/Archives/Archives.h index a430ea7..601060a 100644 --- a/ExamplesCommon/Archives/Archives.h +++ b/ExamplesCommon/Archives/Archives.h @@ -2,7 +2,7 @@ //! \file Archives.h //! archive classes used by e.g. ARATestHost to save/restore plug-in state //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/ExamplesCommon/AudioFiles/AudioFiles.cpp b/ExamplesCommon/AudioFiles/AudioFiles.cpp index 66526ce..9ba574c 100644 --- a/ExamplesCommon/AudioFiles/AudioFiles.cpp +++ b/ExamplesCommon/AudioFiles/AudioFiles.cpp @@ -2,7 +2,7 @@ //! \file AudioFiles.cpp //! classes representing audio files //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -29,6 +29,7 @@ #include #include #include +#include /*******************************************************************************/ @@ -93,11 +94,7 @@ class ARAiXMLChunk persistentID = archive.child_value (ARA::kARAXMLName_PersistentID); -#if __cplusplus >= 201703L return base64_decode (std::string_view { archive.child_value (ARA::kARAXMLName_ArchiveData) }, true); -#else - return base64_decode (archive.child_value (ARA::kARAXMLName_ArchiveData), true); -#endif } void setAudioSourceData (const std::string& documentArchiveID, bool openAutomatically, @@ -232,8 +229,17 @@ bool SineAudioFile::saveToFile (const std::string& path) /*******************************************************************************/ -AudioDataFile::AudioDataFile (const std::string& name, icstdsp::AudioFile&& audioFile) -: AudioFileBase { name }, +inline static const std::string filenameHelper (const std::string& path) +{ + std::filesystem::path fp { path }; + if (fp.has_filename ()) + return fp.filename ().string (); + else + return path; +} + +AudioDataFile::AudioDataFile (const std::string& path, icstdsp::AudioFile&& audioFile) +: AudioFileBase { filenameHelper (path) }, _audioFile { std::move (audioFile) } { unsigned int dataLength { 0 }; diff --git a/ExamplesCommon/AudioFiles/AudioFiles.h b/ExamplesCommon/AudioFiles/AudioFiles.h index d673e8e..f087f88 100644 --- a/ExamplesCommon/AudioFiles/AudioFiles.h +++ b/ExamplesCommon/AudioFiles/AudioFiles.h @@ -2,7 +2,7 @@ //! \file AudioFiles.h //! classes representing audio files //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -97,7 +97,7 @@ class SineAudioFile : public AudioFileBase class AudioDataFile : public AudioFileBase { public: - AudioDataFile (const std::string& name, icstdsp::AudioFile&& audioFile); + AudioDataFile (const std::string& path, icstdsp::AudioFile&& audioFile); int64_t getSampleCount () const noexcept override { return _audioFile.GetSize (); } double getSampleRate () const noexcept override { return _audioFile.GetRate (); } diff --git a/ExamplesCommon/PlugInHosting/AudioUnitLoader.h b/ExamplesCommon/PlugInHosting/AudioUnitLoader.h index b25d820..51d6acb 100644 --- a/ExamplesCommon/PlugInHosting/AudioUnitLoader.h +++ b/ExamplesCommon/PlugInHosting/AudioUnitLoader.h @@ -2,7 +2,7 @@ //! \file AudioUnitLoader.h //! Audio Unit specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -20,7 +20,7 @@ #include "ARA_API/ARAInterface.h" #include "ARA_Library/Debug/ARADebug.h" -//#include "ARA_Library/IPC/ARAIPC.h" +#include "ARA_Library/IPC/ARAIPC.h" #include @@ -37,7 +37,7 @@ bool AudioUnitIsV2(AudioUnitComponent audioUnitComponent); AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, bool useIPC); // On return, *connection will be NULL if Audio Unit does not use IPC, otherwise it will point to // a valid message channel for all factory-related calls until AudioUnitCleanupComponent() is called. -const ARA_NAMESPACE ARAFactory * AudioUnitGetARAFactory(AudioUnitInstance audioUnit/*, ARA_IPC_NAMESPACE ARAIPCConnectionRef * connectionRef*/); +const ARA_NAMESPACE ARAFactory * AudioUnitGetARAFactory(AudioUnitInstance audioUnit, ARA_IPC_NAMESPACE ARAIPCProxyPlugInRef * proxyPlugInRef); const ARA_NAMESPACE ARAPlugInExtensionInstance * AudioUnitBindToARADocumentController(AudioUnitInstance audioUnit, ARA_NAMESPACE ARADocumentControllerRef controllerRef, ARA_NAMESPACE ARAPlugInInstanceRoleFlags assignedRoles); void AudioUnitStartRendering(AudioUnitInstance audioUnit, UInt32 channelCount, UInt32 maxBlockSize, double sampleRate); void AudioUnitRenderBuffer(AudioUnitInstance audioUnit, UInt32 blockSize, SInt64 samplePosition, float ** buffers); diff --git a/ExamplesCommon/PlugInHosting/AudioUnitLoader.m b/ExamplesCommon/PlugInHosting/AudioUnitLoader.m index 515faab..cf9d5e5 100644 --- a/ExamplesCommon/PlugInHosting/AudioUnitLoader.m +++ b/ExamplesCommon/PlugInHosting/AudioUnitLoader.m @@ -2,7 +2,7 @@ //! \file AudioUnitLoader.m //! Audio Unit specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -26,11 +26,10 @@ #import #include "ARA_API/ARAAudioUnit.h" -//#include "ARA_API/ARAAudioUnit_v3.h" +#include "ARA_API/ARAAudioUnit_v3.h" #include "ARA_Library/Debug/ARADebug.h" -//#include "ARA_Library/IPC/ARAIPCAudioUnit_v3.h" -//#include "ARA_Library/IPC/ARAIPCProxyPlugIn.h" -#define ARA_AUDIOUNITV3_IPC_IS_AVAILABLE 0 +#include "ARA_Library/IPC/ARAIPCAudioUnit_v3.h" +#include "ARA_Library/IPC/ARAIPCProxyPlugIn.h" #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE #include @@ -45,7 +44,7 @@ { AudioComponent component; #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE - ARAIPCConnectionRef araProxy; + ARAIPCProxyPlugInRef araProxy; #endif }; @@ -72,7 +71,8 @@ #endif }; -void CallAudioUnitAsyncIfNeeded(AudioUnitInstance ARA_MAYBE_UNUSED_ARG(audioUnitInstance), void (^audioUnitCall)(void)) +void CallAudioUnitAsyncIfNeeded(AudioUnitInstance audioUnitInstance, void (^audioUnitCall)(void)); +void CallAudioUnitAsyncIfNeeded(AudioUnitInstance audioUnitInstance, void (^audioUnitCall)(void)) { #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE if (audioUnitInstance->audioUnitComponent->araProxy) @@ -204,8 +204,8 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b @autoreleasepool { // simply blocking the thread is not allowed here, so we need to add proper NSRunLoop around the instantiation process - NSRunLoop * runloop = [NSRunLoop currentRunLoop]; - [runloop performBlock:^void (void) + NSRunLoop * runLoop = [NSRunLoop currentRunLoop]; + [runLoop performBlock:^void (void) { const AudioComponentInstantiationOptions options = #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE @@ -227,17 +227,17 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b }]; // loading out-of-process can take a considerable amount of time, so loop for up to a second if needed for (int i = 0; (i < 100) && (result->v3AudioUnit == nil); ++i) - [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; } ARA_INTERNAL_ASSERT(result->v3AudioUnit != nil); } return result; } -const ARAFactory * AudioUnitGetARAFactory(AudioUnitInstance audioUnitInstance/*, ARAIPCConnectionRef * connectionRef*/) +const ARAFactory * AudioUnitGetARAFactory(AudioUnitInstance audioUnitInstance, ARAIPCProxyPlugInRef * proxyPlugInRef) { const ARAFactory * result = NULL; // initially assume this plug-in doesn't support ARA -// *connectionRef = NULL; + *proxyPlugInRef = NULL; // check whether the AU supports ARA by trying to get the factory if (audioUnitInstance->isAUv2) @@ -261,7 +261,6 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b } } } -/* else { #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE @@ -273,10 +272,10 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b audioUnitInstance->audioUnitComponent->araProxy = ARAIPCAUProxyPlugInInitialize(audioUnitInstance->v3AudioUnit, NULL, NULL); if (audioUnitInstance->audioUnitComponent->araProxy) { - *connectionRef = audioUnitInstance->audioUnitComponent->araProxy; + *proxyPlugInRef = audioUnitInstance->audioUnitComponent->araProxy; - ARA_VALIDATE_API_CONDITION(ARAIPCProxyPlugInGetFactoriesCount(*connectionRef) == 1); - result = ARAIPCProxyPlugInGetFactoryAtIndex (*connectionRef, 0); + ARA_VALIDATE_API_CONDITION(ARAIPCProxyPlugInGetFactoriesCount(*proxyPlugInRef) == 1); + result = ARAIPCProxyPlugInGetFactoryAtIndex (*proxyPlugInRef, 0); ARA_VALIDATE_API_CONDITION(result != NULL); } } @@ -291,7 +290,6 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b } } } -*/ // validate the AU is properly tagged as ARA (if it supports ARA) #if ARA_VALIDATE_API_CALLS @@ -326,15 +324,6 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b ARAAudioUnitPlugInExtensionBinding audioUnitBinding = { kARAAudioUnitMagic, controllerRef, NULL, knownRoles, assignedRoles }; OSStatus ARA_MAYBE_UNUSED_VAR(status) = AudioUnitGetProperty(audioUnitInstance->v2AudioUnit, kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles, kAudioUnitScope_Global, 0, &audioUnitBinding, &propertySize); -#if defined(ARA_SUPPORT_VERSION_1) && (ARA_SUPPORT_VERSION_1) - if (status != noErr) - { - propertySize = offsetof(ARAAudioUnitPlugInExtensionBinding, knownRoles); - expectedPropertySize = propertySize; - status = AudioUnitGetProperty(audioUnitInstance->v2AudioUnit, kAudioUnitProperty_ARAPlugInExtensionBinding, kAudioUnitScope_Global, 0, &audioUnitBinding, &propertySize); - } -#endif - ARA_VALIDATE_API_CONDITION(status == noErr); ARA_VALIDATE_API_CONDITION(propertySize == expectedPropertySize); ARA_VALIDATE_API_CONDITION(audioUnitBinding.inOutMagicNumber == kARAAudioUnitMagic); @@ -346,7 +335,6 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b else { const ARAPlugInExtensionInstance * instance = NULL; -/* #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE if (audioUnitInstance->isOutOfProcess) { @@ -363,7 +351,6 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b instance = [(AUAudioUnit *)audioUnitInstance->v3AudioUnit bindToDocumentController:controllerRef withRoles:assignedRoles knownRoles:knownRoles]; } ARA_VALIDATE_API_CONDITION(instance != NULL); -*/ return instance; } } @@ -372,6 +359,9 @@ AudioUnitInstance AudioUnitOpenInstance(AudioUnitComponent audioUnitComponent, b // in order for Melodyne to render the ARA data, it must be set to playback mode (in stop, its built-in pre-listening logic is active) // thus we implement some crude, minimal transport information here. +OSStatus GetTransportState2(void * inHostUserData, Boolean * outIsPlaying, Boolean * outIsRecording, + Boolean * outTransportStateChanged, Float64 * outCurrentSampleInTimeLine, + Boolean * outIsCycling, Float64 * outCycleStartBeat, Float64 * outCycleEndBeat); OSStatus GetTransportState2(void * inHostUserData, Boolean * outIsPlaying, Boolean * outIsRecording, Boolean * outTransportStateChanged, Float64 * outCurrentSampleInTimeLine, Boolean * outIsCycling, Float64 * outCycleStartBeat, Float64 * outCycleEndBeat) @@ -397,6 +387,9 @@ OSStatus GetTransportState2(void * inHostUserData, Boolean * outIsPlaying, Boole return noErr; } +OSStatus GetTransportState1(void * inHostUserData, Boolean * outIsPlaying, + Boolean * outTransportStateChanged, Float64 * outCurrentSampleInTimeLine, + Boolean * outIsCycling, Float64 * outCycleStartBeat, Float64 * outCycleEndBeat); OSStatus GetTransportState1(void * inHostUserData, Boolean * outIsPlaying, Boolean * outTransportStateChanged, Float64 * outCurrentSampleInTimeLine, Boolean * outIsCycling, Float64 * outCycleStartBeat, Float64 * outCycleEndBeat) @@ -405,6 +398,9 @@ OSStatus GetTransportState1(void * inHostUserData, Boolean * outIsPlaying, outIsCycling, outCycleStartBeat, outCycleEndBeat); } +OSStatus RenderCallback(void * ARA_MAYBE_UNUSED_ARG(inRefCon), AudioUnitRenderActionFlags * ARA_MAYBE_UNUSED_ARG(ioActionFlags), + const AudioTimeStamp * ARA_MAYBE_UNUSED_ARG(inTimeStamp), UInt32 ARA_MAYBE_UNUSED_ARG(inBusNumber), + UInt32 ARA_MAYBE_UNUSED_ARG(inNumberFrames), AudioBufferList * _Nullable ioData); OSStatus RenderCallback(void * ARA_MAYBE_UNUSED_ARG(inRefCon), AudioUnitRenderActionFlags * ARA_MAYBE_UNUSED_ARG(ioActionFlags), const AudioTimeStamp * ARA_MAYBE_UNUSED_ARG(inTimeStamp), UInt32 ARA_MAYBE_UNUSED_ARG(inBusNumber), UInt32 ARA_MAYBE_UNUSED_ARG(inNumberFrames), AudioBufferList * _Nullable ioData) @@ -415,6 +411,7 @@ UInt32 ARA_MAYBE_UNUSED_ARG(inNumberFrames), AudioBufferList * _Nullable ioData) return noErr; } +AudioStreamBasicDescription GetStreamDescription (UInt32 channelCount, double sampleRate); AudioStreamBasicDescription GetStreamDescription (UInt32 channelCount, double sampleRate) { AudioStreamBasicDescription desc = { sampleRate, kAudioFormatLinearPCM, kAudioFormatFlagsNativeFloatPacked|kAudioFormatFlagIsNonInterleaved, @@ -422,6 +419,7 @@ AudioStreamBasicDescription GetStreamDescription (UInt32 channelCount, double sa return desc; } +void ConfigureBusses(AudioUnit audioUnit, AudioUnitScope inScope, UInt32 channelCount, double sampleRate); void ConfigureBusses(AudioUnit audioUnit, AudioUnitScope inScope, UInt32 channelCount, double sampleRate) { UInt32 busCount = 1; @@ -455,6 +453,7 @@ void ConfigureBusses(AudioUnit audioUnit, AudioUnitScope inScope, UInt32 channel status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, inScope, 0, &shouldAllocate, sizeof(shouldAllocate)); } +void ConfigureBussesArray(AUAudioUnitBusArray * bussesArray, UInt32 channelCount, double sampleRate); void ConfigureBussesArray(AUAudioUnitBusArray * bussesArray, UInt32 channelCount, double sampleRate) { if (bussesArray.countChangeable) @@ -483,7 +482,7 @@ void ConfigureBussesArray(AUAudioUnitBusArray * bussesArray, UInt32 channelCount void AudioUnitStartRendering(AudioUnitInstance audioUnitInstance, UInt32 channelCount, UInt32 maxBlockSize, double sampleRate) { ARA_INTERNAL_ASSERT(audioUnitInstance->audioBuffers == NULL); - audioUnitInstance->audioBuffers = malloc(sizeof(UInt32) + channelCount * sizeof(AudioBuffer)); + audioUnitInstance->audioBuffers = malloc(offsetof(AudioBufferList, mBuffers) + channelCount * sizeof(AudioBuffer)); audioUnitInstance->audioBuffers->mNumberBuffers = channelCount; if (audioUnitInstance->isAUv2) diff --git a/ExamplesCommon/PlugInHosting/CLAPLoader.c b/ExamplesCommon/PlugInHosting/CLAPLoader.c index 9f72eb1..8fef281 100644 --- a/ExamplesCommon/PlugInHosting/CLAPLoader.c +++ b/ExamplesCommon/PlugInHosting/CLAPLoader.c @@ -2,7 +2,7 @@ //! \file CLAPLoader.c //! CLAP specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2022-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2022-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with Timo Kaluza (defiantnerd) //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. @@ -130,6 +130,7 @@ CLAPBinary CLAPLoadBinary(const char * binaryName) return clapBinary; } +void CLAPValidateDescHasARA(CLAPBinary clapBinary, const char * plugin_id); void CLAPValidateDescHasARA(CLAPBinary clapBinary, const char * plugin_id) { const clap_plugin_factory_t * factory = (const clap_plugin_factory_t *)clapBinary->entry->get_factory(CLAP_PLUGIN_FACTORY_ID); @@ -187,11 +188,13 @@ const ARAFactory * CLAPGetARAFactory(CLAPBinary clapBinary, const char * optiona } } +const void * host_get_extension(const clap_host_t * ARA_MAYBE_UNUSED_ARG(host), const char * ARA_MAYBE_UNUSED_ARG(extension_id)); const void * host_get_extension(const clap_host_t * ARA_MAYBE_UNUSED_ARG(host), const char * ARA_MAYBE_UNUSED_ARG(extension_id)) { return NULL; } +void host_request_dummy(const clap_host_t * ARA_MAYBE_UNUSED_ARG(host)); void host_request_dummy(const clap_host_t * ARA_MAYBE_UNUSED_ARG(host)) { } @@ -382,16 +385,19 @@ void CLAPStartRendering(CLAPPlugIn clapPlugIn, uint32_t channelCount, uint32_t m clapPlugIn->plugin->start_processing(clapPlugIn->plugin); } +uint32_t CLAP_ABI input_events_size(const struct clap_input_events* list); uint32_t CLAP_ABI input_events_size(const struct clap_input_events* list) { return 0; } +const clap_event_header_t* CLAP_ABI input_events_get(const struct clap_input_events* list, uint32_t index); const clap_event_header_t* CLAP_ABI input_events_get(const struct clap_input_events* list, uint32_t index) { return NULL; } +bool CLAP_ABI output_events_try_push(const struct clap_output_events* list, const clap_event_header_t* event); bool CLAP_ABI output_events_try_push(const struct clap_output_events* list, const clap_event_header_t* event) { return false; diff --git a/ExamplesCommon/PlugInHosting/CLAPLoader.h b/ExamplesCommon/PlugInHosting/CLAPLoader.h index 279c28b..02d935c 100644 --- a/ExamplesCommon/PlugInHosting/CLAPLoader.h +++ b/ExamplesCommon/PlugInHosting/CLAPLoader.h @@ -2,7 +2,7 @@ //! \file CLAPLoader.h //! CLAP specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2022-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2022-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with Timo Kaluza (defiantnerd) //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. diff --git a/ExamplesCommon/PlugInHosting/VST3Loader.cpp b/ExamplesCommon/PlugInHosting/VST3Loader.cpp index b513de2..7377be7 100644 --- a/ExamplesCommon/PlugInHosting/VST3Loader.cpp +++ b/ExamplesCommon/PlugInHosting/VST3Loader.cpp @@ -2,7 +2,7 @@ //! \file VST3Loader.cpp //! VST3 specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -135,8 +135,7 @@ VST3Binary VST3LoadBinary (const char* binaryName) CFRelease (url); ARA_INTERNAL_ASSERT (vst3Binary->libHandle); - Boolean ARA_MAYBE_UNUSED_VAR (didLoad); - didLoad = CFBundleLoadExecutable (vst3Binary->libHandle); + [[maybe_unused]] const auto didLoad { CFBundleLoadExecutable (vst3Binary->libHandle) }; ARA_INTERNAL_ASSERT (didLoad); const auto initBinaryFunc { (bundleEntryPtr) CFBundleGetFunctionPointerForName (vst3Binary->libHandle, CFSTR ("bundleEntry")) }; @@ -151,11 +150,11 @@ VST3Binary VST3LoadBinary (const char* binaryName) #endif ARA_INTERNAL_ASSERT (initBinaryFunc); - bool ARA_MAYBE_UNUSED_VAR (entrySucceeded); + [[maybe_unused]] const bool entrySucceeded #if defined (_WIN32) - entrySucceeded = initBinaryFunc (); + { initBinaryFunc () }; #else - entrySucceeded = initBinaryFunc (vst3Binary->libHandle); + { initBinaryFunc (vst3Binary->libHandle) }; #endif ARA_INTERNAL_ASSERT (entrySucceeded); @@ -170,8 +169,7 @@ VST3Binary VST3LoadBinary (const char* binaryName) for (int32 i = 0; i < vst3Binary->pluginFactory->countClasses (); ++i) { PClassInfo classInfo; - tresult ARA_MAYBE_UNUSED_VAR (result); - result = vst3Binary->pluginFactory->getClassInfo (i, &classInfo); + [[maybe_unused]] auto result { vst3Binary->pluginFactory->getClassInfo (i, &classInfo) }; ARA_INTERNAL_ASSERT (result == Steinberg::kResultOk); // find and instantiate all ARA::IMainFactory classes and ensure they have unique names and IDs @@ -275,8 +273,7 @@ VST3Effect VST3CreateEffect (VST3Binary vst3Binary, const char* optionalPlugInNa for (int32 i = 0; i < vst3Binary->pluginFactory->countClasses (); ++i) { PClassInfo classInfo; - tresult ARA_MAYBE_UNUSED_VAR (result); - result = vst3Binary->pluginFactory->getClassInfo (i, &classInfo); + [[maybe_unused]] auto result { vst3Binary->pluginFactory->getClassInfo (i, &classInfo) }; ARA_INTERNAL_ASSERT (result == Steinberg::kResultOk); if (std::strcmp (kVstAudioEffectClass, classInfo.category) == 0) @@ -327,13 +324,8 @@ const ARA::ARAPlugInExtensionInstance* VST3BindToARADocumentController (VST3Effe return result; } -#if defined (ARA_SUPPORT_VERSION_1) && (ARA_SUPPORT_VERSION_1) - ARA_INTERNAL_ASSERT (assignedRoles == knownRoles); - return entry->bindToDocumentController (controllerRef); -#else ARA_INTERNAL_ASSERT (false); return nullptr; -#endif } void VST3StartRendering (VST3Effect vst3Effect, int32_t channelCount, int32_t maxBlockSize, double sampleRate) @@ -345,8 +337,7 @@ void VST3StartRendering (VST3Effect vst3Effect, int32_t channelCount, int32_t ma vst3Effect->channelCount = channelCount; ProcessSetup setup = { kRealtime, kSample32, maxBlockSize, sampleRate }; - tresult ARA_MAYBE_UNUSED_VAR (result); - result = processor->setupProcessing (setup); + [[maybe_unused]] auto result { processor->setupProcessing (setup) }; ARA_INTERNAL_ASSERT (result == kResultOk); SpeakerArrangement arrangement; @@ -416,8 +407,7 @@ void VST3RenderBuffer (VST3Effect vst3Effect, int32_t blockSize, double sampleRa data.outputs = &outputs; data.processContext = &context; - tresult ARA_MAYBE_UNUSED_VAR (result); - result = processor->process (data); + [[maybe_unused]] const auto result { processor->process (data) }; ARA_INTERNAL_ASSERT (result == kResultOk); } @@ -425,8 +415,7 @@ void VST3StopRendering (VST3Effect vst3Effect) { ARA_INTERNAL_ASSERT (vst3Effect->channelCount != 0); - tresult ARA_MAYBE_UNUSED_VAR (result); - result = vst3Effect->component->setActive (false); + [[maybe_unused]] auto result { vst3Effect->component->setActive (false) }; ARA_INTERNAL_ASSERT (result == kResultOk); result = vst3Effect->component->activateBus (kAudio, kInput, 0, false); @@ -439,8 +428,7 @@ void VST3StopRendering (VST3Effect vst3Effect) void VST3DestroyEffect (VST3Effect vst3Effect) { - tresult ARA_MAYBE_UNUSED_VAR (result); - result = vst3Effect->component->terminate (); + [[maybe_unused]] const auto result { vst3Effect->component->terminate () }; ARA_INTERNAL_ASSERT (result == kResultOk); vst3Effect->component = nullptr; delete vst3Effect; diff --git a/ExamplesCommon/PlugInHosting/VST3Loader.h b/ExamplesCommon/PlugInHosting/VST3Loader.h index 5771ebf..7930675 100644 --- a/ExamplesCommon/PlugInHosting/VST3Loader.h +++ b/ExamplesCommon/PlugInHosting/VST3Loader.h @@ -2,7 +2,7 @@ //! \file VST3Loader.h //! VST3 specific ARA implementation for the SDK's hosting examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/ExamplesCommon/SignalProcessing/PulsedSineSignal.c b/ExamplesCommon/SignalProcessing/PulsedSineSignal.c index b91417b..766a99d 100644 --- a/ExamplesCommon/SignalProcessing/PulsedSineSignal.c +++ b/ExamplesCommon/SignalProcessing/PulsedSineSignal.c @@ -2,7 +2,7 @@ //! \file PulsedSineSignal.c //! creating a pulsed sine test signal for ARA examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/ExamplesCommon/SignalProcessing/PulsedSineSignal.h b/ExamplesCommon/SignalProcessing/PulsedSineSignal.h index 7fa337e..1039013 100644 --- a/ExamplesCommon/SignalProcessing/PulsedSineSignal.h +++ b/ExamplesCommon/SignalProcessing/PulsedSineSignal.h @@ -2,7 +2,7 @@ //! \file PulsedSineSignal.h //! creating a pulsed sine test signal for ARA examples //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/ExamplesCommon/Utilities/StdUniquePtrUtilities.h b/ExamplesCommon/Utilities/StdUniquePtrUtilities.h index aaec4cf..7ad8b7f 100644 --- a/ExamplesCommon/Utilities/StdUniquePtrUtilities.h +++ b/ExamplesCommon/Utilities/StdUniquePtrUtilities.h @@ -4,7 +4,7 @@ //! to std::unique_ptr (comparison, searching in vectors) //! Also provides an implementation of std::make_unique for older compilers. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -46,7 +46,7 @@ struct unique_ptr_compare /*******************************************************************************/ // Find an a raw pointer inside a vector of std::unique_ptrs in the vector and, // if found, erase it from the vector. Returns true if found & erased, otherwise false -template ::value || std::is_convertible::value, bool>::type = true> +template || std::is_convertible_v, bool> = true> inline bool find_erase (std::vector>& container, const U& ptr) { auto it = std::find_if (container.begin (), container.end (), unique_ptr_compare{ ptr }); @@ -60,7 +60,7 @@ inline bool find_erase (std::vector>& container, const U& ptr /*******************************************************************************/ // Determine if a raw pointer exists in a vector as a std::unique_ptr -template ::value || std::is_convertible::value, bool>::type = true> +template || std::is_convertible_v, bool> = true> inline bool contains (std::vector> const& container, const U& ptr) { return std::any_of (container.begin (), container.end (), unique_ptr_compare{ ptr }); @@ -68,7 +68,7 @@ inline bool contains (std::vector> const& container, const U& /*******************************************************************************/ // Find position of a raw pointer inside a vector of std::unique_ptrs. -template ::value || std::is_convertible::value, bool>::type = true> +template || std::is_convertible_v, bool> = true> inline intptr_t index_of (std::vector> const& container, const U& ptr) { auto it = std::find_if (container.begin (), container.end (), unique_ptr_compare{ ptr }); @@ -80,42 +80,4 @@ inline intptr_t index_of (std::vector> const& container, cons } // namespace ARA -/*******************************************************************************/ -// Implement std::make_unique for pre-C++14 compilers, as published by ISO here: -// https://isocpp.org/files/papers/N3656.txt -#if !defined (_MSC_VER) && (__cplusplus < 201402L) -namespace std -{ - template - struct _Unique_if - { - typedef unique_ptr _Single_object; - }; - template - struct _Unique_if - { - typedef unique_ptr _Unknown_bound; - }; - template - struct _Unique_if - { - typedef void _Known_bound; - }; - - template - typename _Unique_if::_Single_object make_unique (Args&&... args) - { - return unique_ptr (new T (std::forward (args)...)); - } - template - typename _Unique_if::_Unknown_bound make_unique (size_t n) - { - typedef typename remove_extent::type U; - return unique_ptr (new U[n] ()); - } - template - typename _Unique_if::_Known_bound make_unique (Args&&...) = delete; -} // namespace std -#endif // !defined (_MSC_VER) && (__cplusplus < 201402L) - #endif // StdUniquePtrUtilities_h diff --git a/MiniHost/MiniHost.c b/MiniHost/MiniHost.c index 6dedb76..6a973fc 100644 --- a/MiniHost/MiniHost.c +++ b/MiniHost/MiniHost.c @@ -2,7 +2,7 @@ //! \file MiniHost.c //! Implementation of a minimal ARA host example. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -282,10 +282,14 @@ static void ARA_CALL ARANotifyDocumentDataChanged(ARAModelUpdateControllerHostRe { ARA_LOG("document data was updated"); } -static const ARAModelUpdateControllerInterface hostModelUpdateControllerInterface = { ARA_IMPLEMENTED_STRUCT_SIZE(ARAModelUpdateControllerInterface, notifyDocumentDataChanged), +static void ARA_CALL ARANotifyRegionSequenceChanged(ARAModelUpdateControllerHostRef controllerHostRef, ARARegionSequenceHostRef regionSequenceHostRef) +{ + ARA_LOG("region sequence data was updated"); +} +static const ARAModelUpdateControllerInterface hostModelUpdateControllerInterface = { ARA_IMPLEMENTED_STRUCT_SIZE(ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged), &ARANotifyAudioSourceAnalysisProgress, &ARANotifyAudioSourceContentChanged, &ARANotifyAudioModificationContentChanged, &ARANotifyPlaybackRegionContentChanged, - &ARANotifyDocumentDataChanged }; + &ARANotifyDocumentDataChanged, &ARANotifyRegionSequenceChanged }; // asserts #if ARA_VALIDATE_API_CALLS @@ -300,7 +304,7 @@ static ARAAssertFunction * assertFunctionReference = &assertFunction; int main(int argc, const char * argv[]) { ARAInterfaceConfiguration interfaceConfig = { ARA_IMPLEMENTED_STRUCT_SIZE(ARAInterfaceConfiguration, assertFunctionAddress), - kARAAPIGeneration_2_0_Final, NULL /* asserts will be configured later if needed */ }; + kARAAPIGeneration_2_3_Final, NULL /* asserts will be configured later if needed */ }; const ARAPlugInExtensionInstance * plugInInstance = NULL; @@ -324,7 +328,8 @@ int main(int argc, const char * argv[]) ARAMusicalContextRef musicalContextRef; ARARegionSequenceProperties regionSequenceProperties = { ARA_IMPLEMENTED_STRUCT_SIZE(ARARegionSequenceProperties, color), "Track 1", 0, - NULL /* this ref for context must be set properly before using the struct! */, NULL /* no color available */ }; + NULL /* this ref for context must be set properly before using the struct! */, + NULL /* no color available */, "regionSequenceTestPersistentID" }; ARARegionSequenceRef regionSequenceRef; ARAAudioSourceProperties audioSourceProperties = { ARA_IMPLEMENTED_STRUCT_SIZE(ARAAudioSourceProperties, merits64BitSamples), @@ -387,7 +392,7 @@ int main(int argc, const char * argv[]) ARA_WARN("this plug-in doesn't support ARA."); return -1; } - if (factory->lowestSupportedApiGeneration > kARAAPIGeneration_2_0_Final) + if (factory->lowestSupportedApiGeneration > kARAAPIGeneration_2_3_Final) { ARA_WARN("this plug-in only supports newer generations of ARA."); return -1; diff --git a/NOTICE.txt b/NOTICE.txt index 6bbd472..966f8be 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ ARA Audio Random Access Examples -Copyright 2012-2025, Celemony Software GmbH, All Rights Reserved. +Copyright 2012-2026, Celemony Software GmbH, All Rights Reserved. This product is published under the Apache 2.0 license. diff --git a/README.md b/README.md index 97ad316..2f18566 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ARA Audio Random Access: Examples -Copyright (c) 2012-2025, [Celemony Software GmbH](https://www.celemony.com), All Rights Reserved. +Copyright (c) 2012-2026, [Celemony Software GmbH](https://www.celemony.com), All Rights Reserved. Published under the Apache 2.0 license. The ARA_Examples Git repository contains various sample code demonstrating how to both implement diff --git a/TestHost/ARADocumentController.cpp b/TestHost/ARADocumentController.cpp index ca156f5..1b001fb 100644 --- a/TestHost/ARADocumentController.cpp +++ b/TestHost/ARADocumentController.cpp @@ -2,7 +2,7 @@ //! \file ARADocumentController.cpp //! provides access the plug-in document controller //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -154,7 +154,8 @@ const RegionSequenceProperties ARADocumentController::getRegionSequencePropertie regionSequence->getName ().c_str (), regionSequence->getOrderIndex (), getRef (regionSequence->getMusicalContext ()), - ®ionSequence->getColor () + ®ionSequence->getColor (), + regionSequence->getPersistentID ().c_str () }; } @@ -299,11 +300,6 @@ void ARADocumentController::updatePlaybackRegionProperties (PlaybackRegion* play /*******************************************************************************/ -bool ARADocumentController::supportsPartialPersistency () -{ - return _documentController->supportsPartialPersistency (); -} - bool ARADocumentController::storeObjectsToArchive (ArchiveBase* archive, const ARA::ARAStoreObjectsFilter* filter) { ARA_INTERNAL_ASSERT (_currentArchive == nullptr); @@ -322,32 +318,6 @@ bool ARADocumentController::restoreObjectsFromArchive (const ArchiveBase* archiv return result; } -bool ARADocumentController::storeDocumentToArchive (ArchiveBase* archive) -{ - ARA_INTERNAL_ASSERT (_currentArchive == nullptr); - _currentArchive = archive; - const auto result { _documentController->storeDocumentToArchive (toHostRef (archive)) }; - _currentArchive = nullptr; - return result; -} - -bool ARADocumentController::beginRestoringDocumentFromArchive (const ArchiveBase* archive) -{ - ARA_INTERNAL_ASSERT (_currentArchive == nullptr); - _currentArchive = archive; - _isEditingDocument = true; - return _documentController->beginRestoringDocumentFromArchive (toHostRef (archive)); -} - -bool ARADocumentController::endRestoringDocumentFromArchive (const ArchiveBase* archive) -{ - ARA_INTERNAL_ASSERT (_currentArchive == archive); - const auto result { _documentController->endRestoringDocumentFromArchive (toHostRef (archive)) }; - _isEditingDocument = false; - _currentArchive = nullptr; - return result; -} - bool ARADocumentController::supportsStoringAudioFileChunks () { return _documentController->supportsStoringAudioFileChunks (); @@ -477,15 +447,6 @@ void ARADocumentController::setMinimalContentUpdateLogging (bool flag) getModelUpdateController ()->setMinimalContentUpdateLogging (flag); } -void ARADocumentController::logAudioModificationPreservesAudioSourceSignalIfSupported (AudioModification* audioModification) -{ - if (!_documentController->supportsIsAudioModificationPreservingAudioSourceSignal ()) - return; - - ARA_LOG ("ARAAudioModificationRef %p %s audio source signal.", getRef (audioModification), - _documentController->isAudioModificationPreservingAudioSourceSignal ( getRef (audioModification)) ? "preserves" : "modifies"); -} - /*******************************************************************************/ ARAAudioAccessController* ARADocumentController::getAudioAccessController () const noexcept diff --git a/TestHost/ARADocumentController.h b/TestHost/ARADocumentController.h index 6a35a27..a04c5fb 100644 --- a/TestHost/ARADocumentController.h +++ b/TestHost/ARADocumentController.h @@ -2,7 +2,7 @@ //! \file ARADocumentController.h //! provides access the plug-in document controller //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -47,12 +47,12 @@ ARA_MAP_HOST_REF (PlaybackRegion, ARA::ARAPlaybackRegionHostRef) ARA_MAP_HOST_REF (ArchiveBase, ARA::ARAArchiveReaderHostRef, ARA::ARAArchiveWriterHostRef) // These property typedefs implicitly version our properties structs according to the last member -using DocumentProperties = ARA::SizedStruct; -using MusicalContextProperties = ARA::SizedStruct; -using RegionSequenceProperties = ARA::SizedStruct; -using AudioSourceProperties = ARA::SizedStruct; -using AudioModificationProperties = ARA::SizedStruct; -using PlaybackRegionProperties = ARA::SizedStruct; +using DocumentProperties = ARA::SizedStruct<&ARA::ARADocumentProperties::name>; +using MusicalContextProperties = ARA::SizedStruct<&ARA::ARAMusicalContextProperties::color>; +using RegionSequenceProperties = ARA::SizedStruct<&ARA::ARARegionSequenceProperties::persistentID>; +using AudioSourceProperties = ARA::SizedStruct<&ARA::ARAAudioSourceProperties::channelArrangement>; +using AudioModificationProperties = ARA::SizedStruct<&ARA::ARAAudioModificationProperties::persistentID>; +using PlaybackRegionProperties = ARA::SizedStruct<&ARA::ARAPlaybackRegionProperties::color>; // Forward declarations of our controller implementations class ARAAudioAccessController; @@ -110,17 +110,9 @@ class ARADocumentController /*******************************************************************************/ // Archiving functions - // ARA2 style archiving (aka "partial persistency") - bool supportsPartialPersistency (); bool storeObjectsToArchive (ArchiveBase* archive, const ARA::ARAStoreObjectsFilter* filter = nullptr); bool restoreObjectsFromArchive (const ArchiveBase* archive, const ARA::ARARestoreObjectsFilter* filter = nullptr); - // ARA1 style monolithic document archiving functions - bool storeDocumentToArchive (ArchiveBase* archive); - bool beginRestoringDocumentFromArchive (const ArchiveBase* archive); - bool endRestoringDocumentFromArchive (const ArchiveBase* archive); - - // audio file chunk authoring bool supportsStoringAudioFileChunks (); bool storeAudioSourceToAudioFileChunk (ArchiveBase* archive, AudioSource* audioSource, ARA::ARAPersistentID* documentArchiveID, bool* openAutomatically); @@ -157,8 +149,6 @@ class ARADocumentController void setMinimalContentUpdateLogging (bool flag); - void logAudioModificationPreservesAudioSourceSignalIfSupported (AudioModification* audioModification); - /*******************************************************************************/ // Public accessors Document* getDocument () const noexcept { return _document; } diff --git a/TestHost/ARAHostInterfaces/ARAArchivingController.cpp b/TestHost/ARAHostInterfaces/ARAArchivingController.cpp index e046d3b..e63a9b0 100644 --- a/TestHost/ARAHostInterfaces/ARAArchivingController.cpp +++ b/TestHost/ARAHostInterfaces/ARAArchivingController.cpp @@ -2,7 +2,7 @@ //! \file ARAArchivingController.cpp //! implementation of the host ARAArchivingControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAArchivingController.h b/TestHost/ARAHostInterfaces/ARAArchivingController.h index 7f80274..0b38226 100644 --- a/TestHost/ARAHostInterfaces/ARAArchivingController.h +++ b/TestHost/ARAHostInterfaces/ARAArchivingController.h @@ -2,7 +2,7 @@ //! \file ARAArchivingController.h //! implementation of the host ARAArchivingControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAAudioAccessController.cpp b/TestHost/ARAHostInterfaces/ARAAudioAccessController.cpp index 5f70103..88eb56c 100644 --- a/TestHost/ARAHostInterfaces/ARAAudioAccessController.cpp +++ b/TestHost/ARAHostInterfaces/ARAAudioAccessController.cpp @@ -2,7 +2,7 @@ //! \file ARAAudioAccessController.cpp //! implementation of the host ARAAudioAccessControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAAudioAccessController.h b/TestHost/ARAHostInterfaces/ARAAudioAccessController.h index 05b8ba5..d6a09fb 100644 --- a/TestHost/ARAHostInterfaces/ARAAudioAccessController.h +++ b/TestHost/ARAHostInterfaces/ARAAudioAccessController.h @@ -2,7 +2,7 @@ //! \file ARAAudioAccessController.h //! implementation of the host ARAAudioAccessControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAContentAccessController.cpp b/TestHost/ARAHostInterfaces/ARAContentAccessController.cpp index 1b8c61e..462c0d0 100644 --- a/TestHost/ARAHostInterfaces/ARAContentAccessController.cpp +++ b/TestHost/ARAHostInterfaces/ARAContentAccessController.cpp @@ -2,7 +2,7 @@ //! \file ARAContentAccessController.cpp //! implementation of the host ARAContentAccessControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -55,6 +55,7 @@ std::unique_ptr ARAContentAccessController::createContent case ARA::kARAContentTypeStaticTuning: return std::make_unique> (contentContainer->getTuning ()); case ARA::kARAContentTypeKeySignatures: return std::make_unique> (contentContainer->getKeySignatures ()); case ARA::kARAContentTypeSheetChords: return std::make_unique> (contentContainer->getChords ()); + case ARA::kARAContentTypeLyricEntries: return std::make_unique> (contentContainer->getLyrics ()); default: return nullptr; } } diff --git a/TestHost/ARAHostInterfaces/ARAContentAccessController.h b/TestHost/ARAHostInterfaces/ARAContentAccessController.h index dfe05d3..6dfa776 100644 --- a/TestHost/ARAHostInterfaces/ARAContentAccessController.h +++ b/TestHost/ARAHostInterfaces/ARAContentAccessController.h @@ -2,7 +2,7 @@ //! \file ARAContentAccessController.h //! implementation of the host ARAContentAccessControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAModelUpdateController.cpp b/TestHost/ARAHostInterfaces/ARAModelUpdateController.cpp index 1d3cd7c..7d3efc7 100644 --- a/TestHost/ARAHostInterfaces/ARAModelUpdateController.cpp +++ b/TestHost/ARAHostInterfaces/ARAModelUpdateController.cpp @@ -2,7 +2,7 @@ //! \file ARAModelUpdateController.cpp //! implementation of the host ARAModelUpdateControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -129,3 +129,13 @@ void ARAModelUpdateController::notifyDocumentDataChanged () noexcept ARA_LOG ("document data was updated"); } + +void ARAModelUpdateController::notifyRegionSequenceDataChanged (ARA::ARARegionSequenceHostRef regionSequenceHostRef) noexcept +{ + const auto regionSequence = fromHostRef (regionSequenceHostRef); + ARA_VALIDATE_API_ARGUMENT (regionSequence, ARA::contains (getDocument ()->getRegionSequences (), regionSequence)); + ARA_VALIDATE_API_STATE (_araDocumentController->isPollingModelUpdates ()); + ARA_VALIDATE_API_THREAD (_araDocumentController->wasCreatedOnCurrentThread ()); + + ARA_LOG ("data of region sequence %p (ARARegionSequenceRef ref %p) was updated", regionSequence, _araDocumentController->getRef (regionSequence)); +} diff --git a/TestHost/ARAHostInterfaces/ARAModelUpdateController.h b/TestHost/ARAHostInterfaces/ARAModelUpdateController.h index dae6b7c..78da602 100644 --- a/TestHost/ARAHostInterfaces/ARAModelUpdateController.h +++ b/TestHost/ARAHostInterfaces/ARAModelUpdateController.h @@ -2,7 +2,7 @@ //! \file ARAModelUpdateController.h //! implementation of the host ARAModelUpdateControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -56,6 +56,8 @@ class ARAModelUpdateController : public ARA::Host::ModelUpdateControllerInterfac void notifyDocumentDataChanged () noexcept override; + void notifyRegionSequenceDataChanged (ARA::ARARegionSequenceHostRef regionSequenceHostRef) noexcept override; + void setMinimalContentUpdateLogging (bool flag) { _minimalContentUpdateLogging = flag; } private: diff --git a/TestHost/ARAHostInterfaces/ARAPlaybackController.cpp b/TestHost/ARAHostInterfaces/ARAPlaybackController.cpp index 573d3d4..7c92bcc 100644 --- a/TestHost/ARAHostInterfaces/ARAPlaybackController.cpp +++ b/TestHost/ARAHostInterfaces/ARAPlaybackController.cpp @@ -2,7 +2,7 @@ //! \file ARAPlaybackController.cpp //! implementation of the host ARAPlaybackControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/ARAHostInterfaces/ARAPlaybackController.h b/TestHost/ARAHostInterfaces/ARAPlaybackController.h index 2805101..b9366ba 100644 --- a/TestHost/ARAHostInterfaces/ARAPlaybackController.h +++ b/TestHost/ARAHostInterfaces/ARAPlaybackController.h @@ -2,7 +2,7 @@ //! \file ARAPlaybackController.h //! implementation of the host ARAPlaybackControllerInterface //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestHost/CompanionAPIs.cpp b/TestHost/CompanionAPIs.cpp index bef8658..a69556f 100644 --- a/TestHost/CompanionAPIs.cpp +++ b/TestHost/CompanionAPIs.cpp @@ -3,7 +3,7 @@ //! used by the test host to load a companion API plug-in binary //! and create / destroy plug-in instances with ARA2 roles //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -29,7 +29,6 @@ #if defined (__APPLE__) #include "ExamplesCommon/PlugInHosting/AudioUnitLoader.h" - #include #endif #ifndef ARA_ENABLE_CLAP @@ -94,7 +93,7 @@ constexpr auto otherChannelIDSuffix { ".other" }; _Pragma ("GCC diagnostic ignored \"-Wunused-template\"") #endif -ARA_MAP_IPC_REF (ARA::IPC::Connection, ARA::IPC::ARAIPCConnectionRef) +ARA_MAP_IPC_REF (ARA::IPC::ProxyPlugIn, ARA::IPC::ARAIPCProxyPlugInRef) #if defined (__GNUC__) _Pragma ("GCC diagnostic pop") @@ -150,11 +149,6 @@ void PlugInInstance::validateAndSetPlugInExtensionInstance (const ARA::ARAPlugIn { ARA_VALIDATE_API_STATE (plugInExtensionInstance != nullptr); -#if ARA_SUPPORT_VERSION_1 - if (_factory->highestSupportedApiGeneration < kARAAPIGeneration_2_0_Draft) - return; -#endif - if ((assignedRoles & ARA::kARAPlaybackRendererRole) != 0) ARA_VALIDATE_API_INTERFACE (plugInExtensionInstance->playbackRendererInterface, ARAPlaybackRendererInterface); else @@ -309,14 +303,14 @@ class CLAPPlugInInstance : public PlugInInstance #if defined (__APPLE__) // very crude conversion from string to OSType -OSType parseOSType (const std::string& idString) +static OSType parseOSType (const std::string& idString) { ARA_INTERNAL_ASSERT (idString.size () == sizeof (OSType)); return static_cast (idString[3]) | (static_cast (idString[2]) << 8) | (static_cast (idString[1]) << 16) | (static_cast (idString[0]) << 24); } -std::string createAUEntryDescription (const std::string& type, const std::string& subType, const std::string& manufacturer) +static std::string createAUEntryDescription (const std::string& type, const std::string& subType, const std::string& manufacturer) { return std::string { "Audio Unit (" } + type + " - " + subType + " - " + manufacturer + ")"; } @@ -329,7 +323,7 @@ class AUPlugInEntry : public PlugInEntry _audioUnitComponent { AudioUnitPrepareComponentWithIDs (parseOSType (type), parseOSType (subType), parseOSType (manufacturer)) } { AudioUnitInstance audioUnitInstance = AudioUnitOpenInstance (_audioUnitComponent, useIPCIfPossible); - validateAndSetFactory (AudioUnitGetARAFactory (audioUnitInstance/*, &_connectionRef*/)); + validateAndSetFactory (AudioUnitGetARAFactory (audioUnitInstance, &_proxyPlugInRef)); AudioUnitCloseInstance (audioUnitInstance); } @@ -338,41 +332,33 @@ class AUPlugInEntry : public PlugInEntry AudioUnitCleanupComponent (_audioUnitComponent); } -/* bool usesIPC () const override { - return _connectionRef != nullptr; + return _proxyPlugInRef != nullptr; } -*/ void initializeARA (ARA::ARAAssertFunction* assertFunctionAddress) override { -/* if (usesIPC ()) - ARA::IPC::ARAIPCProxyPlugInInitializeARA (_connectionRef, getARAFactory ()->factoryID, getDesiredAPIGeneration (getARAFactory ())); + ARA::IPC::ARAIPCProxyPlugInInitializeARA (_proxyPlugInRef, getARAFactory ()->factoryID, getDesiredAPIGeneration (getARAFactory ())); else -*/ PlugInEntry::initializeARA (assertFunctionAddress); } const ARA::ARADocumentControllerInstance* createDocumentControllerWithDocument (const ARA::ARADocumentControllerHostInstance* hostInstance, const ARA::ARADocumentProperties* properties) override { -/* if (usesIPC ()) - return ARA::IPC::ARAIPCProxyPlugInCreateDocumentControllerWithDocument (_connectionRef, getARAFactory ()->factoryID, hostInstance, properties); + return ARA::IPC::ARAIPCProxyPlugInCreateDocumentControllerWithDocument (_proxyPlugInRef, getARAFactory ()->factoryID, hostInstance, properties); else -*/ return PlugInEntry::createDocumentControllerWithDocument (hostInstance, properties); } void uninitializeARA () override { -/* if (usesIPC ()) - ARA::IPC::ARAIPCProxyPlugInUninitializeARA (_connectionRef, getARAFactory ()->factoryID); + ARA::IPC::ARAIPCProxyPlugInUninitializeARA (_proxyPlugInRef, getARAFactory ()->factoryID); else -*/ PlugInEntry::uninitializeARA (); } @@ -384,14 +370,14 @@ class AUPlugInEntry : public PlugInEntry private: AudioUnitComponent const _audioUnitComponent; -// ARA::IPC::ARAIPCConnectionRef _connectionRef {}; + ARA::IPC::ARAIPCProxyPlugInRef _proxyPlugInRef {}; }; #endif // defined (__APPLE__) /*******************************************************************************/ -std::string createEntryDescription (const std::string& apiName, const std::string& binaryName, const std::string& optionalPlugInName) +static std::string createEntryDescription (const std::string& apiName, const std::string& binaryName, const std::string& optionalPlugInName) { return apiName + " " + ((optionalPlugInName.empty ()) ? "" : optionalPlugInName + " ") + "@ " + binaryName; } @@ -464,47 +450,23 @@ class CLAPPlugInEntry : public PlugInEntry #if ARA_ENABLE_IPC -class Connection : public ARA::IPC::Connection +static std::unique_ptr createConnection (ARA::IPC::MessageHandler&& messageHandler, + std::unique_ptr && mainThreadChannel, std::unique_ptr && otherThreadsChannel) { -public: - Connection (IPCMessageChannel* mainThreadChannel, IPCMessageChannel* otherThreadsChannel) - : ARA::IPC::Connection { &_runReceiveLoop, this }, - _mainThreadChannel { mainThreadChannel } - { - setMainThreadChannel (mainThreadChannel); - setOtherThreadsChannel (otherThreadsChannel); - } - - ARA::IPC::MessageEncoder* createEncoder () override - { + auto result { std::make_unique ( #if USE_ARA_CF_ENCODING - return new ARA::IPC::CFMessageEncoder {}; + &ARA::IPC::CFMessageEncoder::create, #else - return new IPCXMLMessageEncoder {}; + &IPCXMLMessageEncoder::create, #endif - } - - // \todo currently not implemented, we rely on running on the same machine for now - // C++20 offers std::endian which allows for a simple implementation upon connecting... - bool receiverEndianessMatches () override - { - return true; - } - - bool runReceiveLoop (int32_t milliseconds) - { - return _mainThreadChannel->runReceiveLoop (milliseconds); - } - -private: - static void _runReceiveLoop (void* connection) - { - static_cast (connection)->runReceiveLoop (10); - } - -private: - IPCMessageChannel* const _mainThreadChannel; -}; + std::move (messageHandler), + true, // \todo set properly - we rely on running on the same machine for now + // C++20 offers std::endian which allows for a simple implementation upon connecting... + [channel = mainThreadChannel.get ()] { channel->runReceiveLoop (10); }) }; + result->setMainThreadChannel (std::move (mainThreadChannel)); + result->setOtherThreadsChannel (std::move (otherThreadsChannel)); + return result; +} class IPCPlugInInstance : public PlugInInstance, protected ARA::IPC::RemoteCaller { @@ -622,30 +584,29 @@ struct RemoteLauncher class IPCPlugInEntry : public PlugInEntry, private RemoteLauncher { private: - static const ARA::ARAFactory* defaultGetFactory (ARA::IPC::ARAIPCConnectionRef connection) + static const ARA::ARAFactory* defaultGetFactory (ARA::IPC::ARAIPCProxyPlugInRef proxyPlugInRef) { - const auto count { ARA::IPC::ARAIPCProxyPlugInGetFactoriesCount (connection) }; + const auto count { ARA::IPC::ARAIPCProxyPlugInGetFactoriesCount (proxyPlugInRef) }; ARA_INTERNAL_ASSERT (count > 0); - return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (connection, 0U); + return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (proxyPlugInRef, 0U); } IPCPlugInEntry (std::string&& description, const std::string& launchArgs, const std::string& channelID, - const std::function& getFactoryFunction) + const std::function& getFactoryFunction) : PlugInEntry { std::move (description) }, RemoteLauncher { launchArgs, channelID }, - _connection { IPCMessageChannel::createConnectedToID (channelID + mainChannelIDSuffix), - IPCMessageChannel::createConnectedToID (channelID + otherChannelIDSuffix) }, - _proxyPlugIn { &_connection } + _proxyPlugIn { createConnection (ARA::IPC::ProxyPlugIn::handleReceivedMessage, + IPCMessageChannel::createConnectedToID (channelID + mainChannelIDSuffix), + IPCMessageChannel::createConnectedToID (channelID + otherChannelIDSuffix)) } { - _connection.setMessageHandler (&_proxyPlugIn); - validateAndSetFactory (getFactoryFunction (toIPCRef (&_connection))); + validateAndSetFactory (getFactoryFunction (toIPCRef (&_proxyPlugIn))); } public: // \todo the current ARA IPC implementation does not support sending ARA asserts to the host... IPCPlugInEntry (std::string&& description, const std::string& launchArgs, - const std::function& getFactoryFunction = defaultGetFactory) + const std::function& getFactoryFunction = defaultGetFactory) : IPCPlugInEntry { std::move (description), launchArgs, _createChannelID (), getFactoryFunction } {} @@ -659,38 +620,30 @@ class IPCPlugInEntry : public PlugInEntry, private RemoteLauncher return true; } -#if !USE_ARA_BACKGROUND_IPC - void idleThreadForDuration (int32_t milliseconds) override - { - _connection.runReceiveLoop (milliseconds); - } -#endif - void initializeARA (ARA::ARAAssertFunction* /*assertFunctionAddress*/) override { - ARA::IPC::ARAIPCProxyPlugInInitializeARA (toIPCRef (&_connection), getARAFactory ()->factoryID, getDesiredAPIGeneration (getARAFactory ())); + ARA::IPC::ARAIPCProxyPlugInInitializeARA (toIPCRef (&_proxyPlugIn), getARAFactory ()->factoryID, getDesiredAPIGeneration (getARAFactory ())); } const ARA::ARADocumentControllerInstance* createDocumentControllerWithDocument (const ARA::ARADocumentControllerHostInstance* hostInstance, const ARA::ARADocumentProperties* properties) override { - return ARA::IPC::ARAIPCProxyPlugInCreateDocumentControllerWithDocument (toIPCRef (&_connection), getARAFactory ()->factoryID, hostInstance, properties); + return ARA::IPC::ARAIPCProxyPlugInCreateDocumentControllerWithDocument (toIPCRef (&_proxyPlugIn), getARAFactory ()->factoryID, hostInstance, properties); } void uninitializeARA () override { - ARA::IPC::ARAIPCProxyPlugInUninitializeARA (toIPCRef (&_connection), getARAFactory ()->factoryID); + ARA::IPC::ARAIPCProxyPlugInUninitializeARA (toIPCRef (&_proxyPlugIn), getARAFactory ()->factoryID); } std::unique_ptr createPlugInInstance () override { ARA::IPC::ARAIPCPlugInInstanceRef remoteInstanceRef {}; _proxyPlugIn.remoteCall (remoteInstanceRef, kIPCCreateEffectMethodID); - return std::make_unique (remoteInstanceRef, &_connection); + return std::make_unique (remoteInstanceRef, _proxyPlugIn.getConnection ()); } private: - Connection _connection; ARA::IPC::ProxyPlugIn _proxyPlugIn; }; @@ -702,22 +655,22 @@ class IPCGenericPlugInEntry : public IPCPlugInEntry IPCGenericPlugInEntry (const std::string& commandLineArg, const std::string& apiName, const std::string& binaryName, const std::string& optionalPlugInName) : IPCPlugInEntry { createEntryDescription (apiName, binaryName, optionalPlugInName), commandLineArg + " " + binaryName + " " + optionalPlugInName, - [&optionalPlugInName] (ARA::IPC::ARAIPCConnectionRef connection) -> const ARA::ARAFactory* + [&optionalPlugInName] (ARA::IPC::ARAIPCProxyPlugInRef proxyPlugInRef) -> const ARA::ARAFactory* { - const auto count { ARA::IPC::ARAIPCProxyPlugInGetFactoriesCount (connection) }; + const auto count { ARA::IPC::ARAIPCProxyPlugInGetFactoriesCount (proxyPlugInRef) }; ARA_INTERNAL_ASSERT (count > 0); if (optionalPlugInName.empty ()) - return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (connection, 0U); + return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (proxyPlugInRef, 0U); for (auto i { 0U }; i < count; ++i) { - auto factory { ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (connection, i) }; + auto factory { ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (proxyPlugInRef, i) }; if (0 == std::strcmp (factory->plugInName, optionalPlugInName.c_str ())) return factory; } ARA_INTERNAL_ASSERT (false); - return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (connection, 0U); + return ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (proxyPlugInRef, 0U); } } {} }; @@ -765,14 +718,13 @@ bool _shutDown { false }; class ProxyHost : public ARA::IPC::ProxyHost { public: - ProxyHost (Connection* connection) - : ARA::IPC::ProxyHost { connection } - { - connection->setMessageHandler (this); - } + ProxyHost (std::unique_ptr && mainThreadChannel, std::unique_ptr && otherThreadsChannel) + : ARA::IPC::ProxyHost { createConnection ([this] (auto&& ...args) { handleReceivedMessage (args...); }, + std::move (mainThreadChannel), std::move (otherThreadsChannel)) } + {} void handleReceivedMessage (const ARA::IPC::MessageID messageID, const ARA::IPC::MessageDecoder* const decoder, - ARA::IPC::MessageEncoder* const replyEncoder) override + ARA::IPC::MessageEncoder* const replyEncoder) { if (!ARA::IPC::MethodID::isCustomMessageID (messageID)) { @@ -866,9 +818,12 @@ int main (std::unique_ptr plugInEntry, const std::string& channelID { _plugInEntry = std::move (plugInEntry); - Connection connection { IPCMessageChannel::createPublishingID (channelID + mainChannelIDSuffix), - IPCMessageChannel::createPublishingID (channelID + otherChannelIDSuffix) }; - ProxyHost proxy { &connection }; + auto mainThreadMessageChannel { IPCMessageChannel::createPublishingID (channelID + mainChannelIDSuffix) }; + auto otherThreadsMessageChannel { IPCMessageChannel::createPublishingID (channelID + otherChannelIDSuffix) }; + + auto waitLoop { [messageChannel = mainThreadMessageChannel.get ()] { messageChannel->runReceiveLoop (100 /*ms*/); } }; + + ProxyHost proxy { std::move (mainThreadMessageChannel), std::move (otherThreadsMessageChannel) }; ARA::IPC::ARAIPCProxyHostAddFactory (_plugInEntry->getARAFactory ()); ARA::IPC::ARAIPCProxyHostSetBindingHandler ([] (ARA::IPC::ARAIPCPlugInInstanceRef plugInInstanceRef, @@ -883,7 +838,7 @@ int main (std::unique_ptr plugInEntry, const std::string& channelID }); while (!_shutDown) - connection.runReceiveLoop (100 /*ms*/); + waitLoop (); _plugInEntry.reset (); @@ -950,21 +905,15 @@ void PlugInEntry::validateAndSetFactory (const ARA::ARAFactory* factory) ARA_INTERNAL_ASSERT ((factory->supportedPlaybackTransformationFlags & ARA::kARAPlaybackTransformationContentBasedFades) == ARA::kARAPlaybackTransformationContentBasedFades); // ensure that this plug-in is supported by our test host - ARA_INTERNAL_ASSERT (factory->lowestSupportedApiGeneration <= ARA::kARAAPIGeneration_2_0_Final); -#if ARA_SUPPORT_VERSION_1 - ARA_INTERNAL_ASSERT (factory->highestSupportedApiGeneration >= ARA::kARAAPIGeneration_1_0_Final); -#elif ARA_CPU_ARM + ARA_INTERNAL_ASSERT (factory->lowestSupportedApiGeneration <= ARA::kARAAPIGeneration_3_0_Draft); ARA_INTERNAL_ASSERT (factory->highestSupportedApiGeneration >= ARA::kARAAPIGeneration_2_0_Final); -#else - ARA_INTERNAL_ASSERT (factory->highestSupportedApiGeneration >= ARA::kARAAPIGeneration_2_0_Draft); -#endif _factory = factory; } ARA::ARAAPIGeneration PlugInEntry::getDesiredAPIGeneration (const ARA::ARAFactory* const factory) { - ARA::ARAAPIGeneration desiredApiGeneration { ARA::kARAAPIGeneration_2_0_Final }; + ARA::ARAAPIGeneration desiredApiGeneration { ARA::kARAAPIGeneration_3_0_Draft }; if (desiredApiGeneration > factory->highestSupportedApiGeneration) desiredApiGeneration = factory->highestSupportedApiGeneration; return desiredApiGeneration; @@ -975,7 +924,7 @@ void PlugInEntry::initializeARA (ARA::ARAAssertFunction* assertFunctionAddress) ARA_INTERNAL_ASSERT (_factory); // initialize ARA factory with interface configuration - const ARA::SizedStruct interfaceConfig = { getDesiredAPIGeneration (_factory), assertFunctionAddress }; + const ARA::SizedStruct<&ARA::ARAInterfaceConfiguration::assertFunctionAddress> interfaceConfig = { getDesiredAPIGeneration (_factory), assertFunctionAddress }; _factory->initializeARAWithConfiguration (&interfaceConfig); } diff --git a/TestHost/CompanionAPIs.h b/TestHost/CompanionAPIs.h index e6195f1..578410b 100644 --- a/TestHost/CompanionAPIs.h +++ b/TestHost/CompanionAPIs.h @@ -3,7 +3,7 @@ //! used by the test host to load a companion API plug-in binary //! and create / destroy plug-in instances with ARA2 roles //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -29,8 +29,7 @@ #include "ARA_Library/Debug/ARADebug.h" #include "ARA_Library/Dispatch/ARAHostDispatch.h" -//#include "ARA_Library/IPC/ARAIPC.h" -#define ARA_ENABLE_IPC 0 +#include "ARA_Library/IPC/ARAIPC.h" #include #include @@ -98,7 +97,7 @@ class PlugInEntry // If IPC is used, and the main thread is spinning in some loop for a prolonged time, // this call may be necessary to allow handling IPC in time. - virtual void idleThreadForDuration (int32_t milliseconds); + void idleThreadForDuration (int32_t milliseconds); // Initialize ARA before creating any document controllers virtual void initializeARA (ARA::ARAAssertFunction* assertFunctionAddress); diff --git a/TestHost/IPC/IPCMessageChannel.cpp b/TestHost/IPC/IPCMessageChannel.cpp index d33c5c2..16e1c06 100644 --- a/TestHost/IPC/IPCMessageChannel.cpp +++ b/TestHost/IPC/IPCMessageChannel.cpp @@ -3,7 +3,7 @@ //! Proof-of-concept implementation of MessageChannel //! for the ARA SDK TestHost (error handling is limited to assertions). //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -46,7 +46,6 @@ #if defined (_WIN32) //------------------------------------------------------------------------------ - class IPCMessagePort { protected: @@ -136,14 +135,21 @@ class IPCReceivePort : public IPCMessagePort #endif const auto messageID { _sharedMemory->messageID }; - const auto decoder { IPCXMLMessageDecoder::createWithMessageData (_sharedMemory->messageData, _sharedMemory->messageSize) }; + auto decoder { IPCXMLMessageDecoder::createWithMessageData (_sharedMemory->messageData, _sharedMemory->messageSize) }; ::SetEvent (_dataReceived); - _channel->getMessageDispatcher ()->routeReceivedMessage (messageID, decoder); + _channel->routeReceivedMessage (messageID, std::move (decoder)); return true; } +#if USE_ARA_BACKGROUND_IPC + std::thread::id getReceiveThreadID () + { + return _receiveThread->get_id (); + } +#endif + private: IPCMessageChannel* const _channel; #if USE_ARA_BACKGROUND_IPC @@ -186,7 +192,6 @@ class IPCSendPort : public IPCMessagePort #elif defined (__APPLE__) //------------------------------------------------------------------------------ - class IPCReceivePort { public: @@ -241,16 +246,23 @@ class IPCReceivePort return (CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001 * milliseconds, true) != kCFRunLoopRunTimedOut); } +#if USE_ARA_BACKGROUND_IPC + std::thread::id getReceiveThreadID () + { + return _receiveThread->get_id (); + } +#endif + private: static CFDataRef _portCallback (CFMessagePortRef /*port*/, SInt32 messageID, CFDataRef messageData, void* info) { auto channel { static_cast (info) }; #if USE_ARA_CF_ENCODING - const auto decoder { ARA::IPC::CFMessageDecoder::createWithMessageData (messageData) }; + auto decoder { ARA::IPC::CFMessageDecoder::createWithMessageData (messageData) }; #else - const auto decoder { IPCXMLMessageDecoder::createWithMessageData (messageData) }; + auto decoder { IPCXMLMessageDecoder::createWithMessageData (messageData) }; #endif - channel->getMessageDispatcher ()->routeReceivedMessage (messageID, decoder); + channel->routeReceivedMessage (messageID, std::move (decoder)); return nullptr; } @@ -292,7 +304,7 @@ class IPCSendPort void sendMessage (ARA::IPC::MessageID messageID, CFDataRef messageData) { - const auto ARA_MAYBE_UNUSED_VAR (result) { CFMessagePortSendRequest (_port, messageID, messageData, 0.001 * messageTimeout, 0.0, nullptr, nullptr) }; + [[maybe_unused]] const auto result { CFMessagePortSendRequest (_port, messageID, messageData, 0.001 * messageTimeout, 0.0, nullptr, nullptr) }; ARA_INTERNAL_ASSERT (result == kCFMessagePortSuccess); } @@ -306,34 +318,30 @@ class IPCSendPort //------------------------------------------------------------------------------ -IPCMessageChannel* IPCMessageChannel::createPublishingID (const std::string& channelID) +std::unique_ptr IPCMessageChannel::createPublishingID (const std::string& channelID) { - auto channel { new IPCMessageChannel {} }; - channel->_sendPort = new IPCSendPort { channelID + ".from_server" }; - channel->_receivePort = new IPCReceivePort { channelID + ".to_server", channel }; + auto channel { std::make_unique ()}; + channel->_sendPort = std::make_unique (channelID + ".from_server"); + channel->_receivePort = std::make_unique (channelID + ".to_server", channel.get ()); return channel; } -IPCMessageChannel* IPCMessageChannel::createConnectedToID (const std::string& channelID) +std::unique_ptr IPCMessageChannel::createConnectedToID (const std::string& channelID) { - auto channel { new IPCMessageChannel {} }; - channel->_receivePort = new IPCReceivePort { channelID + ".from_server", channel }; - channel->_sendPort = new IPCSendPort { channelID + ".to_server" }; + auto channel { std::make_unique ()}; + channel->_receivePort = std::make_unique ( channelID + ".from_server", channel.get ()); + channel->_sendPort = std::make_unique (channelID + ".to_server"); return channel; } -IPCMessageChannel::~IPCMessageChannel () -{ - delete _sendPort; - delete _receivePort; -} +IPCMessageChannel::~IPCMessageChannel () = default; -void IPCMessageChannel::sendMessage (ARA::IPC::MessageID messageID, ARA::IPC::MessageEncoder* encoder) +void IPCMessageChannel::sendMessage (ARA::IPC::MessageID messageID, std::unique_ptr && encoder) { #if USE_ARA_CF_ENCODING - const auto messageData { static_cast (encoder)->createMessageEncoderData () }; + const auto messageData { static_cast (encoder.get ())->createMessageEncoderData () }; #else - const auto messageData { static_cast (encoder)->createEncodedMessage () }; + const auto messageData { static_cast (encoder.get ())->createEncodedMessage () }; #endif _sendPort->sendMessage (messageID, messageData); @@ -344,10 +352,27 @@ void IPCMessageChannel::sendMessage (ARA::IPC::MessageID messageID, ARA::IPC::Me #endif } +bool IPCMessageChannel::receivesMessagesOnCurrentThread () +{ +#if USE_ARA_BACKGROUND_IPC + return _receivePort->getReceiveThreadID () == std::this_thread::get_id (); +#else + return _receiveThreadID == std::this_thread::get_id (); +#endif +} + +bool IPCMessageChannel::waitForMessageOnCurrentThread () +{ +#if !USE_ARA_BACKGROUND_IPC + ARA_INTERNAL_ASSERT (std::this_thread::get_id () == _receiveThreadID); +#endif + return _receivePort->runReceiveLoop (10); +} + bool IPCMessageChannel::runReceiveLoop (int32_t milliseconds) { #if !USE_ARA_BACKGROUND_IPC - ARA_INTERNAL_ASSERT (std::this_thread::get_id () == _receiveThread); + ARA_INTERNAL_ASSERT (std::this_thread::get_id () == _receiveThreadID); #endif return _receivePort->runReceiveLoop (milliseconds); } diff --git a/TestHost/IPC/IPCMessageChannel.h b/TestHost/IPC/IPCMessageChannel.h index 9a4309b..b573833 100644 --- a/TestHost/IPC/IPCMessageChannel.h +++ b/TestHost/IPC/IPCMessageChannel.h @@ -3,7 +3,7 @@ //! Proof-of-concept implementation of MessageChannel //! for the ARA SDK TestHost (error handling is limited to assertions). //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -67,15 +67,18 @@ class IPCMessageChannel : public ARA::IPC::MessageChannel ~IPCMessageChannel () override; // factory functions for send and receive channels - static IPCMessageChannel* createPublishingID (const std::string& channelID); - static IPCMessageChannel* createConnectedToID (const std::string& channelID); + static std::unique_ptr createPublishingID (const std::string& channelID); + static std::unique_ptr createConnectedToID (const std::string& channelID); // message receiving // waits up to the specified amount of milliseconds for an incoming event and processes it // returns true if some event was processed during that time bool runReceiveLoop (int32_t milliseconds); - void sendMessage (ARA::IPC::MessageID messageID, ARA::IPC::MessageEncoder* encoder) override; + void sendMessage (ARA::IPC::MessageID messageID, std::unique_ptr && encoder) override; + + bool receivesMessagesOnCurrentThread () override; + bool waitForMessageOnCurrentThread () override; protected: using ARA::IPC::MessageChannel::MessageChannel; @@ -84,11 +87,11 @@ class IPCMessageChannel : public ARA::IPC::MessageChannel friend class IPCReceivePort; #if !USE_ARA_BACKGROUND_IPC - std::thread::id _receiveThread { std::this_thread::get_id () }; + std::thread::id _receiveThreadID { std::this_thread::get_id () }; #endif - IPCSendPort* _sendPort {}; - IPCReceivePort* _receivePort {}; + std::unique_ptr _sendPort; + std::unique_ptr _receivePort; }; #endif // ARA_ENABLE_IPC diff --git a/TestHost/IPC/IPCSocketChannel.cpp b/TestHost/IPC/IPCSocketChannel.cpp new file mode 100644 index 0000000..18c9794 --- /dev/null +++ b/TestHost/IPC/IPCSocketChannel.cpp @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketChannel.cpp +//! Proof-of-concept Unix domain socket implementation of MessageChannel +//! for the ARA SDK TestHost on Linux (error handling is limited to assertions). +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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 "IPCSocketChannel.h" + +#if ARA_ENABLE_IPC + +#include "IPC/IPCSocketEncoding.h" +#include "ARA_Library/Debug/ARADebug.h" + +#include +#include +#include + +#include +#include + + +//------------------------------------------------------------------------------ +// Low-level frame I/O +//------------------------------------------------------------------------------ + +static bool _writeAll (int fd, const void* buf, size_t n) +{ + const auto* p = static_cast (buf); + while (n > 0) + { + ssize_t r = ::write (fd, p, n); + if (r <= 0) + return false; + p += r; + n -= static_cast (r); + } + return true; +} + +static bool _readAll (int fd, void* buf, size_t n) +{ + auto* p = static_cast (buf); + while (n > 0) + { + ssize_t r = ::read (fd, p, n); + if (r <= 0) + return false; + p += r; + n -= static_cast (r); + } + return true; +} + + +//------------------------------------------------------------------------------ +// IPCSocketChannel +//------------------------------------------------------------------------------ + +IPCSocketChannel::IPCSocketChannel (int fd) +: _fd { fd } +{ + _recvThread = std::thread { [this] { _receiveLoop (); } }; +} + +IPCSocketChannel::~IPCSocketChannel () +{ + _stop.store (true, std::memory_order_release); + ::shutdown (_fd, SHUT_RDWR); + if (_recvThread.joinable ()) + _recvThread.join (); + ::close (_fd); +} + +void IPCSocketChannel::sendMessage (ARA::IPC::MessageID messageID, + std::unique_ptr && encoder) +{ + const auto payload = IPCSocketEncodeMessage (static_cast (*encoder)); + + uint32_t hdr[2]; + hdr[0] = static_cast (messageID); + hdr[1] = static_cast (payload.size ()); + + std::lock_guard lock { _sendMutex }; + ARA_INTERNAL_ASSERT (_writeAll (_fd, hdr, 8)); + if (!payload.empty ()) + ARA_INTERNAL_ASSERT (_writeAll (_fd, payload.data (), payload.size ())); +} + +void IPCSocketChannel::_receiveLoop () +{ + while (!_stop.load (std::memory_order_acquire)) + { + uint32_t hdr[2]; + if (!_readAll (_fd, hdr, 8)) + break; + + auto messageID = static_cast (static_cast (hdr[0])); + uint32_t payloadLen = hdr[1]; + + std::unique_ptr decoder; + if (payloadLen > 0) + { + std::vector payload (payloadLen); + if (!_readAll (_fd, payload.data (), payloadLen)) + break; + decoder = IPCSocketMessageDecoder::createWithMessageData (payload.data (), payload.size ()); + } + + routeReceivedMessage (messageID, std::move (decoder)); + } +} + +#endif // ARA_ENABLE_IPC diff --git a/TestHost/IPC/IPCSocketChannel.h b/TestHost/IPC/IPCSocketChannel.h new file mode 100644 index 0000000..21b263e --- /dev/null +++ b/TestHost/IPC/IPCSocketChannel.h @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketChannel.h +//! Proof-of-concept Unix domain socket implementation of MessageChannel +//! for the ARA SDK TestHost on Linux (error handling is limited to assertions). +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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. +//------------------------------------------------------------------------------ + +#pragma once + + +#include "ARA_Library/IPC/ARAIPCConnection.h" + +#if ARA_ENABLE_IPC + +#if !defined (__linux__) && !defined (__WINE__) + #error "IPCSocketChannel is a Linux/Wine-only implementation" +#endif + +#include +#include +#include + + +//------------------------------------------------------------------------------ +//! Unix domain socket MessageChannel. +//! +//! Each channel wraps one end of a socketpair() fd. A background receive +//! thread reads frames and calls routeReceivedMessage(), so: +//! receivesMessagesOnCurrentThread() -> false +//! waitForMessageOnCurrentThread() -> false (never called) +//! +//! Wire frame: +//! [int32_t messageID ] 4 bytes LE +//! [uint32_t payloadLen] 4 bytes LE +//! [uint8_t payload[] ] payloadLen bytes +//! +//! The Connection's MainThreadMessageDispatcher uses the WaitableSingleMessageQueue +//! semaphore path to forward messages to the creation thread via +//! dispatchToCreationThread(). +//------------------------------------------------------------------------------ + +class IPCSocketChannel : public ARA::IPC::MessageChannel +{ +public: + //! Takes ownership of the fd. Starts the receive thread immediately. + explicit IPCSocketChannel (int fd); + ~IPCSocketChannel () override; + + void sendMessage (ARA::IPC::MessageID messageID, + std::unique_ptr && encoder) override; + + bool receivesMessagesOnCurrentThread () override { return false; } + bool waitForMessageOnCurrentThread () override { return false; } + +private: + void _receiveLoop (); + + int _fd; + std::mutex _sendMutex; + std::thread _recvThread; + std::atomic _stop { false }; +}; + +#endif // ARA_ENABLE_IPC diff --git a/TestHost/IPC/IPCSocketEncoding.cpp b/TestHost/IPC/IPCSocketEncoding.cpp new file mode 100644 index 0000000..7b6c030 --- /dev/null +++ b/TestHost/IPC/IPCSocketEncoding.cpp @@ -0,0 +1,295 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketEncoding.cpp +//! Proof-of-concept Unix socket-based implementation of ARAIPCMessageEn-/Decoder +//! for the ARA SDK TestHost on Linux (error handling is limited to assertions). +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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 "IPCSocketEncoding.h" + +#if ARA_ENABLE_IPC + +#include "ARA_Library/Debug/ARADebug.h" + +#include +#include + + +//------------------------------------------------------------------------------ +// Wire serialization helpers +//------------------------------------------------------------------------------ + +namespace +{ + +void appendU8 (std::vector& buf, uint8_t v) { buf.push_back (v); } +void appendU32 (std::vector& buf, uint32_t v) +{ + buf.push_back ((v) & 0xFFu); + buf.push_back ((v >> 8) & 0xFFu); + buf.push_back ((v >> 16) & 0xFFu); + buf.push_back ((v >> 24) & 0xFFu); +} +void appendI32 (std::vector& buf, int32_t v) { appendU32 (buf, static_cast (v)); } +void appendU64 (std::vector& buf, uint64_t v) { appendU32 (buf, static_cast (v)); appendU32 (buf, static_cast (v >> 32)); } +void appendI64 (std::vector& buf, int64_t v) { appendU64 (buf, static_cast (v)); } +void appendF32 (std::vector& buf, float v) { uint32_t t; std::memcpy (&t, &v, 4); appendU32 (buf, t); } +void appendF64 (std::vector& buf, double v) { uint64_t t; std::memcpy (&t, &v, 8); appendU64 (buf, t); } +void appendRaw (std::vector& buf, const uint8_t* d, size_t n) { appendU32 (buf, static_cast (n)); buf.insert (buf.end (), d, d + n); } + +uint8_t readU8 (const uint8_t*& p) { return *p++; } +uint32_t readU32 (const uint8_t*& p) +{ + uint32_t v = static_cast (p[0]) | (static_cast (p[1]) << 8) + | (static_cast (p[2]) << 16) | (static_cast (p[3]) << 24); + p += 4; + return v; +} +int32_t readI32 (const uint8_t*& p) { return static_cast (readU32 (p)); } +uint64_t readU64 (const uint8_t*& p) { uint64_t lo = readU32 (p); uint64_t hi = readU32 (p); return lo | (hi << 32); } +int64_t readI64 (const uint8_t*& p) { return static_cast (readU64 (p)); } +float readF32 (const uint8_t*& p) { uint32_t t = readU32 (p); float v; std::memcpy (&v, &t, 4); return v; } +double readF64 (const uint8_t*& p) { uint64_t t = readU64 (p); double v; std::memcpy (&v, &t, 8); return v; } + +} // namespace + + +//------------------------------------------------------------------------------ +// IPCSocketMessageEncoder +//------------------------------------------------------------------------------ + +void IPCSocketMessageEncoder::_store (ARA::IPC::MessageArgumentKey k, IPCSocketValue v, IPCSocketTag t) +{ + for (auto& e : _entries) + { + if (e.key == k) + { + e.val = std::move (v); + e.tag = t; + return; + } + } + _entries.push_back ({ k, t, std::move (v) }); +} + +void IPCSocketMessageEncoder::appendInt32 (ARA::IPC::MessageArgumentKey k, int32_t v) { _store (k, IPCSocketValue { v }, IPCSocketTag::I32); } +void IPCSocketMessageEncoder::appendInt64 (ARA::IPC::MessageArgumentKey k, int64_t v) { _store (k, IPCSocketValue { v }, IPCSocketTag::I64); } +void IPCSocketMessageEncoder::appendSize (ARA::IPC::MessageArgumentKey k, size_t v) { _store (k, IPCSocketValue { static_cast (v) }, IPCSocketTag::Size); } +void IPCSocketMessageEncoder::appendFloat (ARA::IPC::MessageArgumentKey k, float v) { _store (k, IPCSocketValue { v }, IPCSocketTag::Float); } +void IPCSocketMessageEncoder::appendDouble (ARA::IPC::MessageArgumentKey k, double v) { _store (k, IPCSocketValue { v }, IPCSocketTag::Dbl); } + +void IPCSocketMessageEncoder::appendString (ARA::IPC::MessageArgumentKey k, const char* v) +{ + _store (k, IPCSocketValue { std::string { v ? v : "" } }, IPCSocketTag::Str); +} + +void IPCSocketMessageEncoder::appendBytes (ARA::IPC::MessageArgumentKey k, const uint8_t* data, size_t size, bool /*copy*/) +{ + _store (k, IPCSocketValue { std::vector { data, data + size } }, IPCSocketTag::Bytes); +} + +std::unique_ptr IPCSocketMessageEncoder::appendSubMessage (ARA::IPC::MessageArgumentKey k) +{ + auto subEnc = std::make_shared (); + auto holder = std::make_shared (); + holder->enc = subEnc; + _store (k, IPCSocketValue { holder }, IPCSocketTag::Sub); + + struct NonOwning : public ARA::IPC::MessageEncoder + { + IPCSocketMessageEncoder* _t; + explicit NonOwning (IPCSocketMessageEncoder* t) : _t { t } {} + void appendInt32 (ARA::IPC::MessageArgumentKey k, int32_t v) override { _t->appendInt32 (k, v); } + void appendInt64 (ARA::IPC::MessageArgumentKey k, int64_t v) override { _t->appendInt64 (k, v); } + void appendSize (ARA::IPC::MessageArgumentKey k, size_t v) override { _t->appendSize (k, v); } + void appendFloat (ARA::IPC::MessageArgumentKey k, float v) override { _t->appendFloat (k, v); } + void appendDouble (ARA::IPC::MessageArgumentKey k, double v) override { _t->appendDouble (k, v); } + void appendString (ARA::IPC::MessageArgumentKey k, const char* v) override { _t->appendString (k, v); } + void appendBytes (ARA::IPC::MessageArgumentKey k, const uint8_t* d, size_t n, bool c) override { _t->appendBytes (k, d, n, c); } + std::unique_ptr appendSubMessage (ARA::IPC::MessageArgumentKey k) override { return _t->appendSubMessage (k); } + }; + return std::make_unique (subEnc.get ()); +} + + +//------------------------------------------------------------------------------ +// Serialization +//------------------------------------------------------------------------------ + +static void _serializeInto (const IPCSocketMessageEncoder& enc, std::vector& buf) +{ + appendU32 (buf, static_cast (enc.entries ().size ())); + for (const auto& e : enc.entries ()) + { + appendI32 (buf, e.key); + appendU8 (buf, static_cast (e.tag)); + switch (e.tag) + { + case IPCSocketTag::I32: appendI32 (buf, std::get (e.val)); break; + case IPCSocketTag::I64: appendI64 (buf, std::get (e.val)); break; + case IPCSocketTag::Size: appendU64 (buf, std::get (e.val)); break; + case IPCSocketTag::Float: appendF32 (buf, std::get (e.val)); break; + case IPCSocketTag::Dbl: appendF64 (buf, std::get (e.val)); break; + case IPCSocketTag::Str: + { + const auto& s = std::get (e.val); + appendRaw (buf, reinterpret_cast (s.data ()), s.size ()); + break; + } + case IPCSocketTag::Bytes: + { + const auto& b = std::get> (e.val); + appendRaw (buf, b.data (), b.size ()); + break; + } + case IPCSocketTag::Sub: + { + std::vector sub; + _serializeInto (*std::get> (e.val)->enc, sub); + appendRaw (buf, sub.data (), sub.size ()); + break; + } + } + } +} + +std::vector IPCSocketEncodeMessage (const IPCSocketMessageEncoder& enc) +{ + std::vector buf; + _serializeInto (enc, buf); + return buf; +} + + +//------------------------------------------------------------------------------ +// IPCSocketMessageDecoder +//------------------------------------------------------------------------------ + +std::unique_ptr IPCSocketMessageDecoder::createWithMessageData (const uint8_t* data, size_t len) +{ + const uint8_t* p = data; + const uint8_t* end = data + len; + + auto safeRead = [&] (size_t n) -> bool { return static_cast (end - p) >= n; }; + + if (!safeRead (4)) + return nullptr; + uint32_t count = readU32 (p); + + std::vector entries; + entries.reserve (count); + + for (uint32_t i = 0; i < count; ++i) + { + if (!safeRead (5)) + return nullptr; + + Entry e; + e.key = readI32 (p); + e.tag = static_cast (readU8 (p)); + + switch (e.tag) + { + case IPCSocketTag::I32: if (!safeRead (4)) return nullptr; e.i32 = readI32 (p); break; + case IPCSocketTag::I64: if (!safeRead (8)) return nullptr; e.i64 = readI64 (p); break; + case IPCSocketTag::Size: if (!safeRead (8)) return nullptr; e.sz = readU64 (p); break; + case IPCSocketTag::Float: if (!safeRead (4)) return nullptr; e.f32 = readF32 (p); break; + case IPCSocketTag::Dbl: if (!safeRead (8)) return nullptr; e.f64 = readF64 (p); break; + case IPCSocketTag::Str: + { + if (!safeRead (4)) return nullptr; + uint32_t n = readU32 (p); + if (!safeRead (n)) return nullptr; + e.str.assign (reinterpret_cast (p), n); + p += n; + break; + } + case IPCSocketTag::Bytes: + { + if (!safeRead (4)) return nullptr; + uint32_t n = readU32 (p); + if (!safeRead (n)) return nullptr; + e.bytes.assign (p, p + n); + p += n; + break; + } + case IPCSocketTag::Sub: + { + if (!safeRead (4)) return nullptr; + uint32_t n = readU32 (p); + if (!safeRead (n)) return nullptr; + e.subBytes.assign (p, p + n); + p += n; + break; + } + } + entries.push_back (std::move (e)); + } + return std::make_unique (std::move (entries)); +} + +const IPCSocketMessageDecoder::Entry* IPCSocketMessageDecoder::_find (ARA::IPC::MessageArgumentKey k, IPCSocketTag t) const +{ + for (const auto& e : _entries) + if (e.key == k && e.tag == t) + return &e; + return nullptr; +} + +bool IPCSocketMessageDecoder::readInt32 (ARA::IPC::MessageArgumentKey k, int32_t* v) const { const auto* e = _find (k, IPCSocketTag::I32); if (!e) { *v = 0; return false; } *v = e->i32; return true; } +bool IPCSocketMessageDecoder::readInt64 (ARA::IPC::MessageArgumentKey k, int64_t* v) const { const auto* e = _find (k, IPCSocketTag::I64); if (!e) { *v = 0; return false; } *v = e->i64; return true; } +bool IPCSocketMessageDecoder::readSize (ARA::IPC::MessageArgumentKey k, size_t* v) const { const auto* e = _find (k, IPCSocketTag::Size); if (!e) { *v = 0; return false; } *v = static_cast (e->sz); return true; } +bool IPCSocketMessageDecoder::readFloat (ARA::IPC::MessageArgumentKey k, float* v) const { const auto* e = _find (k, IPCSocketTag::Float); if (!e) { *v = 0.0f; return false; } *v = e->f32; return true; } +bool IPCSocketMessageDecoder::readDouble (ARA::IPC::MessageArgumentKey k, double* v) const { const auto* e = _find (k, IPCSocketTag::Dbl); if (!e) { *v = 0.0; return false; } *v = e->f64; return true; } + +bool IPCSocketMessageDecoder::readString (ARA::IPC::MessageArgumentKey k, const char** v) const +{ + const auto* e = _find (k, IPCSocketTag::Str); + if (!e) { *v = nullptr; return false; } + *v = e->str.c_str (); + return true; +} + +bool IPCSocketMessageDecoder::readBytesSize (ARA::IPC::MessageArgumentKey k, size_t* sz) const +{ + const auto* e = _find (k, IPCSocketTag::Bytes); + if (!e) { *sz = 0; return false; } + *sz = e->bytes.size (); + return true; +} + +void IPCSocketMessageDecoder::readBytes (ARA::IPC::MessageArgumentKey k, uint8_t* out) const +{ + const auto* e = _find (k, IPCSocketTag::Bytes); + if (!e) return; + std::memcpy (out, e->bytes.data (), e->bytes.size ()); +} + +std::unique_ptr IPCSocketMessageDecoder::readSubMessage (ARA::IPC::MessageArgumentKey k) const +{ + const auto* e = _find (k, IPCSocketTag::Sub); + if (!e) return nullptr; + return createWithMessageData (e->subBytes.data (), e->subBytes.size ()); +} + +bool IPCSocketMessageDecoder::hasDataForKey (ARA::IPC::MessageArgumentKey k) const +{ + for (const auto& e : _entries) + if (e.key == k) + return true; + return false; +} + +#endif // ARA_ENABLE_IPC diff --git a/TestHost/IPC/IPCSocketEncoding.h b/TestHost/IPC/IPCSocketEncoding.h new file mode 100644 index 0000000..e7880dd --- /dev/null +++ b/TestHost/IPC/IPCSocketEncoding.h @@ -0,0 +1,167 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketEncoding.h +//! Proof-of-concept Unix socket-based implementation of ARAIPCMessageEn-/Decoder +//! for the ARA SDK TestHost on Linux (error handling is limited to assertions). +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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. +//------------------------------------------------------------------------------ + +#pragma once + + +#include "ARA_Library/IPC/ARAIPCMessage.h" + +#if ARA_ENABLE_IPC + +#if !defined (__linux__) && !defined (__WINE__) + #error "IPCSocketEncoding is a Linux/Wine-only implementation" +#endif + +#include +#include +#include +#include +#include +#include +#include + + +//------------------------------------------------------------------------------ +// Wire format +// +// Each encoded message is a flat byte buffer: +// [uint32_t entry_count] +// for each entry: +// [int32_t key ] +// [uint8_t tag ] 0=i32, 1=i64, 2=sz, 3=f32, 4=f64, 5=str, 6=bytes, 7=sub +// [value bytes ] +// i32 : 4 bytes LE +// i64 : 8 bytes LE +// sz : 8 bytes LE (always 64-bit on the wire) +// f32 : 4 bytes +// f64 : 8 bytes +// str : [uint32_t len][len bytes, no NUL] +// bytes: [uint32_t len][len bytes] +// sub : [uint32_t len][len bytes of nested serialised message] +// +// Both ends of the socketpair run on the same machine (same arch, same +// endianness), so values are written in native byte order and the +// Connection is told receiverEndianessMatches = true. +//------------------------------------------------------------------------------ + +enum class IPCSocketTag : uint8_t +{ + I32 = 0, + I64 = 1, + Size = 2, + Float = 3, + Dbl = 4, + Str = 5, + Bytes = 6, + Sub = 7, +}; + +struct IPCSocketSubMsg; +using IPCSocketValue = std::variant< + int32_t, + int64_t, + uint64_t, + float, + double, + std::string, + std::vector, + std::shared_ptr +>; + +class IPCSocketMessageEncoder; + +struct IPCSocketSubMsg +{ + std::shared_ptr enc; +}; + + +class IPCSocketMessageEncoder : public ARA::IPC::MessageEncoder +{ +public: + IPCSocketMessageEncoder () = default; + + void appendInt32 (ARA::IPC::MessageArgumentKey k, int32_t v) override; + void appendInt64 (ARA::IPC::MessageArgumentKey k, int64_t v) override; + void appendSize (ARA::IPC::MessageArgumentKey k, size_t v) override; + void appendFloat (ARA::IPC::MessageArgumentKey k, float v) override; + void appendDouble (ARA::IPC::MessageArgumentKey k, double v) override; + void appendString (ARA::IPC::MessageArgumentKey k, const char* v) override; + void appendBytes (ARA::IPC::MessageArgumentKey k, const uint8_t* data, size_t size, bool copy) override; + std::unique_ptr appendSubMessage (ARA::IPC::MessageArgumentKey k) override; + + struct Entry + { + ARA::IPC::MessageArgumentKey key; + IPCSocketTag tag; + IPCSocketValue val; + }; + const std::vector& entries () const { return _entries; } + +private: + void _store (ARA::IPC::MessageArgumentKey k, IPCSocketValue v, IPCSocketTag t); + std::vector _entries; +}; + + +class IPCSocketMessageDecoder : public ARA::IPC::MessageDecoder +{ +public: + struct Entry + { + ARA::IPC::MessageArgumentKey key; + IPCSocketTag tag; + int32_t i32 {}; + int64_t i64 {}; + uint64_t sz {}; + float f32 {}; + double f64 {}; + std::string str; + std::vector bytes; + std::vector subBytes; + }; + + explicit IPCSocketMessageDecoder (std::vector entries) + : _entries (std::move (entries)) {} + + bool readInt32 (ARA::IPC::MessageArgumentKey k, int32_t* v) const override; + bool readInt64 (ARA::IPC::MessageArgumentKey k, int64_t* v) const override; + bool readSize (ARA::IPC::MessageArgumentKey k, size_t* v) const override; + bool readFloat (ARA::IPC::MessageArgumentKey k, float* v) const override; + bool readDouble (ARA::IPC::MessageArgumentKey k, double* v) const override; + bool readString (ARA::IPC::MessageArgumentKey k, const char** v) const override; + bool readBytesSize (ARA::IPC::MessageArgumentKey k, size_t* sz) const override; + void readBytes (ARA::IPC::MessageArgumentKey k, uint8_t* out) const override; + std::unique_ptr readSubMessage (ARA::IPC::MessageArgumentKey k) const override; + bool hasDataForKey (ARA::IPC::MessageArgumentKey k) const override; + + static std::unique_ptr createWithMessageData (const uint8_t* data, size_t len); + std::vector createEncodedMessage () const; + + static std::unique_ptr createEncoder () + { return std::make_unique (); } + +private: + const Entry* _find (ARA::IPC::MessageArgumentKey k, IPCSocketTag t) const; + std::vector _entries; +}; + +std::vector IPCSocketEncodeMessage (const IPCSocketMessageEncoder& enc); + +#endif // ARA_ENABLE_IPC diff --git a/TestHost/IPC/IPCSocketHostTest.cpp b/TestHost/IPC/IPCSocketHostTest.cpp new file mode 100644 index 0000000..a8e4fea --- /dev/null +++ b/TestHost/IPC/IPCSocketHostTest.cpp @@ -0,0 +1,166 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketHostTest.cpp +//! Proof-of-concept Linux IPC host: forks a plugin process, connects +//! over a socketpair, and exercises the ARA factory API via ProxyPlugIn. +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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. +//------------------------------------------------------------------------------ + +// Usage: +// IPCSocketHostTest [vst3_so_path] +// +// is the path to IPCSocketPlugInTest (or any compatible process +// that accepts [vst3_so_path] as arguments). + +#define ARA_ENABLE_IPC 1 + +#include "ARA_Library/IPC/ARAIPCProxyPlugIn.h" +#include "ARA_Library/IPC/ARAIPCConnection.h" +#include "IPC/IPCSocketChannel.h" +#include "IPC/IPCSocketEncoding.h" +#include "ARA_API/ARAInterface.h" +#include "ARA_Library/Debug/ARADebug.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + + +static std::unique_ptr _makeConnection (int mainFd, int otherFd) +{ + ARA::IPC::Connection* connPtr = nullptr; + + auto conn = std::make_unique ( + [] () -> std::unique_ptr { return std::make_unique (); }, + ARA::IPC::ProxyPlugIn::handleReceivedMessage, + /*receiverEndianessMatches=*/ true, + [&connPtr] () { if (connPtr) connPtr->processPendingMessageOnCreationThreadIfNeeded (); } + ); + connPtr = conn.get (); + + conn->setMainThreadChannel (std::make_unique (mainFd)); + conn->setOtherThreadsChannel (std::make_unique (otherFd)); + return conn; +} + +int main (int argc, char* argv[]) +{ + if (argc < 2) + { + std::fprintf (stderr, "usage: IPCSocketHostTest [vst3_so_path]\n"); + return 1; + } + + const char* pluginBin = argv[1]; + const char* vst3Path = (argc >= 3) ? argv[2] : nullptr; + + std::printf ("[host] plugin binary: %s\n", pluginBin); + std::fflush (stdout); + + int mainSv[2], otherSv[2], ready[2]; + if (::socketpair (AF_UNIX, SOCK_STREAM, 0, mainSv) != 0 || + ::socketpair (AF_UNIX, SOCK_STREAM, 0, otherSv) != 0 || + ::pipe (ready) != 0) + { + std::perror ("socketpair/pipe"); + return 1; + } + + pid_t child = ::fork (); + ARA_INTERNAL_ASSERT (child >= 0); + + if (child == 0) + { + ::close (mainSv[0]); + ::close (otherSv[0]); + ::close (ready[0]); + + char mainArg[32], otherArg[32], readyArg[32]; + std::snprintf (mainArg, sizeof (mainArg), "%d", mainSv[1]); + std::snprintf (otherArg, sizeof (otherArg), "%d", otherSv[1]); + std::snprintf (readyArg, sizeof (readyArg), "%d", ready[1]); + + if (vst3Path) + ::execlp (pluginBin, pluginBin, mainArg, otherArg, readyArg, vst3Path, (char*) nullptr); + else + ::execlp (pluginBin, pluginBin, mainArg, otherArg, readyArg, (char*) nullptr); + + std::perror ("execlp"); + _exit (127); + } + + ::close (mainSv[1]); + ::close (otherSv[1]); + ::close (ready[1]); + + std::printf ("[host] child pid=%d, waiting for ready signal...\n", (int) child); + std::fflush (stdout); + + char readyByte = 0; + if (::read (ready[0], &readyByte, 1) <= 0) + { + std::fprintf (stderr, "[host] plugin never became ready\n"); + ::kill (child, SIGTERM); + ::waitpid (child, nullptr, 0); + return 1; + } + ::close (ready[0]); + + std::printf ("[host] plugin ready, building connection...\n"); + std::fflush (stdout); + + auto conn = _makeConnection (mainSv[0], otherSv[0]); + auto proxyPlugIn = std::make_unique (std::move (conn)); + auto proxyRef = reinterpret_cast (proxyPlugIn.get ()); + + std::printf ("[host] querying factory count...\n"); + std::fflush (stdout); + + size_t count = ARA::IPC::ARAIPCProxyPlugInGetFactoriesCount (proxyRef); + std::printf ("[host] factory count = %zu\n", count); + std::fflush (stdout); + ARA_INTERNAL_ASSERT (count > 0); + + const ARA::ARAFactory* factory = ARA::IPC::ARAIPCProxyPlugInGetFactoryAtIndex (proxyRef, 0); + std::printf ("[host] factory name = %s\n", + factory && factory->plugInName ? factory->plugInName : "(null)"); + std::fflush (stdout); + + std::printf ("[host] calling initializeARA...\n"); + std::fflush (stdout); + ARA::IPC::ARAIPCProxyPlugInInitializeARA (proxyRef, factory->factoryID, ARA::kARAAPIGeneration_2_0_Final); + std::printf ("[host] initializeARA returned\n"); + std::fflush (stdout); + + ARA::IPC::ARAIPCProxyPlugInUninitializeARA (proxyRef, factory->factoryID); + std::printf ("[host] uninitializeARA returned\n"); + std::fflush (stdout); + + ::usleep (100'000); + + proxyPlugIn.reset (); + + ::kill (child, SIGTERM); + int status = 0; + ::waitpid (child, &status, 0); + std::printf ("[host] child exited with status %d\n", WEXITSTATUS (status)); + + return 0; +} diff --git a/TestHost/IPC/IPCSocketPlugInTest.cpp b/TestHost/IPC/IPCSocketPlugInTest.cpp new file mode 100644 index 0000000..3102e7e --- /dev/null +++ b/TestHost/IPC/IPCSocketPlugInTest.cpp @@ -0,0 +1,263 @@ +//------------------------------------------------------------------------------ +//! \file IPCSocketPlugInTest.cpp +//! Proof-of-concept Linux IPC plugin process: loads a Linux VST3 via +//! dlopen, registers its ARAFactory with ProxyHost, and serves messages. +//! \project ARA SDK Examples +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. +//! \license Licensed under the Apache License, Version 2.0 (the "License"); +//! you may not use this file except in compliance with the License. +//! You may obtain a copy of the License at +//! +//! 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. +//------------------------------------------------------------------------------ + +// Usage: +// IPCSocketPlugInTest [vst3_so_path] +// +// Writes one byte to ready_fd when the ProxyHost is ready to receive messages. +// Default vst3_so_path must be supplied at build time via -DARA_DEFAULT_VST3_PATH. + +#define ARA_ENABLE_IPC 1 + +#include +#include +#include +#include +#include +#include + +#include "ARA_API/ARAVST3.h" + +ARA_DISABLE_VST3_WARNINGS_BEGIN +#include "pluginterfaces/base/funknown.h" +#include "pluginterfaces/base/ipluginbase.h" +ARA_DISABLE_VST3_WARNINGS_END + +#include "ARA_Library/IPC/ARAIPCProxyHost.h" +#include "ARA_Library/IPC/ARAIPCConnection.h" +#include "ARA_Library/Debug/ARADebug.h" +#include "IPC/IPCSocketChannel.h" +#include "IPC/IPCSocketEncoding.h" + +DEF_CLASS_IID (Steinberg::IPluginFactory) +DEF_CLASS_IID (ARA::IMainFactory) +DEF_CLASS_IID (ARA::IPlugInEntryPoint) +DEF_CLASS_IID (ARA::IPlugInEntryPoint2) + +using namespace Steinberg; +using namespace ARA; + + +//------------------------------------------------------------------------------ +// VST3 loading +//------------------------------------------------------------------------------ + +typedef bool (*ModuleEntryFunc)(void*); +typedef bool (*ModuleExitFunc)(); + +struct LoadedVST3 +{ + void* handle { nullptr }; + IPluginFactory* factory { nullptr }; + IMainFactory* mainFactory { nullptr }; + const ARAFactory* araFactory { nullptr }; +}; +static LoadedVST3 g_vst3; + +static const ARAFactory* _loadARAFactory (const char* soPath) +{ + std::printf ("[plugin] loading VST3: %s\n", soPath); + std::fflush (stdout); + + g_vst3.handle = ::dlopen (soPath, RTLD_LAZY | RTLD_GLOBAL); + if (!g_vst3.handle) + { + std::fprintf (stderr, "[plugin] dlopen failed: %s\n", ::dlerror ()); + return nullptr; + } + + auto moduleEntry = reinterpret_cast (::dlsym (g_vst3.handle, "ModuleEntry")); + if (moduleEntry && !moduleEntry (g_vst3.handle)) + { + std::fprintf (stderr, "[plugin] ModuleEntry returned false\n"); + ::dlclose (g_vst3.handle); g_vst3.handle = nullptr; + return nullptr; + } + + auto factoryProc = reinterpret_cast (::dlsym (g_vst3.handle, "GetPluginFactory")); + if (!factoryProc) + { + std::fprintf (stderr, "[plugin] GetPluginFactory not found\n"); + ::dlclose (g_vst3.handle); g_vst3.handle = nullptr; + return nullptr; + } + g_vst3.factory = factoryProc (); + if (!g_vst3.factory) + { + std::fprintf (stderr, "[plugin] GetPluginFactory returned nullptr\n"); + ::dlclose (g_vst3.handle); g_vst3.handle = nullptr; + return nullptr; + } + + std::printf ("[plugin] scanning %d classes...\n", (int) g_vst3.factory->countClasses ()); + std::fflush (stdout); + + for (int32 i = 0; i < g_vst3.factory->countClasses (); ++i) + { + PClassInfo info; + if (g_vst3.factory->getClassInfo (i, &info) != kResultOk) + continue; + + if (std::strcmp (info.category, kARAMainFactoryClass) != 0) + continue; + + IMainFactory* mf = nullptr; + tresult r = g_vst3.factory->createInstance (info.cid, IMainFactory::iid, reinterpret_cast (&mf)); + if (r != kResultOk || !mf) + { + std::fprintf (stderr, "[plugin] createInstance(IMainFactory) failed\n"); + continue; + } + + const ARAFactory* af = mf->getFactory (); + if (!af) + { + std::fprintf (stderr, "[plugin] IMainFactory::getFactory returned nullptr\n"); + mf->release (); + continue; + } + + std::printf ("[plugin] found ARAFactory: plugInName='%s' factoryID='%s'\n", + af->plugInName ? af->plugInName : "(null)", + af->factoryID ? af->factoryID : "(null)"); + std::fflush (stdout); + + g_vst3.mainFactory = mf; + g_vst3.araFactory = af; + return af; + } + + std::fprintf (stderr, "[plugin] no ARA::IMainFactory found\n"); + return nullptr; +} + +static void _unloadVST3 () +{ + if (g_vst3.mainFactory) { g_vst3.mainFactory->release (); g_vst3.mainFactory = nullptr; } + g_vst3.araFactory = nullptr; + if (g_vst3.factory) { g_vst3.factory->release (); g_vst3.factory = nullptr; } + if (g_vst3.handle) + { + auto moduleExit = reinterpret_cast (::dlsym (g_vst3.handle, "ModuleExit")); + if (moduleExit) moduleExit (); + ::dlclose (g_vst3.handle); + g_vst3.handle = nullptr; + } +} + + +//------------------------------------------------------------------------------ +// ProxyHost subclass +//------------------------------------------------------------------------------ + +class TestProxyHost : public ARA::IPC::ProxyHost +{ +public: + static std::unique_ptr create (int mainFd, int otherFd) + { + return std::unique_ptr (new TestProxyHost (mainFd, otherFd)); + } + + ARA::IPC::Connection* connection () const + { + return ARA::IPC::RemoteCaller::getConnection (); + } + +private: + explicit TestProxyHost (int mainFd, int otherFd) + : ARA::IPC::ProxyHost (buildConnection (this, mainFd, otherFd)) + {} + + static std::unique_ptr buildConnection (TestProxyHost* self, int mainFd, int otherFd) + { + auto conn = std::make_unique ( + [] () -> std::unique_ptr { return std::make_unique (); }, + [self] (const ARA::IPC::MessageID msgID, + const ARA::IPC::MessageDecoder* decoder, + ARA::IPC::MessageEncoder* replyEncoder) + { + self->handleReceivedMessage (msgID, decoder, replyEncoder); + }, + /*receiverEndianessMatches=*/ true + ); + conn->setMainThreadChannel (std::make_unique (mainFd)); + conn->setOtherThreadsChannel (std::make_unique (otherFd)); + return conn; + } +}; + + +//------------------------------------------------------------------------------ +// main +//------------------------------------------------------------------------------ + +int main (int argc, char* argv[]) +{ + if (argc < 4) + { + std::fprintf (stderr, + "usage: IPCSocketPlugInTest [vst3_so_path]\n"); + return 1; + } + + int mainFd = std::atoi (argv[1]); + int otherFd = std::atoi (argv[2]); + int readyFd = std::atoi (argv[3]); + +#ifdef ARA_DEFAULT_VST3_PATH + const char* soPath = (argc >= 5) ? argv[4] : ARA_DEFAULT_VST3_PATH; +#else + if (argc < 5) + { + std::fprintf (stderr, "[plugin] no vst3_so_path provided and ARA_DEFAULT_VST3_PATH not set\n"); + return 1; + } + const char* soPath = argv[4]; +#endif + + std::printf ("[plugin] starting, main_fd=%d other_fd=%d\n", mainFd, otherFd); + std::fflush (stdout); + + const ARAFactory* araFactory = _loadARAFactory (soPath); + if (!araFactory) + { + std::fprintf (stderr, "[plugin] failed to load ARAFactory\n"); + return 1; + } + ARA::IPC::ARAIPCProxyHostAddFactory (araFactory); + + auto proxyHost = TestProxyHost::create (mainFd, otherFd); + + std::printf ("[plugin] proxy host ready\n"); + std::fflush (stdout); + + char byte = 1; + ::write (readyFd, &byte, 1); + ::close (readyFd); + + ARA::IPC::Connection* conn = proxyHost->connection (); + while (true) + { + conn->processPendingMessageOnCreationThreadIfNeeded (); + ::usleep (1000); + } + + _unloadVST3 (); + return 0; +} diff --git a/TestHost/IPC/IPCXMLEncoding.cpp b/TestHost/IPC/IPCXMLEncoding.cpp index cef2a5f..e0f8c0c 100644 --- a/TestHost/IPC/IPCXMLEncoding.cpp +++ b/TestHost/IPC/IPCXMLEncoding.cpp @@ -3,7 +3,7 @@ //! Proof-of-concept pugixml-based implementation of ARAIPCMessageEn-/Decoder //! for the ARA SDK TestHost (error handling is limited to assertions). //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -101,9 +101,9 @@ pugi::xml_attribute IPCXMLMessageEncoder::_appendAttribute (const MessageArgumen return _root.append_attribute (_getEncodedKey (argKey)); } -ARA::IPC::MessageEncoder* IPCXMLMessageEncoder::appendSubMessage (const MessageArgumentKey argKey) +std::unique_ptr IPCXMLMessageEncoder::appendSubMessage (const MessageArgumentKey argKey) { - return new IPCXMLMessageEncoder { _dictionary, _root.append_child (_getEncodedKey (argKey)) }; + return IPCXMLMessageEncoder::createWithXML (_dictionary, _root.append_child (_getEncodedKey (argKey))); } #if defined (__APPLE__) @@ -142,21 +142,21 @@ std::string IPCXMLMessageEncoder::createEncodedMessage () const #if defined (__APPLE__) -IPCXMLMessageDecoder* IPCXMLMessageDecoder::createWithMessageData (CFDataRef data) +std::unique_ptr IPCXMLMessageDecoder::createWithMessageData (CFDataRef data) { const auto dataSize { static_cast (CFDataGetLength (data)) }; if (dataSize == 0) return nullptr; - return new IPCXMLMessageDecoder { reinterpret_cast (CFDataGetBytePtr (data)), dataSize }; + return std::unique_ptr { new IPCXMLMessageDecoder { reinterpret_cast (CFDataGetBytePtr (data)), dataSize } }; } #else -IPCXMLMessageDecoder* IPCXMLMessageDecoder::createWithMessageData (const char* data, const size_t dataSize) +std::unique_ptr IPCXMLMessageDecoder::createWithMessageData (const char* data, const size_t dataSize) { if (dataSize == 0) return nullptr; - return new IPCXMLMessageDecoder { data, dataSize }; + return std::unique_ptr { new IPCXMLMessageDecoder { data, dataSize } }; } #endif @@ -251,11 +251,7 @@ bool IPCXMLMessageDecoder::readBytesSize (const MessageArgumentKey argKey, size_ return false; } _bytesCacheKey = argKey; -#if __cplusplus >= 201703L _bytesCacheData = base64_decode (std::string_view { attribute.as_string () }, false); -#else - _bytesCacheData = base64_decode (std::string { attribute.as_string () }, false); -#endif *argSize = _bytesCacheData.size (); return true; } @@ -269,22 +265,18 @@ void IPCXMLMessageDecoder::readBytes (const MessageArgumentKey argKey, uint8_t* const auto attribute { _root.attribute (_getEncodedKey (argKey)) }; ARA_INTERNAL_ASSERT (!attribute.empty ()); -#if __cplusplus >= 201703L const auto decodedData { base64_decode (std::string_view { attribute.as_string () }, false) }; -#else - const auto decodedData { base64_decode (std::string { attribute.as_string () }, false) }; -#endif std::memcpy (argValue, decodedData.c_str (), decodedData.size ()); } -ARA::IPC::MessageDecoder* IPCXMLMessageDecoder::readSubMessage (const MessageArgumentKey argKey) const +std::unique_ptr IPCXMLMessageDecoder::readSubMessage (const MessageArgumentKey argKey) const { ARA_INTERNAL_ASSERT (!_root.empty ()); const auto child { _root.child (_getEncodedKey (argKey)) }; if (child.empty ()) return nullptr; - return new IPCXMLMessageDecoder { _dictionary, child }; + return std::unique_ptr { new IPCXMLMessageDecoder { _dictionary, child } }; } bool IPCXMLMessageDecoder::hasDataForKey (const MessageArgumentKey argKey) const diff --git a/TestHost/IPC/IPCXMLEncoding.h b/TestHost/IPC/IPCXMLEncoding.h index fcd4813..d802786 100644 --- a/TestHost/IPC/IPCXMLEncoding.h +++ b/TestHost/IPC/IPCXMLEncoding.h @@ -3,7 +3,7 @@ //! Proof-of-concept pugixml-based implementation of ARAIPCMessageEn-/Decoder //! for the ARA SDK TestHost (error handling is limited to assertions). //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -57,8 +57,6 @@ class IPCXMLMessage class IPCXMLMessageEncoder : public IPCXMLMessage, public ARA::IPC::MessageEncoder { public: - IPCXMLMessageEncoder () = default; - void appendInt32 (MessageArgumentKey argKey, int32_t argValue) override; void appendInt64 (MessageArgumentKey argKey, int64_t argValue) override; void appendSize (MessageArgumentKey argKey, size_t argValue) override; @@ -66,7 +64,7 @@ class IPCXMLMessageEncoder : public IPCXMLMessage, public ARA::IPC::MessageEncod void appendDouble (MessageArgumentKey argKey, double argValue) override; void appendString (MessageArgumentKey argKey, const char * argValue) override; void appendBytes (MessageArgumentKey argKey, const uint8_t * argValue, size_t argSize, bool copy) override; - ARA::IPC::MessageEncoder* appendSubMessage (MessageArgumentKey argKey) override; + std::unique_ptr appendSubMessage (MessageArgumentKey argKey) override; // to be used by IPCMessageChannel only: encoding to channel-internal datas format #if defined (__APPLE__) @@ -75,8 +73,13 @@ class IPCXMLMessageEncoder : public IPCXMLMessage, public ARA::IPC::MessageEncod std::string createEncodedMessage () const; #endif + static std::unique_ptr create () { return std::unique_ptr { new IPCXMLMessageEncoder {} }; } + static std::unique_ptr createWithXML (std::shared_ptr dictionary, pugi::xml_node root) + { return std::unique_ptr { new IPCXMLMessageEncoder { dictionary, root } }; } + private: using IPCXMLMessage::IPCXMLMessage; + IPCXMLMessageEncoder () = default; pugi::xml_attribute _appendAttribute (const MessageArgumentKey argKey); }; @@ -86,9 +89,9 @@ class IPCXMLMessageDecoder : public IPCXMLMessage, public ARA::IPC::MessageDecod public: // to be used by IPCMessageChannel only: encoding from channel-internal datas format #if defined (__APPLE__) - static IPCXMLMessageDecoder* createWithMessageData (CFDataRef data); + static std::unique_ptr createWithMessageData (CFDataRef data); #else - static IPCXMLMessageDecoder* createWithMessageData (const char* data, const size_t dataSize); + static std::unique_ptr createWithMessageData (const char* data, const size_t dataSize); #endif bool readInt32 (MessageArgumentKey argKey, int32_t* argValue) const override; @@ -99,7 +102,7 @@ class IPCXMLMessageDecoder : public IPCXMLMessage, public ARA::IPC::MessageDecod bool readString (MessageArgumentKey argKey, const char** argValue) const override; bool readBytesSize (MessageArgumentKey argKey, size_t* argSize) const override; void readBytes (MessageArgumentKey argKey, uint8_t* argValue) const override; - MessageDecoder* readSubMessage (const MessageArgumentKey argKey) const override; + std::unique_ptr readSubMessage (const MessageArgumentKey argKey) const override; bool hasDataForKey (MessageArgumentKey argKey) const override; private: diff --git a/TestHost/IPC/meson.build b/TestHost/IPC/meson.build new file mode 100644 index 0000000..b0a9886 --- /dev/null +++ b/TestHost/IPC/meson.build @@ -0,0 +1,81 @@ +# meson.build — Linux IPC socket test binaries for ARA SDK Examples +# +# Configure and build from ARA_Examples/: +# +# meson setup build TestHost/IPC/ +# ninja -C build +# build/IPCSocketHostTest build/IPCSocketPlugInTest [/path/to/plugin.so] +# +# Assumes the ARA_Library and ARA_API are siblings of ARA_Examples: +# ara-sdk/ +# ARA_API/ +# ARA_Library/ +# ARA_Examples/ ← project root +# +# Also requires the VST3 SDK at a sibling path (../../../vst3 relative to +# ARA_Examples), adjustable via the vst3_sdk_path build option. + +project ('ara-ipc-socket-test', 'c', 'cpp', + default_options: [ + 'cpp_std=c++20', + 'c_std=c11', + 'warning_level=1', + 'buildtype=debug', + ] +) + +vst3_sdk_path = get_option ('vst3_sdk_path') +if vst3_sdk_path == '' + vst3_sdk_path = '../../../vst3' +endif + +ara_inc = include_directories ( + '..', # ARA_Examples/TestHost/ (so 'IPC/IPCSocket*.h' resolves) + '../..', # ARA_Examples/ (so 'ExamplesCommon/...' resolves) + '../../..', # ARA_Library/ (so 'ARA_Library/IPC/...' resolves) + '../../../..', # ara-sdk/ (so 'ARA_API/ARAInterface.h' resolves) + vst3_sdk_path, # VST3 SDK +) + +ara_defines = ['-DARA_ENABLE_IPC=1'] + +ara_lib_dir = '../../../' # ARA_Library/ relative to IPC/ + +ara_lib_sources = files ( + ara_lib_dir + 'IPC/ARAIPCConnection.cpp', + ara_lib_dir + 'IPC/ARAIPCProxyHost.cpp', + ara_lib_dir + 'IPC/ARAIPCProxyPlugIn.cpp', + ara_lib_dir + 'Dispatch/ARAHostDispatch.cpp', + ara_lib_dir + 'Dispatch/ARAPlugInDispatch.cpp', + ara_lib_dir + 'Utilities/ARAChannelFormat.cpp', + ara_lib_dir + 'Utilities/ARAPitchInterpretation.cpp', + ara_lib_dir + 'Debug/ARADebug.c', + vst3_sdk_path + '/pluginterfaces/base/funknown.cpp', +) + +socket_sources = files ( + 'IPCSocketEncoding.cpp', + 'IPCSocketChannel.cpp', +) + +ara_lib = static_library ('ara_ipc', + ara_lib_sources + socket_sources, + include_directories: ara_inc, + c_args: ara_defines, + cpp_args: ara_defines, +) + +executable ('IPCSocketPlugInTest', + files ('IPCSocketPlugInTest.cpp'), + include_directories: ara_inc, + cpp_args: ara_defines, + link_with: ara_lib, + link_args: ['-ldl'], +) + +executable ('IPCSocketHostTest', + files ('IPCSocketHostTest.cpp'), + include_directories: ara_inc, + cpp_args: ara_defines, + link_with: ara_lib, +) diff --git a/TestHost/IPC/meson.options b/TestHost/IPC/meson.options new file mode 100644 index 0000000..dffc4e0 --- /dev/null +++ b/TestHost/IPC/meson.options @@ -0,0 +1,2 @@ +option ('vst3_sdk_path', type: 'string', value: '', + description: 'Path to the VST3 SDK root (defaults to ../../../vst3 relative to ARA_Examples)') diff --git a/TestHost/ModelObjects.cpp b/TestHost/ModelObjects.cpp index a6316fe..db92220 100644 --- a/TestHost/ModelObjects.cpp +++ b/TestHost/ModelObjects.cpp @@ -2,7 +2,7 @@ //! \file ModelObjects.cpp //! classes used to build the host model graph //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -28,28 +28,63 @@ /*******************************************************************************/ -Document::Document (std::string name) -: _name { name } -{} +PlaybackRegion::PlaybackRegion (AudioModification* audioModification, ARA::ARAPlaybackTransformationFlags transformationFlags, double startInModificationTime, double durationInModificationTime, double startInPlaybackTime, double durationInPlaybackTime, RegionSequence * regionSequence, std::string name, ARA::ARAColor color) +: _audioModification { audioModification }, + _transformationFlags { transformationFlags }, + _startInModificationTime { startInModificationTime }, + _durationInModificationTime { durationInModificationTime }, + _startInPlaybackTime { startInPlaybackTime }, + _durationInPlaybackTime { durationInPlaybackTime }, + _regionSequence { regionSequence }, + _name { name }, + _color { color } +{ + _regionSequence->_addPlaybackRegion (this); +} + +PlaybackRegion::~PlaybackRegion () +{ + _regionSequence->_removePlaybackRegion (this); +} + +// Note that setRegionsequence handles removing this from the old +// region sequence and adding this to the new region sequence +void PlaybackRegion::setRegionSequence (RegionSequence* regionSequence) +{ + if (regionSequence == _regionSequence) + return; + + _regionSequence->_removePlaybackRegion (this); + _regionSequence = regionSequence; + _regionSequence->_addPlaybackRegion (this); +} /*******************************************************************************/ -MusicalContext::MusicalContext (Document * document, std::string name, ARA::ARAColor color) -: _document { document }, +AudioModification::AudioModification (AudioSource * audioSource, std::string name, std::string persistentID) +: _audioSource { audioSource }, _name { name }, - _color { color } + _persistentID { persistentID } {} -int MusicalContext::getOrderIndex () const noexcept +/*******************************************************************************/ + +AudioSource::AudioSource (Document* document, AudioFileBase* audioFile, std::string persistentID) +: _document { document }, + _audioFile { audioFile }, + _persistentID { persistentID } { - return static_cast (ARA::index_of (_document->getMusicalContexts (), this)); + // at this point, only up to stereo formats are supported because the test code + // doesn't handle surround channel arrangements yet. + ARA_INTERNAL_ASSERT (_audioFile->getChannelCount () <= 2); } /*******************************************************************************/ -RegionSequence::RegionSequence (Document * document, std::string name, MusicalContext * musicalContext, ARA::ARAColor color) +RegionSequence::RegionSequence (Document * document, std::string name, std::string persistentID, MusicalContext * musicalContext, ARA::ARAColor color) : _document { document }, _name { name }, + _persistentID { persistentID }, _musicalContext { musicalContext }, _color { color } { @@ -78,53 +113,19 @@ void RegionSequence::setMusicalContext (MusicalContext* musicalContext) /*******************************************************************************/ -AudioSource::AudioSource (Document* document, AudioFileBase* audioFile, std::string persistentID) +MusicalContext::MusicalContext (Document * document, std::string name, ARA::ARAColor color) : _document { document }, - _audioFile { audioFile }, - _persistentID { persistentID } -{ - // at this point, only up to stereo formats are supported because the test code - // doesn't handle surround channel arrangements yet. - ARA_INTERNAL_ASSERT (_audioFile->getChannelCount () <= 2); -} - -/*******************************************************************************/ - -AudioModification::AudioModification (AudioSource * audioSource, std::string name, std::string persistentID) -: _audioSource { audioSource }, - _name { name }, - _persistentID { persistentID } -{} - -/*******************************************************************************/ - -PlaybackRegion::PlaybackRegion (AudioModification* audioModification, ARA::ARAPlaybackTransformationFlags transformationFlags, double startInModificationTime, double durationInModificationTime, double startInPlaybackTime, double durationInPlaybackTime, RegionSequence * regionSequence, std::string name, ARA::ARAColor color) -: _audioModification { audioModification }, - _transformationFlags { transformationFlags }, - _startInModificationTime { startInModificationTime }, - _durationInModificationTime { durationInModificationTime }, - _startInPlaybackTime { startInPlaybackTime }, - _durationInPlaybackTime { durationInPlaybackTime }, - _regionSequence { regionSequence }, _name { name }, _color { color } -{ - _regionSequence->_addPlaybackRegion (this); -} +{} -PlaybackRegion::~PlaybackRegion () +int MusicalContext::getOrderIndex () const noexcept { - _regionSequence->_removePlaybackRegion (this); + return static_cast (ARA::index_of (_document->getMusicalContexts (), this)); } -// Note that setRegionsequence handles removing this from the old -// region sequence and adding this to the new region sequence -void PlaybackRegion::setRegionSequence (RegionSequence* regionSequence) -{ - if (regionSequence == _regionSequence) - return; +/*******************************************************************************/ - _regionSequence->_removePlaybackRegion (this); - _regionSequence = regionSequence; - _regionSequence->_addPlaybackRegion (this); -} +Document::Document (std::string name) +: _name { name } +{} diff --git a/TestHost/ModelObjects.h b/TestHost/ModelObjects.h index b1b7763..78d2420 100644 --- a/TestHost/ModelObjects.h +++ b/TestHost/ModelObjects.h @@ -2,7 +2,7 @@ //! \file ModelObjects.h //! classes used to build the host model graph //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -52,29 +52,26 @@ class ContentContainer using EntryData = std::unique_ptr>; void setNotes (std::vector const& notes) { _notes = makeEntryData (notes); } - void clearNotes () { _notes.reset (); } const EntryData& getNotes () const noexcept { return _notes; } void setTempoEntries (std::vector const& tempoEntries) { _tempoEntries = makeEntryData (tempoEntries); } - void clearTempoEntries () { _tempoEntries.reset (); } const EntryData& getTempoEntries () const noexcept { return _tempoEntries; } void setBarSignatures (std::vector const& barSignatures) { _barSignatures = makeEntryData (barSignatures); } - void clearBarSignatures () { _barSignatures.reset (); } const EntryData& getBarSignatures () const noexcept { return _barSignatures; } void setTuning (ARA::ARAContentTuning const& tuning) { _tuning = makeEntryData (std::vector { tuning }); } - void clearTuning () { _tuning.reset (); } const EntryData& getTuning () const noexcept { return _tuning; } void setKeySignatures (std::vector const& keySignatures) { _keySignatures = makeEntryData (keySignatures); } - void clearKeySignatures () { _keySignatures.reset (); } const EntryData& getKeySignatures () const noexcept { return _keySignatures; } void setChords (std::vector const& chords) { _chords = makeEntryData (chords); } - void clearChords () { _chords.reset (); } const EntryData& getChords () const noexcept { return _chords; } + void setLyrics (std::vector const& lyrics) { _lyrics = makeEntryData (lyrics); } + const EntryData& getLyrics () const noexcept { return _lyrics; } + private: template EntryData makeEntryData (std::vector const& vec) { return std::make_unique> (vec); } @@ -85,98 +82,87 @@ class ContentContainer EntryData _tuning; EntryData _keySignatures; EntryData _chords; + EntryData _lyrics; }; /*******************************************************************************/ -class Document +class PlaybackRegion { public: - Document (std::string name); - - const std::string& getName () const noexcept { return _name; } - void setName (std::string name) { _name = name; } - - std::vector> const& getMusicalContexts () const noexcept { return _musicalContexts; } - void addMusicalContext (std::unique_ptr&& musicalContext) { _musicalContexts.emplace_back (std::move (musicalContext)); } - void removeMusicalContext (MusicalContext* musicalContext) { ARA::find_erase (_musicalContexts, musicalContext); } - - std::vector> const& getRegionSequences () const noexcept { return _regionSequences; } - void addRegionSequence (std::unique_ptr&& regionSequence) { _regionSequences.emplace_back (std::move (regionSequence)); } - void removeRegionSequence (RegionSequence* regionSequence) { ARA::find_erase (_regionSequences, regionSequence); } + PlaybackRegion (AudioModification* audioModification, + ARA::ARAPlaybackTransformationFlags transformationFlags, + double startInModificationTime, + double durationInModificationTime, + double startInPlaybackTime, + double durationInPlaybackTime, + RegionSequence* regionSequence, + std::string name, + ARA::ARAColor color); + ~PlaybackRegion (); - std::vector> const& getAudioSources () const noexcept { return _audioSources; } - void addAudioSource (std::unique_ptr&& audioSource) { _audioSources.emplace_back (std::move (audioSource)); } - void removeAudioSource (AudioSource* audioSource) { ARA::find_erase (_audioSources, audioSource); } + AudioModification* getAudioModification () const noexcept { return _audioModification; } -private: - std::string _name; - std::vector> _audioSources; - std::vector> _musicalContexts; - std::vector> _regionSequences; -}; + ARA::ARAPlaybackTransformationFlags getTransformationFlags () const noexcept { return _transformationFlags; } + void setTransformationFlags (ARA::ARAPlaybackTransformationFlags transformationFlags) { _transformationFlags = transformationFlags; } -/*******************************************************************************/ + double getStartInModificationTime () const noexcept { return _startInModificationTime; } + void setStartInModificationTime (double startInModificationTime) { _startInModificationTime = startInModificationTime; } + double getDurationInModificationTime () const noexcept { return _durationInModificationTime; } + void setDurationInModificationTime (double durationInModificationTime) { _durationInModificationTime = durationInModificationTime; } + double getEndInModificationTime () const noexcept { return _startInModificationTime + _durationInModificationTime; } -class MusicalContext : public ContentContainer -{ -public: - MusicalContext (Document* document, std::string name, ARA::ARAColor color); + double getStartInPlaybackTime () const noexcept { return _startInPlaybackTime; } + void setStartInPlaybackTime (double startInPlaybackTime) { _startInPlaybackTime = startInPlaybackTime; } + double getDurationInPlaybackTime () const noexcept { return _durationInPlaybackTime; } + void setDurationInPlaybackTime (double durationInPlaybackTime) { _durationInPlaybackTime = durationInPlaybackTime; } + double getEndInPlaybackTime () const noexcept { return _startInPlaybackTime + _durationInPlaybackTime; } - Document* getDocument () const noexcept { return _document; } + RegionSequence* getRegionSequence () const noexcept { return _regionSequence; } + void setRegionSequence (RegionSequence* regionSequence); const std::string& getName () const noexcept { return _name; } void setName (std::string name) { _name = name; } - int getOrderIndex () const noexcept; - const ARA::ARAColor& getColor () const noexcept { return _color; } void setColor (ARA::ARAColor color) { _color = color; } - std::vector const& getRegionSequences () const noexcept { return _regionSequences; } - // Do not call those directly: instead use the related calls at the RegionSequence class, which will implictly call this. - void _addRegionSequence (RegionSequence* regionSequence) { _regionSequences.push_back (regionSequence); } - void _removeRegionSequence (RegionSequence* regionSequence) { ARA::find_erase (_regionSequences, regionSequence); } - private: - Document* const _document; + AudioModification* const _audioModification; + ARA::ARAPlaybackTransformationFlags _transformationFlags; + double _startInModificationTime; + double _durationInModificationTime; + double _startInPlaybackTime; + double _durationInPlaybackTime; + RegionSequence* _regionSequence; std::string _name; ARA::ARAColor _color; - std::vector _regionSequences; }; /*******************************************************************************/ -class RegionSequence +class AudioModification { public: - RegionSequence (Document* document, std::string name, MusicalContext* musicalContext, ARA::ARAColor color); - ~RegionSequence (); + AudioModification (AudioSource* audioSource, std::string name, std::string persistentID); - Document* getDocument () const noexcept { return _document; } + AudioSource* getAudioSource () const noexcept { return _audioSource; } const std::string& getName () const noexcept { return _name; } void setName (std::string name) { _name = name; } - int getOrderIndex () const noexcept; - - MusicalContext* getMusicalContext () const noexcept { return _musicalContext; } - void setMusicalContext (MusicalContext* musicalContext); - - const ARA::ARAColor& getColor () const noexcept { return _color; } - void setColor (ARA::ARAColor color) { _color = color; } + const std::string& getPersistentID () const noexcept { return _persistentID; } + void setPersistentID (std::string persistentID) { _persistentID = persistentID; } - std::vector const& getPlaybackRegions () const noexcept { return _playbackRegions; } - // Do not call those directly: instead use the related calls at the PlaybackRegion class, which will implictly call this. - void _addPlaybackRegion (PlaybackRegion* region) { _playbackRegions.push_back (region); } - void _removePlaybackRegion (PlaybackRegion* region) { ARA::find_erase (_playbackRegions, region); } + std::vector> const& getPlaybackRegions () const noexcept { return _playbackRegions; } + void addPlaybackRegion (std::unique_ptr&& region) { _playbackRegions.emplace_back (std::move (region)); } + void removePlaybackRegion (PlaybackRegion* region) { ARA::find_erase (_playbackRegions, region); } private: - Document* const _document; + AudioSource* const _audioSource; std::string _name; - MusicalContext* _musicalContext; - ARA::ARAColor _color; - std::vector _playbackRegions; + std::string _persistentID; + std::vector> _playbackRegions; }; /*******************************************************************************/ @@ -214,12 +200,13 @@ class AudioSource : public ContentContainer /*******************************************************************************/ -class AudioModification +class RegionSequence { public: - AudioModification (AudioSource* audioSource, std::string name, std::string persistentID); + RegionSequence (Document* document, std::string name, std::string persistentID, MusicalContext* musicalContext, ARA::ARAColor color); + ~RegionSequence (); - AudioSource* getAudioSource () const noexcept { return _audioSource; } + Document* getDocument () const noexcept { return _document; } const std::string& getName () const noexcept { return _name; } void setName (std::string name) { _name = name; } @@ -227,67 +214,82 @@ class AudioModification const std::string& getPersistentID () const noexcept { return _persistentID; } void setPersistentID (std::string persistentID) { _persistentID = persistentID; } - std::vector> const& getPlaybackRegions () const noexcept { return _playbackRegions; } - void addPlaybackRegion (std::unique_ptr&& region) { _playbackRegions.emplace_back (std::move (region)); } - void removePlaybackRegion (PlaybackRegion* region) { ARA::find_erase (_playbackRegions, region); } + int getOrderIndex () const noexcept; + + MusicalContext* getMusicalContext () const noexcept { return _musicalContext; } + void setMusicalContext (MusicalContext* musicalContext); + + const ARA::ARAColor& getColor () const noexcept { return _color; } + void setColor (ARA::ARAColor color) { _color = color; } + + std::vector const& getPlaybackRegions () const noexcept { return _playbackRegions; } + // Do not call those directly: instead use the related calls at the PlaybackRegion class, which will implictly call this. + void _addPlaybackRegion (PlaybackRegion* region) { _playbackRegions.push_back (region); } + void _removePlaybackRegion (PlaybackRegion* region) { ARA::find_erase (_playbackRegions, region); } private: - AudioSource* const _audioSource; + Document* const _document; std::string _name; std::string _persistentID; - std::vector> _playbackRegions; + MusicalContext* _musicalContext; + ARA::ARAColor _color; + std::vector _playbackRegions; }; /*******************************************************************************/ -class PlaybackRegion +class MusicalContext : public ContentContainer { public: - PlaybackRegion (AudioModification* audioModification, - ARA::ARAPlaybackTransformationFlags transformationFlags, - double startInModificationTime, - double durationInModificationTime, - double startInPlaybackTime, - double durationInPlaybackTime, - RegionSequence* regionSequence, - std::string name, - ARA::ARAColor color); - ~PlaybackRegion (); + MusicalContext (Document* document, std::string name, ARA::ARAColor color); - AudioModification* getAudioModification () const noexcept { return _audioModification; } + Document* getDocument () const noexcept { return _document; } - ARA::ARAPlaybackTransformationFlags getTransformationFlags () const noexcept { return _transformationFlags; } - void setTransformationFlags (ARA::ARAPlaybackTransformationFlags transformationFlags) { _transformationFlags = transformationFlags; } + const std::string& getName () const noexcept { return _name; } + void setName (std::string name) { _name = name; } - double getStartInModificationTime () const noexcept { return _startInModificationTime; } - void setStartInModificationTime (double startInModificationTime) { _startInModificationTime = startInModificationTime; } - double getDurationInModificationTime () const noexcept { return _durationInModificationTime; } - void setDurationInModificationTime (double durationInModificationTime) { _durationInModificationTime = durationInModificationTime; } - double getEndInModificationTime () const noexcept { return _startInModificationTime + _durationInModificationTime; } + int getOrderIndex () const noexcept; - double getStartInPlaybackTime () const noexcept { return _startInPlaybackTime; } - void setStartInPlaybackTime (double startInPlaybackTime) { _startInPlaybackTime = startInPlaybackTime; } - double getDurationInPlaybackTime () const noexcept { return _durationInPlaybackTime; } - void setDurationInPlaybackTime (double durationInPlaybackTime) { _durationInPlaybackTime = durationInPlaybackTime; } - double getEndInPlaybackTime () const noexcept { return _startInPlaybackTime + _durationInPlaybackTime; } + const ARA::ARAColor& getColor () const noexcept { return _color; } + void setColor (ARA::ARAColor color) { _color = color; } - RegionSequence* getRegionSequence () const noexcept { return _regionSequence; } - void setRegionSequence (RegionSequence* regionSequence); + std::vector const& getRegionSequences () const noexcept { return _regionSequences; } + // Do not call those directly: instead use the related calls at the RegionSequence class, which will implictly call this. + void _addRegionSequence (RegionSequence* regionSequence) { _regionSequences.push_back (regionSequence); } + void _removeRegionSequence (RegionSequence* regionSequence) { ARA::find_erase (_regionSequences, regionSequence); } + +private: + Document* const _document; + std::string _name; + ARA::ARAColor _color; + std::vector _regionSequences; +}; + +/*******************************************************************************/ + +class Document +{ +public: + Document (std::string name); const std::string& getName () const noexcept { return _name; } void setName (std::string name) { _name = name; } - const ARA::ARAColor& getColor () const noexcept { return _color; } - void setColor (ARA::ARAColor color) { _color = color; } + std::vector> const& getMusicalContexts () const noexcept { return _musicalContexts; } + void addMusicalContext (std::unique_ptr&& musicalContext) { _musicalContexts.emplace_back (std::move (musicalContext)); } + void removeMusicalContext (MusicalContext* musicalContext) { ARA::find_erase (_musicalContexts, musicalContext); } + + std::vector> const& getRegionSequences () const noexcept { return _regionSequences; } + void addRegionSequence (std::unique_ptr&& regionSequence) { _regionSequences.emplace_back (std::move (regionSequence)); } + void removeRegionSequence (RegionSequence* regionSequence) { ARA::find_erase (_regionSequences, regionSequence); } + + std::vector> const& getAudioSources () const noexcept { return _audioSources; } + void addAudioSource (std::unique_ptr&& audioSource) { _audioSources.emplace_back (std::move (audioSource)); } + void removeAudioSource (AudioSource* audioSource) { ARA::find_erase (_audioSources, audioSource); } private: - AudioModification* const _audioModification; - ARA::ARAPlaybackTransformationFlags _transformationFlags; - double _startInModificationTime; - double _durationInModificationTime; - double _startInPlaybackTime; - double _durationInPlaybackTime; - RegionSequence* _regionSequence; std::string _name; - ARA::ARAColor _color; + std::vector> _audioSources; + std::vector> _musicalContexts; + std::vector> _regionSequences; }; diff --git a/TestHost/TestCases.cpp b/TestHost/TestCases.cpp index 3287075..1f0314c 100644 --- a/TestHost/TestCases.cpp +++ b/TestHost/TestCases.cpp @@ -2,7 +2,7 @@ //! \file TestCases.cpp //! various tests simulating user interaction with the TestHost //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -60,6 +60,14 @@ AudioFileList createDummyAudioFiles (size_t numFiles) return dummyFiles; } +// Helper function to give individual color to region sequences or playback regions +inline static ARA::ARAColor getUniqueColorForIndex (size_t i) +{ + const auto remainder { i % 3 }; + const auto value { 3.0f / static_cast (i - remainder + 3) }; + return { (remainder != 0) ? value : 0.0f, (remainder != 1) ? value : 0.0f, (remainder != 2) ? value : 0.0f }; +} + /*******************************************************************************/ // Using the supplied binary, this function creates an instance of the TestHost with a document // that contains a musical context with one region sequence. Per file provided in the file list, @@ -67,7 +75,7 @@ AudioFileList createDummyAudioFiles (size_t numFiles) // the entire audio modification is placed on the region sequence. // We can optionally request the plug-in to perform its audio source analysis immediately and // block until analysis completes, or time-stretch the region if the plug-in supports this. -ARADocumentController* createHostAndBasicDocument (PlugInEntry* plugInEntry, std::unique_ptr& testHost, std::string documentName, bool requestPlugInAnalysisAndBlock, const AudioFileList& audioFiles) +static ARADocumentController* createHostAndBasicDocument (PlugInEntry* plugInEntry, std::unique_ptr& testHost, std::string documentName, bool requestPlugInAnalysisAndBlock, const AudioFileList& audioFiles) { // create our ARA host and document if (testHost == nullptr) @@ -83,12 +91,8 @@ ARADocumentController* createHostAndBasicDocument (PlugInEntry* plugInEntry, std araDocumentController->beginEditing (); // add a musical context and describe our timeline - auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", { 1.0f, 0.0f, 0.0f }) }; - - // add a region sequence to describe our arrangement with a single track - auto regionSequence { testHost->addRegionSequence (document, "Track 1", musicalContext, { 0.0f, 1.0f, 0.0f }) }; + auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", ARA::ARAColor {}) }; - double position { 0.0 }; for (size_t i { 0 }; i < audioFiles.size (); ++i) { // add an audio source based on the audio file @@ -100,11 +104,15 @@ ARADocumentController* createHostAndBasicDocument (PlugInEntry* plugInEntry, std const std::string audioModificationPersistentID { "audioModificationTestPersistentID " + std::to_string (i) }; auto audioModification { testHost->addAudioModification (document, audioSource, audioModificationName, audioModificationPersistentID) }; + // add region sequence to place the audio on + const std::string regionSequenceName { "Track for " + audioSource->getName () }; + const std::string regionSequencePersistentID { "regionSequenceTestPersistentID " + std::to_string (i) }; + auto regionSequence { testHost->addRegionSequence (document, regionSequenceName.c_str (), regionSequencePersistentID.c_str (), musicalContext, getUniqueColorForIndex (i)) }; + // add a playback region encompassing the entire audio source to render modifications in our musical context, // enabling time stretching if requested & supported const auto duration { audioSource->getDuration () }; - testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, duration, position, duration, regionSequence, "Test playback region", { 0.0f, 0.0f, 1.0f }); - position += duration; + testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, duration, 0.0, duration, regionSequence, "Test playback region", regionSequence->getColor ()); } // end the document edit cycle @@ -146,6 +154,7 @@ void testPropertyUpdates (PlugInEntry* plugInEntry, const AudioFileList& audioFi // flush the updated properties to the ARA graph using the document controller auto& audioSource { document->getAudioSources ().front () }; ARA_LOG ("Updating the name of audio source %p (ARAAudioSourceRef %p)", audioSource.get (), araDocumentController->getRef (audioSource.get ())); + const auto originalName { audioSource->getName () }; audioSource->setName ("Updated Audio Source Name"); araDocumentController->updateAudioSourceProperties (audioSource.get ()); @@ -164,6 +173,9 @@ void testPropertyUpdates (PlugInEntry* plugInEntry, const AudioFileList& audioFi // end the edit cycle once we're done updating the properties araDocumentController->endEditing (); + + // restore original name in underlying file object + audioSource->setName (originalName); } /*******************************************************************************/ @@ -223,6 +235,9 @@ void testContentUpdates (PlugInEntry* plugInEntry, const AudioFileList& audioFil { 6, 6, { 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00 }, "F#6", 28.0 }, { 6, 6, { 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00 }, "F#6", 32.0 } }; + const std::vector lyrics { { "Hel", ARA::kARAFalse, "en", 0, nullptr, nullptr, ARA::kARAContentGradeInitial, 0.0 }, + { "lo", ARA::kARATrue, "en", 0, nullptr, nullptr, ARA::kARAContentGradeInitial, 1.0 } }; + std::vector notes (12); for (size_t i { 0 }; i < notes.size (); ++i) { @@ -247,7 +262,7 @@ void testContentUpdates (PlugInEntry* plugInEntry, const AudioFileList& audioFil araDocumentController->updateMusicalContextContent (musicalContext.get (), nullptr, musicalContextUpdateScope); araDocumentController->endEditing (); - ARA_LOG ("Updating audio source %p (ARAAudioSourceRef %p) with new notes, tempo, bar signature, tuning, key signature, and chord data", audioSource.get (), araDocumentController->getRef (audioSource.get ())); + ARA_LOG ("Updating audio source %p (ARAAudioSourceRef %p) with new notes, tempo, bar signature, tuning, key signature, chord data and lyrics", audioSource.get (), araDocumentController->getRef (audioSource.get ())); constexpr auto audioSourceUpdateScope { musicalContextUpdateScope + ARA::ContentUpdateScopes::notesAreAffected () }; araDocumentController->beginEditing (); audioSource->setNotes (notes); @@ -295,6 +310,22 @@ void testModificationCloning (PlugInEntry* plugInEntry, const AudioFileList& aud auto araDocumentController { createHostAndBasicDocument (plugInEntry, testHost, "testModificationCloning", true, audioFiles) }; auto document { araDocumentController->getDocument () }; + auto logFunction { + [&araDocumentController] (AudioModification* audioModification) + { + araDocumentController->logAvailableContent (audioModification); + + if (araDocumentController->getDocumentController ()->supportsIsPlaybackRegionPreservingAudioSourceSignal ()) + { + for (const auto& playbackRegion : audioModification->getPlaybackRegions ()) + { + const auto playbackRegionRef { araDocumentController->getRef (playbackRegion.get ()) }; + const auto isPreserving { araDocumentController->getDocumentController ()->isPlaybackRegionPreservingAudioSourceSignal (playbackRegionRef) }; + ARA_LOG ("ARAPlaybackRegionRef %p %s audio source signal.", playbackRegionRef, (isPreserving) ? "preserves" : "modifies"); + } + } + } }; + // read all content for the original audio modification and playback region // and construct a vector of audio modifications to clone std::vector audioModificationsToClone; @@ -302,9 +333,7 @@ void testModificationCloning (PlugInEntry* plugInEntry, const AudioFileList& aud { for (const auto& audioModification : audioSource->getAudioModifications ()) { - araDocumentController->logAvailableContent (audioModification.get ()); - araDocumentController->logAudioModificationPreservesAudioSourceSignalIfSupported (audioModification.get ()); - + logFunction (audioModification.get ()); audioModificationsToClone.push_back (audioModification.get ()); } } @@ -338,10 +367,7 @@ void testModificationCloning (PlugInEntry* plugInEntry, const AudioFileList& aud // read back all the cloned audio modification content for (const auto& audioModificationClone : audioModificationClones) - { - araDocumentController->logAvailableContent (audioModificationClone); - araDocumentController->logAudioModificationPreservesAudioSourceSignalIfSupported (audioModificationClone); - } + logFunction (audioModificationClone); } /*******************************************************************************/ @@ -350,19 +376,16 @@ void testArchiving (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) { ARA_LOG_TEST_HOST_FUNC ("archiving"); - bool supportsARA2Persistency { false }; // will be properly set after creating document controller - MemoryArchive archive { plugInEntry->getARAFactory ()->documentArchiveID }; - // create and archive the document, - // caching the audio source / modification persistent IDs + // create and archive the document, caching all persistent IDs + std::vector regionSequencePersistentIDs; std::vector audioSourcePersistentIDs; std::map> audioModificationPersistentIDs; { // create basic ARA model graph and perform analysis std::unique_ptr testHost; auto araDocumentController { createHostAndBasicDocument (plugInEntry, testHost, "testArchiving", true, audioFiles) }; - supportsARA2Persistency = araDocumentController->supportsPartialPersistency (); // log the audio source and modification content as reference for (const auto& audioSource : araDocumentController->getDocument ()->getAudioSources ()) @@ -384,13 +407,13 @@ void testArchiving (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) for (const auto& audioModification : audioSource->getAudioModifications ()) audioModificationPersistentIDs[audioSource->getPersistentID ()].push_back (audioModification->getPersistentID ()); } + for (const auto& regionSequence : araDocumentController->getDocument ()->getRegionSequences ()) + regionSequencePersistentIDs.push_back (regionSequence->getPersistentID ()); + // this simplistic implementation requires a region sequence per audio source + ARA_INTERNAL_ASSERT (audioSourcePersistentIDs.size () == regionSequencePersistentIDs.size ()); // store our analysis results - bool archivingSuccess { false }; - if (supportsARA2Persistency) - archivingSuccess = araDocumentController->storeObjectsToArchive (&archive); - else - archivingSuccess = araDocumentController->storeDocumentToArchive (&archive); + bool archivingSuccess { araDocumentController->storeObjectsToArchive (&archive) }; ARA_VALIDATE_API_STATE (archivingSuccess); // our archive writer implementation never returns false, so this must always succeed } @@ -405,24 +428,20 @@ void testArchiving (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) auto araDocumentController { testHost->getDocumentController (document) }; // begin the document edit cycle to configure and restore the document - bool unarchivingSuccess { true }; - if (supportsARA2Persistency) - araDocumentController->beginEditing (); - else - unarchivingSuccess = araDocumentController->beginRestoringDocumentFromArchive (&archive); + araDocumentController->beginEditing (); // add a musical context and describe our timeline - auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", { 1.0f, 0.0f, 0.0f }) }; - - // add a region sequence to describe our arrangement with a single track - auto regionSequence { testHost->addRegionSequence (document, "Track 1", musicalContext, { 0.0f, 1.0f, 0.0f }) }; + auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", ARA::ARAColor {}) }; - // recreate the audio sources / modifications based on our cached persistent IDs + // recreate the audio sources / modifications and region sequences based on our cached persistent IDs for (size_t i { 0 }; i < audioFiles.size (); ++i) { auto audioSource { testHost->addAudioSource (document, audioFiles[i].get (), audioSourcePersistentIDs[i]) }; araDocumentController->enableAudioSourceSamplesAccess (audioSource, true); + const std::string regionSequenceName { "Track for " + audioSource->getName () }; + auto regionSequence { testHost->addRegionSequence (document, regionSequenceName.c_str (), regionSequencePersistentIDs[i], musicalContext, getUniqueColorForIndex (i)) }; + for (size_t j { 0 }; j < audioModificationPersistentIDs[audioSource->getPersistentID ()].size (); ++j) { const std::string audioModificationName { "Test audio modification " + std::to_string (i) + " " + std::to_string (j) }; @@ -432,20 +451,13 @@ void testArchiving (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) const auto playbackDuration { audioSource->getDuration () }; testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, audioSource->getDuration (), static_cast (i) * playbackDuration, playbackDuration, - regionSequence, "Test playback region", { 0.0f, 0.0f, 1.0f }); + regionSequence, "Test playback region", regionSequence->getColor ()); } } // inject state and end the document edit cycle - if (supportsARA2Persistency) - { - unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (&archive); - araDocumentController->endEditing (); - } - else - { - unarchivingSuccess = araDocumentController->endRestoringDocumentFromArchive (&archive) && unarchivingSuccess; - } + auto unarchivingSuccess { araDocumentController->restoreObjectsFromArchive (&archive) }; + araDocumentController->endEditing (); ARA_VALIDATE_API_STATE (unarchivingSuccess); // our archive reader implementation never returns false, and the archive // was created on the same machine, so this call must always succeed @@ -464,31 +476,28 @@ void testArchiving (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) // plug-ins must deal with archives containing more data than actually being restored. // to test this, we delete our first source, then restore again. - if (supportsARA2Persistency) + araDocumentController->beginEditing (); + auto sourceToRemove { document->getAudioSources ().front ().get () }; + auto modificationToRemove { sourceToRemove->getAudioModifications ().front ().get () }; + auto regionToRemove { modificationToRemove->getPlaybackRegions ().front ().get () }; + testHost->removePlaybackRegion (document, regionToRemove); + testHost->removeAudioModification (document, modificationToRemove); + testHost->removeAudioSource (document, sourceToRemove); + + unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (&archive); + ARA_VALIDATE_API_STATE (unarchivingSuccess); + araDocumentController->endEditing (); + + // log the restored audio source and modification content to verify proper restoration + for (const auto& audioSource : document->getAudioSources ()) { - araDocumentController->beginEditing (); - auto sourceToRemove { document->getAudioSources().front ().get () }; - auto modificationToRemove { sourceToRemove->getAudioModifications ().front ().get () }; - auto regionToRemove { modificationToRemove->getPlaybackRegions ().front ().get () }; - testHost->removePlaybackRegion (document, regionToRemove); - testHost->removeAudioModification (document, modificationToRemove); - testHost->removeAudioSource (document, sourceToRemove); - - unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (&archive); - ARA_VALIDATE_API_STATE (unarchivingSuccess); - araDocumentController->endEditing (); - - // log the restored audio source and modification content to verify proper restoration - for (const auto& audioSource : document->getAudioSources ()) - { - ARA_LOG ("Audio source %p (ARAAudioSourceRef %p) with persistent ID \"%s\" has been restored", audioSource.get (), araDocumentController->getRef (audioSource.get ()), audioSource->getPersistentID ().c_str ()); - araDocumentController->logAvailableContent (audioSource.get ()); + ARA_LOG ("Audio source %p (ARAAudioSourceRef %p) with persistent ID \"%s\" has been restored", audioSource.get (), araDocumentController->getRef (audioSource.get ()), audioSource->getPersistentID ().c_str ()); + araDocumentController->logAvailableContent (audioSource.get ()); - for (const auto& audioModification : audioSource->getAudioModifications ()) - { - ARA_LOG ("Audio modification %p (ARAAudioModificationRef %p) with persistent ID \"%s\" has been restored", audioModification.get (), araDocumentController->getRef (audioModification.get ()), audioModification->getPersistentID ().c_str ()); - araDocumentController->logAvailableContent (audioModification.get ()); - } + for (const auto& audioModification : audioSource->getAudioModifications ()) + { + ARA_LOG ("Audio modification %p (ARAAudioModificationRef %p) with persistent ID \"%s\" has been restored", audioModification.get (), araDocumentController->getRef (audioModification.get ()), audioModification->getPersistentID ().c_str ()); + araDocumentController->logAvailableContent (audioModification.get ()); } } } @@ -504,6 +513,8 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile // create and archive the document, // caching the audio source / modification persistent IDs MemoryArchive documentDataArchive { plugInEntry->getARAFactory ()->documentArchiveID }; + std::vector regionSequencePersistentIDs; + MemoryArchive regionSequencesArchive { plugInEntry->getARAFactory ()->documentArchiveID }; std::vector audioSourcePersistentIDs; std::vector> audioSourceArchives; std::map> audioModificationPersistentIDs; @@ -512,11 +523,6 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile // create basic ARA model graph and perform analysis std::unique_ptr testHost; auto araDocumentController { createHostAndBasicDocument (plugInEntry, testHost, "testSplitArchives", true, audioFiles) }; - if (!araDocumentController->supportsPartialPersistency ()) - { - ARA_LOG ("ARA2 Partial Persistency not supported by plug-in %s, skipping split archives test", plugInEntry->getARAFactory ()->plugInName); - return; - } // log the audio source and modification content as reference for (const auto& audioSource : araDocumentController->getDocument ()->getAudioSources ()) @@ -534,22 +540,39 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile } // store document data - const ARA::SizedStruct storeDocumentDataFilter { ARA::kARATrue, - 0U, nullptr, - 0U, nullptr - }; + const ARA::SizedStruct<&ARA::ARAStoreObjectsFilter::regionSequenceRefs> storeDocumentDataFilter { ARA::kARATrue, + 0U, nullptr, + 0U, nullptr, + 0U, nullptr + }; auto archivingSuccess { araDocumentController->storeObjectsToArchive (&documentDataArchive, &storeDocumentDataFilter) }; ARA_VALIDATE_API_STATE (archivingSuccess); // our archive writer implementation never returns false, so this must always succeed + // store all region sequence data into a single archive, and store their persistent IDs + std::vector regionSequenceRefs; + for (const auto& regionSequence : araDocumentController->getDocument ()->getRegionSequences ()) + { + regionSequencePersistentIDs.push_back (regionSequence->getPersistentID ()); + regionSequenceRefs.push_back (araDocumentController->getRef (regionSequence.get ())); + } + const ARA::SizedStruct<&ARA::ARAStoreObjectsFilter::regionSequenceRefs> storeRegionSequencesFilter { ARA::kARAFalse, + 0U, nullptr, + 0U, nullptr, + regionSequenceRefs.size (), regionSequenceRefs.data () + }; + archivingSuccess = araDocumentController->storeObjectsToArchive (®ionSequencesArchive, &storeRegionSequencesFilter); + ARA_VALIDATE_API_STATE (archivingSuccess); // our archive writer implementation never returns false, so this must always succeed + // store each audio source and audio modification into an individual archive, and store their persistent IDs for (const auto& audioSource : araDocumentController->getDocument ()->getAudioSources ()) { audioSourcePersistentIDs.push_back (audioSource->getPersistentID ()); const auto audioSourceRef { araDocumentController->getRef (audioSource.get ()) }; - const ARA::SizedStruct storeAudioSourceFilter { ARA::kARAFalse, - 1U, &audioSourceRef, - 0U, nullptr - }; + const ARA::SizedStruct<&ARA::ARAStoreObjectsFilter::regionSequenceRefs> storeAudioSourceFilter { ARA::kARAFalse, + 1U, &audioSourceRef, + 0U, nullptr, + 0U, nullptr + }; audioSourceArchives.emplace_back (new MemoryArchive { plugInEntry->getARAFactory ()->documentArchiveID }); archivingSuccess = araDocumentController->storeObjectsToArchive (audioSourceArchives.back ().get (), &storeAudioSourceFilter); ARA_VALIDATE_API_STATE (archivingSuccess); // our archive writer implementation never returns false, so this must always succeed @@ -558,10 +581,11 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile { audioModificationPersistentIDs[audioSource->getPersistentID ()].push_back (audioModification->getPersistentID ()); const auto audioModificationRef { araDocumentController->getRef (audioModification.get ()) }; - const ARA::SizedStruct storeAudioModificationFilter { ARA::kARAFalse, - 0U, nullptr, - 1U, &audioModificationRef - }; + const ARA::SizedStruct<&ARA::ARAStoreObjectsFilter::regionSequenceRefs> storeAudioModificationFilter { ARA::kARAFalse, + 0U, nullptr, + 1U, &audioModificationRef, + 0U, nullptr, + }; audioModificationArchives.emplace_back (new MemoryArchive { plugInEntry->getARAFactory ()->documentArchiveID }); archivingSuccess = araDocumentController->storeObjectsToArchive (audioModificationArchives.back ().get (), &storeAudioModificationFilter); ARA_VALIDATE_API_STATE (archivingSuccess); // our archive writer implementation never returns false, so this must always succeed @@ -583,13 +607,31 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile araDocumentController->beginEditing (); // add a musical context and describe our timeline - auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", { 1.0f, 0.0f, 0.0f }) }; + auto musicalContext { testHost->addMusicalContext (document, "ARA Test Musical Context", ARA::ARAColor {}) }; + + // recreate the region sequences based on our cached persistent IDs + bool unarchivingSuccess { false }; + std::vector rawRegionSequencePersistentIDs; + for (size_t i { 0 }; i < regionSequencePersistentIDs.size (); ++i) + { + // recreate region sequence + std::string trackName { "Track " + std::to_string (i + 1) }; + testHost->addRegionSequence (document, trackName, regionSequencePersistentIDs[i], musicalContext, getUniqueColorForIndex (i)); + + rawRegionSequencePersistentIDs.push_back (regionSequencePersistentIDs[i].c_str ()); + } - // add a region sequence to describe our arrangement with a single track - auto regionSequence { testHost->addRegionSequence (document, "Track 1", musicalContext, { 0.0f, 1.0f, 0.0f }) }; + // inject region sequences state + const ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreRegionSequencesFilter { ARA::kARAFalse, + 0U, nullptr, nullptr, + 0U, nullptr, nullptr, + rawRegionSequencePersistentIDs.size (), rawRegionSequencePersistentIDs.data (), nullptr + }; + unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (®ionSequencesArchive, &restoreRegionSequencesFilter); + ARA_VALIDATE_API_STATE (unarchivingSuccess); // our archive reader implementation never returns false, and the archive + // was created on the same machine, so this call must always succeed // recreate the audio sources / modifications based on our cached persistent IDs, immediately injecting the respective state - bool unarchivingSuccess { false }; for (size_t i { 0 }; i < audioFiles.size (); ++i) { // recreate audio source @@ -597,14 +639,16 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile // inject audio source state const auto audioSourcePersistentID { audioSource->getPersistentID ().c_str () }; - const ARA::SizedStruct restoreAudioSourceFilter { ARA::kARAFalse, - 1U, &audioSourcePersistentID, nullptr, - 0U, nullptr, nullptr - }; + const ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreAudioSourceFilter { ARA::kARAFalse, + 1U, &audioSourcePersistentID, nullptr, + 0U, nullptr, nullptr, + 0U, nullptr, nullptr + }; unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (audioSourceArchives[i].get (), &restoreAudioSourceFilter); ARA_VALIDATE_API_STATE (unarchivingSuccess); // our archive reader implementation never returns false, and the archive // was created on the same machine, so this call must always succeed + const auto regionSequence { araDocumentController->getDocument ()->getRegionSequences ().front (). get () }; for (size_t j { 0 }; j < audioModificationPersistentIDs[audioSource->getPersistentID ()].size (); ++j) { // recreate audio modification @@ -613,10 +657,11 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile // inject audio modification state const auto audioModificationPersistentID { audioModification->getPersistentID ().c_str () }; - const ARA::SizedStruct restoreAudioModificationFilter { ARA::kARAFalse, - 0U, nullptr, nullptr, - 1U, &audioModificationPersistentID, nullptr - }; + const ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreAudioModificationFilter { ARA::kARAFalse, + 0U, nullptr, nullptr, + 1U, &audioModificationPersistentID, nullptr, + 0U, nullptr, nullptr + }; unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (audioModificationArchives[i].get (), &restoreAudioModificationFilter); ARA_VALIDATE_API_STATE (unarchivingSuccess); // our archive reader implementation never returns false, and the archive // was created on the same machine, so this call must always succeed @@ -625,7 +670,7 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile const auto playbackDuration { audioSource->getDuration () }; testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, audioSource->getDuration (), static_cast (i) * playbackDuration, playbackDuration, - regionSequence, "Test playback region", { 0.0f, 0.0f, 1.0f }); + regionSequence, "Test playback region", regionSequence->getColor ()); } // enable audio source access @@ -633,10 +678,11 @@ void testSplitArchives (PlugInEntry* plugInEntry, const AudioFileList& audioFile } // finally, inject document data and end the document edit cycle - ARA::SizedStruct restoreDocumentDataFilter { ARA::kARATrue, - 0U, nullptr, nullptr, - 0U, nullptr, nullptr - }; + ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreDocumentDataFilter { ARA::kARATrue, + 0U, nullptr, nullptr, + 0U, nullptr, nullptr, + 0U, nullptr, nullptr + }; unarchivingSuccess = araDocumentController->restoreObjectsFromArchive (&documentDataArchive, &restoreDocumentDataFilter); ARA_VALIDATE_API_STATE (unarchivingSuccess); // our archive reader implementation never returns false, and the archive // was created on the same machine, so this call must always succeed @@ -667,12 +713,6 @@ void testDragAndDrop (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) ARA_LOG_TEST_HOST_FUNC ("drag and drop"); std::unique_ptr testHost; - auto testDocController { createHostAndBasicDocument (plugInEntry, testHost, "ARA2PersistencyTestDoc", false, {}) }; - if (!testDocController->supportsPartialPersistency ()) - { - ARA_LOG ("ARA2 Partial Persistency not supported by plug-in %s, skipping drag and drop test", plugInEntry->getARAFactory ()->plugInName); - return; - } // create our "drag" document with two audio sources and perform analysis auto dragDocumentController { createHostAndBasicDocument (plugInEntry, testHost, "Drag Document", true, audioFiles) }; @@ -693,10 +733,11 @@ void testDragAndDrop (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) // use a StoreObjectsFilter to create a "drag" archive containing only draggedAudioSource / Modification auto draggedAudioSourceRef { dragDocumentController->getRef (draggedAudioSource) }; auto draggedAudioModificationRef { dragDocumentController->getRef (draggedAudioModification) }; - const ARA::SizedStruct storeObjectsFilter { ARA::kARATrue, - 1U, &draggedAudioSourceRef, - 1U, &draggedAudioModificationRef - }; + const ARA::SizedStruct<&ARA::ARAStoreObjectsFilter::regionSequenceRefs> storeObjectsFilter { ARA::kARATrue, + 1U, &draggedAudioSourceRef, + 1U, &draggedAudioModificationRef, + 0U, nullptr + }; // store only the dragged audio source's data in the archive ARA_LOG ("Dragging audio source with persistent ID \"%s\" from %s", draggedAudioSource->getPersistentID ().c_str (), dragDocument->getName ().c_str ()); @@ -722,10 +763,11 @@ void testDragAndDrop (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) const auto audioModificationArchiveID { draggedAudioModification->getPersistentID ().c_str () }; const auto audioSourceCurrentID { dropAudioSource->getPersistentID ().c_str () }; const auto audioModificationCurrentID { dropAudioModification->getPersistentID ().c_str () }; - const ARA::SizedStruct restoreObjectsFilter { ARA::kARATrue, - 1U, &audioSourceArchiveID, &audioSourceCurrentID, - 1U, &audioModificationArchiveID, &audioModificationCurrentID - }; + const ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreObjectsFilter { ARA::kARATrue, + 1U, &audioSourceArchiveID, &audioSourceCurrentID, + 1U, &audioModificationArchiveID, &audioModificationCurrentID, + 0U, nullptr, nullptr + }; ARA_LOG ("Dropping dragged data into audio source with persistent ID \"%s\" to %s", audioSourceCurrentID, dropDocument->getName ().c_str ()); const bool unarchivingSuccess { dropDocumentController->restoreObjectsFromArchive (&clipBoardArchive, &restoreObjectsFilter) }; @@ -748,7 +790,7 @@ void testDragAndDrop (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) // using the companion API rendering methods // Can optionally use an ARA plug-in's time stretching capabilities to stretch a playback region - // try loading Melodyne to see this feature in action -void testPlaybackRendering (PlugInEntry* plugInEntry, bool enableTimeStretchingIfSupported, const AudioFileList& audioFiles) +void testPlaybackRendering (PlugInEntry* plugInEntry, bool enableTimestretchingIfSupported, const AudioFileList& audioFiles) { ARA_LOG_TEST_HOST_FUNC ("playback rendering (with time stretching if supported)"); @@ -843,24 +885,24 @@ void testPlaybackRendering (PlugInEntry* plugInEntry, bool enableTimeStretchingI logRenderResults (); // optionally perform the render again if the plug-in supports time stretching - if (enableTimeStretchingIfSupported) + if (enableTimestretchingIfSupported) { const auto supportedTransformationFlags { plugInEntry->getARAFactory ()->supportedPlaybackTransformationFlags }; if ((supportedTransformationFlags & ARA::kARAPlaybackTransformationTimestretch) != 0) { - constexpr double timeStretchFactor { 0.75 }; - ARA_LOG ("Applying time stretch factor of %lg to all playback regions assigned to playback renderer %p", timeStretchFactor, playbackRenderer.getRef ()); + constexpr double timestretchFactor { 0.75 }; + ARA_LOG ("Applying time stretch factor of %lg to all playback regions assigned to playback renderer %p", timestretchFactor, playbackRenderer.getRef ()); araDocumentController->beginEditing (); for (auto& playbackRegion : playbackRegions) { playbackRegion->setTransformationFlags (ARA::kARAPlaybackTransformationTimestretch + playbackRegion->getTransformationFlags ()); - playbackRegion->setDurationInPlaybackTime (timeStretchFactor * playbackRegion->getDurationInPlaybackTime ()); + playbackRegion->setDurationInPlaybackTime (timestretchFactor * playbackRegion->getDurationInPlaybackTime ()); araDocumentController->updatePlaybackRegionProperties (playbackRegion); } araDocumentController->endEditing (); - endOfPlaybackRegions *= timeStretchFactor; + endOfPlaybackRegions *= timestretchFactor; endOfPlaybackRegionSamples = ARA::samplePositionAtTime (endOfPlaybackRegions, renderSampleRate); ARA_LOG ("Rendering %lu region(s) assigned to playback renderer %p with sample rate %lgHz", playbackRegions.size (), playbackRenderer.getRef (), renderSampleRate); @@ -901,7 +943,7 @@ void testEditorView (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) auto editorView { plugInInstance->getEditorView () }; // Selection demonstration - using Selection = ARA::SizedStruct; + using Selection = ARA::SizedStruct<&ARA::ARAViewSelection::timeRange>; // create a "selection" containing all playback regions in the document and notify the editor view std::vector playbackRegionRefs; @@ -994,8 +1036,9 @@ void testAudioFileChunkLoading (PlugInEntry* plugInEntry, const AudioFileList& a auto araDocumentController { createHostAndBasicDocument (plugInEntry, testHost, "testAudioFileChunkLoading", false, {}) }; const auto araFactory { plugInEntry->getARAFactory () }; const auto document { araDocumentController->getDocument () }; + const auto musicalContext { document->getMusicalContexts ().front ().get () }; - auto index { 0 }; + auto index { 0U }; for (const auto& audioFile : audioFiles) { // find matching ARA archive @@ -1039,28 +1082,33 @@ void testAudioFileChunkLoading (PlugInEntry* plugInEntry, const AudioFileList& a // partial persistence - restore this audio source using the archive stored in the XML data const auto oldID { persistentID.c_str () }; const auto newID { newPersistentID.c_str () }; - const ARA::SizedStruct restoreObjectsFilter { ARA::kARAFalse, - 1U, &oldID, &newID, - 0U, nullptr, nullptr - }; + const ARA::SizedStruct<&ARA::ARARestoreObjectsFilter::regionSequenceCurrentIDs> restoreObjectsFilter { ARA::kARAFalse, + 1U, &oldID, &newID, + 0U, nullptr, nullptr, + 0U, nullptr, nullptr + }; // load chunk and enable sample access const auto unarchivingSuccess { araDocumentController->restoreObjectsFromArchive (&archive, &restoreObjectsFilter) }; ARA_VALIDATE_API_STATE (unarchivingSuccess); araDocumentController->enableAudioSourceSamplesAccess (audioSource, true); - // add audio modification and playback region + // add audio modification, region sequence and playback region const std::string audioModificationPersistentID { "audioModificationTestPersistentID " + std::to_string (index) }; - const auto duration { audioSource->getDuration () }; auto audioModification { testHost->addAudioModification (document, audioSource, audioFile->getName () + " Modification", audioModificationPersistentID.c_str ()) }; - testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, duration, 0.0, duration, document->getRegionSequences ()[0].get (), audioFile->getName () + "Playback Region", ARA::ARAColor {}); + + const std::string regionSequenceName { "Track for " + audioSource->getName () }; + const std::string regionSequencePersistentID { "regionSequenceTestPersistentID " + std::to_string (index) }; + auto regionSequence { testHost->addRegionSequence (document, regionSequenceName.c_str (), regionSequencePersistentID.c_str (), musicalContext, getUniqueColorForIndex (index)) }; + + const auto duration { audioSource->getDuration () }; + testHost->addPlaybackRegion (document, audioModification, ARA::kARAPlaybackTransformationNoChanges, 0.0, duration, 0.0, duration, regionSequence, audioFile->getName () + "Playback Region", regionSequence->getColor ()); // conclude loading chunk araDocumentController->endEditing (); // log the restored audio source content - for (auto i { 0U }; i < araFactory->analyzeableContentTypesCount; ++i) - araDocumentController->logAvailableContent (audioSource); + araDocumentController->logAvailableContent (audioSource); ++index; } @@ -1091,8 +1139,7 @@ void testAudioFileChunkSaving (PlugInEntry* plugInEntry, AudioFileList& audioFil for (const auto& audioSource : document->getAudioSources ()) { // log the audio source content to store - for (auto i { 0U }; i < araFactory->analyzeableContentTypesCount; ++i) - araDocumentController->logAvailableContent (audioSource.get ()); + araDocumentController->logAvailableContent (audioSource.get ()); // store archive for this audio source MemoryArchive archive { araFactory->documentArchiveID }; diff --git a/TestHost/TestCases.h b/TestHost/TestCases.h index 37518a1..27b2982 100644 --- a/TestHost/TestCases.h +++ b/TestHost/TestCases.h @@ -2,7 +2,7 @@ //! \file TestCases.h //! various tests simulating user interaction with the TestHost //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -62,7 +62,7 @@ void testDragAndDrop (PlugInEntry* plugInEntry, const AudioFileList& audioFiles) // using the companion API rendering methods // Can optionally use an ARA plug-in's time stretching capabilities to stretch a playback region - // try loading Melodyne to see this feature in action -void testPlaybackRendering (PlugInEntry* plugInEntry, bool enableTimeStretchingIfSupported, const AudioFileList& audioFiles); +void testPlaybackRendering (PlugInEntry* plugInEntry, bool enableTimestretchingIfSupported, const AudioFileList& audioFiles); // Demonstrates how to communicate view selection and region sequence hiding // (albeit this is of rather limited use in a non-UI application) diff --git a/TestHost/TestHost.cpp b/TestHost/TestHost.cpp index d18c03e..d82a9e7 100644 --- a/TestHost/TestHost.cpp +++ b/TestHost/TestHost.cpp @@ -2,7 +2,7 @@ //! \file TestHost.cpp //! class that maintains the model graph and ARA document controller //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -92,9 +92,9 @@ void TestHost::removeMusicalContext (Document* document, MusicalContext* musical document->removeMusicalContext (musicalContext); } -RegionSequence* TestHost::addRegionSequence (Document* document, std::string name, MusicalContext* musicalContext, ARA::ARAColor color) +RegionSequence* TestHost::addRegionSequence (Document* document, std::string name, std::string persistentID, MusicalContext* musicalContext, ARA::ARAColor color) { - document->addRegionSequence (std::make_unique (document, name, musicalContext, color)); + document->addRegionSequence (std::make_unique (document, name, persistentID, musicalContext, color)); auto regionSequence = document->getRegionSequences ().back ().get (); if (auto araDocumentController = getDocumentController (document)) araDocumentController->addRegionSequence (regionSequence); diff --git a/TestHost/TestHost.h b/TestHost/TestHost.h index 6e5377d..953a134 100644 --- a/TestHost/TestHost.h +++ b/TestHost/TestHost.h @@ -2,7 +2,7 @@ //! \file TestHost.h //! class that maintains the model graph and ARA document controller //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -48,7 +48,7 @@ class TestHost MusicalContext* addMusicalContext (Document* document, std::string name, ARA::ARAColor color); void removeMusicalContext (Document* document, MusicalContext* musicalContext); - RegionSequence* addRegionSequence (Document* document, std::string name, MusicalContext* musicalContext, ARA::ARAColor color); + RegionSequence* addRegionSequence (Document* document, std::string name, std::string persistentID, MusicalContext* musicalContext, ARA::ARAColor color); void removeRegionSequence (Document* document, RegionSequence* regionSequence); AudioSource* addAudioSource (Document* document, AudioFileBase* audioFile, std::string persistentID); diff --git a/TestHost/main.cpp b/TestHost/main.cpp index 79cf8a0..cfc0f9c 100644 --- a/TestHost/main.cpp +++ b/TestHost/main.cpp @@ -2,7 +2,7 @@ //! \file main.cpp //! main implementation of the SDK testhost example //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -79,7 +79,7 @@ ARA::ARAAssertFunction* assertFunctionReference { &assertFunction }; ARA_SETUP_DEBUG_MESSAGE_PREFIX ("ARATestHost"); -AudioFileList parseAudioFiles (const std::vector& args) +static AudioFileList parseAudioFiles (const std::vector& args) { AudioFileList parsedFiles; auto it { args.begin () }; @@ -91,8 +91,7 @@ AudioFileList parseAudioFiles (const std::vector& args) ((*it)[0] != '-')) { icstdsp::AudioFile audioFile; - int ARA_MAYBE_UNUSED_VAR (err); - err = audioFile.Load (it->c_str ()); + [[maybe_unused]] const auto err { audioFile.Load (it->c_str ()) }; ARA_INTERNAL_ASSERT (err == 0); parsedFiles.emplace_back (std::make_shared (*it++, std::move (audioFile))); } @@ -105,7 +104,7 @@ AudioFileList parseAudioFiles (const std::vector& args) return createDummyAudioFiles (1); } -const std::vector parseTestCases (const std::vector& args) +static const std::vector parseTestCases (const std::vector& args) { std::vector parsedTests; auto it { args.begin () }; @@ -201,9 +200,17 @@ int main (int argc, const char* argv[]) ARA_LOG (" plug-in does%s support content-based fades.", ((factory->supportedPlaybackTransformationFlags & ARA::kARAPlaybackTransformationContentBasedFades) != 0) ? "" : " not"); - ARA_LOG (" plug-in does%s support storing audio file chunks.", (factory.implements () && + ARA_LOG (" plug-in does%s support storing audio file chunks.", (factory.implements<&ARA::ARAFactory::supportsStoringAudioFileChunks> () && (factory->supportsStoringAudioFileChunks != ARA::kARAFalse)) ? "" : " not"); + ARA_LOG (" plug-in can%s be used with sample-based audio sources.", (!factory.implements<&ARA::ARAFactory::supportsSampleBasedAudioSources> () || + (factory->supportsSampleBasedAudioSources != ARA::kARAFalse)) ? "" : " not"); + ARA_LOG (" plug-in can%s be used with content-only audio sources.", (factory.implements<&ARA::ARAFactory::supportsContentOnlyAudioSources> () && + (factory->supportsContentOnlyAudioSources != ARA::kARAFalse)) ? "" : " not"); + + ARA_LOG (" plug-in does%s require preset audio sources.", (factory.implements<&ARA::ARAFactory::requiresPresetAudioSources> () && + (factory->requiresPresetAudioSources != ARA::kARAFalse)) ? "" : " not"); + // parse any optional test cases or audio files auto audioFiles { parseAudioFiles (args) }; const auto testCases { parseTestCases (args) }; diff --git a/TestPlugIn/ARATestAudioSource.cpp b/TestPlugIn/ARATestAudioSource.cpp index 6295c08..32b9fd0 100644 --- a/TestPlugIn/ARATestAudioSource.cpp +++ b/TestPlugIn/ARATestAudioSource.cpp @@ -3,7 +3,7 @@ //! audio source implementation for the ARA test plug-in, //! customizing the audio source base class of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/ARATestAudioSource.h b/TestPlugIn/ARATestAudioSource.h index 37d0b91..4f97a6f 100644 --- a/TestPlugIn/ARATestAudioSource.h +++ b/TestPlugIn/ARATestAudioSource.h @@ -3,7 +3,7 @@ //! audio source implementation for the ARA test plug-in, //! customizing the audio source base class of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/ARATestDocumentController.cpp b/TestPlugIn/ARATestDocumentController.cpp index 8be322a..1cf28ac 100644 --- a/TestPlugIn/ARATestDocumentController.cpp +++ b/TestPlugIn/ARATestDocumentController.cpp @@ -3,7 +3,7 @@ //! document controller implementation for the ARA test plug-in, //! customizing the document controller and related base classes of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -211,7 +211,7 @@ class AlgorithmPropertiesWrapper private: const TestProcessingAlgorithm* const _algorithm; - ARA::SizedStruct _algorithmProperties; + ARA::SizedStruct<&ARA::ARAProcessingAlgorithmProperties::name> _algorithmProperties; }; /*******************************************************************************/ @@ -589,25 +589,28 @@ void ARATestDocumentController::doUpdateMusicalContextContent (ARA::PlugIn::Musi // just like some alignment plug-in would have - this state would be updated when adding or removing // regions to the arrangement or when their time ranges or stretching mode changes. -void ARATestDocumentController::didAddPlaybackRegionToRegionSequence (ARA::PlugIn::RegionSequence* /*regionSequence*/, ARA::PlugIn::PlaybackRegion* /*playbackRegion*/) noexcept +void ARATestDocumentController::didAddPlaybackRegionToRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* /*playbackRegion*/) noexcept { - notifyDocumentDataChanged (); + notifyRegionSequenceDataChanged (regionSequence); } -void ARATestDocumentController::willRemovePlaybackRegionFromRegionSequence (ARA::PlugIn::RegionSequence* /*regionSequence*/, ARA::PlugIn::PlaybackRegion* /*playbackRegion*/) noexcept +void ARATestDocumentController::willRemovePlaybackRegionFromRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* /*playbackRegion*/) noexcept { - notifyDocumentDataChanged (); + notifyRegionSequenceDataChanged (regionSequence); } void ARATestDocumentController::willUpdatePlaybackRegionProperties (ARA::PlugIn::PlaybackRegion* playbackRegion, ARA::PlugIn::PropertiesPtr newProperties) noexcept { - if ((playbackRegion->isTimestretchEnabled () != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretch) != 0) ) || - (playbackRegion->isTimeStretchReflectingTempo () != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretchReflectingTempo) != 0) ) || - (playbackRegion->getStartInAudioModificationTime () != newProperties->startInModificationTime) || - (playbackRegion->getDurationInAudioModificationTime () != newProperties->durationInModificationTime) || - (playbackRegion->getStartInPlaybackTime () != newProperties->startInPlaybackTime) || - (playbackRegion->getDurationInPlaybackTime () != newProperties->durationInPlaybackTime)) - notifyDocumentDataChanged (); + if (const auto regionSequence { playbackRegion->getRegionSequence () }) // upon creation, there will be no region sequence set yet - + { // the update will happen later upon didAddPlaybackRegionToRegionSequence () + if ((playbackRegion->isTimestretchEnabled () != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretch) != 0)) || + (playbackRegion->isTimestretchReflectingTempo () != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretchReflectingTempo) != 0)) || + (playbackRegion->getStartInAudioModificationTime () != newProperties->startInModificationTime) || + (playbackRegion->getDurationInAudioModificationTime () != newProperties->durationInModificationTime) || + (playbackRegion->getStartInPlaybackTime () != newProperties->startInPlaybackTime) || + (playbackRegion->getDurationInPlaybackTime () != newProperties->durationInPlaybackTime)) + notifyRegionSequenceDataChanged (regionSequence); + } } /*******************************************************************************/ @@ -788,7 +791,7 @@ ARA::PlugIn::ContentReader* ARATestDocumentController::doCreatePlaybackRegionCon /*******************************************************************************/ -void ARATestDocumentController::doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, std::vector const& ARA_MAYBE_UNUSED_ARG (contentTypes)) noexcept +void ARATestDocumentController::doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, [[maybe_unused]] std::vector const& contentTypes) noexcept { ARA_INTERNAL_ASSERT (contentTypes.size () == 1); ARA_INTERNAL_ASSERT (contentTypes[0] == ARA::kARAContentTypeNotes); @@ -805,7 +808,7 @@ void ARATestDocumentController::doRequestAudioSourceContentAnalysis (ARA::PlugIn startOrScheduleAnalysisOfAudioSource (testAudioSource); } -bool ARATestDocumentController::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, ARA::ARAContentType ARA_MAYBE_UNUSED_ARG (type)) noexcept +bool ARATestDocumentController::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, [[maybe_unused]] ARA::ARAContentType type) noexcept { ARA_INTERNAL_ASSERT (type == ARA::kARAContentTypeNotes); @@ -883,13 +886,8 @@ void ARATestDocumentController::rendererDidAccessModelGraph (ARATestPlaybackRend void ARATestDocumentController::disableRendererModelGraphAccess () noexcept { -#if __cplusplus >= 201703L static_assert (decltype (_renderersCanAccessModelGraph)::is_always_lock_free); static_assert (decltype (_countOfRenderersCurrentlyAccessingModelGraph)::is_always_lock_free); -#else - ARA_INTERNAL_ASSERT (_renderersCanAccessModelGraph.is_lock_free ()); - ARA_INTERNAL_ASSERT (_countOfRenderersCurrentlyAccessingModelGraph.is_lock_free ()); -#endif ARA_INTERNAL_ASSERT (_renderersCanAccessModelGraph); _renderersCanAccessModelGraph = false; @@ -911,7 +909,7 @@ static constexpr std::array analyzeableContentTypes { { class ARATestFactoryConfig : public ARA::PlugIn::FactoryConfig { public: - ARA::ARAAPIGeneration getHighestSupportedApiGeneration () const noexcept override { return ARA::kARAAPIGeneration_2_3_Final; } + ARA::ARAAPIGeneration getHighestSupportedApiGeneration () const noexcept override { return ARA::kARAAPIGeneration_3_0_Draft; } const char* getFactoryID () const noexcept override { return TEST_FACTORY_ID; } const char* getPlugInName () const noexcept override { return TEST_PLUGIN_NAME; } const char* getManufacturerName () const noexcept override { return TEST_MANUFACTURER_NAME; } diff --git a/TestPlugIn/ARATestDocumentController.h b/TestPlugIn/ARATestDocumentController.h index 9b24818..4c44ae5 100644 --- a/TestPlugIn/ARATestDocumentController.h +++ b/TestPlugIn/ARATestDocumentController.h @@ -3,7 +3,7 @@ //! document controller implementation for the ARA test plug-in, //! customizing the document controller and related base classes of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -112,6 +112,7 @@ class ARATestDocumentController : public ARA::PlugIn::DocumentController ARA::ARAContentGrade doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification, ARA::ARAContentType type) noexcept override; ARA::PlugIn::ContentReader* doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification, ARA::ARAContentType type, const ARA::ARAContentTimeRange* range) noexcept override; + bool doIsPlaybackRegionPreservingAudioSourceSignal (ARA::PlugIn::PlaybackRegion* /*playbackRegion*/) noexcept override { return true; } bool doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion, ARA::ARAContentType type) noexcept override; ARA::ARAContentGrade doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion, ARA::ARAContentType type) noexcept override; ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, ARA::ARAContentType type, const ARA::ARAContentTimeRange* range) noexcept override; diff --git a/TestPlugIn/ARATestPlaybackRenderer.cpp b/TestPlugIn/ARATestPlaybackRenderer.cpp index 0912cf1..aeebb15 100644 --- a/TestPlugIn/ARATestPlaybackRenderer.cpp +++ b/TestPlugIn/ARATestPlaybackRenderer.cpp @@ -3,7 +3,7 @@ //! playback renderer implementation for the ARA test plug-in, //! customizing the playback renderer base class of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/ARATestPlaybackRenderer.h b/TestPlugIn/ARATestPlaybackRenderer.h index f022ed2..127555f 100644 --- a/TestPlugIn/ARATestPlaybackRenderer.h +++ b/TestPlugIn/ARATestPlaybackRenderer.h @@ -3,7 +3,7 @@ //! playback renderer implementation for the ARA test plug-in, //! customizing the playback renderer base class of the ARA library //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioFileChunkWriter/ARATestChunkWriter.cpp b/TestPlugIn/AudioFileChunkWriter/ARATestChunkWriter.cpp index f32479a..cb66e1a 100644 --- a/TestPlugIn/AudioFileChunkWriter/ARATestChunkWriter.cpp +++ b/TestPlugIn/AudioFileChunkWriter/ARATestChunkWriter.cpp @@ -2,7 +2,7 @@ //! \file ARATestChunkWriter.cpp //! ARA audio file chunk authoring tool for the ARA test plug-in //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -55,7 +55,7 @@ class SynchronousTestAnalysis : public TestAnalysisCallbacks }; -void addChunk (AudioFileBase&& audioFile, bool openAutomatically) +static void addChunkAndSave (AudioFileBase&& audioFile, bool openAutomatically, const std::string& path) { const auto persistentID { "audioSource1" }; const auto documentArchiveID { TEST_FILECHUNK_ARCHIVE_ID }; @@ -83,8 +83,7 @@ void addChunk (AudioFileBase&& audioFile, bool openAutomatically) persistentID, archive); // store audio file - auto wavFileName { audioFile.getName () }; - auto success { audioFile.saveToFile (audioFile.getName ()) }; + auto success { audioFile.saveToFile (path) }; ARA_INTERNAL_ASSERT (success); } @@ -103,17 +102,16 @@ int main (int argc, const char* argv[]) } icstdsp::AudioFile audioFile; - int ARA_MAYBE_UNUSED_VAR (err); - err = audioFile.Load (it.c_str ()); + const auto err { audioFile.Load (it.c_str ()) }; if (err == icstdsp::NOFILE) { ARA_LOG ("Audio File '%s' not found, will be created.", it.c_str ()); - addChunk (SineAudioFile { it, 5.0, 44100.0, 1 }, openAutomatically); + addChunkAndSave (SineAudioFile { it, 5.0, 44100.0, 1 }, openAutomatically, it); } else { ARA_INTERNAL_ASSERT (err == 0); - addChunk (AudioDataFile { it, std::move (audioFile) }, openAutomatically); + addChunkAndSave (AudioDataFile { it, std::move (audioFile) }, openAutomatically, it); } } return 0; diff --git a/TestPlugIn/AudioUnit/TestAudioUnit.cpp b/TestPlugIn/AudioUnit/TestAudioUnit.cpp index c46731b..1db367b 100644 --- a/TestPlugIn/AudioUnit/TestAudioUnit.cpp +++ b/TestPlugIn/AudioUnit/TestAudioUnit.cpp @@ -3,7 +3,7 @@ //! Audio Unit effect class for the ARA test plug-in, //! created via the Xcode 3 project template for Audio Unit effects. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -86,9 +86,6 @@ OSStatus TestAudioUnit::GetPropertyInfo( AudioUnitPropertyID inID, outDataSize = sizeof(ARA::ARAAudioUnitFactory); return noErr; -#if ARA_SUPPORT_VERSION_1 - case ARA::kAudioUnitProperty_ARAPlugInExtensionBinding: -#endif case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: outWritable = false; outDataSize = sizeof(ARA::ARAAudioUnitPlugInExtensionBinding); @@ -117,32 +114,14 @@ OSStatus TestAudioUnit::GetProperty( AudioUnitPropertyID inID, return noErr; } -#if ARA_SUPPORT_VERSION_1 - case ARA::kAudioUnitProperty_ARAPlugInExtensionBinding: -#endif case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: { if (((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->inOutMagicNumber != ARA::kARAAudioUnitMagic) return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics - ARA::ARAPlugInInstanceRoleFlags knownRoles; - ARA::ARAPlugInInstanceRoleFlags assignedRoles; -#if ARA_SUPPORT_VERSION_1 - if (inID == ARA::kAudioUnitProperty_ARAPlugInExtensionBinding) - { - ARA_VALIDATE_API_STATE(ARA::PlugIn::DocumentController::getUsedApiGeneration() < ARA::kARAAPIGeneration_2_0_Draft); - knownRoles = ARA::kARAPlaybackRendererRole | ARA::kARAEditorRendererRole | ARA::kARAEditorViewRole; - assignedRoles = ARA::kARAPlaybackRendererRole | ARA::kARAEditorRendererRole | ARA::kARAEditorViewRole; - - } - else -#endif - { - knownRoles = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->knownRoles; - assignedRoles = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->assignedRoles; - } - - auto documentControllerRef = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->inDocumentControllerRef; + const auto documentControllerRef = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->inDocumentControllerRef; + const auto knownRoles = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->knownRoles; + const auto assignedRoles = ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->assignedRoles; const auto instance = _araPlugInExtension.bindToARA(documentControllerRef, knownRoles, assignedRoles); ((ARA::ARAAudioUnitPlugInExtensionBinding *) outData)->outPlugInExtension = instance; return (instance) ? noErr : kAudioUnitErr_CannotDoInCurrentContext; diff --git a/TestPlugIn/AudioUnit/TestAudioUnit.h b/TestPlugIn/AudioUnit/TestAudioUnit.h index aaa6130..8e9e96a 100644 --- a/TestPlugIn/AudioUnit/TestAudioUnit.h +++ b/TestPlugIn/AudioUnit/TestAudioUnit.h @@ -3,7 +3,7 @@ //! Audio Unit effect class for the ARA test plug-in, //! created via the Xcode 3 project template for Audio Unit effects. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/App/TestAUv3App.m b/TestPlugIn/AudioUnit_v3/App/TestAUv3App.m index efa9ea0..ff96846 100755 --- a/TestPlugIn/AudioUnit_v3/App/TestAUv3App.m +++ b/TestPlugIn/AudioUnit_v3/App/TestAUv3App.m @@ -4,7 +4,7 @@ //! based on Apple's AUv3FilterDemo found at: //! https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/creating_custom_audio_effects //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/App/TestAUv3AppInfo.plist b/TestPlugIn/AudioUnit_v3/App/TestAUv3AppInfo.plist index 2d4344d..c8c60bd 100644 --- a/TestPlugIn/AudioUnit_v3/App/TestAUv3AppInfo.plist +++ b/TestPlugIn/AudioUnit_v3/App/TestAUv3AppInfo.plist @@ -31,6 +31,6 @@ NSSupportsSuddenTermination NSHumanReadableCopyright - Copyright © 2021-2025 ARA SDK Examples. All rights reserved. + Copyright © 2021-2026 ARA SDK Examples. All rights reserved. diff --git a/TestPlugIn/AudioUnit_v3/Extension/TestAUv3Extension.m b/TestPlugIn/AudioUnit_v3/Extension/TestAUv3Extension.m index bbc1db7..4e967d6 100755 --- a/TestPlugIn/AudioUnit_v3/Extension/TestAUv3Extension.m +++ b/TestPlugIn/AudioUnit_v3/Extension/TestAUv3Extension.m @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ -//! \file TestAUv3App.m +//! \file TestAUv3Extension.m //! App Extension placeholder function for the Audio Unit v3 App Extension, //! based on Apple's AUv3FilterDemo found at: //! https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/creating_custom_audio_effects //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -18,6 +18,7 @@ //! limitations under the License. //------------------------------------------------------------------------------ +__attribute__ ((used)) void placeholder(void); __attribute__ ((used)) void placeholder(void) { // This placeholder function ensures the extension correctly loads. diff --git a/TestPlugIn/AudioUnit_v3/Extension/TestAUv3ExtensionInfo.plist b/TestPlugIn/AudioUnit_v3/Extension/TestAUv3ExtensionInfo.plist index 64dfe86..5a65da6 100644 --- a/TestPlugIn/AudioUnit_v3/Extension/TestAUv3ExtensionInfo.plist +++ b/TestPlugIn/AudioUnit_v3/Extension/TestAUv3ExtensionInfo.plist @@ -61,6 +61,6 @@ TestAUv3Factory NSHumanReadableCopyright - Copyright © 2021-2025 ARA SDK Examples. All rights reserved. + Copyright © 2021-2026 ARA SDK Examples. All rights reserved. diff --git a/TestPlugIn/AudioUnit_v3/Framework/BufferedAudioBus.hpp b/TestPlugIn/AudioUnit_v3/Framework/BufferedAudioBus.hpp index a7314c2..4963a64 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/BufferedAudioBus.hpp +++ b/TestPlugIn/AudioUnit_v3/Framework/BufferedAudioBus.hpp @@ -3,7 +3,7 @@ //! Audio Unit App Extension helper class, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.h b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.h index d220770..a246313 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.h +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.h @@ -3,7 +3,7 @@ //! Audio Unit App Extension implementation, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.mm b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.mm index f03f0d1..88c29de 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.mm +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3AudioUnit.mm @@ -3,7 +3,7 @@ //! Audio Unit App Extension implementation, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -27,52 +27,6 @@ #import "ARA_Library/IPC/ARAIPCProxyHost.h" -#if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE - -// \todo this class could be a reusable part of ARA_Library if we could figure out how to avoid the -// ObjC class name clashes that will arise when multiple plug-ins from different binaries are -// using/linking this class. -// The IPC-related additions to TestAUv3AudioUnit could also be moved to a reusable base class -// from which TestAUv3AudioUnit and others could derive. -// A workaround might be a set of macros that can be used to provide the code with adjusted -// class names for client projects, but since some methods may be implemented in the client -// this is somewhat messy. - -API_AVAILABLE(macos(13.0), ios(16.0)) -@interface TestAUv3ARAIPCMessageChannel : NSObject -@end - - -@implementation TestAUv3ARAIPCMessageChannel { - ARA::IPC::ARAIPCMessageChannelRef _messageChannelRef; -} - -@synthesize callHostBlock = _callHostBlock; - -- (instancetype)initAsMainThreadChannel:(BOOL) isMainThreadChannel { - self = [super init]; - - if (self == nil) { return nil; } - - _callHostBlock = nil; - _messageChannelRef = ARA::IPC::ARAIPCAUProxyHostInitializeMessageChannel(self, isMainThreadChannel); - - return self; -} - -- (void)dealloc { - ARA::IPC::ARAIPCAUProxyHostUninitializeMessageChannel(_messageChannelRef); -} - -- (NSDictionary * _Nonnull)callAudioUnit:(NSDictionary *)message { - return ARA::IPC::ARAIPCAUProxyHostCommandHandler(_messageChannelRef, message); -} - -@end - -#endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE - - @interface TestAUv3AudioUnit () @property AUAudioUnitBusArray *inputBusArray; @@ -265,47 +219,19 @@ and modify the (outputData->mBuffers[x].mData) pointers to point to this owned m #if ARA_AUDIOUNITV3_IPC_IS_AVAILABLE -NSObject * __strong _mainMessageChannel API_AVAILABLE(macos(13.0), ios(16.0)) = nil; -NSObject * __strong _otherMessageChannel API_AVAILABLE(macos(13.0), ios(16.0)) = nil; - -void createSharedMessageChannelsIfNeeded() API_AVAILABLE(macos(13.0), ios(16.0)) { - if (_mainMessageChannel) - return; - - ARA::IPC::ARAIPCProxyHostAddFactory(ARATestDocumentController::getARAFactory()); - - _mainMessageChannel = [[TestAUv3ARAIPCMessageChannel alloc] initAsMainThreadChannel:YES]; - _otherMessageChannel = [[TestAUv3ARAIPCMessageChannel alloc] initAsMainThreadChannel:NO]; -} +- (id _Nonnull)messageChannelFor:(NSString * _Nonnull)channelName { + id result = nil; -__attribute__((destructor)) -void destroySharedMessageChannelsIfNeeded() { if (@available(macOS 13.0, iOS 16.0, *)) { - if (_mainMessageChannel) - ARA::IPC::ARAIPCAUProxyHostUninitialize(); + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ ARA::IPC::ARAIPCProxyHostAddFactory(_araFactory); }); - _mainMessageChannel = nil; - _otherMessageChannel = nil; + result = ARA::IPC::ARAIPCAUProxyHostMessageChannelFor(channelName); } -} -// \todo the return value should be _Nullable! -- (id _Nonnull)messageChannelFor:(NSString * _Nonnull)channelName { - if (@available(macOS 13.0, iOS 16.0, *)) - { - if ([channelName isEqualToString:ARA_AUDIOUNIT_MAIN_THREAD_MESSAGES_UTI]) - { - createSharedMessageChannelsIfNeeded(); - return _mainMessageChannel; - } - if ([channelName isEqualToString:ARA_AUDIOUNIT_OTHER_THREADS_MESSAGES_UTI]) - { - createSharedMessageChannelsIfNeeded(); - return _otherMessageChannel; - } - } - return nil; + // \todo the return value should be declared _Nullable in the OS - must be fixed there! + return (id _Nonnull) result; } #endif // ARA_AUDIOUNITV3_IPC_IS_AVAILABLE diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3DSPKernel.hpp b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3DSPKernel.hpp index 41cef13..98eac3f 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3DSPKernel.hpp +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3DSPKernel.hpp @@ -3,7 +3,7 @@ //! Audio Unit App Extension DSP implementation, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.h b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.h index e556842..8b00031 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.h +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.h @@ -3,7 +3,7 @@ //! Audio Unit App Extension factory, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.m b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.m index 7e5e580..7f7bf64 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.m +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3Factory.m @@ -3,7 +3,7 @@ //! Audio Unit App Extension factory, //! created via the Xcode 11 project template for Audio Unit App Extensions. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2021-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2021-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3FrameworkInfo.plist b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3FrameworkInfo.plist index 59cef8d..eaf1547 100644 --- a/TestPlugIn/AudioUnit_v3/Framework/TestAUv3FrameworkInfo.plist +++ b/TestPlugIn/AudioUnit_v3/Framework/TestAUv3FrameworkInfo.plist @@ -19,6 +19,6 @@ CFBundleVersion ${ARA_MAJOR_VERSION}.${ARA_MINOR_VERSION}.${ARA_PATCH_VERSION}.${ARA_BUILD_VERSION} NSHumanReadableCopyright - Copyright © 2021-2025 ARA SDK Examples. All rights reserved. + Copyright © 2021-2026 ARA SDK Examples. All rights reserved. diff --git a/TestPlugIn/CLAP/TestCLAPPlugIn.cpp b/TestPlugIn/CLAP/TestCLAPPlugIn.cpp index 8944ff2..11ef822 100644 --- a/TestPlugIn/CLAP/TestCLAPPlugIn.cpp +++ b/TestPlugIn/CLAP/TestCLAPPlugIn.cpp @@ -3,7 +3,7 @@ //! CLAP implementation for the ARA test plug-in, //! based on the plugin-template.c from the CLAP SDK //! \project ARA SDK Examples -//! \copyright Copyright (c) 2022-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2022-2026, Celemony Software GmbH, All Rights Reserved. //! Developed in cooperation with Timo Kaluza (defiantnerd) //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. @@ -107,9 +107,9 @@ static const clap_plugin_audio_ports_t s_my_plug_audio_ports = { // internal helper that makes sure the requests describes a valid configuration with ins == outs // returns 0 on failure -uint32_t my_plug_get_validated_channel_count_for_configuration(const my_plug_t *plugin, - const struct clap_audio_port_configuration_request *requests, - uint32_t request_count) { +static uint32_t my_plug_get_validated_channel_count_for_configuration(const my_plug_t *plugin, + const struct clap_audio_port_configuration_request *requests, + uint32_t request_count) { if (request_count > 2) return 0; diff --git a/TestPlugIn/TestAnalysis.cpp b/TestPlugIn/TestAnalysis.cpp index a0d681b..c6042cd 100644 --- a/TestPlugIn/TestAnalysis.cpp +++ b/TestPlugIn/TestAnalysis.cpp @@ -2,7 +2,7 @@ //! \file TestAnalysis.cpp //! dummy implementation of audio source analysis for the ARA test plug-in //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -189,7 +189,7 @@ class PseudoAnalysisProcessingAlgorithm : public TestProcessingAlgorithm const auto analysisTargetTime { analysisStartTime + progress * analysisTargetDuration }; const auto timeToSleep { analysisTargetTime - ARA_GET_CURRENT_TIME () }; if (timeToSleep > 0.0) - std::this_thread::sleep_for (std::chrono::milliseconds { std::llround (timeToSleep * 1000) } ); + std::this_thread::sleep_for (std::chrono::milliseconds { std::llround (timeToSleep * 1000) }); #endif } @@ -291,7 +291,7 @@ class SingleNoteProcessingAlgorithm : public TestProcessingAlgorithm } analysisCallbacks->notifyAnalysisProgressUpdated (static_cast (i) / static_cast (count)); - std::this_thread::sleep_for (std::chrono::milliseconds { std::llround (sliceDuration * 1000) } ); + std::this_thread::sleep_for (std::chrono::milliseconds { std::llround (sliceDuration * 1000) }); } #endif diff --git a/TestPlugIn/TestAnalysis.h b/TestPlugIn/TestAnalysis.h index b3db3b1..3cbe5e3 100644 --- a/TestPlugIn/TestAnalysis.h +++ b/TestPlugIn/TestAnalysis.h @@ -2,7 +2,7 @@ //! \file TestAnalysis.h //! dummy implementation of audio source analysis for the ARA test plug-in //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/TestPersistency.cpp b/TestPlugIn/TestPersistency.cpp index 0c9abc8..17bfd15 100644 --- a/TestPlugIn/TestPersistency.cpp +++ b/TestPlugIn/TestPersistency.cpp @@ -2,7 +2,7 @@ //! \file TestPersistency.cpp //! archiver/unarchiver implementation for the ARA test plug-in //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -129,18 +129,7 @@ size_t TestUnarchiver::readSize () noexcept { uint64_t data { read8ByteData () }; -#if __cplusplus >= 201703L if constexpr (sizeof (size_t) < sizeof (uint64_t)) -#else - #if defined (_MSC_VER) - __pragma (warning(push)) - __pragma (warning(disable : 4127)) - #endif - if (sizeof (size_t) < sizeof (uint64_t)) - #if defined (_MSC_VER) - __pragma (warning(pop)) - #endif -#endif { if (data > std::numeric_limits::max ()) { diff --git a/TestPlugIn/TestPersistency.h b/TestPlugIn/TestPersistency.h index 0554035..2ffe063 100644 --- a/TestPlugIn/TestPersistency.h +++ b/TestPlugIn/TestPersistency.h @@ -2,7 +2,7 @@ //! \file TestPersistency.h //! archiver/unarchiver implementation for the ARA test plug-in //! \project ARA SDK Examples -//! \copyright Copyright (c) 2018-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2018-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/TestPlugInConfig.h b/TestPlugIn/TestPlugInConfig.h index c5f2055..0900576 100644 --- a/TestPlugIn/TestPlugInConfig.h +++ b/TestPlugIn/TestPlugInConfig.h @@ -2,7 +2,7 @@ //! \file TestPlugInConfig.h //! IDs and other shared definitions for the ARATestPlugIn //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/VST3/ARATestMainFactory.h b/TestPlugIn/VST3/ARATestMainFactory.h index 4eab0e7..a092979 100644 --- a/TestPlugIn/VST3/ARATestMainFactory.h +++ b/TestPlugIn/VST3/ARATestMainFactory.h @@ -2,7 +2,7 @@ //! \file ARATestMainFactory.h //! VST3 ARA Main Factory implementation for the ARA test plug-in. //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at diff --git a/TestPlugIn/VST3/TestVST3Entry.cpp b/TestPlugIn/VST3/TestVST3Entry.cpp index c771430..98e4471 100644 --- a/TestPlugIn/VST3/TestVST3Entry.cpp +++ b/TestPlugIn/VST3/TestVST3Entry.cpp @@ -3,7 +3,7 @@ //! VST3 factory functions for the ARA test plug-in, //! originally created using the VST project generator from the Steinberg VST3 SDK //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -36,6 +36,7 @@ ARA_DISABLE_VST3_WARNINGS_BEGIN //------------------------------------------------------------------------ // called after library was loaded +bool InitModule (); bool InitModule () { return true; @@ -43,6 +44,7 @@ bool InitModule () //------------------------------------------------------------------------ // called after library is unloaded +bool DeinitModule (); bool DeinitModule () { return true; diff --git a/TestPlugIn/VST3/TestVST3Processor.cpp b/TestPlugIn/VST3/TestVST3Processor.cpp index da27945..6b4eb43 100644 --- a/TestPlugIn/VST3/TestVST3Processor.cpp +++ b/TestPlugIn/VST3/TestVST3Processor.cpp @@ -3,7 +3,7 @@ //! VST3 audio effect class for the ARA test plug-in, //! originally created using the VST project generator from the Steinberg VST3 SDK //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at @@ -31,7 +31,7 @@ ARA_DISABLE_VST3_WARNINGS_END using namespace Steinberg; // helper to improve readability -int32 getAudioBusChannelCount (const IPtr& bus) +static int32 getAudioBusChannelCount (const IPtr& bus) { return Vst::SpeakerArr::getChannelCount (FCast (bus.get ())->getArrangement ()); } @@ -170,20 +170,11 @@ const ARA::ARAFactory* PLUGIN_API TestVST3Processor::getFactory () } //----------------------------------------------------------------------------- -#if ARA_SUPPORT_VERSION_1 -const ARA::ARAPlugInExtensionInstance* PLUGIN_API TestVST3Processor::bindToDocumentController (ARA::ARADocumentControllerRef documentControllerRef) -{ - ARA_VALIDATE_API_STATE (ARA::PlugIn::DocumentController::getUsedApiGeneration () < ARA::kARAAPIGeneration_2_0_Draft); - constexpr auto allRoles = ARA::kARAPlaybackRendererRole | ARA::kARAEditorRendererRole | ARA::kARAEditorViewRole; - return _araPlugInExtension.bindToDocumentController (documentControllerRef, allRoles, allRoles); -} -#else const ARA::ARAPlugInExtensionInstance* PLUGIN_API TestVST3Processor::bindToDocumentController (ARA::ARADocumentControllerRef /*documentControllerRef*/) { ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); return nullptr; } -#endif //----------------------------------------------------------------------------- const ARA::ARAPlugInExtensionInstance* PLUGIN_API TestVST3Processor::bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, diff --git a/TestPlugIn/VST3/TestVST3Processor.h b/TestPlugIn/VST3/TestVST3Processor.h index 4a48cbd..5329e94 100644 --- a/TestPlugIn/VST3/TestVST3Processor.h +++ b/TestPlugIn/VST3/TestVST3Processor.h @@ -3,7 +3,7 @@ //! VST3 audio effect class for the ARA test plug-in, //! originally created using the VST project generator from the Steinberg VST3 SDK //! \project ARA SDK Examples -//! \copyright Copyright (c) 2012-2025, Celemony Software GmbH, All Rights Reserved. +//! \copyright Copyright (c) 2012-2026, Celemony Software GmbH, All Rights Reserved. //! \license Licensed under the Apache License, Version 2.0 (the "License"); //! you may not use this file except in compliance with the License. //! You may obtain a copy of the License at