From 3466e0ef72e04ac10c7c09572898b8ea6e2cedd5 Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:23:16 +0200 Subject: [PATCH 1/7] Consistent spelling of timestrech --- ChangeLog.txt | 3 ++- PlugIn/ARAPlug.cpp | 2 +- PlugIn/ARAPlug.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 989d436..716d65e 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,7 +7,8 @@ Changes since previous releases: 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/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index 2f000f1..987492c 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 (); diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index f907c32..9f46812 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -623,7 +623,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`. From cbc43d5b235125989b3726c890cdeb2b854c09cb Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:23:16 +0200 Subject: [PATCH 2/7] Initial draft of region sequence persistency --- ChangeLog.txt | 1 + IPC/ARAIPCEncoding.h | 9 ++++++ PlugIn/ARAPlug.cpp | 68 ++++++++++++++++++++++++++++++++++++++++---- PlugIn/ARAPlug.h | 17 +++++++++-- 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 716d65e..4c5806c 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -3,6 +3,7 @@ This is a development build of the ARA Library 3.0. Changes since previous releases: +- initial draft of region sequence persistency - 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 diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h index bfb1cd5..b387251 100644 --- a/IPC/ARAIPCEncoding.h +++ b/IPC/ARAIPCEncoding.h @@ -434,6 +434,9 @@ template<> struct _ValueEncoder : public _CompoundValueEncoderBasemusicalContextRef) }; 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 @@ -684,7 +696,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 +713,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 +749,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 +787,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 +803,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 +818,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 @@ -1189,10 +1244,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); } diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index 9f46812..3524255 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) @@ -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 From 661c46a16c35fdae117fc46dc93d8ee6d4277b29 Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Fri, 1 May 2026 12:01:43 +0200 Subject: [PATCH 3/7] Add notifyRegionSequenceDataChanged() to complement region sequence persistency --- Dispatch/ARAHostDispatch.cpp | 10 ++++++++-- Dispatch/ARAHostDispatch.h | 2 ++ Dispatch/ARAPlugInDispatch.cpp | 27 +++++++++++++++++++++++---- Dispatch/ARAPlugInDispatch.h | 8 ++++++++ IPC/ARAIPCEncoding.h | 1 + IPC/ARAIPCProxyHost.cpp | 8 ++++++++ IPC/ARAIPCProxyPlugIn.cpp | 11 +++++++++++ PlugIn/ARAPlug.cpp | 25 ++++++++++++++++++++++++- PlugIn/ARAPlug.h | 8 +++++++- 9 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Dispatch/ARAHostDispatch.cpp b/Dispatch/ARAHostDispatch.cpp index 483ab27..fc204e3 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; } diff --git a/Dispatch/ARAHostDispatch.h b/Dispatch/ARAHostDispatch.h index 3bfb0a6..4b8fbdc 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) diff --git a/Dispatch/ARAPlugInDispatch.cpp b/Dispatch/ARAPlugInDispatch.cpp index 035a9f9..76b2d1e 100644 --- a/Dispatch/ARAPlugInDispatch.cpp +++ b/Dispatch/ARAPlugInDispatch.cpp @@ -645,21 +645,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..39eb62c 100644 --- a/Dispatch/ARAPlugInDispatch.h +++ b/Dispatch/ARAPlugInDispatch.h @@ -452,11 +452,19 @@ class HostModelUpdateController : public InterfaceInstancewasCreatedOnCurrentThread ()); + remoteCall (ARA_IPC_METHOD_ID (ARAModelUpdateControllerInterface, notifyRegionSequenceDataChanged), + _remoteHostRef, regionSequenceHostRef); +} + /*******************************************************************************/ //! Implementation of PlaybackControllerInterface that channels all calls through IPC diff --git a/IPC/ARAIPCProxyPlugIn.cpp b/IPC/ARAIPCProxyPlugIn.cpp index 24ee738..8d8bea5 100644 --- a/IPC/ARAIPCProxyPlugIn.cpp +++ b/IPC/ARAIPCProxyPlugIn.cpp @@ -1780,6 +1780,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 028a931..50a65d3 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -1148,10 +1148,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 (); } @@ -1477,6 +1491,9 @@ void DocumentController::destroyRegionSequence (ARARegionSequenceRef regionSeque ARA_LOG_MODELOBJECT_LIFETIME ("will destroy region sequence", regionSequence); willDestroyRegionSequence (regionSequence); + + _regionSequenceDataUpdates.erase (regionSequence); + doDestroyRegionSequence (regionSequence); } @@ -2336,6 +2353,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; diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index 3524255..7b89767 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -1273,15 +1273,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. @@ -1356,6 +1361,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 From f60f3f07860ff3ac24087011c9604ced4671def0 Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Fri, 1 May 2026 12:01:44 +0200 Subject: [PATCH 4/7] Add isPlaybackRegionPreservingAudioSourceSignal(), superseding isAudioModificationPreservingAudioSourceSignal() --- ChangeLog.txt | 1 + Dispatch/ARAHostDispatch.cpp | 12 ++++++++++++ Dispatch/ARAHostDispatch.h | 8 ++++++-- Dispatch/ARAPlugInDispatch.cpp | 14 ++++++++++---- Dispatch/ARAPlugInDispatch.h | 6 ++++-- IPC/ARAIPCEncoding.h | 1 + IPC/ARAIPCProxyHost.cpp | 8 ++++++++ IPC/ARAIPCProxyPlugIn.cpp | 12 ++++++++++++ PlugIn/ARAPlug.cpp | 11 +++++++++++ PlugIn/ARAPlug.h | 3 +++ 10 files changed, 68 insertions(+), 8 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 4c5806c..1bddb37 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -4,6 +4,7 @@ This is a development build of the ARA Library 3.0. Changes since previous releases: - 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 diff --git a/Dispatch/ARAHostDispatch.cpp b/Dispatch/ARAHostDispatch.cpp index fc204e3..ea62e79 100644 --- a/Dispatch/ARAHostDispatch.cpp +++ b/Dispatch/ARAHostDispatch.cpp @@ -510,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 4b8fbdc..0cf647f 100644 --- a/Dispatch/ARAHostDispatch.h +++ b/Dispatch/ARAHostDispatch.h @@ -349,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; } diff --git a/Dispatch/ARAPlugInDispatch.h b/Dispatch/ARAPlugInDispatch.h index 39eb62c..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 diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h index e47ea9f..ddcf5ee 100644 --- a/IPC/ARAIPCEncoding.h +++ b/IPC/ARAIPCEncoding.h @@ -1113,6 +1113,7 @@ inline const char* decodeHostMessageID (const MessageID messageID, const bool om ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isLicensedForCapabilities) ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, storeAudioSourceToAudioFileChunk) ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isAudioModificationPreservingAudioSourceSignal) + ARA_IPC_INTERFACE_MESSAGE_CASE (ARADocumentControllerInterface, isPlaybackRegionPreservingAudioSourceSignal) ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackRendererInterface, addPlaybackRegion) ARA_IPC_INTERFACE_MESSAGE_CASE (ARAPlaybackRendererInterface, removePlaybackRegion) ARA_IPC_INTERFACE_MESSAGE_CASE (ARAEditorRendererInterface, addPlaybackRegion) diff --git a/IPC/ARAIPCProxyHost.cpp b/IPC/ARAIPCProxyHost.cpp index d88b65b..d2bae8a 100644 --- a/IPC/ARAIPCProxyHost.cpp +++ b/IPC/ARAIPCProxyHost.cpp @@ -1137,6 +1137,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 8d8bea5..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); diff --git a/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index 50a65d3..e76d383 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -1794,6 +1794,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); diff --git a/PlugIn/ARAPlug.h b/PlugIn/ARAPlug.h index 7b89767..b8ccae2 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -982,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(). @@ -1177,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; From a4ab827e4c377f252abf2bce000fd84000d88704 Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Fri, 1 May 2026 12:01:44 +0200 Subject: [PATCH 5/7] Initial draft of lyrics-related content, tailored to support singing voice synthesis --- ChangeLog.txt | 1 + Debug/ARAContentLogger.h | 21 +++++++++++++++++++-- Debug/ARAContentValidator.h | 19 +++++++++++++++++++ Dispatch/ARAContentReader.h | 1 + Dispatch/ARADispatchBase.h | 10 +++++++--- IPC/ARAIPCEncoding.h | 19 +++++++++++++++++++ 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 1bddb37..4c306ad 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -3,6 +3,7 @@ This is a development build of the ARA Library 3.0. Changes since previous releases: +- 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, 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/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h index ddcf5ee..c4f0567 100644 --- a/IPC/ARAIPCEncoding.h +++ b/IPC/ARAIPCEncoding.h @@ -742,6 +742,25 @@ ARA_IPC_BEGIN_DECODE (ARAContentChord) ARA_IPC_DECODE_MEMBER (position) ARA_IPC_END_DECODE +ARA_IPC_BEGIN_ENCODE (ARAContentLyricsEntry) + ARA_IPC_ENCODE_MEMBER (lyrics) + ARA_IPC_ENCODE_MEMBER (continuesPreviousWord) + ARA_IPC_ENCODE_MEMBER (language) + ARA_IPC_ENCODE_VARIABLE_ARRAY (phonemes, phonemeCount) + ARA_IPC_ENCODE_VARIABLE_ARRAY (phonemeOffsets, phonemeCount) + ARA_IPC_ENCODE_MEMBER (phonemesGrade) + ARA_IPC_ENCODE_MEMBER (position) +ARA_IPC_END_ENCODE +ARA_IPC_BEGIN_DECODE (ARAContentLyricsEntry) + ARA_IPC_DECODE_MEMBER (lyrics) + ARA_IPC_DECODE_MEMBER (continuesPreviousWord) + ARA_IPC_DECODE_MEMBER (language) + ARA_IPC_DECODE_VARIABLE_ARRAY (phonemes, phonemeCount, true) + ARA_IPC_DECODE_VARIABLE_ARRAY (phonemeOffsets, phonemeCount, false) + ARA_IPC_DECODE_MEMBER (phonemesGrade) + ARA_IPC_DECODE_MEMBER (position) +ARA_IPC_END_DECODE + ARA_IPC_BEGIN_ENCODE (ARARestoreObjectsFilter) ARA_IPC_ENCODE_MEMBER (documentData) ARA_IPC_ENCODE_VARIABLE_ARRAY (audioSourceArchiveIDs, audioSourceIDsCount) From 5fc1e83385f3af299f91e3f3ef51e4d06ca593ec Mon Sep 17 00:00:00 2001 From: sgretscher <41306803+sgretscher@users.noreply.github.com> Date: Fri, 1 May 2026 12:01:45 +0200 Subject: [PATCH 6/7] Initial draft of ARA generator plug-ins that operate merely based on content descriptions (no sample input) --- ChangeLog.txt | 1 + IPC/ARAIPCEncoding.h | 6 ++++++ PlugIn/ARAPlug.cpp | 9 ++++++++- PlugIn/ARAPlug.h | 18 ++++++++++++++---- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 4c306ad..b5f1c63 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -3,6 +3,7 @@ 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() diff --git a/IPC/ARAIPCEncoding.h b/IPC/ARAIPCEncoding.h index c4f0567..0136536 100644 --- a/IPC/ARAIPCEncoding.h +++ b/IPC/ARAIPCEncoding.h @@ -824,6 +824,9 @@ ARA_IPC_BEGIN_ENCODE (ARAFactory) ARA_IPC_ENCODE_VARIABLE_ARRAY (analyzeableContentTypes, analyzeableContentTypesCount) ARA_IPC_ENCODE_MEMBER (supportedPlaybackTransformationFlags) ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsStoringAudioFileChunks) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsSampleBasedAudioSources) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (supportsContentOnlyAudioSources) + ARA_IPC_ENCODE_ADDENDUM_MEMBER (requiresPresetAudioSources) ARA_IPC_END_ENCODE ARA_IPC_BEGIN_DECODE_SIZED (ARAFactory) ARA_IPC_DECODE_MEMBER (lowestSupportedApiGeneration) @@ -841,6 +844,9 @@ ARA_IPC_BEGIN_DECODE_SIZED (ARAFactory) ARA_IPC_DECODE_VARIABLE_ARRAY (analyzeableContentTypes, analyzeableContentTypesCount, true) ARA_IPC_DECODE_MEMBER (supportedPlaybackTransformationFlags) ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsStoringAudioFileChunks) + ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsSampleBasedAudioSources) + ARA_IPC_DECODE_ADDENDUM_MEMBER (supportsContentOnlyAudioSources) + ARA_IPC_DECODE_ADDENDUM_MEMBER (requiresPresetAudioSources) ARA_IPC_END_DECODE diff --git a/PlugIn/ARAPlug.cpp b/PlugIn/ARAPlug.cpp index e76d383..f0b020c 100644 --- a/PlugIn/ARAPlug.cpp +++ b/PlugIn/ARAPlug.cpp @@ -527,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 @@ -2847,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 b8ccae2..df95120 100644 --- a/PlugIn/ARAPlug.h +++ b/PlugIn/ARAPlug.h @@ -1808,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; } @@ -1848,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; } }; @@ -1965,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) From 057987bb1f19af5b5f04b90162f8e71760582a09 Mon Sep 17 00:00:00 2001 From: samuel-asleep Date: Fri, 19 Jun 2026 15:23:07 +0100 Subject: [PATCH 7/7] feat(IPC): Add Linux IPC support (with Wine compatibility) - Remove #error on non-Apple/non-Windows platforms - Add ThreadRef = size_t for Linux (matches std::thread::id on 64-bit POSIX) - Add Connection members: std::queue + std::mutex + std::condition_variable - Implement dispatchToCreationThread() for Linux - Implement processPendingMessageOnCreationThreadIfNeeded() for Linux - Add Wine-compatible WaitableSingleMessageQueue using POSIX sem_t (std::binary_semaphore hangs under Wine due to broken futex mapping) --- IPC/ARAIPCConnection.cpp | 102 ++++++++++++++++++++++++++++++++---- IPC/ARAIPCConnection.h | 12 +++-- IPC/ARAIPCProxyHost.cpp | 108 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 15 deletions(-) diff --git a/IPC/ARAIPCConnection.cpp b/IPC/ARAIPCConnection.cpp index 06dd81d..aafc5a7 100644 --- a/IPC/ARAIPCConnection.cpp +++ b/IPC/ARAIPCConnection.cpp @@ -127,7 +127,7 @@ namespace ARA { namespace IPC { -#if defined (_WIN32) +#if defined (_WIN32) && !defined (__WINE__) #define readThreadRef readInt32 #define appendThreadRef appendInt32 #else @@ -142,12 +142,11 @@ static bool _debugAsHost {}; class MessageDispatcher { public: // needs to be public for thread-local variables (which cannot be class members) -#if defined (_WIN32) +#if defined (_WIN32) && !defined (__WINE__) using ThreadRef = int32_t; -#elif defined (__APPLE__) - using ThreadRef = size_t; #else - #error "not yet implemented on this platform" + // Apple, Linux, and Wine: std::thread::id is pointer-sized. + using ThreadRef = size_t; #endif static constexpr ThreadRef _invalidThread { 0 }; @@ -234,6 +233,56 @@ std::unique_ptr 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/ARAIPCProxyHost.cpp b/IPC/ARAIPCProxyHost.cpp index d2bae8a..68eaa98 100644 --- a/IPC/ARAIPCProxyHost.cpp +++ b/IPC/ARAIPCProxyHost.cpp @@ -27,12 +27,46 @@ #include "ARA_Library/Utilities/ARAStdVectorUtilities.h" #include +#include #include #include +#if defined (__WINE__) + // Wine: need Win32 APIs for the initializeARAWithConfiguration thread trick + #ifndef NOMINMAX + #define NOMINMAX + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #include + #include + #include + #include +#endif + + +// (assert logger defined inside ARA::IPC namespace below) namespace ARA { namespace IPC { + +// Logging assert function passed to initializeARAWithConfiguration. +// Melodyne fires ARA assertions even in Release builds — logging them +// reveals threading violations and other setup errors (per Stefan Gretscher). +// Must be ms_abi since Melodyne (MSVC PE) calls it with Windows calling convention. +static void __attribute__((ms_abi)) _araAssertLogger (ARA::ARAAssertCategory category, + const void* /*problematicArgument*/, + const char* diagnosis) noexcept +{ + std::fprintf (stderr, "[ARA_ASSERT] category=%d: %s\n", + (int)category, diagnosis ? diagnosis : "(null)"); + std::fflush (stderr); +} + namespace ProxyHostImpl { class AudioAccessController; @@ -685,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) {