diff --git a/ChangeLog.txt b/ChangeLog.txt index 989d436..b5f1c63 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -3,11 +3,16 @@ This is a development build of the ARA Library 3.0. Changes since previous releases: +- initial draft of ARA generator plug-ins that operate merely based on content descriptions (no sample input) +- initial draft of lyrics-related content types, tailored to support singing voice synthesis +- initial draft of region sequence persistency +- added isPlaybackRegionPreservingAudioSourceSignal(), superseding isAudioModificationPreservingAudioSourceSignal() - initial draft of generic ARA IPC library providing a proxy host and a proxy plug-in, based on heavily refactored IPC Example from earlier SDK releases - dropped support for ARA 1 and 2.0 Draft APIs If previously optionally supporting obsolete ARA 1 persistency, now exclusively use ARA 2 persistency. -- C++14 is now consistently required on all platforms +- C++17 is now consistently required on all platforms +- fixed inconsistent spelling of "timestretch" in various identifiers === ARA SDK 2.3 release (aka 2.3.001) (2025/11/07) === diff --git a/Debug/ARAContentLogger.h b/Debug/ARAContentLogger.h index ff310bb..1c930e4 100644 --- a/Debug/ARAContentLogger.h +++ b/Debug/ARAContentLogger.h @@ -55,12 +55,13 @@ struct ContentLogger // array of all content types defined in the api - static inline constexpr std::array getAllContentTypes () noexcept + static inline constexpr std::array getAllContentTypes () noexcept { return { { kARAContentTypeNotes, kARAContentTypeTempoEntries, kARAContentTypeBarSignatures, kARAContentTypeStaticTuning, - kARAContentTypeKeySignatures, kARAContentTypeSheetChords + kARAContentTypeKeySignatures, kARAContentTypeSheetChords, + kARAContentTypeLyricEntries } }; } @@ -88,6 +89,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return ContentTypeMapper::enumName; case kARAContentTypeKeySignatures: return ContentTypeMapper::enumName; case kARAContentTypeSheetChords: return ContentTypeMapper::enumName; + case kARAContentTypeLyricEntries: return ContentTypeMapper::enumName; default: ARA_INTERNAL_ASSERT (false); return "kARAContentType???"; } } @@ -102,6 +104,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return ContentTypeMapper::typeName; case kARAContentTypeKeySignatures: return ContentTypeMapper::typeName; case kARAContentTypeSheetChords: return ContentTypeMapper::typeName; + case kARAContentTypeLyricEntries: return ContentTypeMapper::typeName; default: ARA_INTERNAL_ASSERT (false); return "ARAContent???"; } } @@ -188,6 +191,14 @@ struct ContentLogger (logGivenName && logParsedName) ? " aka " : "", (logGivenName && logParsedName) ? parsedChordName.c_str () : "", chordData.position); } + static inline void logEvent (ARAInt32 idx, const ARAContentLyricsEntry& lyricsEntry) + { + ARA_LOG ("%s[%i] %s%s, %i %s phonemes%s, position = %.3f", getTypeNameForContentType (kARAContentTypeLyricEntries), idx, + (lyricsEntry.continuesPreviousWord) ? "-" : "", lyricsEntry.lyrics, + lyricsEntry.phonemeCount, getNameForContentGrade (lyricsEntry.phonemesGrade), + (lyricsEntry.phonemeOffsets) ? " w/ offsets": "", lyricsEntry.position); + } + // internal helper for log () template = true> @@ -247,6 +258,7 @@ struct ContentLogger case kARAContentTypeStaticTuning: return log (controller, modelObjectRef, range, logIfNotAvailable); case kARAContentTypeKeySignatures: return log (controller, modelObjectRef, range, logIfNotAvailable); case kARAContentTypeSheetChords: return log (controller, modelObjectRef, range, logIfNotAvailable); + case kARAContentTypeLyricEntries: return log (controller, modelObjectRef, range, logIfNotAvailable); default: ARA_INTERNAL_ASSERT (false); return false; } } @@ -316,6 +328,11 @@ struct ContentLogger log (controller, modelObjectRef, range, false); log (controller, modelObjectRef, range, false); } + if (scopeFlags.affectLyrics ()) + { + ARA_LOG ("lyrics scope updated, related content is:"); + log (controller, modelObjectRef, range, false); + } } }; diff --git a/Debug/ARAContentValidator.h b/Debug/ARAContentValidator.h index e578922..666ba27 100644 --- a/Debug/ARAContentValidator.h +++ b/Debug/ARAContentValidator.h @@ -137,6 +137,25 @@ struct ContentReaderValidatorImplementation } }; +template <> +struct ContentReaderValidatorImplementation +{ + static inline void validateEventCount (ARAInt32 eventCount) { ARA_VALIDATE_API_CONDITION (eventCount >= 0); } + + static inline void validateEvent (const ARAContentLyricsEntry* event) + { + ARA_VALIDATE_API_CONDITION ((event->continuesPreviousWord == kARAFalse) || (event->lyrics != nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount == 0) || (event->phonemes != nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount != 0) || (event->phonemes == nullptr)); + ARA_VALIDATE_API_CONDITION ((event->phonemeCount != 0) || (event->phonemeOffsets == nullptr)); + } + + static inline void validateEventSequence (const ARAContentLyricsEntry* event, const ARAContentLyricsEntry* prevEvent) + { + ARA_VALIDATE_API_CONDITION (prevEvent->position < event->position); + } +}; + /*******************************************************************************/ // ContentReaderValidator diff --git a/Dispatch/ARAContentReader.h b/Dispatch/ARAContentReader.h index b852df6..a67edb5 100644 --- a/Dispatch/ARAContentReader.h +++ b/Dispatch/ARAContentReader.h @@ -51,6 +51,7 @@ struct ContentTypeMapper; ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeStaticTuning, ARAContentTuning) ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeKeySignatures, ARAContentKeySignature) ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeSheetChords, ARAContentChord) + ARA_SPECIALIZE_CONTENT_TYPE_MAPPER (kARAContentTypeLyricEntries, ARAContentLyricsEntry) #undef ARA_SPECIALIZE_CONTENT_TYPE_MAPPER diff --git a/Dispatch/ARADispatchBase.h b/Dispatch/ARADispatchBase.h index 4b1da05..cb02043 100644 --- a/Dispatch/ARADispatchBase.h +++ b/Dispatch/ARADispatchBase.h @@ -334,6 +334,8 @@ class ContentUpdateScopes static constexpr ContentUpdateScopes tuningIsAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateTuningScopeRemainsUnchanged; } //! Content readers for key signatures, chords etc. are affected by the change. static constexpr ContentUpdateScopes harmoniesAreAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateHarmonicScopeRemainsUnchanged; } + //! Content readers for lyrics, phonemes etc. are affected by the change. + static constexpr ContentUpdateScopes lyricsAreAffected () noexcept { return nothingIsAffected ()._flags & ~kARAContentUpdateLyricsScopeRemainsUnchanged; } //! Everything is affected by the change. static constexpr ContentUpdateScopes everythingIsAffected () noexcept { return kARAContentUpdateEverythingChanged; } @@ -376,14 +378,16 @@ class ContentUpdateScopes constexpr bool affectTuning () const noexcept { return ((_flags & kARAContentUpdateTuningScopeRemainsUnchanged) == 0); } //! \copybrief harmoniesAreAffected constexpr bool affectHarmonies () const noexcept { return ((_flags & kARAContentUpdateHarmonicScopeRemainsUnchanged) == 0); } + //! \copybrief lyricsAreAffected + constexpr bool affectLyrics () const noexcept { return ((_flags & kARAContentUpdateLyricsScopeRemainsUnchanged) == 0); } //! \copybrief everythingIsAffected constexpr bool affectEverything () const noexcept { return ((_flags & _knownFlags) == 0); } //@} private: - static constexpr ARAContentUpdateFlags _knownFlags { (kARAContentUpdateSignalScopeRemainsUnchanged | - kARAContentUpdateNoteScopeRemainsUnchanged | kARAContentUpdateTimingScopeRemainsUnchanged | - kARAContentUpdateTuningScopeRemainsUnchanged | kARAContentUpdateHarmonicScopeRemainsUnchanged) }; + static constexpr ARAContentUpdateFlags _knownFlags { (kARAContentUpdateSignalScopeRemainsUnchanged | kARAContentUpdateNoteScopeRemainsUnchanged | + kARAContentUpdateTimingScopeRemainsUnchanged | kARAContentUpdateTuningScopeRemainsUnchanged | + kARAContentUpdateHarmonicScopeRemainsUnchanged | kARAContentUpdateLyricsScopeRemainsUnchanged) }; ARAContentUpdateFlags _flags; }; diff --git a/Dispatch/ARAHostDispatch.cpp b/Dispatch/ARAHostDispatch.cpp index 483ab27..ea62e79 100644 --- a/Dispatch/ARAHostDispatch.cpp +++ b/Dispatch/ARAHostDispatch.cpp @@ -218,15 +218,21 @@ namespace ModelUpdateControllerDispatcher fromHostRef (controllerHostRef)->notifyDocumentDataChanged (); } + static void ARA_CALL notifyRegionSequenceDataChanged (ARAModelUpdateControllerHostRef controllerHostRef, ARARegionSequenceHostRef regionSequenceHostRef) noexcept + { + fromHostRef (controllerHostRef)->notifyRegionSequenceDataChanged (regionSequenceHostRef); + } + static const ARAModelUpdateControllerInterface* getInterface () noexcept { - static const SizedStruct<&ARAModelUpdateControllerInterface::notifyDocumentDataChanged> ifc = + static const SizedStruct<&ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged> ifc = { ModelUpdateControllerDispatcher::notifyAudioSourceAnalysisProgress, ModelUpdateControllerDispatcher::notifyAudioSourceContentChanged, ModelUpdateControllerDispatcher::notifyAudioModificationContentChanged, ModelUpdateControllerDispatcher::notifyPlaybackRegionContentChanged, - ModelUpdateControllerDispatcher::notifyDocumentDataChanged + ModelUpdateControllerDispatcher::notifyDocumentDataChanged, + ModelUpdateControllerDispatcher::notifyRegionSequenceDataChanged }; return &ifc; } @@ -504,6 +510,18 @@ void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef pl getInterface ()->updatePlaybackRegionProperties (getRef (), playbackRegionRef, properties); } +bool DocumentController::supportsIsPlaybackRegionPreservingAudioSourceSignal () noexcept +{ + return getInterface ().implements<&ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal> (); +} + +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + if (!supportsIsPlaybackRegionPreservingAudioSourceSignal ()) + return false; + return (getInterface ()->isPlaybackRegionPreservingAudioSourceSignal (getRef (), playbackRegionRef) != kARAFalse); +} + void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { return getInterface ()->getPlaybackRegionHeadAndTailTime (getRef (), playbackRegionRef, headTime, tailTime); diff --git a/Dispatch/ARAHostDispatch.h b/Dispatch/ARAHostDispatch.h index 3bfb0a6..0cf647f 100644 --- a/Dispatch/ARAHostDispatch.h +++ b/Dispatch/ARAHostDispatch.h @@ -180,6 +180,8 @@ class ModelUpdateControllerInterface virtual void notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept = 0; //! \copydoc ARAModelUpdateControllerInterface::notifyDocumentDataChanged virtual void notifyDocumentDataChanged () noexcept = 0; + //! \copydoc ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged + ARA_DRAFT virtual void notifyRegionSequenceDataChanged (ARA::ARARegionSequenceHostRef regionSequenceHostRef) noexcept = 0; }; ARA_MAP_HOST_REF (ModelUpdateControllerInterface, ARAModelUpdateControllerHostRef) @@ -347,7 +349,7 @@ class DocumentController : public InterfaceInstanceupdatePlaybackRegionProperties (playbackRegionRef, properties); } - static void ARA_CALL destroyPlaybackRegion (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept + static ARABool ARA_CALL isPlaybackRegionPreservingAudioSourceSignal (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept { - fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + return fromRef (controllerRef)->isPlaybackRegionPreservingAudioSourceSignal (playbackRegionRef) ? kARATrue : kARAFalse; } static void ARA_CALL getPlaybackRegionHeadAndTailTime (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef, @@ -235,6 +235,11 @@ namespace DocumentControllerDispatcher fromRef (controllerRef)->getPlaybackRegionHeadAndTailTime (playbackRegionRef, headTime, tailTime); } + static void ARA_CALL destroyPlaybackRegion (ARADocumentControllerRef controllerRef, ARAPlaybackRegionRef playbackRegionRef) noexcept + { + fromRef (controllerRef)->destroyPlaybackRegion (playbackRegionRef); + } + // Content Reader Management static ARABool ARA_CALL isAudioSourceContentAvailable (ARADocumentControllerRef controllerRef, ARAAudioSourceRef audioSourceRef, ARAContentType type) noexcept @@ -342,7 +347,7 @@ namespace DocumentControllerDispatcher static const ARADocumentControllerInterface* getInterface () noexcept { - static const SizedStruct<&ARADocumentControllerInterface::isAudioModificationPreservingAudioSourceSignal> ifc = + static const SizedStruct<&ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal> ifc = { DocumentControllerDispatcher::destroyDocumentController, DocumentControllerDispatcher::getFactory, @@ -397,7 +402,8 @@ namespace DocumentControllerDispatcher DocumentControllerDispatcher::requestProcessingAlgorithmForAudioSource, DocumentControllerDispatcher::isLicensedForCapabilities, DocumentControllerDispatcher::storeAudioSourceToAudioFileChunk, - DocumentControllerDispatcher::isAudioModificationPreservingAudioSourceSignal + DocumentControllerDispatcher::isAudioModificationPreservingAudioSourceSignal, + DocumentControllerDispatcher::isPlaybackRegionPreservingAudioSourceSignal }; return &ifc; } @@ -645,21 +651,40 @@ void HostModelUpdateController::notifyAudioModificationContentChanged (ARAAudioM getInterface ()->notifyAudioModificationContentChanged (getRef (), audioModificationHostRef, range, scopeFlags); } +bool HostModelUpdateController::supportsNotifyPlaybackRegionContentChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyPlaybackRegionContentChanged> (); +} + void HostModelUpdateController::notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept { - // notifyPlaybackRegionContentChanged was optional in the ARA 2.0 draft, so check its presence here to be safe - if (getInterface ().implements<&ARAModelUpdateControllerInterface::notifyPlaybackRegionContentChanged> ()) + if (supportsNotifyPlaybackRegionContentChanged ()) getInterface ()->notifyPlaybackRegionContentChanged (getRef (), playbackRegionHostRef, range, scopeFlags); } +bool HostModelUpdateController::supportsNotifyDocumentDataChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyDocumentDataChanged> (); +} + void HostModelUpdateController::notifyDocumentDataChanged () noexcept { - // notifyDocumentDataChanged was added in ARA 2.3 draft, so check its presence here - if (getInterface ().implements<&ARAModelUpdateControllerInterface::notifyDocumentDataChanged> ()) + if (supportsNotifyDocumentDataChanged ()) getInterface ()->notifyDocumentDataChanged (getRef ()); } +bool HostModelUpdateController::supportsNotifyRegionSequenceDataChanged () noexcept +{ + return getInterface ().implements<&ARAModelUpdateControllerInterface::notifyRegionSequenceDataChanged> (); +} + +void HostModelUpdateController::notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept +{ + if (supportsNotifyRegionSequenceDataChanged ()) + getInterface ()->notifyRegionSequenceDataChanged (getRef (), regionSequenceHostRef); +} + /*******************************************************************************/ // PlaybackController /*******************************************************************************/ diff --git a/Dispatch/ARAPlugInDispatch.h b/Dispatch/ARAPlugInDispatch.h index 3470abe..4810d36 100644 --- a/Dispatch/ARAPlugInDispatch.h +++ b/Dispatch/ARAPlugInDispatch.h @@ -186,10 +186,12 @@ class DocumentControllerInterface virtual ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept = 0; //! \copydoc ARADocumentControllerInterface::updatePlaybackRegionProperties virtual void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept = 0; - //! \copydoc ARADocumentControllerInterface::destroyPlaybackRegion - virtual void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; + //! \copydoc ARADocumentControllerInterface::isPlaybackRegionPreservingAudioSourceSignal + ARA_DRAFT virtual bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; //! \copydoc ARADocumentControllerInterface::getPlaybackRegionHeadAndTailTime virtual void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept = 0; + //! \copydoc ARADocumentControllerInterface::destroyPlaybackRegion + virtual void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept = 0; //@} //! @name Content Reader Management @@ -452,11 +454,19 @@ class HostModelUpdateController : public InterfaceInstance MessageDispatcher::_handleReceivedMessage (Messa //------------------------------------------------------------------------------ // helper for MainThreadMessageDispatcher: single-object message queue with semaphore to wait on +// Wine: std::binary_semaphore's try_acquire_for hangs (broken futex mapping). +// Use POSIX sem_t with sem_timedwait instead. +#if defined (__WINE__) +#include +#include +class WaitableSingleMessageQueue +{ + public: + WaitableSingleMessageQueue () { sem_init (&_sem, 0, 0); } + ~WaitableSingleMessageQueue () { sem_destroy (&_sem); } + + std::optional>> waitOnSemaphore (ARATimeDuration timeout) + { + bool didReceiveMessage; + if (timeout <= 0.0) + { + didReceiveMessage = (sem_trywait (&_sem) == 0); + } + else + { + struct timespec ts {}; + clock_gettime (CLOCK_REALTIME, &ts); + long long ns = static_cast (timeout * 1e9); + ts.tv_sec += ns / 1000000000LL; + ts.tv_nsec += ns % 1000000000LL; + if (ts.tv_nsec >= 1000000000LL) { ts.tv_sec++; ts.tv_nsec -= 1000000000LL; } + didReceiveMessage = (sem_timedwait (&_sem, &ts) == 0); + } + if (didReceiveMessage) + { + const auto messageID { _pendingMessageID }; + const auto messageDecoder { _pendingMessageDecoder.load (std::memory_order_acquire) }; + return std::make_pair (messageID, std::unique_ptr (messageDecoder)); + } + return {}; + } + + void signalSemaphore (MessageID messageID, std::unique_ptr && decoder) + { + _pendingMessageID = messageID; + _pendingMessageDecoder.store (decoder.release (), std::memory_order_release); + sem_post (&_sem); + } + + private: + MessageID _pendingMessageID { 0 }; + std::atomic _pendingMessageDecoder { nullptr }; + sem_t _sem; +}; +#else class WaitableSingleMessageQueue { public: @@ -307,6 +356,7 @@ class WaitableSingleMessageQueue std::atomic _pendingMessageDecoder { nullptr }; void* const _waitForMessageSemaphore; // concrete type is platform-dependent }; +#endif // !defined(__WINE__) — close the #else block for the original WaitableSingleMessageQueue //------------------------------------------------------------------------------ @@ -630,7 +680,7 @@ void OtherThreadsMessageDispatcher::_processReceivedMessage (MessageID messageID //------------------------------------------------------------------------------ -#if defined (_WIN32) +#if defined (_WIN32) && !defined (__WINE__) // from https://devblogs.microsoft.com/oldnewthing/20141015-00/?p=43843 BOOL ConvertToRealHandle(HANDLE h, @@ -685,17 +735,20 @@ Connection::Connection (MessageEncoderFactory && messageEncoderFactory, MessageH _messageHandler { std::move (messageHandler) }, _receiverEndianessMatches { receiverEndianessMatches }, _waitForMessageDelegate { std::move (waitForMessageDelegate) }, - _creationThreadID { std::this_thread::get_id () }, -#if defined (_WIN32) - _creationThreadHandle { _GetRealCurrentThread () } + _creationThreadID { std::this_thread::get_id () } +#if defined (_WIN32) && !defined (__WINE__) + , _creationThreadHandle { _GetRealCurrentThread () } {} #elif defined (__APPLE__) - _creationThreadRunLoop { CFRunLoopGetCurrent () } + , _creationThreadRunLoop { CFRunLoopGetCurrent () } { CFRunLoopSourceContext context { 0, this, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, _performRunLoopSource }; _runloopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 0, &context); CFRunLoopAddSource (_creationThreadRunLoop, _runloopSource, kCFRunLoopCommonModes); } +#else + // Linux: _queue, _mutex, and _condition all default-construct. +{} #endif Connection::~Connection () @@ -729,7 +782,7 @@ void Connection::sendMessage (MessageID messageID, std::unique_ptr (funcPtr)) }; ARA_INTERNAL_ASSERT (result != 0); @@ -739,11 +792,38 @@ void Connection::dispatchToCreationThread (DispatchableFunction func) _mutex.unlock (); CFRunLoopSourceSignal (_runloopSource); CFRunLoopWakeUp (_creationThreadRunLoop); +#else + // Linux: push onto the queue and wake the creation thread. + { + std::lock_guard lock { _mutex }; + _queue.emplace (std::move (func)); + } + _condition.notify_one (); #endif } void Connection::processPendingMessageOnCreationThreadIfNeeded () { +#if (!defined (_WIN32) || defined (__WINE__)) && !defined (__APPLE__) + // On Linux there is no run loop to fire dispatch functions automatically, + // so we must drain Connection::_queue here before letting the main thread + // dispatcher check for a pending IPC message. + // We hold the lock only while popping each item, then release it before + // executing so that dispatchToCreationThread() can push more items + // concurrently without deadlocking. + while (true) + { + DispatchableFunction func; + { + std::lock_guard lock { _mutex }; + if (_queue.empty ()) + break; + func = std::move (_queue.front ()); + _queue.pop (); + } + func (); + } +#endif _mainThreadDispatcher->processPendingMessageIfNeeded (); } diff --git a/IPC/ARAIPCConnection.h b/IPC/ARAIPCConnection.h index aae5857..64be8d1 100644 --- a/IPC/ARAIPCConnection.h +++ b/IPC/ARAIPCConnection.h @@ -25,7 +25,7 @@ #if ARA_ENABLE_IPC -#if defined (_WIN32) +#if defined (_WIN32) && !defined (__WINE__) #include #elif defined (__APPLE__) #include @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -161,7 +162,7 @@ class Connection std::unique_ptr _mainThreadDispatcher {}; std::unique_ptr _otherThreadsDispatcher {}; std::thread::id const _creationThreadID; -#if defined (_WIN32) +#if defined (_WIN32) && !defined (__WINE__) HANDLE const _creationThreadHandle; #elif defined (__APPLE__) CFRunLoopRef const _creationThreadRunLoop; @@ -169,7 +170,12 @@ class Connection std::queue _queue; // \todo instead of locking, use a lockless concurrent queue, std::recursive_mutex _mutex; // eg this one: https://github.com/hogliux/farbot #else - #error "not yet implemented on this platform" + // Linux: dispatch queue protected by mutex + condition variable. + // dispatchToCreationThread() pushes a function and notifies; + // processPendingMessageOnCreationThreadIfNeeded() drains it. + std::queue _queue; + std::mutex _mutex; + std::condition_variable _condition; #endif }; diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h index bfb1cd5..0136536 100644 --- a/IPC/ARAIPCEncoding.h +++ b/IPC/ARAIPCEncoding.h @@ -434,6 +434,9 @@ template<> struct _ValueEncoder : public _CompoundValueEncoderBase +#include #include #include +#if defined (__WINE__) + // Wine: need Win32 APIs for the initializeARAWithConfiguration thread trick + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #include + #include + #include + #include +#endif + + +// (assert logger defined inside ARA::IPC namespace below) namespace ARA { namespace IPC { + +// Logging assert function passed to initializeARAWithConfiguration. +// Melodyne fires ARA assertions even in Release builds — logging them +// reveals threading violations and other setup errors (per Stefan Gretscher). +// Must be ms_abi since Melodyne (MSVC PE) calls it with Windows calling convention. +static void __attribute__((ms_abi)) _araAssertLogger (ARA::ARAAssertCategory category, + const void* /*problematicArgument*/, + const char* diagnosis) noexcept +{ + std::fprintf (stderr, "[ARA_ASSERT] category=%d: %s\n", + (int)category, diagnosis ? diagnosis : "(null)"); + std::fflush (stderr); +} + namespace ProxyHostImpl { class AudioAccessController; @@ -472,6 +506,7 @@ class ModelUpdateController : public Host::ModelUpdateControllerInterface, prote void notifyAudioModificationContentChanged (ARAAudioModificationHostRef audioModificationHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept override; void notifyPlaybackRegionContentChanged (ARAPlaybackRegionHostRef playbackRegionHostRef, const ARAContentTimeRange* range, ContentUpdateScopes scopeFlags) noexcept override; void notifyDocumentDataChanged () noexcept override; + void notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept override; private: ARAModelUpdateControllerHostRef _remoteHostRef; @@ -514,6 +549,13 @@ void ModelUpdateController::notifyDocumentDataChanged () noexcept _remoteHostRef); } +void ModelUpdateController::notifyRegionSequenceDataChanged (ARARegionSequenceHostRef regionSequenceHostRef) noexcept +{ + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged), + _remoteHostRef, regionSequenceHostRef); +} + /*******************************************************************************/ //! Implementation of PlaybackControllerInterface that channels all calls through IPC @@ -677,12 +719,84 @@ void ProxyHost::handleReceivedMessage (const MessageID messageID, const MessageD else if (messageID == kInitializeARAMethodID) { ARAPersistentID factoryID; - ARA::SizedStruct<&ARA::ARAInterfaceConfiguration::assertFunctionAddress> interfaceConfig = { kARAAPIGeneration_2_0_Final, nullptr }; + // Use a logging assert so Melodyne's threading checks produce output. + // Cast to ARAAssertFunction (which has no ABI annotation under wineg++) + // — Melodyne will call it via ms_abi, which our function provides. + static ARAAssertFunction _assertFn = + reinterpret_cast(_araAssertLogger); + static ARA::SizedStruct<&ARA::ARAInterfaceConfiguration::assertFunctionAddress> interfaceConfig; + interfaceConfig = { kARAAPIGeneration_2_0_Final, &_assertFn }; decodeArguments (decoder, factoryID, interfaceConfig.desiredApiGeneration); ARA_INTERNAL_ASSERT (interfaceConfig.desiredApiGeneration >= kARAAPIGeneration_2_0_Final); if (const ARAFactory* const factory { getFactoryWithID (factoryID) }) + { +#if defined (__WINE__) + // Under wineg++, ARA_CALL is empty (no ms_abi attribute), but + // Melodyne.vst3 is a real Windows PE compiled with MSVC that + // expects its arguments in %rcx (ms_abi). Cast explicitly. + // + // The creation thread (pluginMainLoop) already has: + // - OleInitialize (STA) + // - Win32 message queue (PeekMessageW) + // - 32MB stack (STACK_SIZE_PARAM_IS_A_RESERVATION) + // Call initFn directly here — adding another thread creates a second + // STA which causes COM cross-apartment failures under Wine. + // + // Melodyne's std::thread objects may call their destructor from + // within the thread they represent, causing join()-on-self → + // EINVAL → std::system_error → std::terminate. Install a terminate + // handler that exits the crashing thread silently instead of + // aborting the whole process. The terminate handler must be set + // process-wide before initFn runs so Melodyne's threads use it. + auto oldTerminate = std::set_terminate([] () noexcept { + // A Melodyne thread (or our std::thread) has an unhandled + // exception. We can't continue safely, so exit cleanly with + // status 0 so the host doesn't treat it as a crash. + // Use _exit() to skip destructors and avoid re-entrancy. + std::fprintf(stderr, "[ProxyHost] terminate intercepted — exiting cleanly\n"); + std::fflush(stderr); + _exit(0); + }); + + // Also handle SIGSEGV from Wine's access violation handler — + // Melodyne's background threads may fault accessing stale data. + // The signal handler must use siglongjmp or just return to let + // the faulting thread exit. + struct sigaction sa_segv{}, old_segv{}; + sa_segv.sa_handler = [](int){ pthread_exit(nullptr); }; + sa_segv.sa_flags = SA_RESETHAND; + sigaction(SIGSEGV, &sa_segv, &old_segv); + + struct sigaction sa_abrt{}, old_abrt{}; + sa_abrt.sa_handler = [](int){ pthread_exit(nullptr); }; + sa_abrt.sa_flags = SA_RESETHAND; + sigaction(SIGABRT, &sa_abrt, &old_abrt); + + std::fprintf (stderr, "[ProxyHost] calling initializeARAWithConfiguration on creation thread\n"); + std::fflush (stderr); + using InitFn = void (__attribute__((ms_abi)) *) (const ARAInterfaceConfiguration*); + auto initFn = reinterpret_cast( + reinterpret_cast(factory->initializeARAWithConfiguration)); + try { + initFn (&interfaceConfig); + } catch (...) { + std::fprintf (stderr, "[ProxyHost] initFn exception swallowed\n"); + std::fflush (stderr); + } + std::fprintf (stderr, "[ProxyHost] initializeARAWithConfiguration returned\n"); + std::fflush (stderr); + + // Restore original terminate handler and signal handlers + std::set_terminate(oldTerminate); + sigaction(SIGSEGV, &old_segv, nullptr); + sigaction(SIGABRT, &old_abrt, nullptr); +#elif defined (_WIN32) + factory->initializeARAWithConfiguration (&interfaceConfig); +#else factory->initializeARAWithConfiguration (&interfaceConfig); +#endif + } } else if (messageID == kCreateDocumentControllerMethodID) { @@ -1129,6 +1243,14 @@ void ProxyHost::handleReceivedMessage (const MessageID messageID, const MessageD fromRef (controllerRef)->updatePlaybackRegionProperties (playbackRegionRef, &properties); } + else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal)) + { + ARADocumentControllerRef controllerRef; + ARAPlaybackRegionRef playbackRegionRef; + decodeArguments (decoder, controllerRef, playbackRegionRef); + + encodeReply (replyEncoder, (fromRef (controllerRef)->isPlaybackRegionPreservingAudioSourceSignal (playbackRegionRef)) ? kARATrue : kARAFalse); + } else if (messageID == ARA_IPC_METHOD_ID (ARADocumentControllerInterface, getPlaybackRegionHeadAndTailTime)) { ARADocumentControllerRef controllerRef; diff --git a/IPC/ARAIPCProxyPlugIn.cpp b/IPC/ARAIPCProxyPlugIn.cpp index 24ee738..6b71eb4 100644 --- a/IPC/ARAIPCProxyPlugIn.cpp +++ b/IPC/ARAIPCProxyPlugIn.cpp @@ -234,6 +234,7 @@ class DocumentController : public PlugIn::DocumentControllerInterface, public Re // Playback Region Management ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept override; void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept override; + bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept override; void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept override; void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override; @@ -784,6 +785,17 @@ void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef pl remoteCall (ARA_IPC_METHOD_ID (ARADocumentControllerInterface, updatePlaybackRegionProperties), _remoteRef, playbackRegionRef, *properties); } +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_INTERNAL_ASSERT (isValidInstance (this)); + ARA_INTERNAL_ASSERT (getConnection ()->wasCreatedOnCurrentThread ()); + + ARABool result; + remoteCall (result, ARA_IPC_METHOD_ID (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal), _remoteRef, playbackRegionRef); + return (result != kARAFalse); +} + void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { ARA_LOG_HOST_ENTRY (playbackRegionRef); @@ -1780,6 +1792,17 @@ void ProxyPlugIn::handleReceivedMessage (const MessageID messageID, const Messag documentController->getHostModelUpdateController ()->notifyDocumentDataChanged (); } + else if (messageID == ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged)) + { + ARAModelUpdateControllerHostRef controllerHostRef; + ARARegionSequenceHostRef regionSequenceHostRef; + decodeArguments (decoder, controllerHostRef, regionSequenceHostRef); + + auto documentController { fromHostRef (controllerHostRef) }; + ARA_VALIDATE_API_ARGUMENT (controllerHostRef, isValidInstance (documentController)); + + documentController->getHostModelUpdateController ()->notifyRegionSequenceDataChanged (regionSequenceHostRef); + } // ARAPlaybackControllerInterface else if (messageID == ARA_IPC_METHOD_ID (ARAPlaybackControllerInterface, requestStartPlayback)) diff --git a/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index 2f000f1..f0b020c 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -157,7 +157,7 @@ std::ostream& operator<< (std::ostream& oss, const OptionalPropertygetStartInAudioModificationTime () << " to " << playbackRegion->getEndInAudioModificationTime () - << ", time-stretching:" << (playbackRegion->isTimestretchEnabled () ? (playbackRegion->isTimeStretchReflectingTempo () ? "musical" : "linear") : "off)") + << ", time-stretching:" << (playbackRegion->isTimestretchEnabled () ? (playbackRegion->isTimestretchReflectingTempo () ? "musical" : "linear") : "off)") << ", content based fades:" << (playbackRegion->hasContentBasedFadeAtHead () ? (playbackRegion->hasContentBasedFadeAtTail () ? "both" : "head only") : (playbackRegion->hasContentBasedFadeAtTail () ? "tail only" : "none")) << ", regionSequence:" << playbackRegion->getRegionSequence ()->getName () << ", color:" << playbackRegion->getColor (); @@ -479,6 +479,18 @@ void RegionSequence::updateProperties (PropertiesPtrmusicalContextRef) }; ARA_VALIDATE_API_ARGUMENT (properties->musicalContextRef, getDocumentController ()->isValidMusicalContext (musicalContext)); setMusicalContext (musicalContext); + + if (properties.implements<&ARARegionSequenceProperties::persistentID> ()) + { + ARA_VALIDATE_API_ARGUMENT (properties->persistentID, properties->persistentID != nullptr); + ARA_VALIDATE_API_ARGUMENT (properties->persistentID, std::strlen (properties->persistentID) > 0); + _persistentID = properties->persistentID; + } + else + { + ARA_VALIDATE_API_ARGUMENT (properties, getDocumentController ()->getUsedApiGeneration () < kARAAPIGeneration_3_0_Draft); + _persistentID = nullptr; + } } void RegionSequence::setMusicalContext (MusicalContext* musicalContext) noexcept @@ -515,10 +527,14 @@ void AudioSource::updateProperties (PropertiesPtr prop ARA_VALIDATE_API_ARGUMENT (properties->persistentID, std::strlen (properties->persistentID) > 0); _persistentID = properties->persistentID; + [[maybe_unused]] const auto supportsContentOnlyAudioSources { getDocumentController ()->getFactory ()->supportsContentOnlyAudioSources != kARAFalse }; + ARA_VALIDATE_API_ARGUMENT (properties, properties->sampleCount >= ((supportsContentOnlyAudioSources) ? 0 : 1)); _sampleCount = properties->sampleCount; + ARA_VALIDATE_API_ARGUMENT (properties, properties->sampleRate > 0.0); _sampleRate = properties->sampleRate; _merits64BitSamples = (properties->merits64BitSamples != kARAFalse); + ARA_VALIDATE_API_ARGUMENT (properties, properties->channelCount >= ((supportsContentOnlyAudioSources) ? 0 : 1)); if (properties.implements<&ARAAudioSourceProperties::channelArrangement> ()) _channelFormat.update (properties->channelCount, properties->channelArrangementDataType, properties->channelArrangement); else @@ -684,7 +700,7 @@ void PlaybackRegion::setRegionSequence (RegionSequence* regionSequence) noexcept /*******************************************************************************/ -RestoreObjectsFilter::RestoreObjectsFilter (const ARARestoreObjectsFilter* filter, Document* document) noexcept +RestoreObjectsFilter::RestoreObjectsFilter (const SizedStructPtr filter, Document* document) noexcept : _filter { filter } { for (const auto& audioSource : document->getAudioSources ()) @@ -701,6 +717,15 @@ RestoreObjectsFilter::RestoreObjectsFilter (const ARARestoreObjectsFilter* filte } } + for (const auto& regionSequence : document->getRegionSequences ()) + { + if (const auto& regionSequenceID { regionSequence->getPersistentID () }) + { + ARA_VALIDATE_API_STATE (_regionSequencesByID.count (regionSequenceID) == 0); // make sure all current region sequence persistentIDs are unique + _regionSequencesByID[regionSequenceID] = regionSequence; + } + } + if (filter) { decltype (_audioSourcesByID) audioSourcesByMappedIDs; @@ -728,6 +753,22 @@ RestoreObjectsFilter::RestoreObjectsFilter (const ARARestoreObjectsFilter* filte audioModificationsByMappedIDs[audioModificationArchiveID] = it->second; } _audioModificationsByID = std::move (audioModificationsByMappedIDs); + + if (filter.implements<&ARARestoreObjectsFilter::regionSequenceIDsCount> ()) + { + decltype (_regionSequencesByID) regionSequencesByMappedIDs; + for (ARASize i { 0 }; i < filter->regionSequenceIDsCount; ++i) + { + auto regionSequenceArchiveID { filter->regionSequenceArchiveIDs[i] }; + ARA_VALIDATE_API_STATE (regionSequencesByMappedIDs.count (regionSequenceArchiveID) == 0); // make sure audio Modification persistentIDs in filter are unique + auto regionSequenceCurrentID { (filter->regionSequenceCurrentIDs != nullptr) ? filter->regionSequenceCurrentIDs[i] : regionSequenceArchiveID }; + + const auto it { _regionSequencesByID.find (regionSequenceCurrentID) }; + if (it != _regionSequencesByID.end ()) + regionSequencesByMappedIDs[regionSequenceArchiveID] = it->second; + } + _regionSequencesByID = std::move (regionSequencesByMappedIDs); + } } } @@ -750,9 +791,15 @@ AudioModification* RestoreObjectsFilter::getAudioModificationToRestoreStateWithI return (it != _audioModificationsByID.end ()) ? it->second : nullptr; } +RegionSequence* RestoreObjectsFilter::getRegionSequenceToRestoreStateWithID (ARAPersistentID regionSequenceID) const noexcept +{ + const auto it { _regionSequencesByID.find (regionSequenceID) }; + return (it != _regionSequencesByID.end ()) ? it->second : nullptr; +} + /*******************************************************************************/ -StoreObjectsFilter::StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noexcept +StoreObjectsFilter::StoreObjectsFilter (const SizedStructPtr filter) noexcept : _filter { filter } { ARA_INTERNAL_ASSERT (filter != nullptr); @@ -760,6 +807,12 @@ StoreObjectsFilter::StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noe _audioSourcesToStore.push_back (fromRef (_filter->audioSourceRefs[i])); for (ARASize i { 0 }; i < _filter->audioModificationRefsCount; ++i) _audioModificationsToStore.push_back (fromRef (_filter->audioModificationRefs[i])); + + if (filter.implements<&ARAStoreObjectsFilter::regionSequenceRefs> ()) + { + for (ARASize i { 0 }; i < _filter->regionSequenceRefsCount; ++i) + _regionSequencesToStore.push_back (fromRef (_filter->regionSequenceRefs[i])); + } } StoreObjectsFilter::StoreObjectsFilter (const Document* document) noexcept @@ -769,6 +822,12 @@ StoreObjectsFilter::StoreObjectsFilter (const Document* document) noexcept _audioModificationsToStore.reserve (_audioSourcesToStore.size ()); for (const auto& audioSource : _audioSourcesToStore) _audioModificationsToStore.insert (_audioModificationsToStore.end (), audioSource->getAudioModifications ().begin (), audioSource->getAudioModifications ().end ()); + + for (const auto& regionSequence : document->getRegionSequences ()) + { + if (regionSequence->getPersistentID ()) + _regionSequencesToStore.emplace_back (regionSequence); + } } bool StoreObjectsFilter::shouldStoreDocumentData () const noexcept @@ -1093,10 +1152,24 @@ void DocumentController::notifyModelUpdates () noexcept hostModelUpdateController->notifyPlaybackRegionContentChanged (playbackRegionUpdate.first->getHostRef (), nullptr, playbackRegionUpdate.second); _playbackRegionContentUpdates.clear (); + if (_regionSequenceDataUpdates.size () > 0) + { + if (hostModelUpdateController->supportsNotifyRegionSequenceDataChanged ()) + { + for (const auto& regionSequence : _regionSequenceDataUpdates) + hostModelUpdateController->notifyRegionSequenceDataChanged (regionSequence->getHostRef ()); + } + else + { + _documentDataChanged = true; // aka notifyDocumentDataChanged() + } + _regionSequenceDataUpdates.clear (); + } + if (_documentDataChanged) hostModelUpdateController->notifyDocumentDataChanged (); _documentDataChanged = false; - + didNotifyModelUpdates (); } @@ -1189,10 +1262,11 @@ bool DocumentControllerDelegate::doStoreAudioSourceToAudioFileChunk (HostArchive *openAutomatically = false; ARAAudioSourceRef audioSourceRef { toRef (audioSource) }; - const SizedStruct<&ARAStoreObjectsFilter::audioModificationRefs> filter { kARATrue, - 1U, &audioSourceRef, - 0U, nullptr - }; + const SizedStruct<&ARAStoreObjectsFilter::regionSequenceRefs> filter { kARATrue, + 1U, &audioSourceRef, + 0U, nullptr, + 0U, nullptr + }; const StoreObjectsFilter storeObjectsFilter { &filter }; return doStoreObjectsToArchive (archiveWriter, &storeObjectsFilter); } @@ -1421,6 +1495,9 @@ void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSeque ARA_LOG_MODELOBJECT_LIFETIME ("will destroy region sequence", regionSequence); willDestroyRegionSequence (regionSequence); + + _regionSequenceDataUpdates.erase (regionSequence); + doDestroyRegionSequence (regionSequence); } @@ -1721,6 +1798,17 @@ void DocumentController::updatePlaybackRegionProperties (ARAPlaybackRegionRef pl ARA_LOG_PROPERTY_CHANGES ("did update properties of playback region", playbackRegion); } +bool DocumentController::isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + ARA_LOG_HOST_ENTRY (playbackRegionRef); + ARA_VALIDATE_API_ARGUMENT (this, isValidDocumentController (this)); + ARA_VALIDATE_API_THREAD (wasCreatedOnCurrentThread ()); + + auto playbackRegion { fromRef (playbackRegionRef) }; + ARA_VALIDATE_API_ARGUMENT (playbackRegionRef, isValidPlaybackRegion (playbackRegion)); + return doIsPlaybackRegionPreservingAudioSourceSignal (playbackRegion); +} + void DocumentController::getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { ARA_LOG_HOST_ENTRY (playbackRegionRef); @@ -2280,6 +2368,12 @@ void DocumentController::notifyPlaybackRegionContentChanged (PlaybackRegion* pla _playbackRegionContentUpdates[playbackRegion] += scopeFlags; } +void DocumentController::notifyRegionSequenceDataChanged (RegionSequence* regionSequence) noexcept +{ + if (getHostModelUpdateController ()) + _regionSequenceDataUpdates.insert (regionSequence); +} + void DocumentController::notifyDocumentDataChanged () noexcept { _documentDataChanged = true; @@ -2757,7 +2851,10 @@ PlugInEntry::PlugInEntry (const FactoryConfig* factoryConfig, factoryConfig->getDocumentArchiveID (), factoryConfig->getCompatibleDocumentArchiveIDsCount (), factoryConfig->getCompatibleDocumentArchiveIDs (), factoryConfig->getAnalyzeableContentTypesCount (), factoryConfig->getAnalyzeableContentTypes (), factoryConfig->getSupportedPlaybackTransformationFlags (), - (factoryConfig->supportsStoringAudioFileChunks ()) ? kARATrue : kARAFalse + (factoryConfig->supportsStoringAudioFileChunks ()) ? kARATrue : kARAFalse, + (factoryConfig->supportsSampleBasedAudioSources ()) ? kARATrue : kARAFalse, + (factoryConfig->supportsContentOnlyAudioSources ()) ? kARATrue : kARAFalse, + (factoryConfig->requiresPresetAudioSources ()) ? kARATrue : kARAFalse } { #if ARA_CPU_ARM diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index f907c32..df95120 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -380,6 +380,7 @@ class RegionSequence const OptionalProperty& getName () const noexcept { return _name; } //!< See ARARegionSequenceProperties::name. ARAInt32 getOrderIndex () const noexcept { return _orderIndex; } //!< See ARARegionSequenceProperties::orderIndex. const OptionalProperty& getColor () const noexcept { return _color; } //!< See ARARegionSequenceProperties::color. + ARA_DRAFT const OptionalProperty& getPersistentID () const noexcept { return _persistentID; } //!< See ARARegionSequenceProperties::persistentID. //@} //! @name Region Sequence Relationships @@ -422,6 +423,7 @@ class RegionSequence OptionalProperty _name; ARAInt32 _orderIndex { 0 }; OptionalProperty _color; + OptionalProperty _persistentID; std::vector _playbackRegions; ARA_HOST_MANAGED_OBJECT (RegionSequence) @@ -623,7 +625,7 @@ class PlaybackRegion ARASamplePosition getEndInPlaybackSamples (ARASampleRate playbackSampleRate) const noexcept; //!< Playback end time in samples, derived using underlying AudioSource sample rate. bool isTimestretchEnabled () const noexcept { return _timestretchEnabled; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretch`. - bool isTimeStretchReflectingTempo () const noexcept { return _timestretchReflectingTempo; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretchReflectingTempo`. + bool isTimestretchReflectingTempo () const noexcept { return _timestretchReflectingTempo; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationTimestretchReflectingTempo`. bool hasContentBasedFadeAtHead () const noexcept { return _contentBasedFadeAtHead; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationContentBasedFadeAtHead`. bool hasContentBasedFadeAtTail () const noexcept { return _contentBasedFadeAtTail; } //!< `ARAPlaybackRegionProperties::transformationFlags & ::kARAPlaybackTransformationContentBasedFadeAtTail`. @@ -732,7 +734,7 @@ class RestoreObjectsFilter }; public: - RestoreObjectsFilter (const ARARestoreObjectsFilter* filter, Document* document) noexcept; + RestoreObjectsFilter (const SizedStructPtr filter, Document* document) noexcept; //! @name Filter Queries //! Use these functions to filter and map the objects restored during DocumentController::doRestoreObjectsFromArchive(). @@ -746,12 +748,17 @@ class RestoreObjectsFilter AudioModification* getAudioModificationToRestoreStateWithID (ARAPersistentID archivedAudioModificationID) const noexcept; template AudioModification_t* getAudioModificationToRestoreStateWithID (ARAPersistentID archivedAudioModificationID) const noexcept { return static_cast (getAudioModificationToRestoreStateWithID (archivedAudioModificationID)); } + + ARA_DRAFT RegionSequence* getRegionSequenceToRestoreStateWithID (ARAPersistentID archivedRegionSequenceID) const noexcept; + template + ARA_DRAFT RegionSequence_t* getRegionSequenceToRestoreStateWithID (ARAPersistentID archivedRegionSequenceID) const noexcept { return static_cast (getRegionSequenceToRestoreStateWithID (archivedRegionSequenceID)); } //@} private: const ARARestoreObjectsFilter* _filter; std::map _audioSourcesByID; std::map _audioModificationsByID; + std::map _regionSequencesByID; }; @@ -760,7 +767,9 @@ class RestoreObjectsFilter class StoreObjectsFilter { public: - explicit StoreObjectsFilter (const ARAStoreObjectsFilter* filter) noexcept; + //! use this c'tor when host-provided filter is not a nullptr + explicit StoreObjectsFilter (const SizedStructPtr filter) noexcept; + //! use this c'tor when host-provided filter is a nullptr explicit StoreObjectsFilter (const Document* document) noexcept; //! @name Filter Queries @@ -772,12 +781,16 @@ class StoreObjectsFilter std::vector const& getAudioSourcesToStore () const noexcept { return vector_cast (_audioSourcesToStore); } template std::vector const& getAudioModificationsToStore () const noexcept { return vector_cast (_audioModificationsToStore); } + + template + ARA_DRAFT std::vector const& getRegionSequencesToStore () const noexcept { return vector_cast (_regionSequencesToStore); } //@} private: const ARAStoreObjectsFilter* _filter; std::vector _audioSourcesToStore; std::vector _audioModificationsToStore; + std::vector _regionSequencesToStore; }; //! @} ARA_Library_ARAPlug_Utility_Classes @@ -969,6 +982,8 @@ class DocumentControllerDelegate virtual void willUpdatePlaybackRegionProperties (PlaybackRegion* playbackRegion, PropertiesPtr newProperties) noexcept {} //! Override to customize post-update behavior of updatePlaybackRegionProperties(). virtual void didUpdatePlaybackRegionProperties (PlaybackRegion* playbackRegion) noexcept {} + //! Override to implement isPlaybackRegionPreservingAudioSourceSignal(). + ARA_DRAFT virtual bool doIsPlaybackRegionPreservingAudioSourceSignal (PlaybackRegion* playbackRegion) noexcept { return false; } //! Override to define a content based fade for \p playbackRegion by assigning positive values to \p headTime and/or \p tailTime - see getPlaybackRegionHeadAndTailTime(). virtual void doGetPlaybackRegionHeadAndTailTime (const PlaybackRegion* playbackRegion, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept { *headTime = 0.0; *tailTime = 0.0; } //! Override to customize behavior before \p playbackRegion is destroyed during destroyPlaybackRegion(). @@ -1164,6 +1179,7 @@ class DocumentController : public DocumentControllerInterface, // Playback Region Management ARAPlaybackRegionRef createPlaybackRegion (ARAAudioModificationRef audioModificationRef, ARAPlaybackRegionHostRef hostRef, PropertiesPtr properties) noexcept override; void updatePlaybackRegionProperties (ARAPlaybackRegionRef playbackRegionRef, PropertiesPtr properties) noexcept override; + ARA_DRAFT bool isPlaybackRegionPreservingAudioSourceSignal (ARAPlaybackRegionRef playbackRegionRef) noexcept override; void getPlaybackRegionHeadAndTailTime (ARAPlaybackRegionRef playbackRegionRef, ARATimeDuration* headTime, ARATimeDuration* tailTime) noexcept override; void destroyPlaybackRegion (ARAPlaybackRegionRef playbackRegionRef) noexcept override; @@ -1260,15 +1276,20 @@ class DocumentController : public DocumentControllerInterface, //! @name Sending content updates to the host //! The implementation will internally enqueue the updates and later send them to the host -//! from notifyModelUpdates (). +//! from notifyModelUpdates(). //! Note that while the ARA API allows for specifying affected time ranges for content updates, //! this feature is not yet supported in our current plug-in implementation (since most hosts //! do not evaluate this either). +//! Since older ARA 2.x hosts will not yet support notifyRegionSequenceDataChanged(), the implementation +//! will eventually fall back to calling notifyDocumentDataChanged() instead if needed. This allows +//! plug-ins to consistently utilize the new update APIs (but they will still need to branch out for +//! old hosts/archives in their implementation of doStoreObjectsToArchive()/doRestoreObjectsFromArchive()). //@{ void notifyAudioSourceContentChanged (AudioSource* audioSource, ContentUpdateScopes scopeFlags) noexcept; void notifyAudioModificationContentChanged (AudioModification* audioModification, ContentUpdateScopes scopeFlags) noexcept; void notifyPlaybackRegionContentChanged (PlaybackRegion* playbackRegion, ContentUpdateScopes scopeFlags) noexcept; void notifyDocumentDataChanged () noexcept; + ARA_DRAFT void notifyRegionSequenceDataChanged (RegionSequence* regionSequence) noexcept; //@} // Helper for analysis requests. @@ -1343,6 +1364,7 @@ class DocumentController : public DocumentControllerInterface, std::map _audioSourceContentUpdates; std::map _audioModificationContentUpdates; std::map _playbackRegionContentUpdates; + std::set _regionSequenceDataUpdates; bool _documentDataChanged { false }; std::atomic_flag _analysisProgressIsSynced {}; // { true } would be better but C++ standard only allows for default-init to false @@ -1786,12 +1808,13 @@ class FactoryConfig virtual ~FactoryConfig () = default; //! \copydoc ARAFactory::lowestSupportedApiGeneration - virtual ARAAPIGeneration getLowestSupportedApiGeneration () const noexcept + virtual ARAAPIGeneration getLowestSupportedApiGeneration () const noexcept { return (supportsSampleBasedAudioSources ()) ? #if ARA_CPU_ARM - { return kARAAPIGeneration_2_0_Final; } + kARAAPIGeneration_2_0_Final : #else - { return kARAAPIGeneration_2_0_Draft; } + kARAAPIGeneration_2_0_Draft : #endif + kARAAPIGeneration_3_0_Draft; } //! \copydoc ARAFactory::highestSupportedApiGeneration virtual ARAAPIGeneration getHighestSupportedApiGeneration () const noexcept { return kARAAPIGeneration_3_0_Draft; } @@ -1826,6 +1849,15 @@ class FactoryConfig //! \copydoc ARAFactory::supportsStoringAudioFileChunks virtual bool supportsStoringAudioFileChunks () const noexcept { return false; } + + //! \copydoc ARAFactory::supportsSampleBasedAudioSources + virtual bool supportsSampleBasedAudioSources () const noexcept { return true; } + + //! \copydoc ARAFactory::supportsContentOnlyAudioSources + virtual bool supportsContentOnlyAudioSources () const noexcept { return false; } + + //! \copydoc ARAFactory::requiresPresetAudioSources + virtual bool requiresPresetAudioSources () const noexcept { return false; } }; @@ -1943,7 +1975,7 @@ class PlugInEntry private: const FactoryConfig* const _factoryConfig; - const SizedStruct<&ARAFactory::supportsStoringAudioFileChunks> _factory; + const SizedStruct<&ARAFactory::requiresPresetAudioSources> _factory; ARAAPIGeneration _usedApiGeneration { 0 }; ARA_DISABLE_COPY_AND_MOVE (PlugInEntry)